分布式锁常见的三种实现方式:数据库乐观锁;基于redis的分布式锁;基于ZooKeeper的分布式锁。本地面试考点是,你对Redis熟悉吗?如何在Redis中实现分布式锁。要点Redis要实现分布式锁,需要满足以下条件互斥任何时候,只有一个客户端可以持有锁。没有僵局。如果客户端在持有锁的时候崩溃了,没有主动解锁,也可以保证后面其他客户端可以加锁。容错只要大部分Redis节点正常运行,客户端就可以加锁和解锁。该实现可以直接通过setkeyvaluepxmillisecondsnx命令实现加锁,通过Lua脚本实现解锁。//获取锁(unique_value可以是UUID等)SETresource_nameunique_valueNXPX30000//释放锁(在lua脚本中,一定要比较值,防止误解锁)ifredis.call("get",KEYS[1])==ARGV[1]thenreturnredis.call("del",KEYS[1])elsereturn0end代码解释set命令需要使用setkeyvaluepxmillisecondsnx而不是setnx+expire命令需要执行两次保证原子性,取值必须是独一无二的。可以使用UUID.randomUUID().toString()方法生成,用于识别锁属于哪个请求,有解锁依据;释放锁时,必须校验该值,防止误解锁;通过LuaScript避免CheckAndSet模型的并发问题,因为释放锁时涉及多个Redis操作(使用eval命令执行Lua脚本的原子性);锁代码分析首先,set()增加了NX参数,可以保证如果存在key存在,函数不会被调用成功,即只有一个client可以持有锁,满足互斥。其次,因为我们给锁设置了一个过期时间,即使后来锁持有者crash了没有解锁,锁也会因为过期时间自动解锁(也就是key被删除),死锁就会产生不会发生。最后,因为我们赋值为requestId来标识这个锁是属于哪个request的,那么在client解锁的时候可以判断是不是同一个client。解锁代码分析将Lua代码传给jedis.eval()方法,参数KEYS[1]为lockKey,ARGV[1]为requestId。执行时会先获取锁对应的值,检查是否与requestId相等,相等则解锁(删除key)。存在的风险如果存储锁对应key的节点挂掉,可能会存在丢失锁的风险,导致多个客户端持有锁,无法实现资源的独占。ClientA从master获取锁。在master同步锁到slave之前,master崩溃了(Redis的主从同步通常是异步的)。主从切换,从节点提升为主节点3.客户端B在客户端A已经获取的同一个资源上又获取了一个锁。结果出现了不止一个线程同时获取锁的情况。redlock算法出现在这个场景假设有一个redis集群有5个redismaster实例。然后执行以下步骤获取锁:获取当前时间戳,单位毫秒;与上面类似,尝试依次在每个master节点上创建锁,过期时间短,一般为几十毫秒;尽量在大多数节点上创建锁要建立锁,比如5个节点需要3个节点n/2+1;客户端计算建立锁的时间,如果建立锁的时间小于超时时间,则建立成功;如果锁建立失败,则删除之前创建的锁即可;只要别人创建了分布式锁,你就必须不停地轮询来尝试获取锁。Redis官方给出了以上两种基于Redis实现分布式锁的方法。详情请参考:https://redis.io/topics/distlock。Redisson实现Redisson是在Redis基础上实现的Java内存数据网格(In-MemoryDataGrid)。它不仅提供了一系列分布式通用Java对象,还实现了ReentrantLock、FairLock、MultiLock、RedLock、ReadWriteLock等,还提供了很多分布式服务。Redisson提供了使用Redis最简单、最方便的方式。Redisson的目的是为Redis的用户提倡关注点分离(SeparationofConcern),让用户可以将更多的精力集中在处理业务逻辑上。Redisson分布式可重入锁使用Redisson支持单点模式、主从模式、哨兵模式、集群模式。这里以单点模式为例://1.构造redisson实现分布式锁必备ConfigConfigconfig=newConfig();config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0);//2.构造RedissonClientRedissonClientredissonClient=Redisson.create(config);//3.获取锁对象实例(不能保证按线程顺序获取)RLockrLock=redissonClient.getLock(lockKey);try{/***4.尝试获取锁*waitTimeout尝试获取锁的最大等待时间,如果超过这个值,则认为获取锁失败*leaseTime锁持有时间,超过这个时间,锁会自动失效(该值应设置大于业务处理时间,保证业务在锁有效期内处理)*/booleanres=rLock.tryLock((long)waitTimeout,(long)leaseTime,TimeUnit.SECONDS);if(res){//成功获取锁,这里处理业务}}catch(Exceptione){thrownewRuntimeException("aquirelockfail");}finally{//无论如何,最后必须解锁rLock...申请无效锁,提高资源利用率名词需要特别注意的是,RedissonLock也没有解决节点挂掉时的失锁风险问题。现实是有些场景是不能容忍的,所以Redisson提供了实现redlock算法的RedissonRedLock。RedissonRedLock真正解决了单点故障问题。代价是需要为RedissonRedLock额外搭建Redis环境。所以,如果业务场景可以容忍这么小概率的错误,建议使用RedissonLock,如果不能容忍,则建议使用RedissonRedLock。
