1. 程式人生 > 其它 >服務降級 nginx+lua

服務降級 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_modulengx_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


效果