本文转载自微信公众号《跨界架构师》,作者Zachary。转载本文请联系跨界架构师公众号。大家好,我是Z哥,最近刚好在写一套新的面试题,其中一道是高并发相关的。题目目的是了解考生在面对大流量场景时的优化思路。在提出这个问题的过程中,我也整理了自己对这个问题的思考。那么之所以要分享到这里,是因为这是我多年来在实战中积累的“血汗”经验,所以我想这可能对很多读者有所帮助。但丑陋的是,我的想法并不完美。毕竟条条大路通罗马,解决“高并发”的方法有很多种。所以欢迎大家在评论区分享你们的看法。/01高并发的定义/首先,高并发的定义是什么?高并发意味着大流量,需要通过技术手段来抵御流量的冲击。这些方法的效果就像你可以操纵流量,让系统按照你的期望更顺畅地处理流量,给用户带来更好的体验。是的,就像大禹治水一样。我想几乎每个程序员心里都有一个疑问,QPS或者TPS达到多少才算高并发?事实上,这个问题不能简单地用一个统一的标准数字来判断。因为不同的业务对应的复杂度不同,不能一概而论。我自己用的标准是,当一个正常运行的系统在没有刻意优化性能的情况下出现性能问题时,就意味着它已经开始进入高并发的范围。是的,就是这么简单粗暴,没有具体数字。但是在高并发正常的场景下,一般会有一个监控系统,会持续观察至少以下几个指标是否正常。TPS。每秒事务处理量,这里的事务是指客户端向服务器发送请求,服务器响应的过程。(从客户端的角度)QPS。每秒的查询率可以通过并发数/平均响应时间来计算。(从服务端来看,一个TPS可能对应多个QPS)并发用户数。系统可同时承载的能正常使用系统功能的用户数。响应时间。很多时候也叫RT,指的是系统响应请求的时间。带宽。如果是数据传输量比较大的业务,还需要考虑带宽问题,比如视频、音频应用。如果你对这些指标的含义不是很清楚,那就先多看几遍,弄清楚,不然你的高并发是在闭着眼睛干的。/02高并发的应对思路/很多没有经验的小伙伴遇到高并发的问题,不管怎样,上来就缓存。使用缓存没错,但是缓存不是万能的,处处适用。毕竟,缓存是“有状态的”。在软件开发中,处理“有状态”的东西总是比“无状态”更麻烦,因为有数据的一致性问题需要考虑。我建议你分以下三个步骤来考虑这个问题。01整理请求流程链接整理请求流程链接,就像手中多了一张“作战地图”,可以更直观、更准确地排兵布阵。毕竟,软件开发本身就是一个工程问题。我们不能靠感觉和拍脑袋来做事。解决高并发问题也不例外。02确定目标俗话说得好,“没有最好,只有更好”。如果不先明确目标,这东西就会变成对业绩永无止境的追求。过高的性能不会产生额外的收益,反而会浪费投入成本。毕竟,资源是有价格的,而且是有限的。我建议您为每个API细化具体的数值目标。比如我一般先确定整个业务线的TPS。比如下单的TPS必须达到100,接下来我根据前面整理的链接图,获取哪些服务,会涉及到哪些API,哪些API会被不同的服务多次调用。然后根据链路中API的调用顺序(一般上游API设置的值适当放大)和重复调用的次数,获取各个API的QPS目标。比如获取购物车列表,调用2次,QPS=200。批量获取商品库存,调用3次,QPS=300。获取会员信息,调用1次,QPS=100。...按照这个思路,确定业务线会涉及到的所有API的QPS,然后将同一个API的QPS值相加,得到整个系统层面各个API的理想QPS值(相当于In所有业务线均达到预估峰值的事件)。例如获取购物车列表,出现在三个业务线,QPS=200+500+100=800。批量获取商品库存,出现在两个业务线,QPS=300+200=500。获取会员信息,出现在两条业务线上,QPS=100+200=300。……当然,除了QPS,还有QPS之外的其他指标。以上只是一个例子。另外,要格外注意TP90和TP99的“响应时间”。因为即使平均响应时间达标了,也不代表整个系统就稳定了。因为可能有80%的请求响应速度极快,拉低了平均值,但同时也有大量的请求特别耗时。我通常的经验法则是,如果TP99超过平均值的两倍,则需要认真对待,因为这意味着某处存在明显的性能瓶颈。03制定具体的优化方案其实具体的优化方案有很多,要根据实际情况选择。但是,具体如何选择,还是需要你有全局的眼光。由于整个系统是集成的,通过相互协作形成合力,可以大大降低优化难度。比如选择优化方案后,上游API可以处理90%的流量,而无需向下游发起请求,那么下游API的QPS目标也可以适当降低。下面是我按照综合复杂度和成本从简单到复杂排序的具体方案。首先在代码层面进行优化,比如代码性能、多线程、请求合并、池化(连接池、线程池、对象池等)。如果可以升级硬件,就升级硬件。能通过缓存解决的坚决不拆分系统。此外,缓存尽可能位于上游。比如cdn>page>api>service。如果数据处理有瓶颈,那么优先考虑业务是否接受异步,如果接受,则使用MQ削峰填谷。或者限流降级。如果MQ没有用,是整体吞吐量的瓶颈,只要不是写入数据的瓶颈,尽量通过拆分程序而不是拆分数据库来解决问题。(此时需要引入服务治理,另外还会有一致性问题,需要解决数据合并问题。)如果非要搬到数据层面,优先考虑数据库读写分离而不是直接拆分数据。确实需要对数据进行拆分,优先按业务进行纵向拆分,而不是横向拆分。(水平拆分的数据合并成本会比垂直拆分大)最后是数据库的水平拆分,支持无限扩容,一劳永逸。你看,虽然解决方案很多,但也可以遵守一些规律:越往上游解决问题,成本越低。所以,用漏斗思维顾全大局是最合适的。从客户端请求到接入层,再到逻辑层,再到DB层,层数减少,请求过滤。即使有异常,FailFast(失败尽快返回)。/03落地/实际落地的时候,涉及到很多具体的技术细节,比如负载均衡、缓存、消息队列、分库分表等等,这里就不展开了,得写一大堆每个点的扩展。有兴趣的朋友可以参考我之前写的分布式系统系列文章——《8个月打磨,一份送给程序员的「分布式系统」合集》其实高并发应该算是一个系统的事情,视野不应该局限于“性能”这个维度另外,至少需要考虑“可用性”和“可扩展性”两个方面。可用性是系统可用于正常服务的时间量。一个虽然访问速度没有那么快,但是一年四季无宕机,无故障;后者的访问速度虽然快,但是事故和宕机时有发生,用户必须选择前者。可扩展性是指系统的快速扩展能力。当遇到突发的大流量冲击时,能否在短时间内完成扩建,承接这部分流量。比如双11活动、明星热搜等场景。毕竟,我们不可能准确预测未来流量的范围,更不可能随时为其准备大量冗余资源。因此,这三个目标需要作为一个整体来考虑,因为它们是相互关联甚至相互影响的。例如,当你考虑系统的可扩展性时,你会把服务设计成无状态的。这种设计不仅提高了可扩展性,还间接提高了系统的性能和可用性,因为你可以随时水平扩展。再比如,为了提高可用性,通常会在服务接口上设置超时设置,防止大量线程阻塞在慢速请求上,造成系统雪崩。具体的超时设置是根据API的性能而定的。好的,总结一下。在这篇文章中,Z哥和大家分享了我在处理高并发问题上的一些思考。按照以下三个步骤操作。梳理请求流程环节,确定目标,制定具体的优化方案。第2点和第3点在文章中给出了很多实用的细节,这里就不一一列举了。希望对你有帮助。业务都是从0到1开始的,在业务量逐渐变成原来10倍、100倍的过程中,你有没有用高并发处理的思想来演进你的系统,从架构设计、编码实现,甚至产品方案等预防和解决高并发问题的维度?
