一:服务限流功能点
1:根据请求入参中的服务标识判断nginx后端服务是否处于流量限制中。如果是,则全部限制访问,否则,转发请求到后端服务。
2:容错机制,如果Redis宕机等异常,限流模块失效,所有客户端请求放行。
3:是否开启限流,及限流类型(AF:全部请求限制访问,PF:设置阈值,每秒限制请求多少次)可热加载。
二:设计思路
1:在Reids中设置服务键值标识,Y标识限速,其它表示不限速。
2:在Nginx中获取请求体,判断服务标识是否流量控制中,如果是,即拦截请求返回403,否则通过。
3:如果在连接Redis和从Redis中读取数据时发生异常,则跳出限流模块,转发请求到服务端。
三:PF(服务降级)
由于上一章节以完成AF(服务下线)的设计说明:https://blog.csdn.net/qq_35723073/article/details/87930011
这次主要说明PF(服务降级)的设计说明。
申请Nginx共享内存,存储限流类型标识limit_flag(NF:不限流、AF:服务下线、PF:服务降级)。
默认为NF。当limit_flag为空或NF时即正常处理业务请求。
四:逻辑流程
五:核心设计说明
在Redis中根据服务名设置一个键(incr),并设置失效时间(expire)。
例如: incr SRV_NAME EXPIRE SRV_NAME 60
再设置一个键值表示最大调用量。 例如 set count 10
这样,当客户端每次调用此服务时,incr将自动加1,当在60s内大于10笔时,将会被限流。
备注:Redis中支持lua代码执行,需要把自增键和计数的逻辑在Redis中执行,才能保证事务的唯一性。因为Redis是单进程的。可以使用eval()方法。
六:代码实现
由于此文章只为大家分享一个设计思路,且博主代码涉及公司的业务。只能贴出博主设计初期的部分代码。
local my_eval_str = [==[
local prestr = "LIMIT_RATE"
local srv_name = ARGV[1]
local flag = ARGV[2]
local retmsg = "0:OK"
local call_counts = 0
local mykey = ''
local key_time = ''
local key_counts = ''
local sf = string.format
if 'F1'==flag then
mykey = sf("%s.%s.%s", prestr,flag,srv_name)
key_time = sf("%s.%s.%s.%s",prestr,flag,srv_name,"TIME")
key_counts= sf("%s.%s.%s.%s",prestr,flag,srv_name,"COUNTS")
else
return retmsg
end
local r = redis.pcall('GET', key_time)
if not r then
retmsg = sf("-1:%s not set value",key_time)
return retmsg
end
local key_time_value = tonumber(r)
r = redis.pcall('GET', key_counts)
if not r then
retmsg = sf("-1:%s not set value",key_counts)
return retmsg
end
local key_counts_value = tonumber(r)
if 0 == key_counts_value then
return sf("1:%s is 0",key_counts)
end
r = redis.pcall('INCR', mykey)
if type(r) == "number" then
if r == 1 then
r = redis.pcall('EXPIRE', mykey, key_time_value)
call_counts = 1
else
call_counts = r
end
if call_counts>key_counts_value then
retmsg = sf("1:%s must be forbidden[%s>%s]",mykey,call_counts,key_counts_value)
else
retmsg = sf("0:%s you can access[%s<%s]",mykey,call_counts,key_counts_value)
end
else
retmsg = sf("-1:%s incr err",mykey)
end
return retmsg
]==]
res,err = red:eval(my_eval_str,0,srv_name,flag)