3个月手搓Gamma架构,这个团队打造出了场景白盒化推理的“下一代内容OS”
2026/4/29 14:57:00
本方案基于Redisson实现分布式锁,结合重试机制和双重检查模式,确保在高并发场景下的数据一致性和系统稳定性。
┌─────────────────────────────────────────┐ │ 1. 检查缓存(无锁) │ │ ↓ 缓存未命中 │ │ 2. 获取分布式锁 │ │ ↓ 获取成功 │ │ 3. 再次检查缓存(双重检查) │ │ ↓ 缓存仍未命中 │ │ 4. 执行业务逻辑 │ │ 5. 更新缓存 │ │ 6. 释放锁 │ └─────────────────────────────────────────┘优势:
尝试次数: 1 ──失败──> 等待 200ms ──> 尝试次数: 2 ──失败──> 等待 400ms ──> 尝试次数: 3 ──失败──> 抛出异常特点:
lock.tryLock(waitTime, leaseTime, timeUnit)方法详解booleantryLock(longwaitTime,longleaseTime,TimeUnitunit)throwsInterruptedException| 参数 | 类型 | 说明 | 示例值 |
|---|---|---|---|
waitTime | long | 等待时间:尝试获取锁的最大等待时间。如果在这段时间内无法获取锁,方法返回false | 15 |
leaseTime | long | 持有时间:锁的自动释放时间。超过这个时间,锁会自动释放,即使业务逻辑未完成 | 30 |
timeUnit | TimeUnit | 时间单位 | TimeUnit.SECONDS |
RLocklock=redissonClient.getLock("lock:key");booleanlocked=lock.tryLock(15,30,TimeUnit.SECONDS);含义:
答案:❌ 不会自动续命
当使用tryLock(waitTime, leaseTime, timeUnit)方法并指定了leaseTime参数时,Redisson 的看门狗机制会被禁用。
原因:
leaseTime时生效leaseTime后,Redisson 认为你希望锁在固定时间后自动释放验证方法:
// 方式1:指定 leaseTime(无看门狗)lock.tryLock(15,30,TimeUnit.SECONDS);// ❌ 无看门狗// 方式2:不指定 leaseTime(有看门狗)lock.tryLock(15,-1,TimeUnit.SECONDS);// ✅ 有看门狗(-1 表示不设置过期时间)// 或者lock.lock();// ✅ 有看门狗,默认30秒续命场景分析:
时间线: T=0s: 线程A获取锁,开始执行业务逻辑(预计需要45秒) T=30s: 锁自动释放(leaseTime到期) T=31s: 线程B获取锁,开始执行业务逻辑 T=45s: 线程A的业务逻辑完成,尝试释放锁(可能失败或释放了线程B的锁)可能的问题:
解决方案:
leaseTime,确保大于业务执行时间lock.isHeldByCurrentThread()检查锁的持有者看门狗是 Redisson 提供的一种自动续命机制,用于防止业务逻辑执行时间超过锁的持有时间。
┌─────────────────────────────────────────────────────┐ │ 1. 获取锁(不指定 leaseTime) │ │ 2. Redisson 启动看门狗线程 │ │ 3. 每 10 秒检查一次锁是否仍被当前线程持有 │ │ 4. 如果持有,自动续命 30 秒(默认值) │ │ 5. 业务逻辑完成后,释放锁,看门狗停止 │ │ 6. 如果线程异常退出,锁在 30 秒后自动释放 │ └─────────────────────────────────────────────────────┘lock()方法(推荐)RLocklock=redissonClient.getLock("lock:key");try{// 先尝试获取锁,最多等待15秒if(lock.tryLock(15,-1,TimeUnit.SECONDS)){try{// 业务逻辑// 看门狗会自动续命}finally{if(lock.isHeldByCurrentThread()){lock.unlock();}}}}catch(InterruptedExceptione){Thread.currentThread().interrupt();thrownewRuntimeException("获取锁被中断",e);}注意:leaseTime设置为-1表示不设置过期时间,启用看门狗。
lock()无参方法RLocklock=redissonClient.getLock("lock:key");try{// 阻塞等待获取锁,启用看门狗lock.lock();try{// 业务逻辑// 看门狗会自动续命}finally{if(lock.isHeldByCurrentThread()){lock.unlock();}}}catch(Exceptione){// 异常处理}注意:lock()方法会阻塞等待,直到获取到锁。
lock(long leaseTime, TimeUnit unit)并手动续命RLocklock=redissonClient.getLock("lock:key");try{if(lock.tryLock(15,30,TimeUnit.SECONDS)){try{// 业务逻辑// 如果预计执行时间超过30秒,需要手动续命if(需要续命){lock.expire(30,TimeUnit.SECONDS);// 手动续命30秒}}finally{if(lock.isHeldByCurrentThread()){lock.unlock();}}}}catch(InterruptedExceptione){Thread.currentThread().interrupt();thrownewRuntimeException("获取锁被中断",e);}Redisson 默认配置:
lockWatchdogTimeout= 30秒(默认值)自定义配置:
Configconfig=newConfig();// 设置看门狗超时时间为60秒config.setLockWatchdogTimeout(60000);// 单位:毫秒RedissonClientredissonClient=Redisson.create(config);| 特性 | 看门狗机制 | 固定过期时间 |
|---|---|---|
| 适用场景 | 业务执行时间不确定 | 业务执行时间可预估 |
| 自动续命 | ✅ 是 | ❌ 否 |
| 死锁风险 | 低(异常退出时自动释放) | 低(固定时间后释放) |
| 重复执行风险 | 低 | 高(超时后可能重复执行) |
| 性能开销 | 略高(需要后台线程) | 低 |
| 推荐使用 | ✅ 业务时间不确定时 | ✅ 业务时间确定且较短时 |
/** * 带重试机制的获取资源方法 * * @param maxRetries 最大重试次数 * @return 资源对象 */privateStringgetResourceWithRetry(intmaxRetries){intretryCount=0;ExceptionlastException=null;while(retryCount<maxRetries){try{returntryGetResourceWithLock();}catch(Exceptione){lastException=e;retryCount++;if(retryCount<maxRetries){// 指数退避:200ms, 400ms, 600ms...longsleepMs=200L*retryCount;log.warn("第{}次尝试失败,{}ms后重试,错误: {}",retryCount,sleepMs,e.getMessage());try{Thread.sleep(sleepMs);}catch(InterruptedExceptionie){Thread.currentThread().interrupt();thrownewRuntimeException("获取资源被中断",ie);}}}}log.error("重试{}次后仍然失败",maxRetries);thrownewRuntimeException("获取资源失败: "+(lastException!=null?lastException.getMessage():"未知错误"));}| 策略 | 公式 | 示例(3次重试) | 适用场景 |
|---|---|---|---|
| 固定间隔 | sleepMs = fixed | 200ms, 200ms, 200ms | 系统负载稳定 |
| 线性递增 | sleepMs = base * retryCount | 200ms, 400ms, 600ms | 通用场景(推荐) |
| 指数退避 | sleepMs = base * 2^(retryCount-1) | 200ms, 400ms, 800ms | 高并发场景 |
| 随机退避 | sleepMs = random(base, max) | 150-250ms, 300-500ms | 避免惊群效应 |
@Component@Slf4jpublicclassResourceManager{privatestaticfinalStringRESOURCE_CACHE_KEY="resource:cache:key";privatestaticfinalStringRESOURCE_LOCK_KEY="lock:resource:key";privatestaticfinalintMAX_RETRIES=3;@ResourceprivateRedissonClientredissonClient;/** * 获取资源(带缓存和重试机制) * * @param forceRefresh 是否强制刷新 * @return 资源对象 */publicStringgetResource(booleanforceRefresh){// 1. 如果不是强制刷新,先从缓存读取if(!forceRefresh){RBucket<String>bucket=redissonClient.getBucket(RESOURCE_CACHE_KEY);StringcachedResource=bucket.get();if(StringUtils.isNotBlank(cachedResource)){log.debug("资源从缓存获取成功");returncachedResource;}}// 2. 使用分布式锁获取资源,增加重试机制returngetResourceWithRetry(forceRefresh,MAX_RETRIES);}/** * 带重试机制的获取资源 * * @param forceRefresh 是否强制刷新 * @param maxRetries 最大重试次数 * @return 资源对象 */privateStringgetResourceWithRetry(booleanforceRefresh,intmaxRetries){intretryCount=0;ExceptionlastException=null;while(retryCount<maxRetries){try{returntryGetResourceWithLock(forceRefresh);}catch(Exceptione){lastException=e;retryCount++;if(retryCount<maxRetries){longsleepMs=200L*retryCount;// 递增延迟log.warn("第{}次尝试失败,{}ms后重试,错误: {}",retryCount,sleepMs,e.getMessage());try{Thread.sleep(sleepMs);}catch(InterruptedExceptionie){Thread.currentThread().interrupt();thrownewRuntimeException("获取资源被中断",ie);}}}}log.error("重试{}次后仍然失败",maxRetries);thrownewRuntimeException("获取资源失败: "+(lastException!=null?lastException.getMessage():"未知错误"));}/** * 使用分布式锁尝试获取资源 * * @param forceRefresh 是否强制刷新 * @return 资源对象 */privateStringtryGetResourceWithLock(booleanforceRefresh){RLocklock=redissonClient.getLock(RESOURCE_LOCK_KEY);try{// 方案1:使用固定过期时间(适合业务时间可预估的场景)booleanlocked=lock.tryLock(15,30,TimeUnit.SECONDS);// 方案2:使用看门狗机制(适合业务时间不确定的场景)// boolean locked = lock.tryLock(15, -1, TimeUnit.SECONDS);if(!locked){log.warn("获取分布式锁超时");// 锁获取失败时,再次尝试从缓存读取if(!forceRefresh){RBucket<String>bucket=redissonClient.getBucket(RESOURCE_CACHE_KEY);StringcachedResource=bucket.get();if(StringUtils.isNotBlank(cachedResource)){log.info("锁超时后从缓存获取到资源");returncachedResource;}}thrownewRuntimeException("获取分布式锁超时,可能系统繁忙");}try{// 3. 双重检查,避免重复获取if(!forceRefresh){RBucket<String>bucket=redissonClient.getBucket(RESOURCE_CACHE_KEY);StringcachedResource=bucket.get();if(StringUtils.isNotBlank(cachedResource)){log.info("获取锁后从缓存获取到资源(双重检查)");returncachedResource;}}// 4. 执行业务逻辑(获取资源)Stringresource=fetchResourceFromSource();// 5. 写入缓存if(StringUtils.isNotBlank(resource)){RBucket<String>bucket=redissonClient.getBucket(RESOURCE_CACHE_KEY);bucket.set(resource,3600,TimeUnit.SECONDS);// 缓存1小时log.info("资源已缓存");}returnresource;}finally{// 6. 释放锁(确保只释放当前线程持有的锁)if(lock.isHeldByCurrentThread()){lock.unlock();log.debug("锁已释放");}}}catch(InterruptedExceptione){log.error("获取锁时被中断",e);Thread.currentThread().interrupt();thrownewRuntimeException("获取资源被中断",e);}}/** * 从数据源获取资源(业务逻辑) */privateStringfetchResourceFromSource(){// 实现具体的业务逻辑// 例如:调用外部API、查询数据库等return"resource_data";}}lock.tryLock()参数推荐// 推荐配置booleanlocked=lock.tryLock(15,30,TimeUnit.SECONDS);参数说明:
waitTime = 15秒:等待时间适中,避免长时间阻塞leaseTime = 30秒:根据业务最大执行时间设置,建议设置为业务最大执行时间 * 1.5// 推荐配置:使用看门狗机制booleanlocked=lock.tryLock(15,-1,TimeUnit.SECONDS);// 或者lock.lock();// 阻塞等待,启用看门狗参数说明:
waitTime = 15秒:等待时间leaseTime = -1:不设置过期时间,启用看门狗// 推荐配置:缩短等待时间,避免线程堆积booleanlocked=lock.tryLock(5,20,TimeUnit.SECONDS);参数说明:
waitTime = 5秒:快速失败,避免线程堆积leaseTime = 20秒:根据实际业务时间设置业务执行时间是否可预估? ├─ 是 │ ├─ 执行时间 < 10秒 → waitTime=10s, leaseTime=15s │ ├─ 执行时间 10-30秒 → waitTime=15s, leaseTime=30s │ └─ 执行时间 > 30秒 → 考虑使用看门狗机制 │ └─ 否 └─ 使用看门狗机制 → waitTime=15s, leaseTime=-1| 业务类型 | waitTime | leaseTime | 是否看门狗 | 说明 |
|---|---|---|---|---|
| Token刷新 | 15s | 30s | ❌ | 通常很快完成 |
| 缓存预热 | 10s | 20s | ❌ | 数据加载较快 |
| 数据同步 | 30s | 60s | ❌ | 中等耗时操作 |
| 批量处理 | 15s | -1 | ✅ | 时间不确定 |
| 复杂计算 | 15s | -1 | ✅ | 时间不确定 |
| 外部API调用 | 15s | 30s | ❌ | 有超时控制 |
| 场景 | 最大重试次数 | 初始延迟 | 退避策略 | 说明 |
|---|---|---|---|---|
| 高可用要求 | 5 | 200ms | 线性递增 | 提高成功率 |
| 快速失败 | 2 | 100ms | 固定间隔 | 快速响应 |
| 网络不稳定 | 3 | 300ms | 指数退避 | 适应网络波动 |
| 通用场景 | 3 | 200ms | 线性递增 | 推荐配置 |
问题:tryLock()返回false时,业务逻辑无法执行。
解决方案:
问题:业务异常导致锁未释放。
解决方案:
try-finally确保锁释放lock.isHeldByCurrentThread()检查leaseTime或使用看门狗问题:线程A的锁被线程B释放。
解决方案:
// ✅ 正确:检查锁的持有者if(lock.isHeldByCurrentThread()){lock.unlock();}// ❌ 错误:直接释放lock.unlock();// 可能释放其他线程的锁解决方案:
StopWatchstopWatch=StopWatch.createStarted();booleanlocked=lock.tryLock(15,30,TimeUnit.SECONDS);longwaitTime=stopWatch.getTime();if(waitTime>5000){log.warn("锁等待时间过长: {}ms",waitTime);}优化建议:
synchronizedwaitTime和leaseTimeleaseTimewaitTime,快速失败