关于redsync的Lock锁住的问题疑惑

关于redsync的Lock锁住的问题疑惑

var (
   RedisDB *redis.Client
   Rsync *redsync.Redsync
)

func InitRedis() {
   RedisDB = redis.NewClient(&redis.Options{
      Addr: "192.168.101.3:6379",
   })
   pool := goredis.NewPool(RedisDB)
   Rsync = redsync.New(pool)
}

func main() {
   InitRedis()

   gNum := 10 // 并发数量

   var wg sync.WaitGroup
   wg.Add(gNum)
   for i := 0; i < gNum; i++ {
      go worker(&wg)
   }
   wg.Wait()
}

func worker(wg *sync.WaitGroup) {
   defer wg.Done()

   mutex := Rsync.NewMutex("test-mutex")

   if err := mutex.Lock(); err != nil {
      panic(fmt.Sprintf("获取分布式锁异常:%+v", err))
   }
   defer func(mutex *redsync.Mutex) {
      // 释放锁,以便其他进程或线程可以获得锁
      if ok, err := mutex.Unlock(); !ok || err != nil {
         panic(fmt.Sprintf("释放分布式锁失败:%+v", err))
      }
   }(mutex)

   time.Sleep(1 * time.Second)
}


老师,我的demo代码如上,特别简单,并发10个执行业务,但是报错:


https://img1.sycdn.imooc.com//climg/64113998095aaa0513350364.jpg

首先确定一下,我代码应该没错吧~哈哈


然后我看了下,貌似就是前几个goroutine拿到锁后,其他的goroutine卡在metex.Lock() 一直重试,结果等待超过了重试次数了,就返回了。


我也看了github上redsync的issue对于这里的问题的解答

github.com/go-redsync/redsync/issues/107

有人回答说增加重试次数WithTries,增加重试延迟WithRetryDelay或者WithRetryDelayFunc给互斥体更多时间来获取锁。


但归根结底,redsync这类也属于互斥锁,悲观锁机制吧,相当于分布式sync.mutex了,底层还是要串行执行代码。无论加多少台服务,全局只有一把锁,同一时间只有一个goroutine在执行业务逻辑,其他全排队等lock。


所以,当并发高一些,前面的goroutine业务(常见情况是多表复杂查询,慢sql之类的)假如一个业务耗时1S,串行执行,终会达到后面goroutine的重试超时时间的,然后大量等待lock的goroutine就超时返回错误。如同我上面的代码,10个并发,每个请求1S,就有报错了。


加参数感觉也治标不治本,并发上去了参数还要调

mutex := Rsync.NewMutex("test-mutex",
   redsync.WithTries(128),
   redsync.WithRetryDelay(time.Duration(rand.Intn(200)+200)*time.Millisecond),
)

所以怎么解决这种问题呢?


当然,实际线上,这里比如说是扣库存,可能前面还有mq之类的队列处理,应该能解决这个问题吗?

正在回答

登陆购买课程后可参与讨论,去登陆

1回答

重试超时不应该出现会返回错误而是应该继续等待, 如果不想用分布式锁可以考虑使用课程中的基于版本号的乐观锁机制,还有一种稍微简单一些的机制: 把查询库存,检查库存是否充足,如果充足则减少一个库存的这个逻辑使用redis的lua脚本功能,让lua去完成,而不是go代码完成,然后把这个lua脚本交给redis执行,由于redis的lua脚本执行的原子性,就可以保证库存查询、校验、扣减是原子性的了,这样就不用redis的锁了,这样会导致redis并发低一点,不过是能接收的,京东内部项目也会采用这种方法的

  • 爱吃apple的阿狸 提问者 #1

     哦!明白了,感觉如果简单点,使用mysql的悲观锁就够用了,简单无脑~,想要并发高一点,就乐观锁或者lua脚本去整。。实际上也还是要看项目需求问题,感谢老师!

    2023-03-17 10:59:43
  • bobby 回复 提问者 爱吃apple的阿狸 #2

    是的, 这样就能满足绝大部分需求了

    2023-03-17 16:16:19
  • bobby #3

    是的, 这样就能满足绝大部分需求了,用lua的缺点就是每个业务都要单独写,无法通用,但是效果好,省去了redis分布式锁的本地无效空循环等待,redis分布式锁的好处就是通用,什么业务都可以直接用

    2023-03-17 16:17:30
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星
Go开发工程师全新版
  • 参与学习       489    人
  • 解答问题       559    个

风口上的技术,薪资水平遥遥领先,现在学习正值红利期! 未来3-5年,Go语言势必成为企业高性能项目中不可替代的语言 从基础到项目实战再到重构,对转行人员友好,真正从入门到精通!

了解课程
请稍等 ...
意见反馈 帮助中心 APP下载
官方微信

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师