YAOTU INSIGHTS

Redisson分布式锁原理与Spring Boot实战指南

Redisson分布式锁原理与Spring Boot实战指南
1. 为什么需要分布式锁在单机环境下我们使用Java的synchronized关键字或者ReentrantLock就能很好地解决多线程并发问题。但在分布式系统中这些本地锁就完全失效了——因为你的应用可能部署在多个服务器上每个服务器都有自己的JVM进程本地锁只能控制当前进程内的线程同步。想象一下电商系统中的库存扣减场景如果三台服务器同时收到购买请求每台服务器都先查询库存假设还剩1件然后各自执行减1操作最终库存就变成了-2。这就是典型的分布式环境下的超卖问题。分布式锁的核心诉求是互斥性同一时刻只能有一个客户端持有锁避免死锁即使客户端崩溃锁也能自动释放可重入性同一个客户端可以多次获取同一把锁高可用锁服务要能承受部分节点故障2. Redisson分布式锁实现原理2.1 基于Redis的分布式锁演进早期常见的Redis分布式锁实现方式是使用SETNX命令SETNX lock_key unique_value EXPIRE lock_key 30这种方式存在原子性问题——如果SETNX成功但EXPIRE失败就会导致锁无法自动释放。后来改进为使用Lua脚本保证原子性if redis.call(setnx, KEYS[1], ARGV[1]) 1 then redis.call(expire, KEYS[1], ARGV[2]) return 1 else return 0 endRedisson在此基础上做了更完善的实现主要解决了以下问题锁自动续期看门狗机制可重入锁实现公平锁支持联锁MultiLock和红锁RedLock2.2 Redisson锁的核心数据结构当你调用getLock(myLock)时Redisson会在Redis中创建一个Hash结构myLock: { mode: redisson_lock, UUID_01:threadId_1: 1 // 客户端ID线程ID作为field值表示重入次数 }加锁成功后会额外设置一个超时时间默认30秒并通过看门狗线程定期每10秒检查并延长锁有效期。3. Spring Boot集成Redisson实战3.1 基础配置单节点模式首先在pom.xml中添加依赖dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.17.7/version /dependencyapplication.yml配置spring: redis: host: 127.0.0.1 port: 6379 password: database: 03.2 Redis Cluster集群配置对于生产环境建议使用Redis Clusterspring: redis: cluster: nodes: - 192.168.1.101:7001 - 192.168.1.101:7002 - 192.168.1.102:7001 - 192.168.1.102:7002 - 192.168.1.103:7001 - 192.168.1.103:7002 max-redirects: 3 password: yourpassword对应的Java配置类Configuration public class RedissonConfig { Bean public RedissonClient redissonClient() { Config config new Config(); config.useClusterServers() .addNodeAddress(redis://192.168.1.101:7001) .addNodeAddress(redis://192.168.1.101:7002) // 其他节点... .setPassword(yourpassword) .setScanInterval(5000); return Redisson.create(config); } }4. Redisson锁的多种用法4.1 基础锁使用// 获取锁对象 RLock lock redissonClient.getLock(orderLock); try { // 尝试加锁最多等待100秒上锁后30秒自动解锁 boolean res lock.tryLock(100, 30, TimeUnit.SECONDS); if (res) { // 执行业务逻辑 processOrder(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 释放锁 if (lock.isLocked() lock.isHeldByCurrentThread()) { lock.unlock(); } }4.2 公平锁实现公平锁按照请求的顺序分配锁RLock fairLock redissonClient.getFairLock(fairLock); fairLock.lock(); try { // 业务代码 } finally { fairLock.unlock(); }4.3 联锁MultiLock同时对多个资源加锁要么全部成功要么全部失败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 锁等待时间设置tryLock(waitTime, leaseTime, unit)中的两个时间参数需要合理设置waitTime获取锁的最大等待时间设置过短会导致高并发下大量请求失败leaseTime锁的持有时间设置过短可能导致业务未执行完锁已释放建议根据业务平均耗时设置leaseTime可以适当加缓冲时间对于不确定耗时的操作可以不指定leaseTime使用看门狗自动续期waitTime根据系统容忍度设置通常不超过5秒5.2 锁释放的正确姿势常见的错误做法// 错误示例1没有判断是否持有锁就释放 lock.unlock(); // 错误示例2没有判断当前线程是否持有锁 if (lock.isLocked()) { lock.unlock(); }正确做法if (lock.isLocked() lock.isHeldByCurrentThread()) { lock.unlock(); }5.3 锁续期与看门狗机制Redisson的看门狗机制默认在锁过期前1/3时间即10秒时自动续期。但需要注意只有不指定leaseTime时才会启用看门狗看门狗线程是后台守护线程应用关闭时会停止网络分区可能导致续期失败对于长时间任务建议// 显式设置较长的leaseTime lock.lock(5, TimeUnit.MINUTES); // 或者实现自定义续期逻辑 while (!finished) { // 每30秒续期一次 lock.expire(1, TimeUnit.MINUTES); Thread.sleep(30000); }6. 性能优化与监控6.1 Redisson配置调优在Config中可调整以下参数config.setLockWatchdogTimeout(30000); // 看门狗超时时间默认30秒 config.setUseScriptCache(true); // 启用Lua脚本缓存 config.setNettyThreads(32); // I/O线程数默认32 config.setThreads(64); // 处理线程数默认64对于高并发场景建议适当增加nettyThreads和threads数量启用连接池config.useClusterServers() .setMasterConnectionPoolSize(64) .setSlaveConnectionPoolSize(64);6.2 监控指标通过JMX监控关键指标redisson.redis_ops: Redis操作统计redisson.lock.wait_time: 锁等待时间redisson.lock.lease_time: 锁持有时间建议设置告警规则锁等待时间超过阈值如500ms锁竞争失败率突然升高Redis节点连接数接近上限7. 常见问题排查7.1 锁无法释放问题现象锁一直处于持有状态其他客户端无法获取排查步骤检查Redis中锁的TTLTTL yourLockKey确认看门狗线程是否正常运行检查网络连接是否正常查看应用日志是否有异常堆栈7.2 锁竞争激烈问题现象获取锁的等待时间明显变长解决方案优化锁粒度如从订单锁改为用户锁实现锁分段如库存锁按商品ID分段考虑改用Redis红锁RedLock算法7.3 Redis集群脑裂问题在Redis Cluster发生网络分区时可能出现多个客户端同时持有锁的情况。对于金融级场景建议使用红锁RedLock算法增加ZooKeeper等协调服务做二次确认业务层做幂等处理