Redis是应对高并发的常用工具,在常用缓存技巧中讲过相关技巧。但有些业务场景,使用Redis会遇到问题,如电商里的秒杀、扣减库存等。
拿减库存举例,一般需要两步:
-
先扣减库存,获取扣减后的库存值V
-
如果V小于0,说明库存不够,需要将扣减的值再加回去;如果V大于等于0,则执行后续操作
但这两步是分开的,很可能扣减时成功,但增加回去时失败,导致库存不一致。
另一种方案是:
-
t1时刻,先查询库存,判断是否够用
-
t2时刻,再减库存
但这两步也是分开的,而且t1和t2有时间差,t2时刻扣减库存时,真正的库存和t1时刻已经不一致了。
那如何保证一致性呢?go-redis又如何来解决这个问题呢?
redis中lua基本介绍
在Redis中通过lua脚本操作Redis,脚本会将多个命令和操作当成一个命令在Redis中执行,也就是说该脚本在执行的过程中,不会被任何其他脚本或命令打断干扰。正是因此这种原子性,lua脚本才可以代替multi和exec的事务功能。同时也是因此,在lua脚本中不宜进行过大的开销操作,避免影响后续的其他请求的正常执行。
go-redis中lua使用
废话不多说,直接上代码!!!!!!!!
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
func main() {
// 创建 Redis 客户端连接
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis 服务器地址
Password: "", // Redis 访问密码,如果没有设置密码则为空字符串
DB: 0, // 选择数据库,默认为0
})
// 定义 Lua 脚本
luaScript := `
local key = KEYS[1]
local value = redis.call('GET', key)
if value then
redis.call('INCR', key)
return redis.call('GET', key)
else
return redis.error_reply("key does not exist")
end
`
// 初始化键的值
rdb.Set(ctx, "mykey", 10, 0)
// 执行 Lua 脚本
result, err := rdb.Eval(ctx, luaScript, []string{"mykey"}).Result()
if err != nil {
fmt.Println("Lua script execution error:", err)
return
}
// 输出 Lua 脚本执行结果
fmt.Println("Script result:", result)
}
结论
使用 go-redis
执行 Lua 脚本可以大大增强 Redis 的功能,确保数据操作的原子性和一致性,提高系统的性能和可靠性。通过这种方式,可以在 Redis 中实现更复杂的业务逻辑,同时简化客户端代码,提高系统的整体效率。