秒杀系统设计的第一个原则就是隔离热点数据,即禁止1%的请求影响99%的请求。在隔离之后,单独针对1%的请求进行针对性的优化。
1、热点隔离 - 业务隔离。把秒杀做成一种营销活动,参加秒杀活动是要单独进行报名。
- 系统隔离。系统隔离更多是指运行时的隔离,即通过分组部署的方式将1%的请求与99%的请求分开来。例如针对秒杀活动申请单独的域名,目的是让不同的请求落入不同的集群中。
- 数据隔离。比如启用大度的Cache集群或者MySQL数据库来释放热点数据。
- 用户隔离。可以给不同的用户分配不同的cookie,或者针对不同的URL设置不同的限流策略等。
什么是静态化系统?
静态化系统通常有以下特征:
1、一个页面对应一个固定的URL。也就是说能够通过URL能唯一标识一个页面。
2、页面中不包含有浏览者(用户)的相关因素。例如用户名,身份标识即cookie相关的信息。
3、页面中不包含有时间相关的因素。这个“时间”指的是服务器端的输出时间,例如秒杀系统中,一到某个时间点,页面中的“立即购买”按钮就可以点击使用--这个时间点就是从服务器获取的。
4、页面中不包含有地域因素。例如商品详情中的商品是否有货(根据用户所在地判断),不同地区的运费不同等情况。
5、页面中不能包含cookie等私有数据。
PS:静态化页面不仅仅是传统意义上完全存储在磁盘上的HTML页面,也可能是经过Java系统产生的页面。
静态化系统能解决什么问题?
由于Java天生就存在不擅长处理大量连接请求,每个连接消耗的内存较多以及Servlet容器解析HTTP协议较慢等弱点,在面对海量请求(上完QPS)时,Java系统本身很快就会达到瓶颈。因此,在这种情况下,我们必须跳出Java系统,也就是在请求达到Java系统之前的Web层就尽快的返回用户需要的数据。
如何改造动态系统
就是按照静态化系统的特征,将对应的元素从动态系统中剥离出去。
分离出动态内容以后,如何组织这些内容,如何获取他们并把他们与静态文件组装在一起。(例如判断用户是否登录等)
通常有两种方式:
- ESI(Edge Side Includes)。即在Web代理服务器上做动态内容请求,并将请求结果插入到静态页面中。当用户拿到页面时,已经是一个完整的页面。例如详情系统。这种方式对服务端的性能有影响,但是用户体验较好;
- CSI(Client Side Includes)。即客户端发起一个移步的JS请求,单独向服务器获取动态内容。这种方式服务端性能更佳,但是用户端的页面会有延时,体验稍差。
静态化方案选择
要先确认以下问题:
- 是否一致性Hash分组?缓存就要考虑命中率,命中率又与数据集中度有关,数据集中就必然要求一致性Hash。但是一致性Hash天然缺陷就是会产生热点,热点特别集中时可能会造成网络瓶颈。
- 是否使用ESI?
- 是否使用物理机?
- 谁来压缩数据?在哪里压缩?
- 网卡的选择?万兆网卡和交换机。
方案1: 实体机单机部署
方案2: 统一Cache层
方案3: 上CDN
上CDN要解决以下问题:
1.失效问题。分布在全国的CDN要在秒级时间内实现?主动失效?被动失效?
2.命中率问题。
3.发布更新问题。发布系统的快速简单是要考虑的,以及出现问题是必须能够快速回滚,必须能简便的排查问题,
基于时间分片削峰
前端:增加秒杀答题。
后端:通过锁或者队列控制瞬间的请求量。
数据分层校验
“漏斗式”的分层校验设计:核心思想是在不同的层次,不断尽可能地过滤掉无效的请求,只有“漏斗”最末端才是有效的请求,才能最终到达“DB”。
设计原则:
1、把大量静态,不需要校验的数据放在距离用户最近的地方-必然CDN;
2、在前端检验一些基本的信息,如用户是否具备秒杀资格,商品状态是否正常,用户答题是否正确,秒杀活动是都已经结束等
3、在写数据系统中再增加一些信息
4、判断请求是否非法,营销等价物(淘金币等)是否充足等
5、最后在数据层保障数据最终的准确性(例如库存不能减为负值)
实时热点发现
秒杀系统的本质仍然是读数据的热点问题。
前面提到过的业务隔离能够提前识别热点数据,例如通过分析历史成交记录可以发现热门商品,通过分析用户购物车记录也可以发现比较好卖的商品。但是针对与未能提前发现的商品,突然成为热点时---就要通过实时热点分析来判断了。
关键技术优化点
- Java处理大并发动态请求优化的问题。
- 同一商品被大并发读的问题。Localcache本地缓存和分层校验,尤其注意“超卖”的问题。
- 同一数据发并发更新的问题。“减库存”是秒杀场景中最核心的问题。同一数据在数据库里肯定是一行存储(MySQL),所以当又大量的线程来竞争InnoDB行锁时,并发度越高等待的线程就会越多,TPS会下降RT会上升,数据的吞吐量会严重受到影响。缓解的办法是将热点的数据分离到单独的数据库,但是并没有从根本上解决并发锁的问题。
要解决并发锁的问题,又两种办法:
1、在应用层做排队。按照商品唯独设置队列顺序执行。
2、在数据库层做排队。应用层排队只能做到单机的排队,当应用层机器数量很多时,这种排队的方式控制并发仍然是有限的。数据库团队开发了MySQL的InnoDB层上的patch,可以做到字数据库层对单行记录并发排队。