前言
总结Java实现秒杀系统设计方案
方案如下
为了实现秒杀系统,需要解决高并发和防止库存被击穿两个主要问题。以下是一些Java实现秒杀系统的建议:
- 使用Redis等内存数据库作为库存和订单数据的存储,减少数据库访问压力。
- 使用消息队列,将订单信息异步入库,降低后端压力。可以使用RabbitMQ或Kafka等。
- 对订单业务逻辑使用分布式锁,如Redis的SETNX命令,防止同一个商品被重复下单导致超卖。
- 使用缓存(Redis)预热热点商品库存数量,减少数据库访问。
- 对下单接口进行限流,比如使用Guava的RateLimiter,控制请求流量在系统承受能力范围内。
- 前端使用CDN缓存静态资源,减少后端压力。
- 对下单接口进行防刷处理,如谷歌 reCAPTCHA 验证码,限制机器人恶意刷单。
- 按用户分批下单,每批次只允许限定数量的请求,然后休眠一小段时间再处理下一批,以平滑请求流量。
- 采用无状态服务设计,使用Nginx等做反向代理和负载均衡。
- 垂直拆分下单服务,按商品 ID 来路由分发请求到不同的服务器。
秒杀系统解决高并发问题,具体代码实现
- 使用Redis的incr命令作为原子自增操作,确保商品库存数量准确:
//商品库存数量原子自减 Long stock = redisTemplate.opsForValue().increment("product_" + productId, -1);
- 在Service层使用synchronized或者ReentrantLock进行加锁,防止并发导致的超卖:
//Service层方法加synchronized锁 public synchronized void orderProduct(int productId){ //下单逻辑 } //或使用ReentrantLock加锁 Lock lock = new ReentrantLock(); lock.lock(); try{ //下单逻辑 }finally{ lock.unlock() }
- 使用Redis的SETNX实现分布式锁,防止分布式环境下的并发:
//占有分布式锁 if(redisTemplate.opsForValue().setIfAbsent("lock", "1")){ //下单逻辑 //释放分布式锁 redisTemplate.delete("lock"); }
- 使用Guava的RateLimiter限流,平滑突发流量:
RateLimiter rateLimiter = RateLimiter.create(100); //限流大小100 rateLimiter.acquire(); //请求限流 //下单逻辑
5.异步处理下单请求:
//接收下单请求,扔到RabbitMQ队列 orderService.asyncOrder(order); //消费端异步处理下单逻辑 @RabbitListener(queues="order_queue") public void consumeOrder(Order order){ //下单逻辑.. }
解决库存被击穿问题,特别是当库存有一个,然后两个人同时抢到锁,代码实现
库存被击穿是秒杀场景下常见的问题。当商品库存仅剩 1 个时,如果不做特殊处理,可能出现 2 个线程同时检查库存均大于0的情况,然后继续执行减库存,这就会造成超卖。
解决库存被击穿问题的Java代码实现思路如下: - 加锁 + 二次检查
// 加锁 synchronized(productId) { // 获取库存 int stock = getStockFromRedis(productId); // 库存不足,直接返回 if(stock <= 0) { return; } // 二次检查库存 if(stock > 1) { // 减库存 reduceStock(productId); // 下单 } else { // 库存不足提示 } }
上面代码先对商品加锁,再获取库存,如果库存不足则直接返回。如果还有库存,则再进行二次检查,这个时候即使有并发也不可能被击穿。
- 使用库存预减
直接在取库存时就使用 decr 命令把库存减 1,如果减 1 成功则下单,如果减 1 失败则说明库存不足。// 库存预减 boolean stockOk = redis.decr(productId) > 0; if(stockOk) { // 下单 } else { // 库存不足 }
这种方式利用了 Redis 的 decr 原子性,也可以避免击穿。
综上,要防止击穿主要还是要正确使用加锁和原子操作。
Java实现秒杀系统,怎么解决并发和库存不被击穿