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