Java 分布式锁实战:秒杀场景下的高并发数据一致性保障
高并发秒杀:首选 Redis 分布式锁(Redisson),结合乐观锁 + MQ 提升性能;数据一致性要求高:选择 ZooKeeper 或 etcd 分布式锁;云原生环境:优先使用 etcd(Kubernetes 原生支持);低并发场景:可使用数据库乐观锁,降低部署复杂度。最小锁粒度:尽量缩小锁的范围,避免粗粒度锁;最短锁持有时间:业务逻辑尽量精简,减少锁占用时间;高可用设计:锁服务集群部署,避免
某电商平台秒杀活动曾遭遇重大故障:并发量突破 10 万 / 秒时,分布式锁失效导致超卖 1200 件商品,直接损失超 50 万元。事后复盘发现,传统 Redis 分布式锁因未处理超时续期、锁释放异常等问题,导致锁机制失效。本文将以电商秒杀场景为核心,从分布式锁原理、主流实现方案、性能优化到一致性保障,提供一套可落地的高并发解决方案。
一、分布式锁核心:解决集群环境下的资源竞争
1. 为什么需要分布式锁?单机锁的局限性
在单体应用中,synchronized或ReentrantLock可解决多线程资源竞争问题,但在分布式集群环境下:
- JVM 隔离:不同节点的线程无法共享 JVM 锁,导致分布式事务冲突;
- 数据一致性风险:多节点同时操作同一数据(如库存扣减),引发超卖、重复下单等问题;
- 高可用挑战:单点锁服务故障会导致整个系统瘫痪。
电商秒杀场景的核心痛点:
- 商品库存是有限资源,需保证全局唯一扣减;
- 并发量极高(每秒数万请求),锁机制需高性能;
- 业务逻辑需原子性(扣库存 + 创建订单 + 记录日志)。
2. 分布式锁的核心特性
一个可靠的分布式锁需满足以下特性:
- 互斥性:同一时刻只能有一个客户端持有锁;
- 可重入性:同一客户端可重复获取同一把锁;
- 超时释放:防止客户端宕机导致锁永久占用;
- 高可用:锁服务集群部署,避免单点故障;
- 公平性:按请求顺序获取锁(可选);
- 性能:加锁 / 解锁操作延迟低,支持高并发。
3. 主流分布式锁方案对比
| 方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Redis 分布式锁 | 基于 SETNX 命令 | 性能高、部署简单 | 需处理超时续期、主从同步延迟 | 高并发秒杀、缓存更新 |
| ZooKeeper 分布式锁 | 基于临时有序节点 | 可靠性高、支持公平锁 | 性能较低、部署复杂 | 数据一致性要求高的场景 |
| etcd 分布式锁 | 基于 CAS 机制 + Lease | 高可用、支持 TTL 自动过期 | 生态不如 Redis 成熟 | Kubernetes 生态下的服务 |
| 数据库分布式锁 | 基于行锁或乐观锁 | 无需额外组件 | 性能差、易造成数据库瓶颈 | 低并发场景 |
二、实战实现:三种主流分布式锁的代码落地
1. Redis 分布式锁:高性能秒杀场景首选
Redis 分布式锁基于SETNX(SET if Not Exists)命令,结合 Lua 脚本保证原子性。
1.1 基础实现(Redisson 客户端)
Redisson 是 Redis 官方推荐的 Java 客户端,内置分布式锁实现:
java
运行
@Service
public class RedisLockStockService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private StockMapper stockMapper;
@Autowired
private OrderMapper orderMapper;
// 秒杀下单(Redis分布式锁)
public boolean seckill(Long productId, Long userId) {
// 1. 构建锁对象(锁名:product_stock_1001)
RLock lock = redissonClient.getLock("product_stock_" + productId);
try {
// 2. 尝试获取锁(最多等待3秒,持有锁10秒后自动释放)
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
return false; // 获取锁失败,秒杀失败
}
// 3. 业务逻辑(原子性操作)
// 3.1 查库存
Stock stock = stockMapper.selectByProductId(productId);
if (stock == null || stock.getQuantity() <= 0) {
return false; // 库存不足
}
// 3.2 扣库存
int rows = stockMapper.deductStock(productId);
if (rows == 0) {
return false; // 扣减失败
}
// 3.3 创建订单
Order order = new Order();
order.setProductId(productId);
order.setUserId(userId);
order.setStatus(OrderStatus.CREATED);
orderMapper.insert(order);
return true; // 秒杀成功
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
// 4. 释放锁(确保只有持有锁的客户端释放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
1.2 高级特性:超时续期(Watch Dog)
Redisson 自动实现锁超时续期:
java
运行
// 配置Redisson(application.yml)
redisson:
singleServerConfig:
address: redis://localhost:6379
password: 123456
lockWatchdogTimeout: 30000 # 看门狗超时时间(30秒),默认30秒
当业务逻辑执行时间超过锁超时时间时,Redisson 会自动续期(每 10 秒续期一次),避免锁提前释放。
1.3 集群模式:解决 Redis 单点故障
java
运行
// Redisson集群配置
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://redis-node1:6379")
.addNodeAddress("redis://redis-node2:6379")
.addNodeAddress("redis://redis-node3:6379")
.setScanInterval(2000); // 集群节点扫描间隔
RedissonClient client = Redisson.create(config);
2. ZooKeeper 分布式锁:高可靠性场景
Curator 是 ZooKeeper 的 Java 客户端,提供分布式锁实现:
java
运行
@Service
public class ZkLockStockService {
@Autowired
private CuratorFramework curatorClient;
@Autowired
private StockMapper stockMapper;
// 秒杀下单(ZooKeeper分布式锁)
public boolean seckill(Long productId, Long userId) {
// 1. 创建分布式锁(锁路径:/seckill/lock/product_1001)
InterProcessMutex lock = new InterProcessMutex(
curatorClient, "/seckill/lock/product_" + productId
);
try {
// 2. 尝试获取锁(最多等待3秒)
boolean locked = lock.acquire(3, TimeUnit.SECONDS);
if (!locked) {
return false;
}
// 3. 业务逻辑(同Redis锁)
// ... 扣库存、创建订单
return true;
} catch (Exception e) {
log.error("ZooKeeper锁异常", e);
return false;
} finally {
// 4. 释放锁
try {
if (lock.isAcquiredInThisProcess()) {
lock.release();
}
} catch (Exception e) {
log.error("释放ZooKeeper锁失败", e);
}
}
}
}
ZooKeeper 锁的优势:
- 基于临时节点,客户端宕机后自动释放锁;
- 支持公平锁,按请求顺序获取锁;
- 集群部署,可靠性高。
3. etcd 分布式锁:云原生场景首选
etcd 基于 Raft 协议,提供强一致性的分布式锁:
java
运行
@Service
public class EtcdLockStockService {
@Autowired
private EtcdClient etcdClient;
@Autowired
private StockMapper stockMapper;
// 秒杀下单(etcd分布式锁)
public boolean seckill(Long productId, Long userId) {
// 1. 锁键名
String lockKey = "/seckill/lock/product_" + productId;
// 2. 客户端唯一标识(防止误解锁)
String lockValue = UUID.randomUUID().toString();
try {
// 3. 尝试获取锁(TTL=10秒)
boolean locked = etcdClient.tryLock(lockKey, lockValue, 10);
if (!locked) {
return false;
}
// 4. 业务逻辑
// ... 扣库存、创建订单
return true;
} finally {
// 5. 释放锁(通过CAS确保只有持有者释放)
etcdClient.unlock(lockKey, lockValue);
}
}
}
// etcd客户端工具类
@Component
public class EtcdClient {
private final Client client;
public EtcdClient(@Value("${etcd.endpoints}") String endpoints) {
this.client = Client.builder().endpoints(endpoints.split(",")).build();
}
// 尝试获取锁
public boolean tryLock(String key, String value, long ttl) {
try (LockClient lockClient = client.getLockClient()) {
Lease lease = client.getLeaseClient().grant(ttl).get();
LockResponse response = lockClient.lock(key.getBytes(), lease.getId()).get();
return response.getSucceeded();
} catch (Exception e) {
return false;
}
}
// 释放锁
public void unlock(String key, String value) {
try (LockClient lockClient = client.getLockClient()) {
lockClient.unlock(key.getBytes()).get();
} catch (Exception e) {
log.error("释放etcd锁失败", e);
}
}
}
三、性能优化:秒杀场景的分布式锁调优
1. Redis 锁性能优化
1.1 批量扣减库存
将多次扣减合并为一次操作,减少锁持有时间:
java
运行
// 批量处理秒杀请求
public List<Boolean> batchSeckill(Long productId, List<Long> userIds) {
RLock lock = redissonClient.getLock("product_stock_" + productId);
try {
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
return userIds.stream().map(u -> false).collect(Collectors.toList());
}
// 批量扣库存(一次SQL操作)
int totalCount = userIds.size();
int rows = stockMapper.batchDeductStock(productId, totalCount);
if (rows == 0) {
return userIds.stream().map(u -> false).collect(Collectors.toList());
}
// 批量创建订单
List<Order> orders = userIds.stream().map(userId -> {
Order order = new Order();
order.setProductId(productId);
order.setUserId(userId);
return order;
}).collect(Collectors.toList());
orderMapper.batchInsert(orders);
return userIds.stream().map(u -> true).collect(Collectors.toList());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return userIds.stream().map(u -> false).collect(Collectors.toList());
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
1.2 缓存预热 + 限流
- 缓存预热:秒杀开始前将商品库存加载到 Redis 缓存;
- 令牌桶限流:控制每秒请求数,避免 Redis 过载:
java
运行
@Autowired
private RateLimiter rateLimiter; // Guava RateLimiter
public boolean seckillWithRateLimit(Long productId, Long userId) {
// 限流(每秒1000请求)
if (!rateLimiter.tryAcquire()) {
return false;
}
// 执行秒杀逻辑
return seckill(productId, userId);
}
1.3 主从分离 + 读写分离
- 主库:处理写操作(扣库存);
- 从库:处理读操作(查库存);
- Redis Cluster:分片存储,提高并发能力。
2. 锁粒度优化:从粗粒度到细粒度
2.1 商品维度锁→用户维度锁
java
运行
// 粗粒度锁(商品维度):所有用户竞争同一把锁
RLock coarseLock = redissonClient.getLock("product_stock_" + productId);
// 细粒度锁(用户维度):每个用户竞争自己的锁
RLock fineLock = redissonClient.getLock("user_seckill_" + userId + "_" + productId);
2.2 分段锁:将库存分段,减少锁竞争
java
运行
// 库存分段(如商品ID%10=0~9)
int segment = (int) (productId % 10);
RLock segmentLock = redissonClient.getLock("product_stock_segment_" + segment);
3. 无锁方案:乐观锁 + 消息队列
对于超高并发场景,可完全避免分布式锁:
java
运行
// 乐观锁扣库存(无锁方案)
public boolean seckillWithOptimisticLock(Long productId, Long userId) {
int retryCount = 3;
while (retryCount-- > 0) {
// 1. 查库存(带版本号)
Stock stock = stockMapper.selectByProductId(productId);
if (stock == null || stock.getQuantity() <= 0) {
return false;
}
// 2. 乐观锁扣减(CAS操作)
int rows = stockMapper.deductStockWithVersion(
productId, 1, stock.getVersion()
);
if (rows > 0) {
// 3. 发送订单创建消息到MQ(异步处理)
mqTemplate.send("seckill_order_topic", new SeckillOrderMsg(productId, userId));
return true;
}
// 4. 冲突重试
LockSupport.parkNanos(100_000); // 100微秒
}
return false;
}
四、一致性保障:分布式事务解决方案
1. 分布式事务问题
秒杀场景中,扣库存、创建订单、记录日志需保证原子性,传统分布式锁无法解决跨服务事务问题。
2. 解决方案 1:TCC(Try-Confirm-Cancel)
java
运行
// TCC接口定义
public interface StockTccService {
// Try:预扣库存
boolean tryDeductStock(Long productId, Long userId);
// Confirm:确认扣库存
boolean confirmDeductStock(Long productId, Long userId);
// Cancel:取消扣库存
boolean cancelDeductStock(Long productId, Long userId);
}
// TCC实现
@Service
public class StockTccServiceImpl implements StockTccService {
@Autowired
private StockMapper stockMapper;
@Override
@Transactional
public boolean tryDeductStock(Long productId, Long userId) {
// 预扣库存(冻结库存)
return stockMapper.freezeStock(productId, userId) > 0;
}
@Override
@Transactional
public boolean confirmDeductStock(Long productId, Long userId) {
// 确认扣库存(扣除冻结库存)
return stockMapper.confirmFreezeStock(productId, userId) > 0;
}
@Override
@Transactional
public boolean cancelDeductStock(Long productId, Long userId) {
// 取消扣库存(解冻库存)
return stockMapper.unfreezeStock(productId, userId) > 0;
}
}
3. 解决方案 2:SAGA 模式
基于事件驱动的长事务解决方案:
java
运行
// SAGA状态机配置
@Configuration
public class SeckillSagaConfig {
@Bean
public StateMachineFactory<SeckillStates, SeckillEvents> stateMachineFactory() {
StateMachineBuilder.Builder<SeckillStates, SeckillEvents> builder = StateMachineBuilder.builder();
builder.configureStates()
.withStates()
.initial(SeckillStates.INIT)
.state(SeckillStates.STOCK_DEDUCTED)
.state(SeckillStates.ORDER_CREATED)
.end(SeckillStates.SUCCESS)
.end(SeckillStates.FAIL);
builder.configureTransitions()
.withExternal()
.source(SeckillStates.INIT).target(SeckillStates.STOCK_DEDUCTED)
.event(SeckillEvents.DEDUCT_STOCK)
.action(deductStockAction(), errorHandler())
.and()
.withExternal()
.source(SeckillStates.STOCK_DEDUCTED).target(SeckillStates.ORDER_CREATED)
.event(SeckillEvents.CREATE_ORDER)
.action(createOrderAction(), errorHandler())
.and()
.withExternal()
.source(SeckillStates.ORDER_CREATED).target(SeckillStates.SUCCESS)
.event(SeckillEvents.SUCCESS)
.and()
.withExternal()
.source(SeckillStates.STOCK_DEDUCTED).target(SeckillStates.FAIL)
.event(SeckillEvents.CANCEL)
.action(cancelStockAction());
return builder.build();
}
}
4. 解决方案 3:最终一致性(本地消息表 + MQ)
java
运行
// 本地消息表记录
@Service
public class SeckillMessageService {
@Autowired
private SeckillMessageMapper messageMapper;
@Autowired
private MQTemplate mqTemplate;
// 1. 本地事务:扣库存+记录消息
@Transactional
public boolean seckillWithLocalMessage(Long productId, Long userId) {
// 扣库存
int rows = stockMapper.deductStock(productId);
if (rows == 0) {
return false;
}
// 记录本地消息
SeckillMessage message = new SeckillMessage();
message.setProductId(productId);
message.setUserId(userId);
message.setStatus(MessageStatus.PENDING);
messageMapper.insert(message);
return true;
}
// 2. 定时任务:发送消息到MQ
@Scheduled(fixedRate = 5000)
public void sendPendingMessages() {
List<SeckillMessage> messages = messageMapper.selectByStatus(MessageStatus.PENDING);
for (SeckillMessage message : messages) {
try {
mqTemplate.send("seckill_order_topic", message);
// 更新消息状态为已发送
messageMapper.updateStatus(message.getId(), MessageStatus.SENT);
} catch (Exception e) {
log.error("发送消息失败", e);
}
}
}
// 3. 消费MQ消息:创建订单
@RabbitListener(queues = "seckill_order_queue")
public void handleOrderMessage(SeckillMessage message) {
// 创建订单
Order order = new Order();
order.setProductId(message.getProductId());
order.setUserId(message.getUserId());
orderMapper.insert(order);
// 更新消息状态为已完成
messageMapper.updateStatus(message.getId(), MessageStatus.COMPLETED);
}
}
五、生产环境避坑指南
1. Redis 锁常见问题
1.1 锁超时导致业务未完成
解决方案:使用 Redisson 的 Watch Dog 自动续期,或预估业务执行时间设置合理超时。
1.2 主从切换导致锁丢失
解决方案:使用 Redis RedLock 算法,同时获取多个 Redis 节点的锁:
java
运行
// RedLock实现
public boolean redLockSeckill(Long productId, Long userId) {
RLock lock1 = redissonClient1.getLock("product_stock_" + productId);
RLock lock2 = redissonClient2.getLock("product_stock_" + productId);
RLock lock3 = redissonClient3.getLock("product_stock_" + productId);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
boolean locked = redLock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
return false;
}
// 业务逻辑
// ...
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
redLock.unlock();
}
}
1.3 误解锁其他客户端的锁
解决方案:锁值设置为客户端唯一标识(如 UUID),释放时校验:
lua
-- Lua脚本:原子性释放锁
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
2. 性能监控与告警
- Redis 监控:监控
SETNX命令执行次数、锁等待时间; - 数据库监控:监控库存表的行锁等待次数;
- 业务监控:监控秒杀成功率、超卖数量;
- 告警设置:锁等待时间超过 100ms 触发告警。
3. 降级与熔断
秒杀系统需设置降级策略:
- 库存不足时降级:直接返回 “已售罄”;
- Redis 故障时降级:使用本地缓存或数据库锁;
- 并发过高时降级:触发限流,返回 “请稍后重试”。
六、总结:分布式锁选型与实践原则
分布式锁的选型需结合业务场景:
- 高并发秒杀:首选 Redis 分布式锁(Redisson),结合乐观锁 + MQ 提升性能;
- 数据一致性要求高:选择 ZooKeeper 或 etcd 分布式锁;
- 云原生环境:优先使用 etcd(Kubernetes 原生支持);
- 低并发场景:可使用数据库乐观锁,降低部署复杂度。
实践原则:
- 最小锁粒度:尽量缩小锁的范围,避免粗粒度锁;
- 最短锁持有时间:业务逻辑尽量精简,减少锁占用时间;
- 高可用设计:锁服务集群部署,避免单点故障;
- 监控与告警:实时监控锁性能,及时发现问题;
- 降级预案:制定锁服务故障时的降级策略。
在电商秒杀等高并发场景中,分布式锁是保障数据一致性的核心技术,但需结合业务特点选择合适的方案,并通过性能优化、监控告警、降级预案等手段,构建高可用、高性能的分布式系统。
更多推荐



所有评论(0)