分布式锁
01 CAP理论
CAP理论:在一个分布式系统中,最多满足下面三项中的两项
C
一致性
: 在分布式系统中所有的数据备份,在同一时刻都有相同的值A
可用性
: 保证每个请求不管成功还是失败都有响应P
分区容错性
:任何信息的丢失和失败都不影响系统的运行
02 分布式锁
0201 概述
介绍
在分布式系统中访问共享资源时需要采取一致互斥机制,来防止相互干扰,以保证一致性
分布式锁:在分布式模型中,保证数据只有一份(限制),锁技术
分布式锁应该具备的条件
互斥性: 在分布式系统中,一个方法在同一时间 只能被一个机器的一个线程执行
高可用的获取锁与释放锁
高性能的获取锁与释放锁
可重入性:具备可重入性,具备锁失效机制,防止死锁,,即就算一个客户端持有锁,因系统失败未主动释放锁,也需要保证后续其他用户能够加锁成功
非阻塞:没有获取到锁直接返回获取锁失败
应用场景:
秒杀与抢优惠卷
0202 分布式锁的几种实现方式
基于数据库、基于redis,基于zookeeper
020201 基于数据库
A. 悲观锁
利用
select … where xx=yy for update
排他锁,不建议使用
B. 乐观锁
在表中添加一个时间戳或者是版本号的字段来实现
版本号不一致,更新失败
020202 zookeeper分布式锁
利用zookeeper的临时有序节点,顺序号最小的获取锁
实现步骤:
- 创建一个mylock目录
- 线程a在mylock目录下创建临时有序节点
- 获取mylock目录下所有的子节点,然后获取比自己小的节点,如果不存在,则说明当前线程的顺序号最小,获取锁
- 线程b创建节点向该节点注册监听事件,监听该节点,其他节点依次监听自己的上一个节点
- 线程a处理完后,删除自己的节点,其他节点监听变更事件后,重新去获取锁
使用Apache的开源库Curator
,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex
是分布式锁的实现,acquire
方法用于获取锁,release
方法用于释放锁
使用案例
InterProcessMutex interProcessMutex = new InterProcessMutex(client,"/anyLock");
interProcessMutex.acquire();
//业务逻辑
interProcessMutex.release();
优缺点:
- 可靠性高
- 性能较好
- CAP模型中属于CP
020203 redis分布式锁
主要基于命令:SETNX key value
redis分布式锁:在分布式系统中采取一致互斥机制,
考虑因素:
原子性
:lua脚本
synchronized只能解决单进程的原子性问题
Redis2.6.12之前的版本,只能通过lua脚本来保证原子性
Redis 2.6.12版本及以上推荐使用set原生命令
死锁
,删除锁失败
异常导致删除锁失败,解决方案: finally里删除锁
系统奔溃不执行删除锁逻辑,解决方案: 设置锁过期时间,考虑原子性,和时长合理性
错误的释放锁
流程:
A进程加锁并设置超时时间,比如10秒
当10秒后进程A的锁已经被redis中释放了
B进程请求进来,加锁成功
A进程执行完毕,就把B进程的锁释放了,
接着C进程进来,加锁成功,等等......
以此类推
解决方案:
加锁时将值设为唯一值,比如uuid,
Redlock
Redisson
使用如下:
a.引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.4</version>
</dependency>
b.增加配置文件
@Configuration
public class RedissonConfig {
@Bean
public Redisson redisson() {
Config config = new Config();
//单机版
//config.useSingleServer().setAddress("redis://192.168.1.1:8001").setDatabase(0);
//集群版
config.useClusterServers()
.addNodeAddress("redis://192.168.1.1:8001")
.addNodeAddress("redis://192.168.1.1:8002")
.addNodeAddress("redis://192.168.1.2:8001")
.addNodeAddress("redis://192.168.1.2:8002")
.addNodeAddress("redis://192.168.1.3:8001")
.addNodeAddress("redis://192.168.1.3:8002");
return (Redisson) Redisson.create(config);
}
}
c.分布式锁的实现
@Service
public class RedisLockDemo {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private Redisson redisson;
public String deduceStock() {
String lockKey = "lockKey";
RLock redissonLock = redisson.getLock(lockKey);
try {
//加锁(超时默认30s), 实现锁续命的功能(后台启动一个timer, 默认每10s检测一次是否持有锁)
redissonLock.lock();
//------ 执行业务逻辑 ----start------
int stock = Integer.valueOf(redisTemplate.opsForValue().get("stock"));
if (stock > 0) {
int newStock = stock - 1;
//执行业务操作减库存
redisTemplate.opsForValue().set("stock", newStock + "");
System.out.println("扣减库存成功, 剩余库存:" + newStock);
} else {
System.out.println("库存已经为0,不能继续扣减");
}
//------ 执行业务逻辑 ----end------
} finally {
//解锁
redissonLock.unlock();
}
return "success";
}
}
03 总结:
- 追求数据可靠性/强一致性:使用Zookeeper
- 追求性能:选择Redis,推荐Redisson
- Redis分布式锁目前最大问题在于:主从模式下/集群模式下,master节点宕机,异步同步数据导致锁丢失问题
- Redis的RedLock算法具有很大争议性,一般不推荐使用
参考原文地址:
【分布式锁】三种分布式锁的实现