0.分布式锁的背景
1.100台服务器处理1000万个用户抢1亿红包的问题
如下图所示,分布式锁要保证红包扣钱方法在同一时间只能被一个机器的一个线程执行;否则可能会导致超发。
所以可以额外加入1个服务(分布式锁),让100台服务器去她那里获取锁。
1.什么是分布式锁?
1.分布式锁应该具备哪些条件?
- 1.在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
- 2.高可用的获取锁与释放锁;
- 3.高性能的获取锁与释放锁;
- 4.具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误);
- 5.具备锁失效机制,防止死锁;
- 6.具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
2.分布式锁的实现
Redis:利用setnx命令,此命令同样是原子性操作,只有在key不存在时,才能set成功。
3.通过 Redis 分布式锁的实现理解概念
1.3个核心要素:
1.加锁
2.解锁
3.锁超时——防止死锁
2.上面3个操作存在什么问题?
- 1.setnx 和 expire 的非原子性
- 2.del 导致误删
- 3.出现并发的可能性
1.setnx(SET if Not eXists) 和 expire 的非原子性
极端场景:
- 如果线程1执行了setnx获得了锁,但在执行expire设置锁超时之前,线程1挂掉了;
- 这就会导致死锁,因为锁没有设置过期时间。
解决方法:set指令的可选参数,让setnx、expire两步合1步
set(lock_sale_商品ID,1,30,NX)
2.del导致误删
极端场景
- 1.线程A获得了锁,也设置了过期时间30秒,线程A在30秒内没执行完任务,锁超时释放了;
- 此时因为线程A锁过期了,但临界区的逻辑还没有执行完,线程B就提前拥有了这把锁,导致临界区的代码不能得到严格的串行执行。
- 解决方法:Redis 分布式锁不要用于较长时间的任务。
- 2.线程B自动获得锁;
- 3.线程A执行完后,执行del指令删除锁,却误删了线程B的锁。
解决方案:给锁加线程ID,防止误删
- 1.在加锁的时候,把当前线程ID当作value;
String threadId = Thread.currentThread().getId()
set(key,threadId ,30,NX)
- 2.线程A在执行del指令时,先检查锁key的value是不是自己的ID。
if(threadId .equals(redisClient.get(key))){
del(key)
}
3.出现并发的可能性
- 如果同一时间有2个线程A、B在访问代码块,线程A在锁超时释放时还没执行完,线程B就进入同一代码块了。
解决方法:设置守护线程
- 1.线程A锁超时限为30秒;
- 2.当线程A执行到29秒时,守护线程为线程A续航30秒;
- 3.重复2,知道线程A执行完毕。
- 4.线程A执行完,会显式的关闭守护线程;
- 5.若节点1挂掉了,守护线程也没了,没人续命,锁自然超时释放。
线程A挂掉了,那守护线程怎么办?还会给锁续命吗?(不懂!!)
4.RedLock算法——解决redis主从不一致的问题
1.Master宕机导致分布式锁丢失的问题
- 在Redis的master节点上拿到了锁;
- 但是这个加锁的key还没有同步到slave节点;
- master故障,发生故障转移,slave节点升级为master节点;
- 导致锁丢失。
2.RedLock解决方案
2.Zookeeper实现分布式锁
1.Zookeeper的节点
1.1.四种类型的节点(同一目录下节点名不可以重复)
- 1.持久性节点
- 2.持久性顺序节点
- 3.临时性节点
- 4.临时性顺序节点
1.2.持久性节点和临时性节点的区别
- 1.持久性节点表示只要你创建了这个节点,那不管你ZooKeeper的客户端是否断开连接,ZooKeeper的服务端都会记录这个节点;
- 2.临时性节点刚好相反,一旦你ZooKeeper客户端断开了连接,那ZooKeeper服务端就不再保存这个节点;
- 3.顺序性节点是指,在创建节点的时候,ZooKeeper会自动给节点编号比如0000001,0000002这种的。
1.3.Zookeeper的watcher监听机制
客户端注册监听它关心的目录节点,当目录节点发生变化(数据修改、被删除、子目录节点增删)等,Zookeeper都会通知客户端。
2.Zookeeper实现分布式锁
1.使用临时性顺序节点
临时性节点:万一某个服务器挂了,那么就会与zookeeper断开连接,则临时性节点自动删除,不必担心死锁;
顺序性节点:假设100个服务器同时发来请求,,为100个服务器按编号创建100个临时顺序节点,每一个节点只监听它前面的节点,防止惊群效应。