mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-14 02:10:00 +08:00
feat: migrate lua script to lua file (#4069)
This commit is contained in:
@@ -2,6 +2,7 @@ package bloom
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -17,19 +18,15 @@ var (
|
|||||||
// ErrTooLargeOffset indicates the offset is too large in bitset.
|
// ErrTooLargeOffset indicates the offset is too large in bitset.
|
||||||
ErrTooLargeOffset = errors.New("too large offset")
|
ErrTooLargeOffset = errors.New("too large offset")
|
||||||
|
|
||||||
setScript = redis.NewScript(`
|
//go:embed setscript.lua
|
||||||
for _, offset in ipairs(ARGV) do
|
setScript string
|
||||||
redis.call("setbit", KEYS[1], offset, 1)
|
|
||||||
end
|
scriptSet = redis.NewScript(setScript)
|
||||||
`)
|
|
||||||
testScript = redis.NewScript(`
|
//go:embed testscript.lua
|
||||||
for _, offset in ipairs(ARGV) do
|
testScript string
|
||||||
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
|
|
||||||
return false
|
scriptTest = redis.NewScript(testScript)
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -129,7 +126,7 @@ func (r *redisBitSet) check(ctx context.Context, offsets []uint) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := r.store.ScriptRunCtx(ctx, testScript, []string{r.key}, args)
|
resp, err := r.store.ScriptRunCtx(ctx, scriptTest, []string{r.key}, args)
|
||||||
if errors.Is(err, redis.Nil) {
|
if errors.Is(err, redis.Nil) {
|
||||||
return false, nil
|
return false, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@@ -161,7 +158,7 @@ func (r *redisBitSet) set(ctx context.Context, offsets []uint) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = r.store.ScriptRunCtx(ctx, setScript, []string{r.key}, args)
|
_, err = r.store.ScriptRunCtx(ctx, scriptSet, []string{r.key}, args)
|
||||||
if errors.Is(err, redis.Nil) {
|
if errors.Is(err, redis.Nil) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
3
core/bloom/setscript.lua
Normal file
3
core/bloom/setscript.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
for _, offset in ipairs(ARGV) do
|
||||||
|
redis.call("setbit", KEYS[1], offset, 1)
|
||||||
|
end
|
||||||
6
core/bloom/testscript.lua
Normal file
6
core/bloom/testscript.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
for _, offset in ipairs(ARGV) do
|
||||||
|
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
@@ -2,6 +2,7 @@ package limit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -28,20 +29,10 @@ var (
|
|||||||
// ErrUnknownCode is an error that represents unknown status code.
|
// ErrUnknownCode is an error that represents unknown status code.
|
||||||
ErrUnknownCode = errors.New("unknown status code")
|
ErrUnknownCode = errors.New("unknown status code")
|
||||||
|
|
||||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
//go:embed periodscript.lua
|
||||||
periodScript = redis.NewScript(`local limit = tonumber(ARGV[1])
|
periodScript string
|
||||||
local window = tonumber(ARGV[2])
|
|
||||||
local current = redis.call("INCRBY", KEYS[1], 1)
|
scriptPeriod = redis.NewScript(periodScript)
|
||||||
if current == 1 then
|
|
||||||
redis.call("expire", KEYS[1], window)
|
|
||||||
end
|
|
||||||
if current < limit then
|
|
||||||
return 1
|
|
||||||
elseif current == limit then
|
|
||||||
return 2
|
|
||||||
else
|
|
||||||
return 0
|
|
||||||
end`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -82,7 +73,7 @@ func (h *PeriodLimit) Take(key string) (int, error) {
|
|||||||
|
|
||||||
// TakeCtx requests a permit with context, it returns the permit state.
|
// TakeCtx requests a permit with context, it returns the permit state.
|
||||||
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
|
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
|
||||||
resp, err := h.limitStore.ScriptRunCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
|
resp, err := h.limitStore.ScriptRunCtx(ctx, scriptPeriod, []string{h.keyPrefix + key}, []string{
|
||||||
strconv.Itoa(h.quota),
|
strconv.Itoa(h.quota),
|
||||||
strconv.Itoa(h.calcExpireSeconds()),
|
strconv.Itoa(h.calcExpireSeconds()),
|
||||||
})
|
})
|
||||||
|
|||||||
14
core/limit/periodscript.lua
Normal file
14
core/limit/periodscript.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
||||||
|
local limit = tonumber(ARGV[1])
|
||||||
|
local window = tonumber(ARGV[2])
|
||||||
|
local current = redis.call("INCRBY", KEYS[1], 1)
|
||||||
|
if current == 1 then
|
||||||
|
redis.call("expire", KEYS[1], window)
|
||||||
|
end
|
||||||
|
if current < limit then
|
||||||
|
return 1
|
||||||
|
elseif current == limit then
|
||||||
|
return 2
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
@@ -2,6 +2,7 @@ package limit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -20,37 +21,12 @@ const (
|
|||||||
pingInterval = time.Millisecond * 100
|
pingInterval = time.Millisecond * 100
|
||||||
)
|
)
|
||||||
|
|
||||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
var (
|
||||||
// KEYS[1] as tokens_key
|
//go:embed tokenscript.lua
|
||||||
// KEYS[2] as timestamp_key
|
tokenScript string
|
||||||
var script = redis.NewScript(`local rate = tonumber(ARGV[1])
|
|
||||||
local capacity = tonumber(ARGV[2])
|
|
||||||
local now = tonumber(ARGV[3])
|
|
||||||
local requested = tonumber(ARGV[4])
|
|
||||||
local fill_time = capacity/rate
|
|
||||||
local ttl = math.floor(fill_time*2)
|
|
||||||
local last_tokens = tonumber(redis.call("get", KEYS[1]))
|
|
||||||
if last_tokens == nil then
|
|
||||||
last_tokens = capacity
|
|
||||||
end
|
|
||||||
|
|
||||||
local last_refreshed = tonumber(redis.call("get", KEYS[2]))
|
scriptToken = redis.NewScript(tokenScript)
|
||||||
if last_refreshed == nil then
|
)
|
||||||
last_refreshed = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local delta = math.max(0, now-last_refreshed)
|
|
||||||
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
|
|
||||||
local allowed = filled_tokens >= requested
|
|
||||||
local new_tokens = filled_tokens
|
|
||||||
if allowed then
|
|
||||||
new_tokens = filled_tokens - requested
|
|
||||||
end
|
|
||||||
|
|
||||||
redis.call("setex", KEYS[1], ttl, new_tokens)
|
|
||||||
redis.call("setex", KEYS[2], ttl, now)
|
|
||||||
|
|
||||||
return allowed`)
|
|
||||||
|
|
||||||
// A TokenLimiter controls how frequently events are allowed to happen with in one second.
|
// A TokenLimiter controls how frequently events are allowed to happen with in one second.
|
||||||
type TokenLimiter struct {
|
type TokenLimiter struct {
|
||||||
@@ -112,7 +88,7 @@ func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp, err := lim.store.ScriptRunCtx(ctx,
|
resp, err := lim.store.ScriptRunCtx(ctx,
|
||||||
script,
|
scriptToken,
|
||||||
[]string{
|
[]string{
|
||||||
lim.tokenKey,
|
lim.tokenKey,
|
||||||
lim.timestampKey,
|
lim.timestampKey,
|
||||||
|
|||||||
31
core/limit/tokenscript.lua
Normal file
31
core/limit/tokenscript.lua
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-- to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
||||||
|
-- KEYS[1] as tokens_key
|
||||||
|
-- KEYS[2] as timestamp_key
|
||||||
|
local rate = tonumber(ARGV[1])
|
||||||
|
local capacity = tonumber(ARGV[2])
|
||||||
|
local now = tonumber(ARGV[3])
|
||||||
|
local requested = tonumber(ARGV[4])
|
||||||
|
local fill_time = capacity/rate
|
||||||
|
local ttl = math.floor(fill_time*2)
|
||||||
|
local last_tokens = tonumber(redis.call("get", KEYS[1]))
|
||||||
|
if last_tokens == nil then
|
||||||
|
last_tokens = capacity
|
||||||
|
end
|
||||||
|
|
||||||
|
local last_refreshed = tonumber(redis.call("get", KEYS[2]))
|
||||||
|
if last_refreshed == nil then
|
||||||
|
last_refreshed = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local delta = math.max(0, now-last_refreshed)
|
||||||
|
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
|
||||||
|
local allowed = filled_tokens >= requested
|
||||||
|
local new_tokens = filled_tokens
|
||||||
|
if allowed then
|
||||||
|
new_tokens = filled_tokens - requested
|
||||||
|
end
|
||||||
|
|
||||||
|
redis.call("setex", KEYS[1], ttl, new_tokens)
|
||||||
|
redis.call("setex", KEYS[2], ttl, now)
|
||||||
|
|
||||||
|
return allowed
|
||||||
5
core/stores/redis/delscript.lua
Normal file
5
core/stores/redis/delscript.lua
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||||
|
return redis.call("DEL", KEYS[1])
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
6
core/stores/redis/lockscript.lua
Normal file
6
core/stores/redis/lockscript.lua
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
if redis.call("GET", KEYS[1]) == ARGV[1] then
|
||||||
|
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
|
||||||
|
return "OK"
|
||||||
|
else
|
||||||
|
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
|
||||||
|
end
|
||||||
@@ -2,6 +2,7 @@ package redis
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -20,17 +21,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
lockScript = NewScript(`if redis.call("GET", KEYS[1]) == ARGV[1] then
|
//go:embed lockscript.lua
|
||||||
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
|
lockScript string
|
||||||
return "OK"
|
|
||||||
else
|
scriptLock = NewScript(lockScript)
|
||||||
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
|
|
||||||
end`)
|
//go:embed delscript.lua
|
||||||
delScript = NewScript(`if redis.call("GET", KEYS[1]) == ARGV[1] then
|
delScript string
|
||||||
return redis.call("DEL", KEYS[1])
|
|
||||||
else
|
scriptDel = NewScript(delScript)
|
||||||
return 0
|
|
||||||
end`)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A RedisLock is a redis lock.
|
// A RedisLock is a redis lock.
|
||||||
@@ -62,7 +61,7 @@ func (rl *RedisLock) Acquire() (bool, error) {
|
|||||||
// AcquireCtx acquires the lock with the given ctx.
|
// AcquireCtx acquires the lock with the given ctx.
|
||||||
func (rl *RedisLock) AcquireCtx(ctx context.Context) (bool, error) {
|
func (rl *RedisLock) AcquireCtx(ctx context.Context) (bool, error) {
|
||||||
seconds := atomic.LoadUint32(&rl.seconds)
|
seconds := atomic.LoadUint32(&rl.seconds)
|
||||||
resp, err := rl.store.ScriptRunCtx(ctx, lockScript, []string{rl.key}, []string{
|
resp, err := rl.store.ScriptRunCtx(ctx, scriptLock, []string{rl.key}, []string{
|
||||||
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
|
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
|
||||||
})
|
})
|
||||||
if errors.Is(err, red.Nil) {
|
if errors.Is(err, red.Nil) {
|
||||||
@@ -90,7 +89,7 @@ func (rl *RedisLock) Release() (bool, error) {
|
|||||||
|
|
||||||
// ReleaseCtx releases the lock with the given ctx.
|
// ReleaseCtx releases the lock with the given ctx.
|
||||||
func (rl *RedisLock) ReleaseCtx(ctx context.Context) (bool, error) {
|
func (rl *RedisLock) ReleaseCtx(ctx context.Context) (bool, error) {
|
||||||
resp, err := rl.store.ScriptRunCtx(ctx, delScript, []string{rl.key}, []string{rl.id})
|
resp, err := rl.store.ScriptRunCtx(ctx, scriptDel, []string{rl.key}, []string{rl.id})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user