Python实现的一个验证码服务降级的简单例子
在微服务设计场景, 当系统负载较高时, 可以通过抛弃部分非核心服务来保证核心服务高可用.
网上的文章都是讲降级的策略, 基本没有讲到如何具体去实现. 我自己用python为验证码服务写了一个例子.
验证码通常服务是独立出去的(废话), 工作流程通常分为几步:
判断是否需要进行验证
获取验证码图片
校验用户输入结果
更新/清空错误次数
上面每一步, 都需要应用服务器与验证码服务交互, 通常只需在第一步进行降级. 这里截取部分代码
@coroutine
def service_down_captcha_img(*args, **kwargv):
result = ''
with open('./assets/captcha_unreachable.png', 'rb') as f:
result = f.read()
raise Return(result)
@coroutine
def service_down_captcha_check(*args, **kwargv):
raise Return(None)
service_run_time_stat = {}
def async_sevice_downgrade(service_name, side_effect):
def downgrade(func):
@coroutine
@wraps(func)
def wrapped(*args, **kwargv):
service = service_run_time_stat.get(service_name, {'t':[], 'f':[], 'd': False})
now = int(time.time())
window = 300
down = False
if (service['f'] and len(service['t']) + len(service['f']) > 10) or len(service['t']) > 10000:
service['t'] = [i for i in service['t'] if i > now - window]
service['f'] = [i for i in service['f'] if i > now - window]
if len(service['f']) * 100 / (len(service['t']) + len(service['f'])) > 10:
down = True
if not service['d']:
log.exception('service %s down', service_name)
service['d'] = down
if down:
service_run_time_stat[service_name] = service
ret = yield side_effect(*args, **kwargv)
raise Return(ret)
else:
try:
ret = yield func(*args, **kwargv)
except RetValueNotExpected:
service['t'].append(now)
service_run_time_stat[service_name] = service
raise
except Exception:
service['f'].append(now)
service_run_time_stat[service_name] = service
raise
else:
service['t'].append(now)
service_run_time_stat[service_name] = service
raise Return(ret)
return wrapped
return downgrade
@async_sevice_downgrade('captcha', service_down_captcha_check)
@coroutine
def check_captcha(user, code, client, validate=False):
"""验证码校验
Arguments:
user {string} -- 用户名
code {string} -- 输入的验证码字符
client {string} -- 用户客户端标识
Keyword Arguments:
validate {bool} -- 是否强制校验 (default: {False})
Raises:
RetValueNotExpected -- 校验不通过
远程api返回结果:
error = 0 通过
error = 1 参数错误
error = 2 验证码错误或没有输入
"""
comment = '验证码校验'
url = base_url + 'captcha'
params = {
'project': project_name,
'user': user,
'pysessid': client,
'code': code
}
if validate:
params.update({'validate': True})
ret = yield get(url, params=params, comment=comment)
if ret['error'] != 0:
raise RetValueNotExpected(ret['msg'])
在上面的例子中, 主要起作用的是async_sevice_downgrade
这个装饰器. 因为我们使用的是tornado和tornado的asynchttpclient, 所以代码中可以见到coroutine.
主要原理就是维护一个全局变量, 当captcha这个服务的请求报了异常, 则将其添加到f列表内, 当登录函数调用验证码服务时, 装饰器会先判断最近错误请求是否达到阈值. 当达到阈值时, 直接调用side_effect 函数. 如果接口功能是验证用户输入, 则直接返回用户验证码正确, 如果接口是请求验证码图片, 则返回一个”验证码服务不可以, 请重新登录”的图片, 引导用户继续操作.