一、GO调用核心函数
这里用go代码来展示调用,首先是核心函数介绍:
go
func NewScript(src string) *redisv9.Script {
func (s *Script) Load(ctx context.Context, c Scripter) *StringCmd {
func (c cmdable) EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd {
第一个函数传入 lua脚本字符串,然后生成 redisv9.Script 对象
第二个函数 是通过redisv9.Script 对象的load函数 将对象存的 lua脚本字符串,传到 redis节点, 同时返回 sha,和error
第三个 redis.EvalSha 通过 sha值来确定 redis存的lua脚本,keys是传入的key值, args是参数。
keys再lua代码里是 KEYS, args在lua代码 是ARGS
直接上代码:
go
func TestRedisLua(t *testing.T) {
InitRedis() // 注意这个是我本地写的链接redis的函数,读者需要自己写(这里的 db.RedisCon2 就是 *redis.Client)
// 这个是要执行的lua脚本
luaScript = `return "keys="..KEYS[1] .. "|" ..KEYS[2].. "\nargs=" ..ARGV[1].. "|" ..ARGV[2]`
redisScript := redis.NewScript(luaScript) // 初始化脚本
// 将脚本传入reids节点
sha, err := redisScript.Load(context.TODO(), db.RedisCon2.GetClient()).Result()
if err != nil {
appzaplog.Error("redis load err", zap.Error(err))
return
}
ret, retErr := db.RedisCon2.GetClient().EvalSha(context.Background(), sha, []string{
"v1:confession_twall",
"v1:confession_pwall:rec:1000001",
}, 1758273500, "3331").Result()
if retErr != nil {
appzaplog.Error("redis eval err", zap.Error(retErr))
return
}
fmt.Println(ret.(string))
end
执行结果
干货:
- **测试用法 :**测试的时候这三个函数其实也可以用一个函数替代:
直接传入 lua脚本字符串,key,和参数即可
go
func (c cmdable) Eval(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd {
- 正式用法 NewScript 和 Load的作用是为了缓存对象,在频繁调用的时候可以显著减少 因构建对象的产生的消耗;NewScript 是go对象构建, Load是redis里的lua对象构建。
正常代码写法(伪代码)
go
var Sha1 = ""
// 判断 sha值是否过期
ret, err := db.RedisCon2.GetClient().ScriptExists(context.TODO(), Sha1 ).Result()
if err != nil{
...
}
if ret == false{ // sha值过期了,重新Load(redisScript 之前初始化过后,不用二次初始化)
Sha1 , err = redisScript.Load(context.TODO(), db.RedisCon2.GetClient()).Result()
if err != nil {
appzaplog.Error("redis load err", zap.Error(err))
return
}
}
// 执行EvalSha操作
二、高级技巧:debug调试(linux端)
1.调试命令
go
redis-cli --ldb-sync-mode --eval lua脚本路径 key1 key2 , 参数1 参数2
redis-cli --ldb --eval lua脚本路径 key1 key2 , 参数1 参数2
1.1坑点讲解
- redis-cli --ldb-sync-mode 是阻塞的 可以断点调试,但是生产环境不要用,内网也别用(可能会被骂),最好用本地的
- redis-cli --ldb 不阻塞,但是这个不能断点
- 这个点比较离谱 逗号(,)用来区分可变参数 KEYS 和 ARGV的,但是这个逗号前后一定要空格,不要识别不了
2. 示例:
2.1构造数据
构造完成后 含 Content ReqTime的数据在最左边
bash
redis-cli # 进入redis,插入数据
lpush KEYAAA 111 222 333
lpush KEYBBB 111 222 333
lpush KEYAAA {"BlackFlag":0,"RecId":1000001,"SendId":1000013,"Content":"3331","ReqTime":1758273500}
lpush KEYBBB {"BlackFlag":0,"RecId":1000001,"SendId":1000013,"Content":"3332","ReqTime":1758273500}
2.1 编写lua代码:
代码大意:搜索两个KEY中LIST元素,满足ReqTime 和 Content 和参数一致,且没有被处理过的元素。搜索到后将其删除并在最右边生成(实现右移)
lua
local function DelInfo(delKey, reqTime, content)
local len = redis.call('LLEN', delKey)
for i = 0, len - 1 do
local element = redis.call('LINDEX', delKey, i)
local ok, data = pcall(cjson.decode, element)
if ok and type(data) == 'table' then
-- 条件判断
if tostring(data.ReqTime) == reqTime and data.Content == content and data.BlackFlag == 0 then
-- 设置 BlackFlag = 1
data.BlackFlag = 1
-- 重新序列化并更新该位置元素
local new_element = cjson.encode(data)
redis.call('LREM', delKey, 1, element) -- 删除原数据 (json相等的字符串绝对包含》decode后三个字段满足的元素)
redis.call('RPUSH', delKey, new_element) -- 放入最右边
return data.SendId
end
else
return 6
end
end
return nil
end
local function CallDel()
local sendId = DelInfo(KEYS[1], ARGV[1], ARGV[2])
if sendId == nil then
return 1
end
sendId = DelInfo(KEYS[2], ARGV[1], ARGV[2])
if sendId == nil then
return 2
end
return 0
end
return CallDel()
2.2 尝试执行
bash
redis-cli --ldb-sync-mode --eval confess_lua.lua KEYAAA KEYBBB , 1758273500 3331
c # 回车
正确的效果应该是:
含 Content ReqTime的数据在最左边 ,但发现KEYAAA 是正常的,但是KEYBBB没有变化(其实故意造错数据,方便用调试测试)
2.3 调试
先重新构建数据:(每次调试都执行一遍)
bash
redis-cli # 进入redis,插入数据
del KEYAAA
del KEYBBB
lpush KEYAAA 111 222 333
lpush KEYBBB 111 222 333
lpush KEYAAA {"BlackFlag":0,"RecId":1000001,"SendId":1000013,"Content":"3331","ReqTime":1758273500}
lpush KEYBBB {"BlackFlag":0,"RecId":1000001,"SendId":1000013,"Content":"3332","ReqTime":1758273500}
进入调试:
bash
redis-cli --ldb-sync-mode --eval confess_lua.lua KEYAAA KEYBBB , 1758273500 3331
# 下面输入 调试命令后自己加回车
b 8
c # 运行直到遇到断点
p data # 打印变量
p reqTime
p content
# 最后发现 是KEYBBB 运行到 第八行的时候没有继续,打印变量发现是 content == 3331, data.Content == 3332 条件不满足
上面的调试命令应该是
加完断点输入运行(c+enter)后,遇到断点 打印 p data 和 p content,会发现第二次触发断点时打印数值发现content对不上:
原因是构建redis数据的时候
Content 应该是 3331 但是写成了 3332
所以你成功通过调试找到bug所在!!!
下面我们重新构建数据
go
redis-cli # 进入redis,插入数据
del KEYAAA
del KEYBBB
lpush KEYAAA 111 222 333
lpush KEYBBB 111 222 333
lpush KEYAAA {"BlackFlag":0,"RecId":1000001,"SendId":1000013,"Content":"3331","ReqTime":1758273500}
lpush KEYBBB {"BlackFlag":0,"RecId":1000001,"SendId":1000013,"Content":"3331","ReqTime":1758273500}```
然后执行不阻塞命令:
bash
redis-cli --ldb --eval confess_lua.lua KEYAAA KEYBBB , 1758273500 3331
查看redis数据,就发现目标数据已经右移了
调试命令大全:
当你进入调试时,输入命令 help可以看到调试命令
常见调试命令:
s 单步执行 进入函数
n 单步执行 不进入函数
c 继续执行直到遇到断点
b 设置断点输入数字 指定某行,输入 函数指定函数开头,输入0清除所有
l 查看当前断点前后代码
p 打印
r 执行redis命令