无锁秒杀系统设计:基于Java的高效实现

引言

在电商促销活动中,秒杀场景是非常常见的。为了确保高并发下的数据一致性、性能以及用户体验,本文将介绍几种不依赖 Redis 实现的无锁秒杀方案,并提供简化后的 Java 代码示例和架构图

一、基于数据库乐观锁机制

✅ 实现思路:

  • 使用版本号字段控制库存更新,避免超卖问题。
  • 每次扣减前检查版本号是否匹配,保证原子性操作。

📌 架构图:

在这里插入图片描述

🧱 数据库表结构(伪 SQL):

CREATE TABLE product_stock (
    product_id BIGINT PRIMARY KEY,
    stock_count INT NOT NULL,
    version INT DEFAULT 0
);

🧾 核心实体类(Lombok):

@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;
    }
}

二、利用数据库唯一约束防重下单

✅ 实现思路:

  • 利用数据库唯一索引特性防止重复下单。
  • 先插入预订单记录,再执行库存扣除。

📌 架构图:

在这里插入图片描述

🧱 数据库表结构(伪 SQL):

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;
}

🧾 消息生产者(Spring Boot 示例):

@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());
        // 调用库存服务或其他实际业务逻辑
    }
}

四、内存计算 + 定期同步至数据库(推荐用于超高频读写)

✅ 实现思路:

  • 使用 ConcurrentHashMap 维护商品库存缓存。
  • 用户请求优先修改内存中的值,降低 DB 压力。
  • 定时任务定期将变更同步到数据库。

📌 架构图:

在这里插入图片描述

🧾 内存库存管理器:

@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 秒使用内存计算,后续切为乐观锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值