【併發優化】一、OpenResty 結合 Lua、Redis 實現請求高併發策略

目錄

一、整體架構描述

1.1 方案v1.0

1.2 方案v1.0優化

1.3 方案對比

二、方案2.0部署

2.1 OpenResty 安裝

2.2 Lua腳本編寫

2.3 配置 OpenResty

三、測試


一、整體架構描述

1.1 方案v1.0

在優化舊的設計框架前,先看一下常見的查詢請求處理

  

步驟如下:

  • ① 請求到 Nginx 或 restApi 服務後, 向 Redis 請求數據
  • ②、③ Redis 查看緩存中是否有數據,若有數據則直接進入流程⑥,沒有則進入流程④
  • ④ 、⑤ 查詢數據庫數據,並更新數據到 Redis ,以免下次還需要訪問數據庫
  • ⑥ 返回數據給用戶

 

1.2 方案v1.0優化

在方案v1.0,存在了大量簡單的請求,如在IM軟件中查詢用戶/羣組信息、在商城項目中查詢商品信息等,這些簡單的查詢卻頻繁的查詢無疑會對服務造成一定壓力。那麼是否有辦法進行優化呢。

對方案v1.0進行優化後,方案v2.0結合了 OpenResty、Lua、Redis 實現了二級緩存。利用 Nginx 高併發的特性,使得上述這些請求無需再通過 restAPI 服務,而是通過Lua腳本直接查詢和操作 redis 和 mysql,降低服務壓力。

  

步驟如下:

  • ①、② 請求到 Nginx 後,Nginx 先查詢 Nginx-Cache,若緩存中沒有數據,則調用對應的 Lua 腳本
  • ③、④ Lua 腳本查詢 Redis,若Redis 緩存中有數據則直接返回並更新Nginx緩存, 沒有數據則調用 Lua腳本,查詢數據庫
  • ⑤ 查詢數據庫數據並更新Redis緩存

注意:

方案v2.0 採用的是逐級緩存的方式:

  • 第一次訪問:nginx 和 redis 中均沒有緩存,數據從數據庫中查出,並存入 redis 緩存
  • 第二次訪問:nginx 中沒有緩存,數據從 redis 中查出,並存入 nginx 緩存
  • 第三次訪問:數據從 nginx 中查出

這樣做有以下原因:

  • nginx 緩存過期時間小於 redis 緩存過期時間,有利於降低 redis 雪崩的危險
  • 若同時對 nginx 和 redis 設置緩存,則數據訪問到的都是 nginx。當數據庫變化時,無法在不訪問數據庫的情況下實現快速響應。解決方案爲使用 Canal 實現數據庫和 redis 的數據同步,將 nginx 的有效時間設置得更短,做到防止大量併發請求到數據庫的同時,又能拿到最新數據

 

1.3 方案對比

根據不同的項目需求和實際方案,選擇使用哪一種方案。

  方案v1.0 方案v2.0
靈活性 訪問 redis 和數據庫由 restApi 控制,可處理複雜請求。但如果有修改必須重啓服務

利用 Nginx 高併發特性處理大量簡單請求,且修改腳本無需重啓後端服務,只要重新加載 Nginx 即可。

但只能處理簡單的請求,且需要有特殊的識別方式,如加特定的 API

併發性 高,相對來說肯定比方案v1.0高,但要根據實際情況使用,而不是爲了炫技而部署
部署難度 簡單 中等。需要學習 OpenResty框架和Lua語言
適用場景 通用場景

更適用於存在大量簡單的查詢請求的項目,如倉庫管理系統、商城系統等。

同時這個方案還可以處理權限控制,如直接在 Nginx 拒絕不攜帶 token 的請求,或進行 token 驗證等。

 

二、方案2.0部署

2.1 OpenResty 安裝

安裝流程:https://blog.csdn.net/qq_34416331/article/details/106421783

 

2.2 Lua腳本編寫

Lua 的基本用法:https://blog.csdn.net/qq_34416331/article/details/106419100

Lua 腳本編寫:

# 創建存放 lua 腳本的文件夾,名字自定義
mkdir /usr/local/lua_conf

# 進入文件夾
cd /usr/local/lua_conf

# 創建 lua 腳本
vim read_conf.lua
ngx.header.content_type="application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
-- 加載nginx緩存模塊
local cache_ngx = ngx.shared.dis_cache;
-- 根據ID獲取本地緩存數據
local contentCache = cache_ngx:get('content_cache_'..id);

