服了,头条4面:因为一个问题问题砍了我10万薪水

今天我们来探讨一下分布式锁的4种实现:
1、通过MySQL实现分布式锁
2、通过redis实现分布式锁
3、通过zookeeper实现分布式锁
4、通过etcd实现分布式锁
1、什么是分布式锁?如何确保共享资源在同一时刻只能被一个线程访问?
大家可能觉得这个很简单吧 , 在一个jvm中 , 通过synchronized或者ReentrantLock是很容易实现的 。
确实 , 单个jvm中确实没有问题 。
但是 , 通常我们的系统会采用集群的方式部署 , 此时集群中的每个节点都是一个jvm环境 , 那么通过synchronized或者ReentrantLock是无法解决共享资源访问的问题了 。
此时就要用到分布式锁了:分布式锁就是解决分布式环境中共享资源顺序访问的问题 , 同一时刻 , 集群中所有节点中 , 只允许有一个线程可以访问共享资源 。
2、分布式锁的功能

  1. 分布式锁使用者位于不同的机器中 , 锁获取成功之后 , 才可以对共享资源进行操作
  2. 同一时刻所有机器中只有一个使用者可以获取到分布式锁
  3. 锁具有重要的功能:即一个使用者可以多次获取某个分布式锁
  4. 获取锁的过程允许指定定时功能:在指定的时间内尝试获取锁 , 过了超时时间 , 若还未获取到锁 , 则获取失败
  5. 防止死锁:如:A机器获取锁之后 , 在释放锁之前 , A机器挂了 , 导致锁未释放 , 结果锁一直被A机器占有着 , 遇到这种情况时 , 分布式锁要能够自动解决;解决方式:持有锁的时候可以加个持有超时时间 , 超过了这个时间锁将自动释放 , 此时其他机器将有机会获取锁
下面我们来看一下分布式锁的4种实现 。
3、方式1:数据库的方式3.1、原理锁的获取过程
假如:一个集群环境中有n个系统 , 每个系统中有一个jvm , 每个jvm中有m个线程去获取分布式锁 , 那么同时可能就有n*m个线程去获取分布式锁 , 此时分布式锁的压力是比较大的 , 每个jvm中多个线程同时去获取锁其实是没有意义的 , 可以在每个jvm中先加一把本地的锁 , 获取分布式锁之前需要先获取jvm本地的锁 , 本地锁获取成功之后 , 才可以尝试获取分布式锁 , 此时n个系统中最多有n个线程尝试获取分布式锁 , 获取锁的步骤主要2步:
1、先尝试获取jvm本地锁2、jvm本地锁获取成功之后尝试获取分布式锁超时时间
获取锁的时候可以传递获取锁最大等待时间 , 在指定的时间内多次尝试获取锁 , 获取失败之后 , 休眠一会 , 再继续尝试获取 , 直到时间耗尽 。
锁有效期
获取锁的时候需要指定有效期 , 有效期就是获取锁之后 , 使用者希望使用多长时间 , 为什么需要有效期?
如果没有有效期 , 当使用者获取成功之后 , 系统突然down机了 , 那么这个锁就无法释放 , 其他线程就再也无法获取到这个锁了 。
所以需要有有效期 , 超过了有效期 , 锁将失效 , 其他线程将可以尝试获取锁 。
锁续命
什么是锁续命?
比如:使用者获取锁的时候 , 指定有效期是5分钟 , 但是5分钟之后 , 使用者事情还未干完 , 还想继续使用一会 , 那么可以使用续命功能 , 延迟锁的有效期 。
可以启动一个子线程 , 自动完成续命的操作 , 比如:原本有效期是5分钟 , 当使用4分钟的时候 , 续命2分钟 , 那么有效期是7分钟 , 这个比较简单 , 大家可以随意发挥 。
3.2、准备sqlcreate table t_lock(lock_key varchar(32) PRIMARY KEY NOT NULL COMMENT '锁唯一标志',request_id varchar(64) NOT NULL DEFAULT '' COMMENT '用来标识请求对象的',lock_count INT NOT NULL DEFAULT 0 COMMENT '当前上锁次数',timeout BIGINT NOT NULL DEFAULT 0 COMMENT '锁超时时间',version INT NOT NULL DEFAULT 0 COMMENT '版本号 , 每次更新+1')COMMENT '锁信息表';注意:表中有个版本号字段 , 版本号主要用于乐观锁的方式更新数据 , 确保并发情况下更新数据的正确性 。
3.3、锁工具类代码代码比较简单 , 大家主要看获取锁的lock方法和释放锁的unlock方法 , 注释比较详细 , 大家看看就懂了 。
代码中的重点是更新数据的时候 , 通过比对版本号 , 采用cas的方式 , 确保并发情况下更新数据的正确性 。
本代码实现了获取锁和释放锁的操作 , 续命操作未实现 , 大家可以尝试实现一下 。
package lock;import lombok.Builder;import lombok.Getter;import lombok.Setter;import lombok.extern.slf4j.Slf4j;import java.sql.*;import java.util.Map;import java.util.Objects;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;@Slf4jpublic class DbLockUtil {//将requestid保存在该变量中static ThreadLocal requestIdTL = new ThreadLocal