一些数据
你们还记得2013年的红米秒杀吗?三款红米手机各11万台开售,走的都是大秒系统,3分钟后成为双十二第一家也是最快破亿的旗舰店。经过日志统计,后端系统双十一峰值有效恳求约60w以上的QPS,而前端cache的集群峰值近2000w/s、单机也近30w/s,但到真正的写时流量要小好多了,当时最高下单减库存tps是小米创造,达到1500/s。
热点隔离
秒杀系统设计的第一个原则就是将这些热点数据隔离下来,不要让1%的恳求影响到另外的99%,隔离下来后也更便捷对这1%的恳求做针对性优化。针对秒杀我们做了多个层次的隔离:
其实实现隔离很有多办法淘宝秒杀要回答问题,如可以根据用户来分辨,给不同用户分配不同cookie,在接入层路由到不同服务插口中;还有在接入层可以对URL的不同Path来设置限流策略等。服务层通过调用不同的服务插口;数据层可以给数据打上特殊的标来分辨。目的都是把早已辨识下来的热点和普通恳求区分开来。
动静分离
后面介绍在系统层面上的原则是要做隔离,接下去就是要把热点数据进行动静分离,这也是解决大流量系统的一个重要原则。怎样给系统做动静分离的静态化改建我曾经写过一篇《高访问量系统的静态化构架设计》详细介绍了网店商品系统的静态化设计思路,感兴趣的可以在《程序员》杂志上找一下。我们的大秒系统是从商品详情系统发展而至,所以本身早已实现了动静分离,如图1。
除此之外还有如下特征:
这样把90%的静态数据缓存在用户端或则CDN上,当真正秒杀时用户只须要点击特殊的按键“刷新抢宝”即可,而不须要刷新整个页面,这样只向服务端恳求极少的有效数据,而不须要重复恳求大量静态数据。秒杀的动态数据和普通的详情页面的动态数据相比更少,性能也比普通的详情提高3倍以上。所以“刷新抢宝”这种设计思路挺好地解决了不刷新页面能够恳求到服务端最新的动态数据。
基于时间分片削峰
熟悉天猫秒杀的都晓得淘宝秒杀要回答问题,第一版的秒杀系统本身并没有答题功能,前面才降低了秒杀答题,其实秒杀答题一个很重要的目的是为了避免秒杀器,2011年秒杀十分火的时侯,秒杀器也比较泛滥,而没有达到全民参与和营销的目的,所以降低的答题来限制秒杀器。降低答题后,下单的时间基本控制在2s后,秒杀器的下单比列也增长到5%以下。新的答题页面如图2。
虽然降低答题还有一个重要的功能,就是把峰值的下单恳求给拉长了,从原先的1s之内延长到2~10s左右,恳请峰值基于时间分片了,这个时间的分片对服务端处理并发十分重要,会减少很大压力,另外因为恳求的先后,靠后的恳求自然也没有库存了,也根本到不了最后的下单步骤,所以真正的并发写就十分有限了。虽然这些设计思路目前也十分普遍,如支付宝的“咻一咻”已及陌陌的摇一摇。
不仅在后端通过答题在用户端进行流量削峰外,在服务端通常通过锁或则队列来控制顿时恳求。
数据分层校准
对大流量系统的数据做分层校准也是最重要的设计原则,所谓分层校准就是对大量的恳求弄成“漏斗”式设计,如图3所示:在不同层次尽可能把无效的恳求过滤,“漏斗”的最末端才是有效的恳求,要达到这个疗效必须对数据做分层的校准,下边是一些原则:
秒杀系统正是根据这个原则设计的系统构架,如图4所示。
把大量静态不须要检验的数据放到离用户近来的地方;在后端读系统中检验一些基本信息,如用户是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否早已结束等;在写数据系统中再校准一些如是否是非法恳求,营销等价物是否充足(淘金币等),写的数据一致性如检测库存是否还有等;最后在数据库层保证数据最终确切性,如库存不能减为正数。
实时热点发觉
虽然秒杀系统本质是还是一个数据读的热点问题,并且是最简单一种,由于在文提及通过业务隔离,我们已能提早辨识出这种热点数据,我们可以提早做一些保护,提早辨识的热点数据处理上去还相对简单,例如剖析历史成交记录发觉什么商品比较热门,剖析用户的购物车记录也可以发觉这些商品可能会比较好卖,那些都是可以提早剖析下来的热点。比较困难的是那个我们提早发觉不了忽然成为热点的商品成为热点,这些就要通过实时热点数据剖析了,目前我们设计可以在3s内发觉交易链路上的实时热点数据,之后按照实时发觉的热点数据每位系统做实时保护。具体实现如下:
重要的几个:其中关键部发包括:
关键技术及优化点
后面介绍了一些怎样设计大流量读系统中用到的原则,并且当这种手段都用了,还是有大流量涌向该怎么处理呢?秒杀系统要解决几个关键问题。
Java处理大并发动态恳求优化
虽然Java和通用的Web服务器相比(Nginx或Apache)在处理大并发HTTP恳求时要弱一点,所以通常我们还会对大流量的Web系统做静态化改建,让大部份恳求和数据直接在Nginx服务器或则Web代理服务器(Varnish、Squid等)上直接返回(可以降低数据的序列化与反序列化),不要将恳求落到Java层上,让Java层只处理极少数据量的动态恳求,其实针对那些恳求也有一些优化手段可以使用:
同一商品大并发读问题
你会说这个问题很容易解决,无非放在Tair缓存上面就行,集中式Tair缓存为了保证命中率,通常还会采用一致性Hash,所以同一个key会落到一台机器上,尽管我们的Tair缓存机器单台也能支撑30w/s的恳求,并且像大秒这些级别的热点商品还远不够,那怎么彻底解决这些单点困局?答案是采用应用层的Localcache,即在秒杀系统的单机上缓存商品相关的数据,怎么cache数据?也分动态和静态:
你可能会有疑惑,像库存这些频繁更新数据一旦数据不一致会不会造成超买?虽然这就要用到我们上面介绍的读数据分层校准原则了,读的场景可以容许一定的脏数据,由于这儿的错判只会造成少量一些先前早已没有库存的下单恳求误觉得还有库存而已,等到真正写数据时再保证最终的一致性。这样在数据的高可用性和一致性做平衡来解决这些高并发的数据读取问题。
同一数据大并发更新问题
解决大并发读问题采用Localcache和数据的分层校准的方法,而且无论怎么像减库存这些大并发写还是避开不了,这也是秒杀这个场景下最核心的技术困局。
同一数据在数据库里肯定是一行储存(MySQL),所以会有大量的线程来竞争InnoDB行锁,当并发度越高时等待的线程也会越多,TPS会增长RT会上升,数据库的吞吐量会严重遭到影响。说到这儿会出现一个问题,就是单个热点商品会影响整个数据库的性能,还会出现我们不乐意见到的0.01%商品影响99.99%的商品,所以一个思路也是要遵守上面介绍第一个原则进行隔离,把热点商品放在单独的热点库中。并且无疑也会带来维护的麻烦(要做热点数据的动态迁移以及单独的数据库等)。
分离热点商品到单独的数据库还是没有解决并发锁的问题,要解决并发锁有两层办法。
你可能会问排队和锁竞争不要等待吗?有啥区别?假如熟悉MySQL会晓得,InnoDB内部的死锁检查以及MySQLServer和InnoDB的切换会比较耗性能,天猫的MySQL核心团队还做了好多其他方面的优化,如COMMIT_ON_SUCCESS和ROLLBACK_ON_FAIL的patch,配合在SQL上面加hint,在事务里不须要等待应用层递交COMMIT而在数据执行完最后一条SQL后直接按照TARGET_AFFECT_ROW结果递交或回滚,可以降低网路的等待时间(平均约0.7ms)。据我所知,目前阿里MySQL团队已将这种patch及递交给MySQL官方评审。
大促热点问题思索
以秒杀这个典型系统为代表的热点问题依照多年经验我总结了些通用原则:隔离、动态分离、分层校准,必须从整个全链路来考虑和优化每位环节,不仅优化系统提高性能,做好限流和保护也是必备的功课。
去除上面介绍的那些热点问题外,淘系还有多种其他数据热点问题:
根据某种维度建的索引形成热点数据,例如实时搜索中根据商品维度关联评价数据,有些热点商品的评价十分多,造成搜索系统根据商品ID建评价数据的索引时显存早已放不下,交易维度关联订单信息也同样有这种问题。这类热点数据须要做数据散列,再降低一个维度,把数据重新组织。