电商平台本质是在线上撮合买卖双方的购销需求,达成交易。虽然是线上交易,但也遵守朴素的经济学原理,供求关系决定了商品的经济活动。当供求平衡时,买方和卖方处于对等关系,双方相对稳定和谐;当供大于求时,这时候市场成了买方市场,买方处于主动地位;当供不应求时,这时候市场是卖方市场,卖方处于有利主导地位。
当供需出现极度不平衡时,不管是线下还是线上,会出现抢购的情况,这需要秒杀系统。对极度供不应求的爆品,设置合理活动,释放库存,可以持续为电商平台带来稳定的热度和流量,可观的 VIP 会员费,以及技术口碑的认同。当然,也可以对参与用户进行筛选,杜绝黄牛和刷子,让爆品惠及更广,更公平。
案例:
促销活动
促销活动中,指定了一定数量(比如:10个)的商品(比如:手机),以极低的价格(比如:0.1元),让大量用户参与活动,但只有极少数用户能够购买成功。这类活动商家绝大部分是不赚钱的,说白了是找个噱头宣传自己。
爆品上架
当 1499 元平价茅台上架时,当抢到口罩意味着守护更多一份健康时,流量激增,更是盛况空前。具体12306的春节抢票、各大电商搞的定时抢购活动,如小米手机的在线抢购
把技术挑战放在一边,先从用户或是产品的角度来看一下,秒杀的流程是什么样的。
从技术上来说,这个倒计时按钮上的时间和按钮可以被点击的时间是需要后台服务器来校准的,这意味着:
这个不断轮询的过程,就好像大家等着抢。你想想,有 100 万人来不停地询问有没有开始了这个事,估计后端也扛不住。
面对上面我们要解决的技术问题,我们的技术上的挑战就是怎么应对这 100 万人同时下单请求?100 万的同时并发会导致我们的网站瞬间就崩溃了,一方面是 100 万人同时请求,我们的网络带宽不够,另一方面是理论上来说要扛 100 万的 TPS,需要非常多的机器。
但是最恐怖的是,所有的请求都会集中在同一条数据库记录上,无论是怎么分库分表,还是使用了分布式数据库都无济于事,因为你面对的是单条的热点数据。
1、短时间内高并发,系统负载压力大
2、竞争的资源有限,数据库锁冲突严重
3、避免对其他业务的影响
秒杀设计要面对的压力和难度有几点:
怎么保证超高的流量和并发下系统的稳定性?如果峰值的QPS达到几十万,面对巨大的流量的压力系统怎么设计保证不被打崩?
怎么保证数据最终一致性?比如库存不能超卖,超卖了那亏本的要么就是商家要么就是平台,用户反正不背这个锅,超卖了就今年325预订。
当然,涉及到这种大型的活动,还需要考虑到数据统计分析,总不能活动做完了,效果不知道怎么样。
高并发绝不意味着只追求高性能,这是很多人片面的理解。从宏观角度看,高并发系统设计的目标有三个:高性能、高可用,以及高可扩展。
1、高性能:性能体现了系统的并行处理能力,在有限的硬件投入下,提高性能意味着节省成本。同时,性能也反映了用户体验,响应时间分别是100毫秒和1秒,给用户的感受是完全不同的。
2、高可用:表示系统可以正常服务的时间。一个全年不停机、无故障;另一个隔三差五出线上事故、宕机,用户肯定选择前者。另外,如果系统只能做到90%可用,也会大大拖累业务。
3、高扩展:表示系统的扩展能力,流量高峰时能否在短时间内完成扩容,更平稳地承接峰值流量,比如双11活动、明星离婚等热点事件。
再从微观角度来看,高性能、高可用和高扩展又有哪些具体的指标来衡量?为什么会选择这些指标呢?
❇ 性能指标
通过性能指标可以度量目前存在的性能问题,同时作为性能优化的评估依据。一般来说,会采用一段时间内的接口响应时间作为指标。
1、平均响应时间:最常用,但是缺陷很明显,对于慢请求不敏感。比如1万次请求,其中9900次是1ms,100次是100ms,则平均响应时间为1.99ms,虽然平均耗时仅增加了0.99ms,但是1%请求的响应时间已经增加了100倍。
2、TP90、TP99等分位值:将响应时间按照从小到大排序,TP90表示排在第90分位的响应时间, 分位值越大,对慢请求越敏感。
3、吞吐量:和响应时间呈反比,比如响应时间是1ms,则吞吐量为每秒1000次。
通常,设定性能目标时会兼顾吞吐量和响应时间,比如这样表述:在每秒1万次请求下,AVG控制在50ms以下,TP99控制在100ms以下。对于高并发系统,AVG和TP分位值必须同时要考虑。
另外,从用户体验角度来看,200毫秒被认为是第一个分界点,用户感觉不到延迟,1秒是第二个分界点,用户能感受到延迟,但是可以接受。
因此,对于一个健康的高并发系统,TP99应该控制在200毫秒以内,TP999或者TP9999应该控制在1秒以内。
❇ 可用性指标
高可用性是指系统具有较高的无故障运行能力,可用性 = 平均故障时间 / 系统总运行时间,一般使用几个9来描述系统的可用性。
对于高并发系统来说,最基本的要求是:保证3个9或者4个9。原因很简单,如果你只能做到2个9,意味着有1%的故障时间,像一些大公司每年动辄千亿以上的GMV或者收入,1%就是10亿级别的业务影响。
❇ 可扩展性指标
面对突发流量,不可能临时改造架构,最快的方式就是增加机器来线性提高系统的处理能力。
对于业务集群或者基础组件来说,扩展性 = 性能提升比例 / 机器增加比例,理想的扩展能力是:资源增加几倍,性能提升几倍。通常来说,扩展能力要维持在70%以上。
但是从高并发系统的整体架构角度来看,扩展的目标不仅仅是把服务设计成无状态就行了,因为当流量增加10倍,业务服务可以快速扩容10倍,但是数据库可能就成为了新的瓶颈。
像MySQL这种有状态的存储服务通常是扩展的技术难点,如果架构上没提前做好规划(垂直和水平拆分),就会涉及到大量数据的迁移。
因此,高扩展性需要考虑:服务集群、数据库、缓存和消息队列等中间件、负载均衡、带宽、依赖的第三方等,当并发达到某一个量级后,上述每个因素都可能成为扩展的瓶颈点。
通用的设计方法主要是从「纵向」和「横向」两个维度出发,俗称高并发处理的两板斧:纵向扩展和横向扩展。
它的目标是提升单机的处理能力,方案又包括:
1、提升单机的硬件性能:通过增加内存、CPU核数、存储容量、或者将磁盘升级成SSD等堆硬件的方式来提升。
2、提升单机的软件性能:使用缓存减少IO次数,使用并发或者异步的方式增加吞吐量。
因为单机性能总会存在极限,所以最终还需要引入横向扩展,通过集群部署以进一步提高并发处理能力,又包括以下2个方向:
1、做好分层架构:这是横向扩展的提前,因为高并发系统往往业务复杂,通过分层处理可以简化复杂问题,更容易做到横向扩展。
上面这种图是互联网最常见的分层架构,当然真实的高并发系统架构会在此基础上进一步完善。比如会做动静分离并引入CDN,反向代理层可以是LVS+Nginx,Web层可以是统一的API网关,业务服务层可进一步按垂直业务做微服务化,存储层可以是各种异构数据库。
2、各层进行水平扩展:无状态水平扩容,有状态做分片路由。业务集群通常能设计成无状态的,而数据库和缓存往往是有状态的,因此需要设计分区键做好存储分片,当然也可以通过主从同步、读写分离的方案提升读性能。
上述方案无外乎从计算和 IO 两个维度考虑所有可能的优化点,需要有配套的监控系统实时了解当前的性能表现,并支撑你进行性能瓶颈分析,然后再遵循二八原则,抓主要矛盾进行优化。
1)从客户端看
尽量减少请求数量,比如:依靠客户端自身的缓存或处理能力
尽量减少对服务端资源的不必要耗费,比如:重复使用某些资源,如连接池客户端处理的基本原则就是:能不访问服务端就不要访问
2)从服务端看
增加资源供给,比如:更大的网络带宽,使用更高配置的服务器,使用高性能的Web服务器,使用高性能的数据库
请求分流,比如:使用集群,分布式的系统架构
应用优化,比如:使用更高效的编程语言,优化处理业务逻辑的算法,优化访问数据库的SQL
基本原则:分而治之,并提高单个请求的处理速度
1)客户端发出请求层面,常见的手段有:
尽量利用浏览器的缓存功能,减少访问服务端,比如:js、css、图片等
可以考虑使用压缩传输的功能,减少网络流量,也会提高传输速度
考虑使用异步请求,分批获取数据
2)前端接收客户端请求层面,常见的手段有:
动静分离,部分静态资源可以直接从Nginx返回
按请求的不同,分发到不同的后端进行处理,比如:负载均衡、业务拆分访问等
前面再加上一层来做多个Nginx的负载均衡,比如:LVS、F5等
还可以在更前面使用CDN服务
还可以对动态内容进行缓存,尽量减少访问后端服务
3)Web服务器层面,常见的手段有:
使用最新的JVM,并进行配置优化
对Web服务器进行配置优化,比如:调整内存数量、线程数量等
提供多个能提供相同服务的Web服务器,以实现负载均衡
仔细规划Web服务器上部署的应用规模
对Web服务器进行集群
4)Web应用层面,常见的手段有:
动态内容静态化
Java开发优化
优化处理业务逻辑的算法
合理高效的利用缓存
优化访问数据库的Sql,可以考虑利用存储过程等数据库的能力
合理使用多线程,加快业务处理
部分业务可以考虑内存数据库,或者是进行纯内存处理
尽量避免远程调用、大量I/O等耗时的操作
合理规划事务等较为耗资源的操作
合理使用异步处理
对部分业务考虑采用预处理或者预计算的方式,减少实时计算量
内部系统间的业务尽量直接调用、直接处理,减少WebService、工作流等
5)数据库层面,常见的手段有:
合理选择数据库的引擎,比如Mysql的InnoDB与MyISAM引擎
进行配置优化
可以考虑使用存储过程来处理复杂的数据逻辑
数据库集群,进行读写分离
合理设计数据库的表结构、索引等
分库、分表,降低单库、单表的数据量
下面再结合我的个人经验,针对高性能、高可用、高扩展3个方面,总结下可落地的实践方案。
1、集群部署,通过负载均衡减轻单机压力。
2、多级缓存,包括静态数据使用CDN、本地缓存、分布式缓存等,以及对缓存场景中的热点key、缓存穿透、缓存并发、数据一致性等问题的处理。
3、分库分表和索引优化,以及借助搜索引擎解决复杂查询问题。
4、考虑NoSQL数据库的使用,比如HBase、TiDB等,但是团队必须熟悉这些组件,且有较强的运维能力。
5、异步化,将次要流程通过多线程、MQ、甚至延时任务进行异步处理。
6、限流,需要先考虑业务是否允许限流(比如秒杀场景是允许的),包括前端限流、Nginx接入层的限流、服务端的限流。
7、对流量进行削峰填谷,通过MQ承接流量。
8、并发处理,通过多线程将串行逻辑并行化。
9、预计算,比如抢红包场景,可以提前计算好红包金额缓存起来,发红包时直接使用即可。
10、缓存预热,通过异步任务提前预热数据到本地缓存或者分布式缓存中。
11、减少IO次数,比如数据库和缓存的批量读写、RPC的批量接口支持、或者通过冗余数据的方式干掉RPC调用。
12、减少IO时的数据包大小,包括采用轻量级的通信协议、合适的数据结构、去掉接口中的多余字段、减少缓存key的大小、压缩缓存value等。
13、程序逻辑优化,比如将大概率阻断执行流程的判断逻辑前置、For循环的计算逻辑优化,或者采用更高效的算法。
14、各种池化技术的使用和池大小的设置,包括HTTP请求池、线程池(考虑CPU密集型还是IO密集型设置核心参数)、数据库和Redis连接池等。
15、JVM优化,包括新生代和老年代的大小、GC算法的选择等,尽可能减少GC频率和耗时。
16、锁选择,读多写少的场景用乐观锁,或者考虑通过分段锁的方式减少锁冲突。
上述方案无外乎从计算和 IO 两个维度考虑所有可能的优化点,需要有配套的监控系统实时了解当前的性能表现,并支撑你进行性能瓶颈分析,然后再遵循二八原则,抓主要矛盾进行优化。
1、对等节点的故障转移,Nginx和服务治理框架均支持一个节点失败后访问另一个节点。
2、非对等节点的故障转移,通过心跳检测并实施主备切换(比如redis的哨兵模式或者集群模式、MySQL的主从切换等)。
3、接口层面的超时设置、重试策略和幂等设计。
4、降级处理:保证核心服务,牺牲非核心服务,必要时进行熔断;或者核心链路出问题时,有备选链路。
5、限流处理:对超过系统处理能力的请求直接拒绝或者返回错误码。
6、MQ场景的消息可靠性保证,包括producer端的重试机制、broker侧的持久化、consumer端的ack机制等。
7、灰度发布,能支持按机器维度进行小流量部署,观察系统日志和业务指标,等运行平稳后再推全量。
8、监控报警:全方位的监控体系,包括最基础的CPU、内存、磁盘、网络的监控,以及Web服务器、JVM、数据库、各类中间件的监控和业务指标的监控。
9、灾备演练:类似当前的“混沌工程”,对系统进行一些破坏性手段,观察局部故障是否会引起可用性问题。
高可用的方案主要从冗余、取舍、系统运维3个方向考虑,同时需要有配套的值班机制和故障处理流程,当出现线上问题时,可及时跟进处理。
1、合理的分层架构:比如上面谈到的互联网最常见的分层架构,另外还能进一步按照数据访问层、业务逻辑层对微服务做更细粒度的分层(但是需要评估性能,会存在网络多一跳的情况)。
2、存储层的拆分:按照业务维度做垂直拆分、按照数据特征维度进一步做水平拆分(分库分表)。
3、业务层的拆分:最常见的是按照业务维度拆(比如电商场景的商品服务、订单服务等),也可以按照核心接口和非核心接口拆,还可以按照请求源拆(比如To C和To B,APP和H5)。
高并发确实是一个复杂且系统性的问题,由于篇幅有限,诸如分布式Trace、全链路压测、柔性事务都是要考虑的技术点。另外,如果业务场景不同,高并发的落地方案也会存在差异,但是总体的设计思路和可借鉴的方案基本类似。
高并发设计同样要秉承架构设计的3个原则:简单、合适和演进。“过早的优化是万恶之源”,不能脱离业务的实际情况,更不要过度设计,合适的方案就是最完美的。
……
Geek
1、开篇词|如何设计一个高并发、高可用的秒杀系统?
2、61 | 性能设计篇之“秒杀”
秒杀面试
1、高频面试题:秒杀场景设计
2、面试必考:秒杀系统要如何设计?
高并发面试
1、面试官再问高并发,求你把这篇发给他!
2、面试官:了解秒杀?简单分析下高并发场景下秒杀系统的设计思路
3、面试题:如何设计一个高并发系统?
4、面试题:如何设计一个高并发系统?