赞
踩
实验环境:
redis: 6.0.9
我们知道, 使用lua脚本可以在执行一串redis命令时, 实现一定原子性(lua脚本中多条指令执行过程中不会被插入新的指令), 但是并不能在命令执行出错时回滚之前的结果, 如下示例:
demo.lua
redis.call('get', 'xx')
redis.call('set', 'a1', 'b1')
redis.call('set', 'a2')
显然最后的set a2
是有语法错误的, 在执行前先确认下数据库是空的:
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
然后执行下demo.lua(测试用的redis-server密码是123456):
$ redis-cli -a '123456' --eval demo.lua
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
(error) ERR Error running script (call to f_c49b51d8e861ccfd226dedb5ed80d1bd3c257235): @user_script:7: @user_script: 7: Wrong number of args calling Redis command From Lua script
上述demo.lua最后一条命令执行报错了, 我们看下数据库, 发现set a1 b1
还是写进去了, 并没有回退
127.0.0.1:6379> keys *
1) "a1"
127.0.0.1:6379> get a1
"b1"
127.0.0.1:6379>
小结:
redis在执行lua脚本在执行出错(往往是语法错误: 如参数个数不对, 类型不对)时, 已经执行的结果不会回滚.
为了避免一些低级的错误, 我们在使用编程语言(如go, java)中利用lua脚本实现一些较复杂的功能时, 需要对lua脚本先进行调试. redis-cli工具自带了调试功能. 用一个例子来说明:
简单介绍下需求:
红包活动, 业务上需要控制红包个数(每个红包多少钱由业务上控制, 脚本不用管).
保证不能发超, 且每人最多只能领一次红包
测试用的脚本 test.lua:
-- 剩余红包个数 local rpRemainedKey = KEYS[1]; -- 已领红包 hash local userAwardedKey = KEYS[2]; -- 领取红包的用户id local userID = ARGV[1]; -- 领取红包实际多少钱 local money = ARGV[2]; -- 判断用户是否已经领过红包了 local userAwarded = redis.call("HGET", userAwardedKey, userID); if userAwarded then return -1; end -- 判断红包剩余数 local remained = redis.call("GET", rpRemainedKey); local iRemained = tonumber(remained); if iRemained <= 0 then return -2; end -- 红包数 减一 iRemained = redis.call("DECR", rpRemainedKey); redis.call("HSET", userAwardedKey, userID, money); -- 返回还剩余多少个红包 return iRemained
请注意redis-cli eval时的格式: KEYS 和 ARGV是通过","来分隔的
$ redis-cli -a '123456' --eval test.lua rp-remained user-awarded , zhangsan 88
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
(error) ERR Error running script (call to f_359ab1f8ebcc70ab7f608a16e245baecf9b6bcdf): @user_script:22: user_script:22: attempt to compare nil with number
报错说是: 22行的if iRemained <= 0 then
将nil和number比较, 说明GET rp-remained
是nil
, 也就是没有在redis-server中设置红包个数, 通过SET rp-remained 5
设置5个红包再执行上述命令:
$ redis-cli -a '123456' --eval test.lua rp-remained user-awarded , zhangsan 88
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
(integer) 4
说明发红包成功, 目前红包剩余4个.
再执行一次, 发现用户已领红包, 直接返回-1
$ redis-cli -a '123456' --eval test.lua rp-remained user-awarded , zhangsan 88
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
(integer) -1
关于redis使用lua debugger的更多信息, 看这里: Redis Lua scripts debugger
注意下面的命令加了–ldb选项
我们这里来演示下:
$ redis-cli -a '123456' --ldb --eval test.lua rp-remained user-awarded , zhangsan 88 Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. Lua debugging session started, please use: quit -- End the session. restart -- Restart the script in debug mode again. help -- Show Lua script debugging commands. * Stopped at 2, stop reason = step over -> 2 local rpRemainedKey = KEYS[1]; lua debugger> s * Stopped at 5, stop reason = step over -> 5 local userAwardedKey = KEYS[2]; lua debugger> s * Stopped at 8, stop reason = step over -> 8 local userID = ARGV[1]; lua debugger> s * Stopped at 11, stop reason = step over -> 11 local money = ARGV[2]; lua debugger> s * Stopped at 14, stop reason = step over -> 14 local userAwarded = redis.call("HGET", userAwardedKey, userID); lua debugger> p money <value> "88" lua debugger> s <redis> HGET user-awarded zhangsan <reply> "88" * Stopped at 15, stop reason = step over -> 15 if userAwarded then lua debugger> p userAwarded <value> "88" lua debugger> s * Stopped at 16, stop reason = step over -> 16 return -1; lua debugger> s (integer) -1 (Lua debugging session ended -- dataset changes rolled back) 127.0.0.1:6379>
熟悉gdb或者dlv的同学应该对上述调试界面感觉非常亲切, 这里直接体验下效果更好.
更多调试指令, 通过在 lua debugger>
下输入help来查看.
先说下结论: lua中只有false
和nil
是假值, 其他都是真值, 测试如下:
test.lua:
function judge(v) if v then print(v, "is true value") else print(v, "is false value") end end judge(0) judge(1) judge(-1) judge(nil) judge(false) judge(true) judge({}) judge("") judge("abc")
运行看看:
$ lua test.lua
0 is true value
1 is true value
-1 is true value
nil is false value
false is false value
true is true value
table: 0x87c420 is true value
is true value
abc is true value
上述脚本我们在自己开发机上调试好了, 最终还是要用其他语言来调的, 这里以go语言为例子:
跑go代码之前先将数据复原:
127.0.0.1:6379> keys *
1) "rp-remained"
127.0.0.1:6379> get "rp-remained"
"5"
main.go:
package main import ( "context" "fmt" "log" "sync" "github.com/go-redis/redis/v8" ) var ( ctx = context.Background() rc *redis.Client ) const rpLuaScript = ` -- 剩余红包个数 local rpRemainedKey = KEYS[1]; -- 已领红包 hash local userAwardedKey = KEYS[2]; -- 领取红包的用户id local userID = ARGV[1]; -- 领取红包实际多少钱 local money = ARGV[2]; -- 判断用户是否已经领过红包了 local userAwarded = redis.call("HGET", userAwardedKey, userID); if userAwarded then return -1; end -- 判断红包剩余数 local remained = redis.call("GET", rpRemainedKey); local iRemained = tonumber(remained); if iRemained <= 0 then return -2; end -- 红包数 减一 iRemained = redis.call("DECR", rpRemainedKey); redis.call("HSET", userAwardedKey, userID, money); -- 返回还剩余多少个红包 return iRemained ` func main() { InitRedisCli() var wg sync.WaitGroup for i := 0; i < 10000; i++ { userID := fmt.Sprintf("zhangsan:%d", i%10) // zhangsan0 - zhangsan9 每个人分别请求100次, 共1000次 money := i + 88 wg.Add(1) go func() { defer wg.Done() cmd := rc.Eval(ctx, rpLuaScript, []string{"rp-remained", "user-awarded"}, userID, money) remained, err := cmd.Int() // 因为lua脚本中return的是int, 所以这里转为int if err != nil { log.Printf("userID:%v, money:%v, err:%\n", userID, money, err) return } if remained >= 0 { log.Printf("success: userID:%v, money:%v, after get rp, remained:%v\n", userID, money, remained) } else { log.Printf("failed: userID:%v, money:%v, after get rp, remained:%v\n", userID, money, remained) } }() } wg.Wait() } func InitRedisCli() { rc = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "123456", // no password set }) if err := rc.Ping(ctx).Err(); err != nil { panic(err) } }
实验步骤:
go run main.go 2>&1 | tee main.log
127.0.0.1:6379> get "rp-remained"
"0"
127.0.0.1:6379> hgetall "user-awarded"
1) "zhangsan:4"
2) "92"
3) "zhangsan:5"
4) "93"
5) "zhangsan:7"
6) "115"
7) "zhangsan:9"
8) "137"
9) "zhangsan:2"
10) "90"
127.0.0.1:6379>
$ grep success main.log
2022/01/26 16:46:33 success: userID:zhangsan:4, money:92, after get rp, remained:4
2022/01/26 16:46:33 success: userID:zhangsan:5, money:93, after get rp, remained:3
2022/01/26 16:46:33 success: userID:zhangsan:7, money:115, after get rp, remained:2
2022/01/26 16:46:33 success: userID:zhangsan:9, money:137, after get rp, remained:1
2022/01/26 16:46:33 success: userID:zhangsan:2, money:90, after get rp, remained:0
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。