咔叽游戏

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 738|回复: 0

[Golang] 用Go+Redis实现分布式锁的示例代码

[复制链接]
  • TA的每日心情
    无聊
    2019-5-27 08:20
  • 签到天数: 4 天

    [LV.2]圆转纯熟

    发表于 2022-1-10 10:50:38 | 显示全部楼层 |阅读模式
    目录

      为什么需要分布式锁分布式锁需要具备特性实现 Redis 锁应先掌握哪些知识点
        set 命令
        Redis.lua 脚本
      go-zero 分布式锁 RedisLock 源码分析关于分布式锁还有哪些实现方案项目地址


    为什么需要分布式锁


    用户下单
    锁住 uid,防止重复下单。
    库存扣减
    锁住库存,防止超卖。
    余额扣减
    锁住账户,防止并发操作。
    分布式系统中共享同一个资源时往往需要分布式锁来保证变更资源一致性。

    分布式锁需要具备特性


    排他性
    锁的基本特性,并且只能被第一个持有者持有。
    防死锁
    高并发场景下临界资源一旦发生死锁非常难以排查,通常可以通过设置超时时间到期自动释放锁来规避。
    可重入
    锁持有者支持可重入,防止锁持有者再次重入时锁被超时释放。
    高性能高可用
    锁是代码运行的关键前置节点,一旦不可用则业务直接就报故障了。高并发场景下,高性能高可用是基本要求。

    实现 Redis 锁应先掌握哪些知识点



    set 命令

    1. 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
    加锁流程
    1. -- KEYS[1]: 锁key
    2. -- ARGV[1]: 锁value,随机字符串
    3. -- ARGV[2]: 过期时间
    4. -- 判断锁key持有的value是否等于传入的value
    5. -- 如果相等说明是再次获取锁并更新获取时间,防止重入时过期
    6. -- 这里说明是“可重入锁”
    7. if redis.call("GET", KEYS[1]) == ARGV[1] then
    8.     -- 设置
    9.     redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
    10.     return "OK"
    11. else
    12.     -- 锁key.value不等于传入的value则说明是第一次获取锁
    13.     -- SET key value NX PX timeout : 当key不存在时才设置key的值
    14.     -- 设置成功会自动返回“OK”,设置失败返回“NULL Bulk Reply”
    15.     -- 为什么这里要加“NX”呢,因为需要防止把别人的锁给覆盖了
    16.     return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
    17. end
    复制代码
    用Go+Redis实现分布式锁的示例代码-1.jpg

    解锁流程
    1. -- 释放锁
    2. -- 不可以释放别人的锁
    3. if redis.call("GET", KEYS[1]) == ARGV[1] then
    4.     -- 执行成功返回“1”
    5.     return redis.call("DEL", KEYS[1])
    6. else
    7.     return 0
    8. end
    复制代码
    用Go+Redis实现分布式锁的示例代码-2.jpg

    源码解析
    1. package redis
    2. import (
    3.     "math/rand"
    4.     "strconv"
    5.     "sync/atomic"
    6.     "time"
    7.     red "github.com/go-redis/redis"
    8.     "github.com/tal-tech/go-zero/core/logx"
    9. )
    10. const (
    11.     letters     = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    12.     lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
    13.     redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
    14.     return "OK"
    15. else
    16.     return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
    17. end`
    18.     delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
    19.     return redis.call("DEL", KEYS[1])
    20. else
    21.     return 0
    22. end`
    23.     randomLen = 16
    24.     // 默认超时时间,防止死锁
    25.     tolerance       = 500 // milliseconds
    26.     millisPerSecond = 1000
    27. )
    28. // A RedisLock is a redis lock.
    29. type RedisLock struct {
    30.     // redis客户端
    31.     store *Redis
    32.     // 超时时间
    33.     seconds uint32
    34.     // 锁key
    35.     key string
    36.     // 锁value,防止锁被别人获取到
    37.     id string
    38. }
    39. func init() {
    40.     rand.Seed(time.Now().UnixNano())
    41. }
    42. // NewRedisLock returns a RedisLock.
    43. func NewRedisLock(store *Redis, key string) *RedisLock {
    44.     return &RedisLock{
    45.         store: store,
    46.         key:   key,
    47.         // 获取锁时,锁的值通过随机字符串生成
    48.         // 实际上go-zero提供更加高效的随机字符串生成方式
    49.         // 见core/stringx/random.go:Randn
    50.         id:    randomStr(randomLen),
    51.     }
    52. }
    53. // Acquire acquires the lock.
    54. // 加锁
    55. func (rl *RedisLock) Acquire() (bool, error) {
    56.     // 获取过期时间
    57.     seconds := atomic.LoadUint32(&rl.seconds)
    58.     // 默认锁过期时间为500ms,防止死锁
    59.     resp, err := rl.store.Eval(lockCommand, []string{rl.key}, []string{
    60.         rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
    61.     })
    62.     if err == red.Nil {
    63.         return false, nil
    64.     } else if err != nil {
    65.         logx.Errorf("Error on acquiring lock for %s, %s", rl.key, err.Error())
    66.         return false, err
    67.     } else if resp == nil {
    68.         return false, nil
    69.     }
    70.     reply, ok := resp.(string)
    71.     if ok && reply == "OK" {
    72.         return true, nil
    73.     }
    74.     logx.Errorf("Unknown reply when acquiring lock for %s: %v", rl.key, resp)
    75.     return false, nil
    76. }
    77. // Release releases the lock.
    78. // 释放锁
    79. func (rl *RedisLock) Release() (bool, error) {
    80.     resp, err := rl.store.Eval(delCommand, []string{rl.key}, []string{rl.id})
    81.     if err != nil {
    82.         return false, err
    83.     }
    84.     reply, ok := resp.(int64)
    85.     if !ok {
    86.         return false, nil
    87.     }
    88.     return reply == 1, nil
    89. }
    90. // SetExpire sets the expire.
    91. // 需要注意的是需要在Acquire()之前调用
    92. // 不然默认为500ms自动释放
    93. func (rl *RedisLock) SetExpire(seconds int) {
    94.     atomic.StoreUint32(&rl.seconds, uint32(seconds))
    95. }
    96. func randomStr(n int) string {
    97.     b := make([]byte, n)
    98.     for i := range b {
    99.         b[i] = letters[rand.Intn(len(letters))]
    100.     }
    101.     return string(b)
    102. }
    复制代码
    关于分布式锁还有哪些实现方案


    etcd
    redis redlock

    项目地址


    https://github.com/zeromicro/go-zero
    到此这篇关于用Go+Redis实现分布式锁的示例代码的文章就介绍到这了,更多相关Go Redis分布式锁内容请搜索咔叽论坛以前的文章或继续浏览下面的相关文章希望大家以后多多支持咔叽论坛!

    原文地址:https://www.jb51.net/article/232174.htm

    QQ|免责声明|小黑屋|手机版|Archiver|咔叽游戏

    GMT+8, 2024-3-29 18:33

    Powered by Discuz! X3.4

    © 2001-2023 Discuz! Team.

    快速回复 返回顶部 返回列表