一般Web基本系统涉及的方面
- 用户
- 前端
- 服务
- 数据库
秒杀系统的特点
- 高并发 有很多用户同时请求秒杀接口
- 事务的特点
- 一般是对单一的商品进行库存扣除
我们可以改变的地方
- 前端
- 服务
- 数据库
秒杀流程
用户发起秒杀请求,请求到达后端服务器。
首先,检查用户是否已经参与过秒杀,如果是,则拒绝请求或给出相应提示。
然后,检查商品库存数量是否大于0,如果不是,则拒绝请求或给出相应提示。
如果上述检查通过,说明用户可以参与秒杀,开始执行以下操作:
- 生成一个唯一的订单号,用于标识秒杀订单。
- 减少商品库存数量。
- 创建秒杀订单并写入订单表。
- 返回秒杀成功的响应给用户。
秒杀问题点
- 服务器是否可以抗住突发流量
- 数据库是否可以抗住这个流量
- 怎么保证库存可以安全的扣除 不会出现超卖问题 也就是并发控制问题
本质
- 在链路中尽可能的来拦截不需要的请求 因为越到后面的链路 抗突发流量能力低
- 来增大组件的处理请求的能力 这里延迟 增大都算处理
服务器
- 对流量进行缓冲 用MQ进行处理
- 采用分布式架构
- 对单机进行限流 熔断或者降级
数据库
1.可以使用库存前置 提前到redis上 扣除库存的操作的redis+lua 来解决并发控制问题 再秒杀结束之前定时任务进行一个对数据库的写入
结构图
简单实现
package ms;
import lombok.SneakyThrows;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
/**
* 限流数
*/
private static final Integer limit = 5;
/**
* 设置秒杀商品 库存为10 商品Id是1
*/
private static final Product product = new Product(10,1);
/**
* 并发用户数
*/
private static final Integer USERNUM = 100;
/**
* 初始化请求数
*/
private static final AtomicInteger count = new AtomicInteger(0);
/**
* 队列实现
*/
private static final LinkedBlockingQueue<Callable<Boolean>> queue = new LinkedBlockingQueue<>();
/**
* 去重处理
*/
private static final HashSet<Integer> uSet = new HashSet<>();
/**
* reids锁
*/
private static final HashMap<Integer,Object> lock = new HashMap<>();
/**
* 填充对象
*/
private static final Object objects = new Object();
/**
* 成功抢到的用户
*/
private static final List<Integer> successList = new LinkedList<>();
/**
* 失败抢到的用户
*/
private static final Set<Integer> failSet = new HashSet<>();
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10,100,10,
TimeUnit.SECONDS,new LinkedBlockingQueue<>());
// 模拟 MQ 监听
poolExecutor.execute(Main::processQueue);
// 模拟用户秒杀
for (int i = 0;i < USERNUM;i++) {
// 模拟不同用户id
int finalI = new Random().nextInt(100);
poolExecutor.execute(()->{
try {
seckill(1, finalI,false);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
@SneakyThrows
public static void processQueue() {
try {
while (true) {
// 检查队列是否有元素
System.out.println("queue size = " + queue.size() + " stock = " + product.getStock());
if (!queue.isEmpty()) {
// 处理队列中的元素
Callable<Boolean> element = queue.take();
element.call();
}
// 可以添加一些适当的延迟,避免过于频繁地检查队列
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static boolean seckill(Integer productId, Integer uid,boolean isDeque) throws InterruptedException {
count.getAndIncrement();
// 限制流量 超过流量的使用队列处理 对流量进行缓冲
if (count.intValue() > limit && !isDeque) {
queue.put(() -> seckill(productId,uid,true));
return false;
}
// 模拟redis+lua脚本 synchronize来模拟lua的原子性
synchronized (objects) {
// 加锁
Object loc = lock.get(productId);
if (Objects.isNull(loc)) {
return false;
}
lock.put(productId,objects);
// 去重
if (uSet.contains(uid)) {
return false;
}
// 检查库存
if (product.getStock() <= 0) {
failSet.add(uid);
return false;
}
// 先成订单
Order order = new Order();
order.setProduct(productId);
order.setUid(uid);
order.setOrderId(String.valueOf(UUID.randomUUID()));
// 扣除库存
product.setStock(product.getStock()-1);
uSet.add(uid);
successList.add(uid);
// 解放锁
lock.remove(productId);
System.out.println("用户Id为: " + uid + "抢到商品: " + productId);
return true;
}
}
}