关于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个执行业务,但是报错:
首先确定一下,我代码应该没错吧~哈哈
然后我看了下,貌似就是前几个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之类的队列处理,应该能解决这个问题吗?
正在回答
重试超时不应该出现会返回错误而是应该继续等待, 如果不想用分布式锁可以考虑使用课程中的基于版本号的乐观锁机制,还有一种稍微简单一些的机制: 把查询库存,检查库存是否充足,如果充足则减少一个库存的这个逻辑使用redis的lua脚本功能,让lua去完成,而不是go代码完成,然后把这个lua脚本交给redis执行,由于redis的lua脚本执行的原子性,就可以保证库存查询、校验、扣减是原子性的了,这样就不用redis的锁了,这样会导致redis并发低一点,不过是能接收的,京东内部项目也会采用这种方法的
- 参与学习 489 人
- 解答问题 559 个
风口上的技术,薪资水平遥遥领先,现在学习正值红利期! 未来3-5年,Go语言势必成为企业高性能项目中不可替代的语言 从基础到项目实战再到重构,对转行人员友好,真正从入门到精通!
了解课程
恭喜解决一个难题,获得1积分~
来为老师/同学的回答评分吧
0 星