在技术交流群里看到这样一条消息:“工作5年了,简历上写着熟悉分布式系统,结果一次分布式锁都没用到过。是我太菜,还是公司系统太稳了?“这句话像一块石头投入水中,立刻激起了无数共鸣。有人说"我们连Redis集群都没有,就一台单机Redis”,有人调侃"别说分布式锁,我们连’分布’俩字都少见”。其实这不是个例,而是大多数开发者的真实工作状态。
分布式锁本质上是分布式系统中的"交通规则"。当多个服务实例同时争抢同一个资源时,需要通过分布式锁来保证操作的原子性和一致性。就像十字路口的红绿灯,确保车辆有序通行,避免碰撞。
生活中类似的场景随处可见:演唱会门票限量发售时, thousands of用户同时抢购,需要防止超卖;银行转账时,两个账户间的资金划转必须原子化,不能出现一方扣款成功而另一方未到账的情况;甚至连公司里的打印机,也需要通过排队机制避免多人同时操作导致的混乱。
在技术领域,分布式锁主要解决三类问题:
但现实是,很多公司的业务规模根本达不到需要分布式锁的程度。当用户量不足百万、日均订单不过万时,单机系统配合数据库锁就能稳定运行,完全不需要引入分布式锁这种"重型武器"。
只有当业务规模达到一定量级,分布式锁才会从"选择题"变成"必答题"。以下两个真实场景,或许能帮你理解分布式锁的实际价值。
超卖事故背后的技术盲区
某电商平台在周年庆期间推出"限量100件"的秒杀活动,结果活动结束后后台显示成交130单。仓库爆仓、客服被投诉淹没、用户因无法发货发起集体维权,整个团队通宵处理客诉。
事故根源在于架构设计的疏漏:十台应用服务器同时处理下单请求,每台服务器都独立检查库存。当剩余库存为5时,可能同时有6台服务器检测到"库存充足",并分别执行扣减操作,最终导致超卖。
这时候分布式锁就成了救命稻草:在扣减库存前,所有服务器必须先争抢锁资源,只有抢到锁的服务器才能执行库存检查和扣减操作,从而保证同一时间只有一个操作在修改库存。
重复退款引发的财务危机
一位用户在退款页面连续点击两次按钮,由于服务部署在三个节点上,两个节点同时接收到请求。系统没有防重机制,导致同一笔订单被退款两次。虽然最终通过财务手段追回资金,但这个过程中产生的银行手续费、人工核查成本,以及用户对平台的信任危机,都让团队付出了沉重代价。
这类问题的技术解决方案,正是通过分布式锁将退款操作串行化:以订单号作为锁标识,确保同一订单的退款请求只能被一个服务实例处理,从根本上杜绝重复执行的可能。
Redis凭借其高性能和原子操作特性,成为实现分布式锁的首选工具。基于SpringBoot的简单实现如下:
@Component
public class RedisDistributedLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 尝试获取分布式锁
* @param lockKey 锁的key
* @param requestId 请求标识(用于释放锁时验证)
* @param expireTime 锁的过期时间(秒)
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, String requestId, long expireTime) {
return redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
}
/**
* 释放分布式锁
* @param lockKey 锁的key
* @param requestId 请求标识
*/
public void releaseLock(String lockKey, String requestId) {
String currentValue = redisTemplate.opsForValue().get(lockKey);
if (requestId.equals(currentValue)) {
redisTemplate.delete(lockKey);
}
}
}在业务代码中使用时,只需围绕核心逻辑添加锁控制:
@Service
public class OrderService {
@Autowired
private RedisDistributedLock distributedLock;
public void createOrder(String productId, int quantity) {
String lockKey = "lock:order:" + productId;
String requestId = UUID.randomUUID().toString();
try {
// 尝试获取锁,锁过期时间为10秒
boolean locked = distributedLock.tryLock(lockKey, requestId, 10);
if (!locked) {
throw new RuntimeException("系统繁忙,请重试");
}
// 执行核心业务逻辑
checkStock(productId);
reduceStock(productId, quantity);
createOrderRecord(productId, quantity);
} finally {
// 释放锁
distributedLock.releaseLock(lockKey, requestId);
}
}
}但这个基础版本存在三个致命问题:锁过期时间设置不合理会导致业务未完成就释放锁;服务宕机可能导致锁无法释放;并发场景下可能误释放他人持有锁。这些问题在生产环境中可能引发更严重的故障。
工业级应用中,推荐使用成熟的分布式锁框架Redisson,它已内置解决了上述所有问题。
集成步骤只需两步
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.24.1</version>
</dependency>@Service
public class PaymentService {
@Autowired
private RedissonClient redissonClient;
public void processRefund(String orderNo) {
String lockKey = "lock:refund:" + orderNo;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,最多等待100秒,上锁10秒后自动解锁
boolean isLocked = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (isLocked) {
if (hasRefunded(orderNo)) {
throw new RuntimeException("该订单已退款,请勿重复操作");
}
doRefund(orderNo);
recordRefund(orderNo);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取锁失败", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}Redisson的优势在于:
如果你工作多年从未接触过分布式锁,不必焦虑。这通常意味着两种情况:
业务规模尚未达到分布式需求
很多公司的用户量和交易量,用单机系统加数据库锁就能稳定支撑。比如地方性企业的内部管理系统,日均访问量不足万次,根本不需要分布式架构。这种情况下,不用分布式锁反而是合理的技术选择——简单的架构意味着更低的维护成本和更高的稳定性。
隐形的技术保障在默默工作
即使在分布式环境中,你也可能在不知不觉中使用了替代方案:
这些技术方案在特定场景下同样能解决并发问题,只是实现方式与分布式锁不同而已。
分布式锁是解决特定问题的工具,而非衡量技术水平的标准。真正的技术能力体现在:理解业务本质,选择合适的技术方案,在稳定性、性能和开发效率之间找到平衡。
当然,了解分布式锁的原理和实现仍然很有必要——它不仅是面试高频考点,更是应对业务增长的技术储备。当你的系统用户量从十万级跃升到百万级,当订单峰值从每秒几十笔增长到几千笔时,这些知识将帮助你平稳度过架构升级的关键期。
技术成长就像治水,平时的储备如同筑堤,看似无用,实则在危机来临时才能显现价值。与其纠结是否用过分布式锁,不如理解它背后的分布式一致性原理——这才是真正能迁移到各种场景的核心能力。