分布式锁获取失败属正常业务分支,应返回结构化结果而非抛异常;仅redis连接失败等底层故障才抛lockunavailableexception并统一转503/500。

Java 中分布式锁获取失败(如超时或被其他节点抢占)本身不是 Java 异常,而是业务逻辑结果,不应抛出全局异常来统一处理。强行包装成 RuntimeException 并用 @ControllerAdvice 拦截,会混淆“错误”与“预期业务分支”,导致监控失真、重试逻辑混乱、调用方误判。
区分锁失败类型,走明确的业务返回路径
Redisson、ShardedJedis 或自研基于 Redis 的锁通常提供 boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) 方法。返回 false 就是“未抢到”,属于正常流程:
- 前端调用:返回 HTTP 200 + { "code": 409, "msg": "资源正被操作,请稍后重试" },前端可提示用户或自动轮询
- 内部服务调用:封装 Result
或自定义 LockResult(含 status: SUCCESS/LOCK_TIMEOUT/OTHER_NODE_HOLDING),由上游决定是否重试或降级 - 避免 throw new IllegalStateException("Lock acquire timeout") —— 这会让 FeignClient 认为是服务端崩溃,触发熔断
需要异常场景?仅限底层通信故障
真正该抛异常的,是锁组件本身不可用,例如:
- Redis 连接池耗尽、节点全部失联
- 序列化失败(如锁 key 含不可序列化对象)
- Lua 脚本执行报错(语法错误或 Redis 版本不兼容)
这类问题才适合在锁客户端内部捕获,包装为 LockUnavailableException 等受检或非受检异常,并通过 @ControllerAdvice 统一转为 503 Service Unavailable 或 500 Internal Error。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
推荐实践:模板化锁执行器
封装一个 LockTemplate,把“尝试加锁 → 执行业务 → 自动释放”流程标准化,失败时返回结构化结果:
public <t> LockResult<t> execute(String lockKey, long waitSec, long holdSec, Supplier<t> action) {
RLock lock = redisson.getLock(lockKey);
try {
if (!lock.tryLock(waitSec, holdSec, TimeUnit.SECONDS)) {
return LockResult.lockTimeout();
}
return LockResult.success(action.get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return LockResult.interrupted();
} catch (RedisConnectionClosedException | RedisTimeoutException e) {
throw new LockUnavailableException("Redis unavailable", e);
} finally {
if (lock.isHeldByCurrentThread()) lock.unlock();
}
}
</t></t></t>
Controller 层直接处理 LockResult,不再出现 try-catch 锁逻辑,也无需全局异常拦截锁失败。
补充:超时时间要分层设置
避免所有地方都写 3s,应按场景分级:
- 高频短操作(如库存预占):wait=500ms,hold=2s
- 中低频事务(如订单创建):wait=2s,hold=10s
- 后台批任务:wait=0(不等待),用 setIfAbsent + TTL 实现“抢占即得”
不同 wait 时间对应不同前端提示文案和重试策略,比统一抛异常更精准可控。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










