← 返回卷宗
編程筆記

使用OpenResty及Redis實現API池,進行動態分配請求及隊列等待及超時處理

為了提供一個完整的實現方案,我們將詳細說明如何使用OpenResty和Redis來構建一個系統,該系統不僅能夠根據QPS限制動態分配請求,還能在超出QPS限制時將額外的請求放入隊列中等待處理,並設置超時機制。

完整實現步驟

1. 環境準備

確保你已經安裝了以下組件:

  • OpenResty:Nginx的一個擴展版本,支持Lua腳本。
  • Redis:用於存儲API QPS限制、計數以及排隊請求的信息。
  • lua-cjson:用於JSON序列化/反序列化。
安裝OpenResty和Redis
# 安裝OpenResty
sudo apt-get update
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:openresty/ppa
sudo apt-get update
sudo apt-get install -y openresty

# 安裝Redis
sudo apt-get install redis-server

# 安裝lua-cjson(如果未包含在OpenResty中)
sudo luarocks install lua-cjson

2. 配置OpenResty

編輯OpenResty配置文件nginx.conf,通常位於/usr/local/openresty/nginx/conf/nginx.conf/etc/openresty/nginx.conf

http {
    lua_shared_dict api_limits 10m; # 用於存儲API QPS限制和計數
    lua_package_path "/path/to/lua/scripts/?.lua;;"; # 包含自定義Lua腳本路徑

    upstream backend_apis {
        server api1.example.com;
        server api2.example.com;
        # 添加更多的API實例
    }

    server {
        listen 80;

        location /api/ {
            access_by_lua_file /path/to/lua_scripts/api_limit.lua; # 處理QPS限制和排隊邏輯
            
            proxy_pass http://backend_apis;
            proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
        }
    }
}

3. 編寫Lua腳本

創建目錄/path/to/lua_scripts/並在其中創建兩個Lua腳本文件:api_limit.luaprocess_queue.lua

api_limit.lua
local redis = require "resty.redis"
local cjson = require "cjson"

local red = redis:new()
red:set_timeout(1000) -- 1秒超時

-- 連接Redis服務器
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
    ngx.log(ngx.ERR, "failed to connect to Redis: ", err)
    return ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end

local api_key = ngx.var.uri
local limit = 100 -- 默認QPS限制,可根據實際需要調整
local current_count = tonumber(red:get(api_key)) or 0

if current_count < limit then
    red:incr(api_key)
    red:expire(api_key, 60) -- 每60秒重置一次計數
else
    local queue_name = "queue:" .. api_key
    local request_info = {
        uri = ngx.var.request_uri,
        timestamp = ngx.time() -- 當前時間戳
    }
    local queued, err = red:rpush(queue_name, cjson.encode(request_info))
    if not queued then
        ngx.log(ngx.ERR, "failed to push to queue: ", err)
        return ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
    end
    return ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
process_queue.lua
local redis = require "resty.redis"
local cjson = require "cjson"

local function process_queue()
    local red = redis:new()
    red:set_timeout(1000)

    local ok, err = red:connect("127.0.0.1", 6379)
    if not ok then
        ngx.log(ngx.ERR, "failed to connect to Redis: ", err)
        return
    end

    local queue_name = "queue:/api/path" -- 替換為你的API路徑
    local timeout_seconds = 30 -- 設置超時時間為30秒

    while true do
        local request_json, err = red:lpop(queue_name)
        if not request_json then
            ngx.sleep(1) -- 如果沒有請求在隊列中,稍作休眠
            goto continue
        end

        local request_info = cjson.decode(request_json)
        local request_time = request_info.timestamp
        local current_time = ngx.time()
        if (current_time - request_time) > timeout_seconds then
            ngx.log(ngx.WARN, "Request timed out and will be discarded: ", request_info.uri)
            goto continue
        end

        -- 發送請求到後端API
        local res = ngx.location.capture("/proxy_backend", { args = { uri = request_info.uri } })
        if res.status ~= ngx.HTTP_OK then
            ngx.log(ngx.ERR, "failed to process queued request: ", request_info.uri)
        end

        ::continue::
    end
end

process_queue()

4. 創建後臺任務腳本

創建一個簡單的Shell腳本或使用cron作業定期運行process_queue.lua腳本。

示例Shell腳本 (run_process_queue.sh)
#!/bin/bash

/usr/local/openresty/bin/resty /path/to/lua_scripts/process_queue.lua

賦予執行權限:

chmod +x /path/to/run_process_queue.sh
使用Cron定時執行

編輯crontab以每分鐘運行一次該腳本:

crontab -e

添加如下行:

* * * * * /path/to/run_process_queue.sh

測試與驗證

  1. 啟動OpenResty
  1. sudo systemctl start openresty
  2. 測試API限流: 使用curl或其他工具向/api/your_api_path發送大量請求,觀察是否按照預期行為進行限流並排隊。
  3. 檢查Redis: 使用redis-cli命令行工具檢查Redis中的數據結構,確保請求被正確地加入隊列並且超時機制正常工作。

總結

通過上述步驟,我們實現了基於OpenResty和Redis的API限流與排隊系統。該系統能夠在超過QPS限制時將請求排隊,並通過後臺任務異步處理這些請求,同時設置了合理的超時機制以避免長時間等待影響用戶體驗。根據具體需求,你可以進一步優化和擴展這個系統。

本文由 三符道長 撰於 2025年2月24日。轉載請註明出處。