企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ## 概述 网上有很多golang操作redis的例子,我使用`github.com/gomodule/redigo/redis`包写一个关于redis分布式锁的问题。 ## redis分布式锁 锁需要的几个组件 * 获取锁 * 删除锁 分布式锁还需要 * 给锁加上唯一id (只能获取和删除自己的锁) * 给锁加上过期时间 (防止死锁) ## golang代码 推荐使用redis的连接池 ```golang pool := redis.Pool{ MaxIdle: 8, MaxActive: 0, IdleTimeout: 100, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "127.0.0.1:6100") }, } ``` 通过lua脚本保证`获取锁/加过期时间`和`获取锁的值/删除锁`为原子操作 ```golang var ( updateLockExpireUidScript = redis.NewScript(1, ` local res = redis.call("SETNX", KEYS[1], ARGV[1]) if res == 1 then return redis.call("EXPIRE", KEYS[1], ARGV[2]) end return res `) deleteLockByUidScript = redis.NewScript(1, ` local res = redis.call("GET", KEYS[1]) if res == ARGV[1] then return redis.call("DEL", KEYS[1]) end return res `) ) ``` golang完整代码 ```golang package main import ( "fmt" "github.com/gomodule/redigo/redis" "time" ) func getLock(conn redis.Conn, key []string, uid int, expire int) bool { lock := false for !lock { res, err := updateLockExpireUidScript.Do(conn, key, uid, expire) if err != nil { fmt.Println(err.Error()) break } if res.(int64) == 1 { lock = true fmt.Println("获取锁成功") return true } } fmt.Println("获取锁失败") return false } func delLock(conn redis.Conn, key []string, uid int) bool { _, err := deleteLockByUidScript.Do(conn, key, uid) if err != nil { fmt.Println("删除锁失败") fmt.Println(err.Error()) return false } fmt.Println("删除锁成功") return true } var ( updateLockExpireUidScript = redis.NewScript(1, ` local res = redis.call("SETNX", KEYS[1], ARGV[1]) if res == 1 then return redis.call("EXPIRE", KEYS[1], ARGV[2]) end return res `) deleteLockByUidScript = redis.NewScript(1, ` local res = redis.call("GET", KEYS[1]) if res == ARGV[1] then return redis.call("DEL", KEYS[1]) end return res `) ) func main() { pool := redis.Pool{ MaxIdle: 8, MaxActive: 0, IdleTimeout: 100, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", "127.0.0.1:6100") }, } key := []string{"distribute"} uid := 1 expire := 20 fmt.Println(time.Now()) for i:= 1; i< 100; i++ { go func() { conn := pool.Get() if getLock(conn, key, uid, expire) { fmt.Println("开始执行逻辑...") fmt.Println("逻辑结束,删除锁") delLock(conn, key, uid) } }() } fmt.Println(time.Now()) time.Sleep(time.Second * 20) } ``` ## LUA **Lua脚本的限制** * Redis不提供引入额外的包,例如os等,只有redis这一个包可用。 * Lua脚本将会在一个函数中运行,所有变量必须使用local声明 * return返回多个值时,Redis将会只给你第一个 **脚本中的类型限制** * 脚本返回nil时,Go中得到的是`err = redis.Nil`(与Get找不到值相同) * 脚本返回false时,Go中得到的是nil,脚本返回true时,Go中得到的是int64类型的1 * 脚本返回{"ok": ...}时,Go中得到的是redis的status类型(true/false) * 脚本返回{"err": ...}时,Go中得到的是err值,也可以通过`return redis.error_reply("My Error")`达成 * 脚本返回number类型时,Go中得到的是int64类型 * 传入脚本的KEYS/ARGV中的值一律为string类型,要转换为数字类型应当使用to\_number **如果脚本运行了很久会发生什么?** Lua脚本运行期间,为了避免被其他操作污染数据,这期间将不能执行其它命令,一直等到执行完毕才可以继续执行其它请求。当Lua脚本执行时间超过了lua-time-limit时,其他请求将会收到Busy错误,除非这些请求是SCRIPT KILL(杀掉脚本)或者SHUTDOWN NOSAVE(不保存结果直接关闭Redis) 更多内容参考以下地址,我这里主要是根据使用Go的经验提供一些总结。[https://redis.io/commands/eval](https://redis.io/commands/eval) 一段更“复杂”的脚本,它要求在获取一个key值时,如果该值访问较多,就延长生存周期。此外还要比较更新时间,如果不需要更新,则直接返回取到的值,否则返回redis.Nil ck: https://cloud.tencent.com/developer/article/1830969