在分布式系统中,缓存 和分布式锁都是用于解决高并发场景下的并发控制和资源竞争问题,但它们的适用场景和机制不同,选择哪种方案取决于业务需求。以下是两者的区别:
✅ 一、分布式锁
⚙️ 工作原理
-
核心思想 :确保同一时刻只有一个线程或服务实例能够获取到锁,从而实现串行化处理。
-
实现方式:
-
Redis 分布式锁(如 Redisson)
-
Zookeeper 分布式锁
-
数据库表(基于行级锁或唯一索引)
-
🏗️ 适用场景
-
需要严格保证资源的独占性(如资金扣减、库存扣减等)。
-
任务耗时较长,且不允许出现并发修改。
-
需要支持事务性,如事务内的数据一致性。
⚡ 优缺点
优点 | 缺点 |
---|---|
串行化,严格保证资源独占性 | 串行化导致并发效率低,吞吐量受限 |
可配合超时机制避免死锁 | 需要额外的锁管理器(Redis、Zookeeper等) |
易于处理复杂的业务逻辑 | 依赖外部服务,存在可用性和性能瓶颈 |
✅ 二、缓存(Caffeine / Redis 等)
⚙️ 工作原理
-
核心思想:将处理中的资源 ID 存储到缓存中,其他线程在发现缓存中已有该资源时,直接返回"正在处理中",从而避免重复处理。
-
实现方式:
-
内存缓存(如 Caffeine)
-
分布式缓存(如 Redis)
-
🏗️ 适用场景
-
高并发读写场景,希望在某一短暂时间内避免重复处理。
-
任务耗时较短,允许一定程度的"最终一致性"。
-
业务允许一定的重试或幂等处理,例如重复扣库存、重复生成报告等。
⚡ 优缺点
优点 | 缺点 |
---|---|
并发量大,吞吐量高,延时小 | 无法严格保证资源独占性 |
实现简单,依赖较少 | 缓存超时时可能会导致并发问题 |
适合读多写少或允许最终一致性场景 | 需要手动设计缓存失效、过期策略 |
🏷️ 三、应用场景对比
场景 | 适用方案 | 原因 |
---|---|---|
库存扣减 | 分布式锁 | 需要严格保证库存准确性,防止超卖。 |
订单发货 | 缓存 (Caffeine/Redis) | 发货状态可异步轮询或幂等,减少锁等待。 |
定时任务调度 | 分布式锁 | 只允许一个节点在某一时间执行任务。 |
支付状态查询 | 缓存 | 高频查询,短暂过期即可,避免高并发。 |
防止重复提交 | 缓存 (Token/ID) | 提交频繁,数据可通过重试或最终一致性修复。 |
处理同一单据(短时) | 缓存 (Caffeine/Redis) | 处理时间短,使用缓存避免分布式锁开销。 |
长时间任务执行 | 分布式锁 | 任务耗时长,缓存容易过期或频繁刷新。 |
🏅 四、如何选择?
考量因素 | 分布式锁 | 缓存 |
---|---|---|
业务一致性 | 需要严格一致性 | 最终一致性可接受 |
任务耗时 | 较长 | 较短 |
并发量 | 低并发,串行执行 | 高并发,批量处理 |
可用性 | 外部锁服务,存在单点风险 | 缓存本地或分布式,简单易用 |
实现复杂度 | 较高 | 较低 |
性能开销 | 较高,存在锁等待 | 较低,直接返回 |
🎯 五、推荐方案
-
优先使用缓存:
-
如果场景允许"最终一致性",并且任务耗时短,使用缓存会更简单高效。
-
例如:防止订单重复处理、限制高频接口调用。
-
-
必须使用分布式锁:
-
涉及资金、库存等关键资源,必须保证唯一性,适合使用分布式锁。
-
例如:库存扣减、任务调度等。
-
-
混合使用:
- 在大部分情况下使用缓存进行并发控制,但在极端情况下(例如缓存失效或长任务)使用分布式锁作为兜底策略。
🏁 六、Caffeine 示例(5秒缓存)
java
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class CacheService {
private final Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.maximumSize(1000)
.build();
public boolean tryLock(String key) {
// 如果返回 null,表示没人持有锁,获取锁
return cache.asMap().putIfAbsent(key, "LOCKED") == null;
}
public void releaseLock(String key) {
cache.invalidate(key);
}
public void processOrder(String orderId) {
if (tryLock(orderId)) {
try {
// 模拟业务处理
System.out.println("Processing order: " + orderId);
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
releaseLock(orderId);
}
} else {
System.out.println("Order " + orderId + " is already being processed.");
}
}
}
🚀 总结
-
缓存 适合短期并发控制,效率更高,但对强一致性不友好。
-
分布式锁 适合长时间任务和强一致性场景,但存在性能瓶颈。
-
混合方案可在大部分场景下使用缓存,极端情况下使用锁兜底。