石墨文档:https://shimo.im/docs/iTDoZs4CVfICgSfV/
(二期)19、开源秒杀项目miaosha解读(上)
瞬时并发量大
- 秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增。
库存量少
- 秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功。
业务简单
- 秒杀业务流程比较简单,一般就是下订单减库存。
限流:
削峰:
异步处理:
内存缓存:
可拓展:
- 对现有业务冲击
- 高并发应用负载高
- 突然增加网络与服务宽带
- 直接下单
- 控制商品页面购买按钮点亮
- 下单前置检查
- 前端:
页面静态化:
禁止重复提交:
- 服务端:
(1)将请求尽量拦截在系统上游
(2)充分利用缓存
(3)消息队列:
(4)用户限流:
- 数据库层:
- https://gitee.com/1028125449/miaosha
- a、用户请求过来,将请求入消息队列;
- b、消息处理,先减redis库存量,如果减库存成功,则生成下单token存入redis(设定有效期,比如2分钟之内下单有效),等待用户下单(这样就避免下单也面对大量并发);如果减库存失败,则消息记录回到消息队列中,等待再次处理;
- c、用户下单:判断token是否失效(比对时间)了,如果未失效则扣减库存(也可能扣减库存失败),生成订单;如果已经失效了,则redis库存增加1; 如何确保下单token过期了释放资格?JOB 每分钟扫token缓存,如果失效了的则清除调,并回馈redis缓存(redis库存+1);
- d、前端用户如何获知抢购成功了(获得了下单资格):ajax轮训查询接口。 说明:为什么要采用轮询而不是用实时的websocket推送?经测试,一台tomcat最多能连接3000个websocket,如果类似抢购的大量用户抢购,机器肯定是扛不住这么多长连接的,而查询用户是否抢购成功也只是查询的redis,因此采用轮询是很好的选择。
- 为什么要秒杀和下单操作分离?
- 一方面,秒杀接口可以阻挡大部分并发流程,从而让下单操作错开并发高峰;
- 另一方面,可以让秒杀操作和下单操作从业务上相分离,使得秒杀操作可以独立于订单相关业务。
- 前端
- ajax
- countdown
- 后端
- redis作为缓存、消息队列
- 秒杀详情页,等待秒杀开始
- 秒杀开始,获取秒杀链接
- 进入秒杀,限流,判断是否重复秒杀,把请求消息推入redis消息处理队列
- 消息处理器监听到消息后开始处理:
- 监测黑名单
- 判断秒杀是否已结束
- 先减redis库存(占位)
- 生成下单token,存入redis供前端查询
- 前端查询秒杀结果
- 成功:显示下单按钮,用户使用token去下单
- 用户下单
- 检查token有效性,检查库存
- 减库存,下单。(真正减库存)
- 恶意IP检测拦截器
- 获取真实ip
- 匹配ip是否是正常ip,是否在黑名单库
- 使用redis增加ip的访问次数
- 超过限定次数就加入到黑名单库
- 恶意用户检测拦截器
- 同ip监测拦截
- 实时限流:限制正在处理的请求量(通过消息队列获取正在处理的请求数目)为库存的100倍请求(这个可自定义);
- 如果出现了限流器满了,但仍然有库存的情况怎么办?直接拒绝请求,允许用户重新提交请求 ##请求减库存
- 请求通过了过滤之后,交给消息队列减库存+下单 ##消息队列处理
- 消息队列再次过滤请求是否是恶意的用户
- 否则,执行减库存+下单
- ip黑名单
- 用户黑名单
- 获取消息队列全局对象MessageTrunk(可以用spring注入),put入消息即可。
// 获取MessageTrunk实例
MessageTrunk mt = (MessageTrunk) SpringBeanUtils.getBean("messageTrunk");
Message<DemoMessage> message = new Message<DemoMessage>(MessageType.DEMO_MESSAGE, new DemoMessage(value));
// 消息入MT
mt.put(message);
- 消息处理器:继承AbstarctMessageHandler抽象类。
public class DemoHandler extends AbstarctMessageHandler<DemoMessage>{
private static Log logger = LogFactory.getLog(DemoHandler.class);
public DemoHandler(){
// 说明该handler监控的消息类型
super(MessageType.DEMO_MESSAGE);
}
/**
* 监听到消息后处理方法
*/
@Override
public void handle(DemoMessage message){
// do handle
}
@Override
public void handleFailed(DemoMessage obj){
// handle failed
}
}
- 哈希
- 存储黑名单(ip,用户)
- 秒杀处理请求列表
- 字符串
- 增减库存
- 商品描述是否结束标志
- 下单token
- ...
- 列表
- 用户访问记录
FOUND_ROWS : 获取上一个select语句查询到的行数;
ROW_COUNT : 获取上一条update, insert ,delete 影响的行数;
参数:
(1).存储过程增强了SQL语言的功能和灵活性
(2).存储过程允许标准组件是编程