-- 獲取IP信息,可刪除
-- [[
local headers=ngx.header;
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
ngx.say(ip)
ngx.say(contentCache)
]]--

-- 若nginx中沒有相應的緩存信息
if contentCache == "" or contentCache == nil then

    -- 獲取redis模塊
    local redis = require("resty.redis");
    local red = redis:new()
    red:set_timeout(2000)
    red:connect("192.168.47.142", 6379)
    local rescontent=red:get("content_"..id);

    -- 若redis模塊也沒有這個信息
    if ngx.null == rescontent then
        -- 從數據庫中獲取數據
        local cjson = require("cjson");
        local mysql = require("resty.mysql");
        local db = mysql:new();
        db:set_timeout(2000)
        local props = {
            host = "192.168.47.142",
            port = 3306,
            database = "changgou_content",
            user = "root",
            password = "123456"
        }
        local res = db:connect(props);
        local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order";
        res = db:query(select_sql);
        local responsejson = cjson.encode(res);

        -- 存儲到redis中
        red:set("content_"..id,responsejson);

        -- 返回數據
        ngx.say(responsejson);
        -- 關閉數據庫連接
        db:close()
    else
        -- 若redis中有緩存,則設置的到nginx緩存中,並返回
        -- 2*60 表示設置 nginx 緩存時間爲2分鐘,應根據實際情況修改
        cache_ngx:set('content_cache_'..id, rescontent, 2*60);
        ngx.say(rescontent)
    end
    -- 關閉redis連接
    red:close()
else
-- 若nginx中有對應的信息,則返回
    ngx.say(contentCache)
end

 

2.3 配置 OpenResty

要讓 OpenResty 來使用 Lua 腳本,只需要配置 Nginx 的配置文件即可。

### 修改 Nginx 配置文件 ###
# 進入 OpenResty 自帶的 Nginx 目錄
cd /usr/local/openresty/nginx/conf

# 編輯配置文件
vim nginx.conf

  

 lua_shared_dict 的作用是聲明一個共享內存區域 name,以充當基於 Lua 字典的共享存儲。簡單來說就是當nginx運行時的 lua 腳本緩存空間大小

  

在http中配置要訪問的接口:

  

重啓 nginx

# 重新加載 nginx
/usr/local/openresty/nginx/sbin/nginx -s reload

 

三、測試

修改 read_content.lua

ngx.header.content_type="application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
-- 加載nginx緩存模塊
local cache_ngx = ngx.shared.dis_cache;
-- 根據ID獲取本地緩存數據
local contentCache = cache_ngx:get('content_cache_'..id);

--[[
local headers=ngx.header;
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
ngx.say(ip)
ngx.say(contentCache)
]]--

if contentCache==nil then
  ngx.say("nginx緩存爲空")
else 
  ngx.say("從nginx緩存中查到了數據")
end

-- 若nginx中沒有相應的緩存信息
if contentCache == "" or contentCache == nil then

    -- 獲取redis模塊
    local redis = require("resty.redis");
    local red = redis:new()
    red:set_timeout(2000)
    red:connect("192.168.47.142", 6379)
    local rescontent=red:get("content_"..id);

    if ngx.null == rescontent then
       ngx.say("redis爲空")
    else
       ngx.say("nginx爲空,從redis中查到了數據")
    end
    -- 若redis模塊也沒有這個信息
    if ngx.null == rescontent then
        -- 從數據庫中獲取數據
        local cjson = require("cjson");
        local mysql = require("resty.mysql");
        local db = mysql:new();
        db:set_timeout(2000)
        local props = {
            host = "192.168.47.142",
            port = 3306,
            database = "changgou_content",
            user = "root",
            password = "123456"
        }
        local res = db:connect(props);
        local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order";
        res = db:query(select_sql);
        local responsejson = cjson.encode(res);

        -- 存儲到redis中
        red:set("content_"..id,responsejson);

        -- 返回數據
        ngx.say(responsejson);
        -- 關閉數據庫連接
        db:close()
    else
        -- 若redis中有緩存,則設置的到nginx緩存中,並返回
        cache_ngx:set('content_cache_'..id, rescontent, 30);
    end
    -- 關閉redis連接
    red:close()
else
-- 若nginx中有對應的信息,則返回
    ngx.say(contentCache)
end

清理redis

訪問:

第一次訪問

第二次訪問:

第三次訪問:

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章