Skip to content

Commit 9108c98

Browse files
Use SHA1 cache with NOSCRIPT fallback for Redis Cluster Lua scripts
Precompute the Lua script SHA1 locally and always execute scripts via EVALSHA to avoid repeated SCRIPT LOAD operations. Add a robust NOSCRIPT fallback to EVAL to ensure compatibility with both resty.redis and resty.rediscluster, especially in Redis Cluster setups where scripts are cached per node. This improves performance and makes script execution resilient to Redis node restarts, failovers, and resharding.
1 parent 8263caa commit 9108c98

File tree

1 file changed

+27
-22
lines changed

1 file changed

+27
-22
lines changed

apisix/plugins/limit-req/util.lua

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
local ngx_now = ngx.now
1818
local tonumber = tonumber
1919
local core = require("apisix.core")
20+
local resty_string = require("resty.string")
2021

2122

2223
local _M = {version = 0.1}
@@ -53,8 +54,24 @@ local script = core.string.compress_script([=[
5354
return {1, new_excess}
5455
]=])
5556

56-
-- SHA1 Cache for script
57-
local script_sha1
57+
-- Pre-calculate SHA1 for EVALSHA optimization
58+
local script_sha1 = resty_string.to_hex(ngx.sha1_bin(script))
59+
60+
61+
local function is_noscript_error(err)
62+
if not err then
63+
return false
64+
end
65+
66+
local s
67+
if type(err) == "table" then
68+
s = tostring(err[1] or err.err or err.message or err.msg or err)
69+
else
70+
s = tostring(err)
71+
end
72+
73+
return s:find("NOSCRIPT", 1, true) ~= nil
74+
end
5875

5976

6077
-- the "commit" argument controls whether should we record the event in shm.
@@ -66,30 +83,18 @@ function _M.incoming(self, red, key, commit)
6683

6784
local commit_flag = commit and "1" or "0"
6885

69-
local res, err
70-
86+
-- Try EVALSHA first (fast path).
87+
local res, err = red:evalsha(script_sha1, 1, state_key,
88+
rate, now, self.burst, commit_flag)
7189

72-
if script_sha1 then
73-
res, err = red:evalsha(script_sha1, 1, state_key,
74-
rate, now, self.burst, commit_flag)
90+
-- If the script isn't cached on this Redis node, fall back to EVAL.
91+
if not res and is_noscript_error(err) then
92+
res, err = red:eval(script, 1, state_key,
93+
rate, now, self.burst, commit_flag)
7594
end
7695

7796
if not res then
78-
if not script_sha1 or (err and err:find("NOSCRIPT", 1, true)) then
79-
local sha1, load_err = red:script("load", script)
80-
if not sha1 then
81-
return nil, "Failed to load script: " .. (load_err or "unknown error")
82-
end
83-
script_sha1 = sha1
84-
85-
res, err = red:evalsha(script_sha1, 1, state_key,
86-
rate, now, self.burst, commit_flag)
87-
if not res then
88-
return nil, err
89-
end
90-
else
91-
return nil, err
92-
end
97+
return nil, err
9398
end
9499

95100
local allowed = tonumber(res[1]) == 1

0 commit comments

Comments
 (0)