怎么用Go+Redis实现分布式锁
这篇文章主要介绍怎么用Go+Redis实现分布式锁,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
为什么需要分布式锁
用户下单
锁住 uid,防止重复下单。
库存扣减
锁住库存,防止超卖。
余额扣减
锁住账户,防止并发操作。
分布式系统中共享同一个资源时往往需要分布式锁来保证变更资源一致性。
分布式锁需要具备特性
排他性
锁的基本特性,并且只能被第一个持有者持有。
防死锁
高并发场景下临界资源一旦发生死锁非常难以排查,通常可以通过设置超时时间到期自动释放锁来规避。
可重入
锁持有者支持可重入,防止锁持有者再次重入时锁被超时释放。
高性能高可用
锁是代码运行的关键前置节点,一旦不可用则业务直接就报故障了。高并发场景下,高性能高可用是基本要求。
实现 Redis 锁应先掌握哪些知识点
set 命令
SET key value [EX seconds] [PX milliseconds] [NX|XX]
EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。
Redis.lua 脚本
使用 redis lua 脚本能将一系列命令操作封装成 pipline 实现整体操作的原子性。
go-zero 分布式锁 RedisLock 源码分析
core/stores/redis/redislock.go
加锁流程
-- KEYS[1]: 锁key-- ARGV[1]: 锁value,随机字符串-- ARGV[2]: 过期时间-- 判断锁key持有的value是否等于传入的value-- 如果相等说明是再次获取锁并更新获取时间,防止重入时过期-- 这里说明是“可重入锁”if redis.call("GET", KEYS[1]) == ARGV[1] then -- 设置 redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2]) return "OK"else -- 锁key.value不等于传入的value则说明是第一次获取锁 -- SET key value NX PX timeout : 当key不存在时才设置key的值 -- 设置成功会自动返回“OK”,设置失败返回“NULL Bulk Reply” -- 为什么这里要加“NX”呢,因为需要防止把别人的锁给覆盖了 return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])end
解锁流程
-- 释放锁-- 不可以释放别人的锁if redis.call("GET", KEYS[1]) == ARGV[1] then -- 执行成功返回“1” return redis.call("DEL", KEYS[1])else return 0end
源码解析
package redisimport ( "math/rand" "strconv" "sync/atomic" "time" red "github.com/go-redis/redis" "github.com/tal-tech/go-zero/core/logx")const ( letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" lockCommand = `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` delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1])else return 0end` randomLen = 16 // 默认超时时间,防止死锁 tolerance = 500 // milliseconds millisPerSecond = 1000)// A RedisLock is a redis lock.type RedisLock struct { // redis客户端 store *Redis // 超时时间 seconds uint32 // 锁key key string // 锁value,防止锁被别人获取到 id string}func init() { rand.Seed(time.Now().UnixNano())}// NewRedisLock returns a RedisLock.func NewRedisLock(store *Redis, key string) *RedisLock { return &RedisLock{ store: store, key: key, // 获取锁时,锁的值通过随机字符串生成 // 实际上go-zero提供更加高效的随机字符串生成方式 // 见core/stringx/random.go:Randn id: randomStr(randomLen), }}// Acquire acquires the lock.// 加锁func (rl *RedisLock) Acquire() (bool, error) { // 获取过期时间 seconds := atomic.LoadUint32(&rl.seconds) // 默认锁过期时间为500ms,防止死锁 resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{ rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance), }) if err == red.Nil { return false, nil } else if err != nil { logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error()) return false, err } else if resp == nil { return false, nil } reply, ok := resp.(string) if ok && reply == "OK" { return true, nil } logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp) return false, nil}// Release releases the lock.// 释放锁func (rl *RedisLock) Release() (bool, error) { resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id}) if err != nil { return false, err } reply, ok := resp.(int64) if !ok { return false, nil } return reply == 1, nil}// SetExpire sets the expire.// 需要注意的是需要在Acquire()之前调用// 不然默认为500ms自动释放func (rl *RedisLock) SetExpire(seconds int) { atomic.StoreUint32(&rl.seconds, uint32(seconds))}func randomStr(n int) string { b := make([]byte, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b)}
以上是“怎么用Go+Redis实现分布式锁”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网行业资讯频道!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341