马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
1. 分布式锁简介
1.1 什么是分布式锁
在单机应用中,可以利用 Java 内置的锁机制(如 synchronized、ReentrantLock 等)来实现线程间的同步。但在分布式情况下,由于应用部署在多台服务器上,传统的单机锁无法满足需求,这时就需要分布式锁。
分布式锁是一种跨 JVM、跨服务器的锁机制,它能够在分布式系统中对共享资源举行互斥访问控制,确保在同一时间只有一个客户端可以获得锁并执行操作。
1.2 分布式锁应用场景
- 防止重复下单:在电商系统中,防止用户重复提交订单
- 秒杀系统:控制商品库存的并发访问,克制超卖
- 定时使命:确保集群情况下,定时使命只被一个节点执行
- 数据一致性掩护:掩护跨系统的数据一致性操作
1.3 分布式锁的核心要求
- 互斥性:在任何时刻,只有一个客户端能持有锁
- 可重入性:同一个客户端可以多次获取同一把锁
- 防死锁:纵然客户端崩溃,锁也应该在肯定时间后自动开释
- 高可用:分布式锁服务不应成为系统的单点故障
- 性能:锁操作应该具备高性能、低延迟的特性
2. Redisson 简介
2.1 什么是 Redisson
Redisson 是一个在 Redis 基础上实现的 Java 驻内存数据网格(In-Memory Data Grid)。它提供了分布式和可扩展的 Java 数据结构,包括分布式锁、分布式集合、分布式对象等功能。
2.2 Redisson 与 Jedis、Lettuce 对比
- Jedis:Redis 的 Java 客户端,提供了 Redis 命令的基本封装,API 简朴直观,但功能相对基础
- Lettuce:高级 Redis 客户端,基于 Netty,支持异步操作,性能优于 Jedis
- Redisson:不仅提供了 Redis 客户端功能,还提供了分布式锁、分布式集合等更高级的分布式特性,对分布式开辟更加友好
2.3 Redisson 的主要功能
- 分布式锁和同步器:分布式锁、读写锁、信号量、闭锁等
- 分布式集合:Map、Set、List 等数据结构的分布式实现
- 分布式服务:远程服务、实时对象服务等
- 分布式执行服务:分布式执行服务、调度使命服务等
3. Redisson 分布式锁的实现原理
3.1 基于 Redis 的锁实现
Redisson 的分布式锁基于 Redis 的EVAL命令(执行 Lua 脚本)实现。它利用了一个 Redis 键值对来表现锁,键是锁的名称,值包罗锁的持有者信息和过期时间。
基本流程:
- 获取锁:通过 Lua 脚本尝试在 Redis 中设置一个键值对,如果键不存在则获取乐成
- 锁的持有:为该键设置过期时间(克制死锁)
- 锁的开释:通过执行 Lua 脚本删除对应的键
- 锁的续期:通过看门狗机制延长锁的过期时间
3.2 锁的实现方案
Redisson 提供了多种锁的实现方案:
- 普通可重入锁(RLock):最基本的分布式锁实现,支持可重入
- 公平锁(RFairLock):按照请求序次获取锁
- 读写锁(RReadWriteLock):读锁共享,写锁独占
- 多重锁(RedissonMultiLock):可以组合多个锁为一个锁
- 红锁(RedissonRedLock):基于 Redis 集群的高可靠性锁实现,可以抵御部分节点故障
3.3 锁的获取和开释流程
锁的获取:- -- KEYS[1]是锁的key,ARGV[1]是线程标识,ARGV[2]是过期时间
- if (redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then
- redis.call('hincrby', KEYS[1], ARGV[1], 1);
- redis.call('pexpire', KEYS[1], ARGV[2]);
- return nil;
- end;
- return redis.call('pttl', KEYS[1]);
复制代码 锁的开释:- -- KEYS[1]是锁的key,ARGV[1]是线程标识
- if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then
- return nil;
- end;
- local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1);
- if (counter > 0) then
- return 0;
- else
- redis.call('del', KEYS[1]);
- return 1;
- end;
复制代码 4. Redisson 分布式锁的利用
4.1 Maven 依赖配置
- <dependency>
- <groupId>org.redisson</groupId>
- <artifactId>redisson</artifactId>
- <version>3.23.3</version>
- </dependency>
复制代码 4.2 基本配置
- @Configuration
- public class RedissonConfig {
- @Value("${spring.data.redis.host}")
- private String host;
- @Value("${spring.data.redis.port}")
- private int port;
- @Value("${spring.data.redis.password}")
- private String password;
- @Value("${spring.data.redis.database}")
- private int database;
- @Bean
- public RedissonClient redissonClient() {
- Config config = new Config();
- config.useSingleServer()
- .setAddress("redis://" + host + ":" + port)
- .setDatabase(database);
- if (password != null && !password.isEmpty()) {
- config.useSingleServer().setPassword(password);
- }
- return Redisson.create(config);
- }
- }
复制代码 4.3 基本锁的利用
- @Service
- public class LockService {
- @Resource
- private RedissonClient redissonClient;
- public void doSomething() {
- RLock lock = redissonClient.getLock("myLock");
- try {
- // 尝试获取锁,最多等待100秒,锁有效期为30秒
- boolean isLocked = lock.tryLock(100, 30, TimeUnit.SECONDS);
- if (isLocked) {
- // 业务处理
- System.out.println("执行业务逻辑");
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } finally {
- // 释放锁
- if (lock.isHeldByCurrentThread()) {
- lock.unlock();
- }
- }
- }
- }
复制代码 4.4 不同类型锁的利用
- RLock lock = redissonClient.getLock("myLock");
- lock.lock();
- try {
- try {
- lock.lock();
- } finally {
- lock.unlock();
- }
- } finally {
- lock.unlock();
- }
复制代码- RLock fairLock = redissonClient.getFairLock("myFairLock");
- fairLock.lock();
- try {
- // 业务处理
- } finally {
- fairLock.unlock();
- }
复制代码- RReadWriteLock rwLock = redissonClient.getReadWriteLock("myRWLock");
- // 读锁(共享)
- RLock readLock = rwLock.readLock();
- readLock.lock();
- try {
- // 读取操作
- } finally {
- readLock.unlock();
- }
- // 写锁(排他)
- RLock writeLock = rwLock.writeLock();
- writeLock.lock();
- try {
- // 写入操作
- } finally {
- writeLock.unlock();
- }
复制代码- RLock lock1 = redissonClient.getLock("lock1");
- RLock lock2 = redissonClient.getLock("lock2");
- RLock lock3 = redissonClient.getLock("lock3");
- // 组合多个锁
- RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);
- multiLock.lock();
- try {
- // 业务处理
- } finally {
- multiLock.unlock();
- }
复制代码 5. 看门狗机制详解
5.1 什么是看门狗机制
看门狗(Watchdog)机制是 Redisson 为分布式锁提供的一种自动续期功能。它能够在客户端持有锁期间,自动延长锁的有效期,防止因为执行时间过长导致锁过期被其他客户端获取,从而破坏互斥性。
5.2 看门狗的工作原理
- 当客户端调用lock()方法获取锁时(不设置过期时间),Redisson 会默认设置一个 30 秒的锁有效期
- 同时,它会启动一个定时使命,默认每 10 秒检查一次(锁有效期的 1/3 时间)
- 如果客户端仍然持有锁,定时使命会自动革新锁的有效期为 30 秒
- 这个过程会不停持续,直到客户端主动开释锁,或者客户端崩溃(此时看门狗停止工作,锁会在 30 秒后自动开释)
5.3 看门狗的关键源码分析
Redisson 中看门狗的核心实如今RedissonLock.java类中:- // 锁的自动续期逻辑
- private void scheduleExpirationRenewal(long threadId) {
- ExpirationEntry entry = new ExpirationEntry();
- ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
- if (oldEntry != null) {
- oldEntry.addThreadId(threadId);
- } else {
- entry.addThreadId(threadId);
- renewExpiration();
- }
- }
- // 续期定时任务
- private void renewExpiration() {
- Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
- @Override
- public void run(Timeout timeout) throws Exception {
- // 续期逻辑
- // ...
- // 每internalLockLeaseTime/3时间后,重新检查并续期
- commandExecutor.getConnectionManager().newTimeout(this,
- internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
- }
- }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
- }
复制代码 5.4 看门狗机制的启用和关闭
启用看门狗机制(默认):- // 不指定过期时间,默认启用看门狗机制
- lock.lock();
复制代码 禁用看门狗机制:- // 明确指定过期时间,看门狗机制将被禁用
- lock.lock(10, 30, TimeUnit.SECONDS);
复制代码 5.5 看门狗配置参数
可以通过配置修改看门狗的默认行为:- // 设置锁的默认过期时间(看门狗续期间隔为该值的1/3)
- Config config = new Config();
- config.setLockWatchdogTimeout(30000); // 30秒
- RedissonClient redisson = Redisson.create(config);
复制代码 6. 分布式锁的最佳实践
6.1 合理利用看门狗机制
- 对于执行时间不确定的使命,保举利用看门狗机制
- 对于执行时间确定且较短的使命,可以明确设置过期时间,关闭看门狗
6.2 锁的粒度选择
- 只管降低锁的粒度,例如对特定对象加锁,而不是整个方法
- 利用不同的锁名称来区分不同的业务操作
6.3 锁的开释包管
- 始终在 finally 块中开释锁
- 开释前检查当前线程是否持有锁(isHeldByCurrentThread)
- try {
- // 业务逻辑
- } finally {
- if (lock.isHeldByCurrentThread()) {
- lock.unlock();
- }
- }
复制代码 6.4 处理锁的获取失败
- int retryCount = 3;
- while (retryCount > 0) {
- boolean locked = lock.tryLock(5, TimeUnit.SECONDS);
- if (locked) {
- try {
- // 业务逻辑
- return result;
- } finally {
- lock.unlock();
- }
- }
- retryCount--;
- Thread.sleep(1000);
- }
- // 降级处理
- return fallbackMethod();
复制代码 7. Redisson 分布式锁与其他实现的对比
7.1 与 Redis 原生命令实现对比
Redis 原生命令(SETNX + EXPIRE):
- 优点:实现简朴,不依赖额外库
- 缺点:原子性包管困难,无法解决锁过期问题,不支持可重入
Redisson:
- 优点:实现了可重入、自动续期、公平锁等高级特性
- 缺点:额外的依赖,有肯定的学习成本
7.2 与 Zookeeper 实现对比
Zookeeper:
- 优点:强一致性包管,临时节点机制自动开释,有序性支持
- 缺点:性能较低,得当高可靠低频操作
Redisson:
- 优点:性能高,功能丰富,得当高频操作
- 缺点:在某些极端情况下一致性不如 Zookeeper
7.3 各种实现的实用场景
- Redisson:得当高性能场景,对一致性要求不是极高但要求低延迟
- Zookeeper:得当高可靠性场景,对性能要求不高但要求强一致性
- 数据库锁:得当与数据操作细密结合的场景
- etcd:得当对可靠性和一致性都有较高要求的中等性能场景
8. 常见问题及解决方案
8.1 锁的误删除问题
问题:一个客户端开释了其他客户端持有的锁
解决方案:
- Redisson 通过锁值内存储线程标识,包管只有持有锁的线程才能开释
- 开释时通过isHeldByCurrentThread()方法检查
8.2 锁的过期问题
问题:业务执行时间凌驾锁的有效期
解决方案:
- 利用看门狗机制自动续期
- 合理评估业务执行时间,设置足够的锁有效期
8.3 缓存崩溃和恢复
问题:Redis 服务器故障或重启
解决方案:
- 利用 Redis 集群提高可用性
- 在关键业务利用 RedissonRedLock(红锁)
- 实现业务赔偿机制
8.4 性能优化
- 减小锁粒度
- 适当设置锁等待超时时间
- 克制长时间持有锁
- 利用读写锁分离读写操作
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |