前言
在分布式应用开发中,如秒杀、商品抢购等业务,为防止库存超卖,都需要使用分布式锁。其本质就是不同进程共同访问共享资源的一种实现。
其实现有三种方式:
- 基于数据库实现
- 基于redis实现
- 基于zookeeper实现
一、基于数据库实现
1.1 悲观锁实现方式
对于这种其本质是在数据库中新建一个表,用于存储分布式锁,而加锁则是根据特定标记判断表中有没有记录,如果没有或者记录中锁标记(锁状态、是否超时)为未锁状态那么获取锁,如果状态为已锁状态,那么获取锁失败。
1.2 乐观锁实现方式
这种方式基于版本号以及一个原值比对,每次修改操作版本号加一然后去更新值,将版本号查出,带上相关条件去更新,如果版本号未变那么更新,否则继续重试
二、基于redis实现
2.1 单节点实现分布式锁
在单节点中的redis实现分布式使用的是set ex px nx+校验唯一随机值
//伪代码如下
if(redis.set(key,uniqueId,"NX","EX",100s)==1){
//业务代码
}
finally{
if(uniqueId.equals(redis.get(key))){
redis.del(key);
}
}
此处的校验唯一随机值是为了防止由于锁过期释放但是业务还没有执行完,而其他业务获取锁执行期间,上一业务误将锁释放
2.2 Redission看门狗
对于在使用redis设置锁时存在的锁过期释放、业务还没有执行完问题, 我们除了可以设置更长的过期时间以外,我们还可以在获取锁的线程中开启一个守护线程, 每隔一段时间 检测线程是否持有锁
2.3 集群实现redis分布式锁-RedLock算法
RedLock算法实现步骤
- 获取当前时间,毫秒为单位
- 按照顺序向所有Master节点请求加锁,加锁时间要小于锁的失效时间。如果超过那么跳过这个节点,尝试下一个节点
- 在完成所有节点的加锁操作后,计算总耗时,加锁成功条件有两个
- 客户端超过半数实例加锁成功
- 加锁总耗时没有超过锁的失效时间
- 重新计算这把锁有效时间
- 如果加锁失败,客户端要在所有master节点上解锁
三、Zookeeper分布式锁
3.1 Zookeeper加锁过程
- 客户端A请求加锁,ZK会创建一个持久节点/locks,如果A想获取锁,需要在locks创建一个顺序节点lock1
- A查找locks先所有临时顺序子节点,判断自己的节点lock1是不是排序最小的,如果是,获取锁
- 客户端B此时尝试获取锁,在locks下创建临时节点lock2,B查找临时顺序子节点,看自己是不是最小的,此时发现lock1才是最小的,获取锁失败,接着在lock1上注册watcher事件,用来监听lock1是否存在
- 客户端C尝试获取锁,创建临时节点lock3,查找locks下所有临时顺序子节点,判断自己是不是最小的,发现前面有临时子节点lock2,在lock2上注册watcher事件
3.2 Zookeeper释放锁过程
- zookeeper客户端业务完成或者故障都会删除临时节点释放锁
- 如果完成,那么会显示调用删除lock1指令,如果故障,那么lock1会自动删除
- lock1删除,B由于一直监听lock1,会收到通知,再次查找所有临时顺序子节点,发现自己最小,获取锁
四、对比
| 类型 | 优点 | 缺点 |
|---|---|---|
| 数据库 | 简单,使用方便,无需引入中间件 | db操作性能差,不适合高并发场景,有锁表的风险 |
| redis | 性能好,适合高并发 | 锁删除失败、过期时间不好控制 |
| zookeeper | 性能相对良好,可靠性高 | 较redis性能差,实现复杂 |