1. 分布式锁具备的条件
1. 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2. 高可用的获取锁与释放锁;
3. 高性能的获取锁与释放锁;
4. 具备可重入特性;
5. 具备锁失效机制,防止死锁;
6. 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
2. 基于数据库实现
基于数据库对method_name做了唯一性约束,这里如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功。
缺点:
1. 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
2. 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
3. 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
4. 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。
解决方案:
1. 数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
2. 没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
3. 非阻塞的?搞一个while循环,直到insert成功再返回成功。
4. 非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给它。
3. 基于redis实现
为了redis的高可用,master节点挂slave节点,然后采用哨兵模式进行主备切换。但由于Redis的主从复制是异步的,这可能会出现在数据同步过程中,master宕机,slave来不及同步数据就被选为master,从而数据丢失。为了应对这个情形, redis提出了RedLock算法,假设有N个master节点(将N设置成5,其实大于等于3就行)
1. 获取当前时间(单位是毫秒)。
2. 轮流用相同的key和随机值在N个节点上请求锁。
3. 计算获取锁所花的时间,只有当客户端在过半数的master节点上成功获取锁,而且总共消耗的时间不超过锁释放时间,这样才认为成功获取锁。
4. 如果成功获取锁,那现在锁自动释放时间就是最初的锁释放时间减去获取锁消耗的时间。
5. 如果失败,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功锁。
3.1 RedLock算法存在的问题
1. 节点崩溃重启
假设一共有5个Redis节点:A, B, C, D, E。
a. 客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。
b. 节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。
c. 节点C重启后,客户端2锁住了C, D, E,获取锁成功。这样,客户端1和客户端2同时获得了锁。
为了应对节点重启引发的锁失效问题,redis提出了延迟重启的概念,即一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,等待的时间大于锁的有效时间。
2. 时间跳跃问题
假设一共有5个Redis节点:A, B, C, D, E。
a. 客户端1从Redis节点A, B, C成功获取了锁。由于网络问题,与D和E通信失败。
b. 节点C上的时钟发生了向前跳跃,导致它上面维护的锁快速过期。
c. 客户端2从Redis节点C, D, E成功获取了同一个资源的锁(多数节点)。客户端1和客户端2都持有了锁。
为了应对时间跳跃引发的锁失效问题,redis提出了应该禁止人为修改系统时间。
3. 超时导致锁失效问题
RedLock算法并没有解决,操作共享资源超时,导致锁失效的问题。
网友评论