
文章目录1.为什么需要分布式锁2.分布式锁的实现方案2.1 用数据库实现2.2 基于redis分布式锁2.3 基于ZookeeperRestTemplate和OpenFeign的使用1.为什么需要分布式锁1在单体应用的时候如果多个线程要访问共享资源的时候我们通常使用线程间加锁的机制在某一个时刻只有一个线程可以对这个资源进行操作其他线程需要等待锁的释放Java中也有一些处理锁的机制比如synchronized。2而到了分布式的环境中当某个资源可以被多个系统访问使用到的时候为了保证大家访问这个数据是一致性的那么就要求再同一个时刻只能被一个系统使用这时候线程之间的锁机制就无法起到作用了。因为分布式环境中系统是会部署到不同的机器上面的每个机器都有自己的jvm每个jvm都有自己的synchronized锁不住其他系统的数据。2.分布式锁的实现方案2.1 用数据库实现①就是创建一张锁表数据库对字段作唯一性约束。②加锁的时候在锁表中增加一条记录即可释放锁的时候删除锁记录就行。③如果有并发请求同时提交到数据库数据库会保证只有一个请求能够得到锁。④这种属于数据库IO操作效率不高而且频繁操作会增大数据库的开销因此这种方式在高并发、高性能的场景中用的不多。2.2 基于redis分布式锁1理论上来说使用缓存来实现分布式锁的效率最高加锁速度最快因为Redis几乎都是纯内存操作而基于数据库的方案和基于Zookeeper的方案都会涉及到磁盘文件IO效率相对低下。2redis提供了SETNX命令去实现锁的排他性还可以使用expire命令去设置锁的失效时间从而避免死锁的问题。对于加锁与设置过期时间是非原子操作我们可以使用Lua脚本。补充基于setnx实现分布式锁存在的问题①不可重入同一个线程无法多次获取同一把锁线程外层方法获取后在内层方法不能再次获取②不可重试获取锁只尝试一次就返回false没有重试机制③超时释放锁超时释放虽然可以避免死锁但是如果业务执行耗时特别长也会导致锁超时释放存在安全隐患④主从一致性如果redis提供了主从集群主从同步存在延迟当主机宕机时如果从节点没有及时同步锁那么其他线程就有可能认为没有锁会抢占到锁3Redisson框架提供了一个分布式锁的封装实现并且内置了一个叫看门狗Watch Dog的机制来对加锁成功后还想继续持有锁的进行key的续期。a.可重入利用hash结构记录线程id和重入次数b.可重试利用信号量和PubSub功能实现等待、唤醒获取锁失败的重试机制c.超时续约利用watchDog每隔一段时间releaseTime(锁的持有时间) / 3重置超时时间①Redisson的使用/** waitTimeout 尝试获取锁的最大等待时间超过这个值则认为获取锁失败 * leaseTime 锁的持有时间,超过这个时间锁会自动失效 * 值应设置为大于业务处理的时间确保在锁有效期内业务能处理完 */booleanresrLock.tryLock((long)waitTimeout,(long)leaseTime,TimeUnit.SECONDS);②Redisson分布式锁原理图注意使用默认的leaseTime才会启动看门狗机制4如果线程1在Redis的master节点上拿到了锁但是加锁的key还没同步到slave节点。恰好这时master节点发生了故障一个slave节点就会升级为master节点。线程2就可以获取到这个key的锁了但是线程1已经拿到锁了锁的安全性就没有了可以使用RedLock。2.3 基于ZookeeperZookeeper利用临时有序节点实现分布式锁。缺点客户端在持有锁期间需要定期向Zookeeper发送心跳以保持锁的状态。如果客户端因为异常退出或网络故障等原因无法发送心跳Zookeeper会认为客户端已经释放了锁。在zookeeper中建一个分布式锁的节点。步骤1客户端A在锁的节点创建一个临时有序节点001步骤2看001是不是第一个节点看序号有没有比它小的是第一个节点就获取到锁。步骤3客户端B创建临时有序节点002步骤4判断002是否是第一个节点不是第一个节点则给上一个节点用watch加监听器。步骤5客户端A执行完业务逻辑后需要释放锁了删除临时有序节点步骤5等到第一个节点释放锁删除了节点后就会被002监听到。步骤6zookeeper通知客户端B第一个节点被删除了。步骤8此时客户端B就会再次判断自己是不是第一个节点步骤9是的话就会加锁成功补充1.1 zookeeper节点分类①临时节点与客户端断开连接后删除a.临时目录节点节点名称不编号b.临时有序节点节点名称进行顺序编号②持久节点与客户端断开连接后不删除a.持久目录节点节点名称不编号b.持久有序节点节点名称进行顺序编号1.2 临时有序节点可以通过watch命令监听到节点的增删改RestTemplate和OpenFeign的使用选型不看对方用什么语言看对方是不是我注册中心里的服务。内部的不管什么语言都用 OpenFeign外部的第三方 API 都用 RestTemplate