采访者:你们的系统是如何实现分布式锁的?我:我们用的是redis分布式锁。具体方法是后端收到请求后加分布式锁。如果加锁成功,业务就会执行。如果加锁失败,则等待加锁或拒绝请求。业务执行完成后释放锁。面试官:能说说具体用到的命令吗?我:我们使用SETNX命令,如下:SETNXKEY_NAMEVALUE设置成功返回1,设置失败返回0。如下图,客户端1加锁成功,客户端2获取锁失败:面试官:这个设置有没有问题?锁定成功的客户端挂了怎么办?我:比如上图1中的客户端挂了,锁无法释放。可以设置一个过期时间,命令如下:SETkeyvalue[EXseconds][PXmilliseconds]NX面试官:如果设置了过期时间,如果业务还没做完,但是redis锁已经过期了,怎么办?我:我需要更新锁。面试官:你能告诉我怎么做吗?我:设置锁成功后,启动看门狗,每隔一段时间(比如10s)为当前分布式锁续约一次,即每10s重新设置一次当前key的超时时间。命令如下:EXPIRE整个过程如下:面试官:看门狗是如何实现的?我:客户端加锁成功后,可以启动一个定时任务,每隔10s检测一次业务(最好支持配置)是否处理??完成,检测的依据是判断分布式锁的key是否还存在,如果是,则续签合同。面试官:如果当前线程已经处理完了,这个key是其他client写的吗?我:可以为每个client指定一个clientID,在VALUE上加上clientID前缀,这样在更新锁的时候,可以通过判断当前分布式锁的value前缀来判断是否属于当前client。如果是更新锁,否则不处理。面试官:锁更新功能是你们自己实现的吗?我:我们用的是redisson的分布式锁方案。使用redisson获取分布式锁非常简单。代码如下:RLocklock=redisson.getLock("client-lock");lock.lock();try{//处理业务}catch(Exceptione){//处理异常}finally{lock.unlock();}具体原理是:如果client1加锁成功,则分布式锁超时默认时间为30秒(可以通过Config.lockWatchdogTimeout修改)。添加锁成功后,会启动一个看门狗。watchdog是一个后台线程,每10秒检查一次client1是否还持有lockkey。如果是这样,它将延长锁定键的使用寿命。锁定键的超时时间设置为30s。面试官:redisson中的定时器是怎么实现的?我:redisson定时器是使用netty-common包中的HashedWheelTime实现的。面试官:如果client1宕机了,这个时候分布式锁可以更新吗?我:因为分布式锁的更新是在client上进行的,如果client1挂了,更新线程就不能工作了,无法更新。这时候应该删除分布式锁,让其他客户端去获取。面试官:那如果client1宕机了,其他client需要等30秒才能拿到锁。有没有办法立即删除锁?我:因为client1宕机了,只能超时自动删除锁。如果想马上删除,就需要增加额外的工作,比如增加一个sentinel机制,让sentinel维护一个所有redisclients的列表。Sentinel定时监控客户端是否宕机,如果检测到宕机,立即删除客户端的锁。如下图所示:这里的哨兵不是redis哨兵,而是业务系统做的检测客户端故障的哨兵。面试官:如果不用redisson,如何实现分布式锁更新?例如springboot2.0使用Lettuce作为默认的redis客户端。我:Lettuce没有像redisson那样提供watchdog机制,所以锁更新需要业务系统自己实现。可以按以下步骤实现:1.lock命令,我们参考了spring包中的分布式锁代码。如果锁存在并且是当前客户端加的锁,则更新锁。如果锁不存在,则锁定。代码如下:privatestaticfinalStringOBTAIN_LOCK_SCRIPT="locallockClientId=redis.call('GET',KEYS[1])\n"+"iflockClientId==ARGV[1]then\n"+"redis.call('PEXPIRE',KEYS[1]],ARGV[2])\n"+"returntrue\n"+"elseifnotlockClientIdthen\n"+"redis.call('SET',KEYS[1],ARGV[1],'PX',ARGV[2])\n"+"returntrue\n"+"end\n"+"returnfalse";2.把锁保存在一个数据结构中,比如HashMap,定时任务定时扫描map,对每一个锁进行锁更新操作。代码如下:privatefinalMaplocks=newConcurrentHashMap<>();3.更新锁命令privatestaticfinalStringRENEW_LOCK_SCRIPT="locallockClientId=redis.call('GET',KEYS[1])\n"+"iflockClientId==ARGV[1]then\n"+"redis.call('PEXPIRE',KEYS[1],ARGV[2])\n"+"returntrue\n"+"end\n"+"returnfalse";4.如果锁是当前客户端添加的,则更新锁,否则会失败。写一个定时任务,定时执行锁更新代码:redisTemplate.execute(renewLockScript,Collections.singletonList(lockKey),clientId,String.valueOf(expireAfter));面试官:这道题就这么多,我们进入下一题。..