秒杀笔记 —— 流量削峰
秒杀业务场景,商品是有限的,请求数无论多少,最终获得商品的用户还是有限的。可是在业务的角度讲,能承载越多的人参与当然越好,但是实际下单时,秒杀请求并不是越多越好。因此可以设计一些规则,让并发请求可以延缓,甚至过滤一部分无效请求。
为什么要削峰
削峰,为什么?峰值有什么坏处?
- 控制成本,保证质量
服务器资源是恒等的,假设满分资源是100分,平时保证系统正常运行使用是80分,可是秒杀活动时,如果想让所有用户的请求都可以实际处理,那服务器资源可能就要做到200甚至更高分,为了控制成本同时保证应用质量,所以我们要削峰。 - 削峰为什么存在
- 可以让服务端处理变得更加平稳
- 可以节省服务器资源成本
基于“请求数要尽量少”的设计原则,削峰本质上就是延缓更多用户请求的发出,减少和过滤一些无效请求。
排队
消息队列缓冲瞬时流量,同步操作转换为异步间接推送,中间通过队列在一端承接瞬时流量洪峰,另一端平滑推送消息出去。
比喻:消息队列好比“水库”,拦蓄上游洪水,削减进入下游河道的洪峰流量,达到减免洪水灾害目的。
但是,流量峰值持续一段时间达到消息队列的处理上限,例如本机消息积压达到存储空间上限,消息队列会被压垮,这样虽然做到了队列削峰,可是会丢弃一部分请求。
除了消息队列,类似的排队方式还有:
- 利用线程池加锁等待排队方式;线程加锁等待实现削峰(待完善)
- 先进先出、先进后出等常用内存排队算法实现方式;常用内存排队算法实现方式(待完善)
- 把请求序列化到文件中,然后再顺序地读文件(例如基于MySql binlong同步机制)来恢复请求方式;MySQL binlong 同步机制(待完善)
以上三个都有一个共同特征,把“一步操作”编程“两步操作”,增加了一步缓冲操作。
增加了访问请求路径并不符合“4要1不要”原则。确实,但是如果不增加一个缓冲,实际场景就可能会出现崩溃,导致系统宕机,在这个角度,我们需要在程序设计时综合权衡。
答题
早起秒杀并没有答题,那为什么后来增加了答题功能?有两个目的:
- 防范秒杀器作弊
秒杀器,作弊,如果没有答题,写脚本刷接口就行。秒杀答题截图:
- 延缓请求
对请求流量削峰,提高系统对流量高峰的抗压力。此功能就是把峰值下单请求拉长,从1s变为2s~10s。这样,请求就会基于时间分片了。时间分片大大降低了服务器的并发压力,并且,请求具有先后顺序,靠后的请求就没有库存了,因此根本到不了实际下单的服务处理,达到真正并发写限制作用。
秒杀答题实际思路:
上图所示,秒杀答题逻辑分为3部分:
- 题库生成模块
生成题目和答案,不需要很复杂,达到能够避免秒杀器自动答题即可,目的是防刷。 - 题库推送模块
秒杀答题前,题目提前推送给详情系统和交易系统。题库推送主要目的是保证用户请求的题目是唯一的,目的也是防止答题作弊。 - 题目图片生成模块
用于题目生成图片格式,并且在图片中增加一些干扰元素。同样是防止机器自动答题,要求人才能理解题目本身的含义。这里需要注意,把图片提前推送到CDN并且进行预热,可以避免用户在答题时,由于网络拥挤导致的图片加载过慢。
答题逻辑其实很简单,用户提交的答案与题目对应答案做比较,如果通过进行一步下单逻辑,反之失败。
可以把问题和答案用下面的Key来进行MD5加密:
说明 | |
---|---|
问题Key | userId + itemId + questionId + time + PK |
答案Key | userId + itemId + answer + PK |
验证逻辑示意图:
注意:验证逻辑除了验证问题的答案以外,还包含用户身份的验证,例如是否已经登陆、用户的Cookie是否完整、用户是否重复频繁提交等。
除了做正确性验证,还可以对提交答案的时间做些限制,例如从开始答题到接受答案要超过1s,因为小于1s是人为操作的可能性很小,这样也能防止机器答题的情况。
答题系统的实现(待完善)
分层过滤
无论使用队列还是设置答题,都是对请求进行缓冲,也就是分层过滤,从而过滤掉一些无效请求。分层过滤其实就是采用“漏斗式”设计处理请求,如下:
假设请求分别经过CDN、前台读系统(如商品详情系统)、后台系统(如交易系统)和数据库这几层,那么:
- 大部分数据和流量在用户浏览器或者CDN上获取,这一层可以拦截大部分数据的读取;
- 经过第二层(即前台系统)时数据(包括强一致性的数据)尽量得走Cache,过滤一些无效请求;
- 再到第三层后台系统,主要做数据的二次检验,对系统做好保护和限流,数据量和请求就会进一步减少;
- 最后在数据层完成数据强一致性校验;
这样就像漏斗一样,尽量把数据量和请求量一层一层过滤和减少了;
分层过滤的核心思想:
- 将动态请求的读数据缓存(Cache)在Web端(离用户最近的并且最合理的位置),过滤掉无效的数据读。
- 对
读数据
不做强一致性校验,减少因为一致性校验产生瓶颈的问题; - 对
写数据
进行强一致性校验,只保留最后有效数据; - 对
写数据
进行基于时间的合理分片,过滤掉过期的失效请求; - 对
写请求
做限流保护,将超出系统承载能力的请求过滤掉;
分层家考验的目的:
系统 | 描述 |
---|---|
读系统 | 尽量减少由于一致性校验带来的系统瓶颈,尽量将不影响性能的检查提前,如用户是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束、是否非法请求、营销等价物是否充足等; |
写系统 | 主要对写的数据(如“库存”)做一致性检查,最后在数据库层保证数据的最终准确性(如“库存”不能为负数) |
总结
削峰的三种处理方式:
- 通过队列缓冲请求,控制请求发出;
- 通过答题延长请求发出时间,请求发出后,承接(服务端、缓存、数据库)请求时进行控制,最后再对不符合条件的请求进行过滤;
- 对请求进行分层过滤;
适用场景
场景 | |
---|---|
队列缓冲 | 更通用,适用于内部上下游系统之间调用请求不平缓的场景,由于内部系统的服务质量不允许随意丢弃请求,所以使用消息队列能起到很好的削峰和缓冲作用。 |
答题 | 秒杀或营销活动,在请求发起端控制请求速度,越到后面无效请求越多,所以配合后面介绍分层拦截方式,可进一步减少无效请求对系统资源对占用。 |
分层过滤 | 非常适合交易性的写请求,比如减库存、拼车场景,因为库存和座位是不断变化的,读的时候不需要非常准确,可以先放一些请求过去,然后真正减少时一定要做到强一致性,这样即过滤一些请求又解决了强一致性读的瓶颈。 |