在电商促销活动中,秒杀场景是非常常见的。为了确保高并发下的数据一致性、性能以及用户体验,本文将介绍几种不依赖 Redis 实现的无锁秒杀方案,并提供简化后的 Java 代码示例和架构图。
CREATE TABLE product_stock (
product_id BIGINT PRIMARY KEY,
stock_count INT NOT NULL,
version INT DEFAULT 0
);
@Data
public class ProductStock {
private Long productId;
private Integer stockCount;
private Integer version;
}
@Service
public class OptimisticLockService {
@Autowired
private JdbcTemplate jdbcTemplate;
public boolean reduceStock(Long productId) {
String selectSql = "SELECT stock_count, version FROM product_stock WHERE product_id = ?";
Map<String, Object> result = jdbcTemplate.queryForMap(selectSql, productId);
int stockCount = (int) result.get("stock_count");
int version = (int) result.get("version");
if (stockCount <= 0) return false;
String updateSql = "UPDATE product_stock SET stock_count = ?, version = ? WHERE product_id = ? AND version = ?";
int rowsAffected = jdbcTemplate.update(
updateSql,
stockCount - 1,
version + 1,
productId,
version
);
return rowsAffected > 0;
}
}
CREATE TABLE pre_order (
user_id BIGINT,
product_id BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(user_id, product_id)
);
@Data
public class PreOrder {
private Long userId;
private Long productId;
}
@Service
public class UniqueConstraintService {
@Autowired
private JdbcTemplate jdbcTemplate;
public boolean placeOrder(Long userId, Long productId) {
try {
// 插入预订单(唯一约束)
jdbcTemplate.update("INSERT INTO pre_order(user_id, product_id) VALUES (?, ?)", userId, productId);
// 扣减库存
int rowsAffected = jdbcTemplate.update("UPDATE product_stock SET stock_count = stock_count - 1 WHERE product_id = ? AND stock_count > 0", productId);
if (rowsAffected == 0) {
rollbackOrder(userId, productId);
return false;
}
return true;
} catch (DuplicateKeyException e) {
// 唯一键冲突
return false;
}
}
private void rollbackOrder(Long userId, Long productId) {
jdbcTemplate.update("DELETE FROM pre_order WHERE user_id = ? AND product_id = ?", userId, productId);
}
}
@Data
public class OrderMessage {
private Long userId;
private Long productId;
}
@Component
public class MessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrder(OrderMessage message) {
rabbitTemplate.convertAndSend("order_queue", message);
}
}
@Component
public class OrderConsumer {
@RabbitListener(queues = "order_queue")
public void process(OrderMessage message) {
// 异步处理下单逻辑
System.out.println("Processing order: " + message.getUserId() + " -> " + message.getProductId());
// 调用库存服务或其他实际业务逻辑
}
}
@Component
public class InMemoryStockManager {
private final Map<Long, Integer> stockCache = new ConcurrentHashMap<>();
public void initStock(Long productId, int stockCount) {
stockCache.put(productId, stockCount);
}
public boolean reduceStock(Long productId) {
return stockCache.computeIfPresent(productId, (k, v) -> v > 0 ? v - 1 : v) != null;
}
public int getStock(Long productId) {
return stockCache.getOrDefault(productId, 0);
}
}
@Service
public class AsyncOrderService {
@Autowired
private InMemoryStockManager stockManager;
private final ExecutorService executor = Executors.newFixedThreadPool(5);
public void placeOrder(Long userId, Long productId) {
if (stockManager.reduceStock(productId)) {
executor.submit(() -> savePreOrderToDB(userId, productId));
} else {
System.out.println("库存不足");
}
}
private void savePreOrderToDB(Long userId, Long productId) {
// 这里可以调用 DAO 或 JdbcTemplate 插入预订单
System.out.println("保存预订单:" + userId + " 购买了商品ID:" + productId);
}
}
@Component
public class SyncTask {
@Scheduled(fixedRate = 60_000) // 每分钟执行一次
public void syncOrdersToDatabase() {
List<PreOrder> orders = fetchUnsyncedOrders();
for (PreOrder order : orders) {
updateProductStock(order.getProductId());
}
deleteSyncedOrders(orders);
}
private List<PreOrder> fetchUnsyncedOrders() {
// 查询待同步订单
return List.of(new PreOrder(1L, 1001L), new PreOrder(2L, 1001L));
}
private void updateProductStock(Long productId) {
// 执行更新库存
System.out.println("同步库存:" + productId);
}
private void deleteSyncedOrders(List<PreOrder> orders) {
// 删除已经同步的订单
orders.forEach(order -> System.out.println("删除订单:" + order));
}
}
方案 | 特点 | 适用场景 |
---|---|---|
乐观锁 | 简单、数据一致性高 | 并发量适中 |
唯一约束下单 | 防止重复下单 | 单用户限购 |
消息队列异步 | 解耦、削峰填谷 | 大流量场景 |
内存计算+定时同步 | 高吞吐、低延迟 | 超高并发秒杀 |
推荐组合使用:
- “内存计算 + 唯一约束” + “定时同步” 是一个非常实用且高效的组合,在秒杀中表现优异。
- 可根据业务阶段动态启用不同策略,例如前 5 秒使用内存计算,后续切为乐观锁。