一:服務限流功能點
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)