服務降級 nginx+lua
服務降級是什麼
服務降級是當伺服器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放伺服器資源以保證核心任務的正常執行。過程就是丟卒保帥,有些服務是無法降級的,比如支付。
為什麼要服務降級
當我們的伺服器壓力劇增為了保證核心功能的可用性 ,而選擇性的降低一些功能的可用性,或者直接關閉該功能。這就是典型的丟車保帥了。 就比如貼吧型別的網站,當伺服器吃不消的時候,可以選擇把發帖功能關閉,註冊功能關閉,改密碼,改頭像這些都關了,為了確保登入和瀏覽帖子這種核心的功能
降級的原理:就是降低次要功能的可用性實用性,增加核心功能的高可用性。
怎麼降級
降級實現過程原理:利用一個降級開關,以這個開關為判斷依據,切換資料的獲取方式,比如當mysql負載高的時候,可以從mysql切換到redis,比如從redis切換到靜態檔案,比如從錯誤頻發的新版本切換到老版本等等。 這個開關是根據現狀來配置的,比如當新版本錯誤頻發的時候,我們可以配置這個開關為從老版本獲取資料。
1.降級的種類
1.1 根據降級的開關位置:分為服務程式碼降級、前置降級
程式碼降級就是利用程式碼控制,這種方式比前置降級效果差,並不推薦
前置降級是把降級開關放到了http請求鏈路層的上游,降低鏈路層消耗。比如提升到nginx,甚至可以提升到前端。當提升到前端,後端訪問壓力接近於0
可以通過一個伺服器獲取js指令碼進行控制
3.2根據讀寫:分為讀降級、寫降級
讀降級:比如,讀取動態資料改為讀取快取、靜態資料
寫降級:比如,寫入MySQL,降級為寫入訊息佇列,等高峰期過後,再從佇列中寫入MySQL
3.2根據降級的性質:分為返回內容降級、限流降級、限速降級
返回內容降級:比如,返回實時資料,降級為返回兜底資料
限流降級:比如,1000個請求,降級為接受500個請求
限速降級:比如,將訪問頻繁的ip進行限速
限流降級、限速降級可以使用nginx的模組
ngx_http_limit_req_module
和ngx_http_limit_conn_module
3.2根據降級的維護特點:分為手動降級、自動降級
手動降級:人為看到系統負載異常後,手動調整降級
自動降級:是系統監測到異常後,自動降級,自動降級雖然更加智慧,但有時候自動指令碼可能會幹一些超乎預料的事情
實操
廣告推薦模組業務如下:
大家知道,廣告推薦模組的特點:
1 是要經過對資料模型進行大量分析,並結合使用者剛剛的瀏覽記錄,計算出使用者喜歡喜歡什麼商品,然後給他打什麼廣告,運算量相當大。
2 是廣告推薦模組不是商城的核心模組,沒有了這個模組, 買家照樣可以完成商品的購買。
總結上面兩點, 我們可以在商城負載過高時,對廣告推薦模組進行降級,讓它只從快取或靜態檔案中讀取資料,或者乾脆nginx不返回任何資料給它。
設計分析:
需要降級的介面有很多,一般是和產品以及市場一起確定的,依據功能的重要程度, 不重要的功能就可以為它配置降級,我們這裡挑選廣告推薦進行實戰
廣告推薦模組分析:
降級的線路圖
詳解:
1 降級配置中心, 用於統一管理所有的微服務的降級開關, 該中心是單獨的伺服器,和微服務伺服器叢集是分開的
2 每個微服務都有一個降級開關。
開關前置:
這個降級開關是個什麼東西,結構如何呢?
實際上是一條條的redis資料, 每條資料就是一個開關。
這條開關資料的結構是這麼設計的:
key:url連結中的請求地址。
value:記錄這個請求從哪裡讀取資料,也就是配置從哪裡查詢資料
實現降級
nginx+lua+redis實現降級
lua-redis擴充套件下載:
連結:https://pan.baidu.com/s/1RXzcTpd7bd9Dfr_uqgjRFw
提取碼:qqai
nginx配置檔案 /nginx.conf
user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
lua_package_path "/www/wwwroot/swoole/lua/5.1/lua-resty-redis/lib/?.lua;;/www/wwwroot/swoole/lua/5.1/lua-resty-redis-cluster/lib/resty‘7/?.lua;;";
lua_package_cpath "/www/wwwroot/swoole/lua/5.1/lua-resty-redis-cluster/lib/libredis_slot.so;;";
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 7999;
server_name localhost;
#獲取廣告推薦資料 goods_list_advert_from_data
location /goods_list_advert {
default_type 'application/x-javascript;charset=utf-8';
content_by_lua_file /www/wwwroot/lua/goods_list_advert.lua;
}
location /goods_list_advert_from_data {
default_type 'application/x-javascript;charset=utf-8';
content_by_lua '
ngx.say("從伺服器mysql獲取資料")
';
}
# location /lua_redis {
# content_by_lua_file /etc/nginx/lua/lua_redis.lua;
# }
# #這次的操作,主要是加入了下面這幾行程式碼
# location /lua_redis_sku_num {
# #將要執行的lua操作的redis庫存程式碼封裝在/etc/nginx/lua/lua_redis.lua中
#
# content_by_lua_file /etc/nginx/lua/lua_redis_sku_num.lua;
# }
}
}
lua 降級程式碼 /www/wwwroot/lua/goods_list_advert.lua
---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by bogiang.
--- DateTime: 2021/8/4 16:38
---
local redis = require("resty.redis");
--獲取get 或post 引數--
local request_method = ngx.var.request_method
local args = nil
local param = nil
--獲取引數值
if "GET" == request_method then
args=ngx.req.get_uri_args()
elseif "POST" == request_method then
ngx.req.read_body()
args=ngx.req.get_post_args()
end
sku_id=args["sku_id"]
--關閉redis的函式--------------------
local function close_redis(redis_instance)
if not redis_instance then
return
end
local ok,err = redis_instance:close();
if not ok then
ngx.say("close redis error : ",err);
end
end
-- 建立一個redis物件例項。在失敗,返回nil和描述錯誤的字串的情況下
local redis_instance = redis:new();
--設定後續操作的超時(以毫秒為單位)保護,包括connect方法
redis_instance:set_timeout(1000)
--建立連線
local ip = '127.0.0.1'
local port = 6379
--嘗試連線到redis伺服器正在偵聽的遠端主機和埠
local ok,err = redis_instance:connect(ip,port)
if not ok then
ngx.say("connect redis error : ",err)
end
--從redis裡面讀取開關--------------------
local key = "level_goods_list_advert"
local switch, err = redis_instance:get(key)
if not switch then
ngx.say("get msg error : ", err)
return close_redis(redis_instance)
end
--得到的開關為空處理--------------------
if switch==ngx.null then
switch="FROM_DATA" --比如預設值
end
if "FROM_DATA" == switch then
ngx.exec('/goods_list_advert_from_data')
elseif "FROM_CACHE" == switch then
local resp, err = redis_instance:get("nihao")
ngx.say(resp)
elseif "FROM_STATIC" == switch then
ngx.header.content_type="application/x-javascript;charset=utf-8"
local file = "/tmp/goods_list_advert.json"
local f =io.open(file)
local content = f:read("*all")
f:close()
ngx.print(content)
elseif "SHUT_DOWN" == switch then
ngx.say('no data')
elseif "NIMABI" == switch then
ngx.say('error')
ngx.var.status="400"
end
--判斷錯誤的響應,並進行計數, 後續便可以參考這個數值進行降級
if tonumber(ngx.var.status) == 200 then
local count_key="error_count_goods_list_advert"
ngx.say(ngx.var.status)
ngx.log(ngx.ERR,"upstream reponse status is " .. ngx.var.status .. ",please notice it")
local error_count,err = redis_instance:get(count_key)
if error_count == ngx.null then
error_count=0
end
error_count = error_count+1
local resp,err = redis_instance:set(count_key,error_count)
if not resp then
ngx.say("set msg error : ",err)
return close_redis(redis_instance)
end
end