前几天不是整理那个字节跳动的问题么,整理到了分布式锁,自己看了看有点无感,趁着业务需要,就问了下组长,并解释了情景及解决办法。
kk问:在忙吗?
组长答:不忙,在看昨天的问题。
kk问:我看**那业务那你写了个分布式锁,能讲下实现思路吗?
组长答:那个啊,那都写了好久了,我看下...
kk小声bb:我想问下咱们业务场景
组长答:这个就是咱们业务线在**业务这,以前没有定时任务,拉取热数据的时候,防止多服务拉取,出现重复调用什么的,情景很多,我先看下代码。
kk问:这个是基于redis实现的,但是我没看到哪里上锁啊?
组长答:这里么,大概思路就是先尝试获取下锁,
时时间,如果返回1,就返回,因为K是唯一的么,这个判断及时多JVM线程进来
2.如果没获取到锁,ttl(key)判断下锁是不是过期了,锁已经过期,则认为业务流程失败,或出现意外,需要释放锁,释放锁时判断是否过期,以及是否是自己的锁,get(key).qeuals(v),然后del(key)
3.释放掉锁后,再加入新的锁。
kk问:这个我们传入的lock是啥啊?
组长答:这个我当时是用方法以及业务类型+时间戳的方式传进来的。
kk问:那这个超时时间呢?
组长答:这个不太好确定,因为你根据业务流程来看,有可能就是业务线响应慢,暂时没找到一个合适的超时时间。
kk问:那我不能在调用方法上加一个锁,使调用时串行化,或者用ReentryLock去加锁么?
组长答:你synchronized在多jvm下是无效的,单J锁在这种情况下都是失效的,现在分布式事务的话还是要用分布式锁,当然这个方法也存在很多问题。
kk问:什么情况会出现这个问题呢?
组长答:测试的时候,出现测试人员连点两下,极端情况下,一个线程打进来,进入A服务器进行获取,另一个线程进入B服务器获取,A获取到就返回了,B回去不到,就给释放了,不考虑redis崩的情况,后来让前端同学给按钮加了个禁用,点一次就让你等着就行了,然后测试拿着两台测试机,点一个页面后,在一一分钟后又点了一下,效果和这个相同,就为了防止这种现象。
kk问:这种场景会有什么问题吗?
组长(mmp,问个没完了):第一步获取锁的时候没问题,但是当连个线程同时打进来,都没过期的话,多JVM,一个线程去unlock了,两一个就的在这等,等等等,然后业务线下层调用响应时间又长,很容易超时,前端就得等
kk问:响应慢的话有什么解决办法吗?
组长答:有一个DefaultRedisList的实现,底层是队列BlockingDeque,你可以看下实现结合redis做的。
kk问:能举个例子么?
组长答:比如我们**业务,放两个队列,一个用来接收请求,一个用来异步回调,放两个MQ里,try...catch try send(),然后异步callback,这样的话基本时没有延迟的,用户感知很低,一个就去发送,异步的去拉队列就行了
kk问:callback能用Http吗?
组长答:不行,只能是mq去推,你看下其他的实现方法吧,整体逻辑是没问题的,但是现在这个方法被弃用了。
kk问:为什么被弃用了啊?
组长答:开始我们是为了防止多端定时任务同时拉取热数据的,但后来我们给定时任务Quartz做了,他基于数据库做的分布式锁,有一个定时在拉,其他的就不去执行了,你可以看看Quartz的实现。总体我们处理分布式锁的逻辑没有问题,你可以看看其他人的实现方式。
kk:好的,基本是了解了,我再消化消化,自己看还是不够主观,对场景不是很了解。
分布式锁总结见博客。