一篇搞定C端系统性能优化

流量高峰时,系统在性能、可用性方面存在不足导致的。那当时你们咋办?

1 啥是性能优化

应用系统上线那一刻开始,随着用户量的增加、业务功能的持续迭代,系统会面临各种不同程度的挑战,如果不及时采取优化措施,我们会发现诸多问题:系统怎么越来越慢了,流量一高系统就卡顿、甚至宕机等等。性能优化贯穿整个软件生命周期。

1.1 性能衡量指标

1.1.1 响应时间(RT)

所谓响应时间,是指完成某一功能所需要的时间,一般可以通过“平均响应时间”、“百分位数”等指标来考量。

① 平均响应时间(AVG)

接口平均处理能力。

计算方式:接口请求所有的响应时间和 / 总请求数。

如接口A总共发起了8次请求,其中,有1次3ms,3次5ms,4次6ms,接口平均响应时间 (1 * 3 + 3 * 5 + 4 * 6) / 8 = 5.25ms 。

② 百分位数(Top Percentile)

超过n%的请求都在m时间内返回,一般用TPn=m描述,如:TP99=5,表示超过99%的请求都能在5ms内返回。

计算方式是:将接口的响应时间按从小到大的顺序进行排列,取特定百分位的耗时,即为该接口的百分位数。举个例子:接口A总共发起了100次请求,响应时间依次是1、2、3、...、100,那么,TP95就是95ms。

一般而言,百分位数更能反映接口的整体响应情况,因为在高并发场景中,常常会出现一些长尾请求,如果采用平均响应时间去衡量,由于长尾请求会被大量低RT平均掉(此时很多用户的请求已经很慢了),进而无法及时感知真实业务状况。举个例子:接口A有100次请求,其中97次1ms,3次100ms,平均响应时间为 (1 * 97 + 3 * 100) / 100 = 3.97ms,此时3.97ms并不能真实反映接口的整体性能,因为其中97次请求RT才1ms。

1.1.2 并发能力

并发能力一般用QPS或TPS来衡量。QPS指的是每秒请求数,TPS是指每秒事务数。一般在做性能评估时,TPS用的比较多。

1.2 性能优化本质

评价算法效率看时间复杂度和空间复杂度。同理,将“响应时间”比作时间维度,“并发能力”类比空间维度。性能优化本质上也是从“优化时间”、“优化空间”、“时空互换(用时间换空间或用空间换时间)”思考,然后在空间、时间取舍。

例子

长度为5km的道路限速50km/h,规定任何时刻车道有且仅有一辆。1h内,从A点出发到达B点的汽车最多只有10辆。假设想提升这块路段车流,咋办?

可增加车道数(空间维度):将道路从单车道变为多车道,比如增加到6车道,那么,在1h内,从A点出发到达B点的汽车数可提量到60辆:

第二种方式,道路提速(时间维度):将道路限速从50km/h提升到100km/h,那么,在1h内,从A点出发到达B点的汽车数可提量到20辆:

2 咋做性能优化

2.1 系统性思考性能优化点

性能优化是由人来执行,然后服务于产品的,人和产品共同参与性能优化的落地。

人维:性能优化是属于技术团队的,技术团队包括开发、测试和运维,其中,运维负责提供一些监控数据,测试负责提供一些压测数据,开发基于压测、监控数据,明确具体的优化点以及优化手段

产品维度:性能优化是业务功能的一部分,是为了满足某些业务场景。

性能优化考虑:

  1. 本次性能优化的业务场景是什么,有哪些场景需要优化
  2. 这些场景的运维监控数据、测试压测数据是什么,要优化哪里
  3. 这些数据里面反映的系统瓶颈在哪里,如何去优化
  4. 重复(2)、(3)过程,直至满足优化目标

结合性能优化的本质,整个优化过程:先从业务需求角度出发,思考待优化场景是否值得投入,如一个任务每次需要跑半小时,从技术层面,可以做下优化,但结合业务情况却发现,此任务的执行频次是每周一次,如果优化此场景需要耗费较大人力,那么,这个投入就是不值得的;

技术实现角度出发,不停地去思考怎么优化时间、怎么优化空间、怎么牺牲空间换时间、怎么牺牲时间换空间等问题。总之,我们在做性能优化时,需要以一个更全面的视角去看待它,避免进入头痛医头脚痛医脚的误区。

2.2 性能优化方式

在实际业务场景中,一个外部请求进入系统后,会先后经历多个软硬件节点,所有节点的处理时间加起来才是用户请求的处理时间,如果其中任意一个节点性能有问题,系统整体的性能就会上不去。由于节点自身差异性,性能提升方法也不同,但总体分为:

  • 提升单个请求处理效率
  • 并行处理多个请求

2.2.1 提升单个请求处理效率

一个外部请求进来后,让其在尽可能短的时间内处理完成。常见方法:

① 提升调用链上各节点的处理速度

技术角度:

  • 数据库层面,可以考虑加索引、读写分离、分库分表等
  • 应用层,加缓存(本地缓存,分布式缓存,或叠加)、复杂查询走ES索引
  • 代码编写,考虑更高效算法数据结构,如:读多写少用数组、写多读少用链表、取余采用位运算

业务角度:

  • 尽量避免重复查询
  • 查询类操作,尽可能批量查询
  • 上游调用方尽可能使用更合适的下游接口,如:下游服务方有分别返回A、B、AB的三类接口,如果上游使用方仅需要A信息,应使用A接口;如果同时需要AB信息,应使用AB接口,而不是依次调用A、B接口,再在内存聚合
② 请求内部做并行化处理

将单个请求拆分为多个子请求,各子请求并行处理,最后对子请求结果合并后返回。可基于 CompletableFuture 实现一套并行处理框架,运用到商品详情页加载场景。

③ 请求处理异步化

最典型采用MQ,如:下单操作时,除了扣减库存、生成订单外,还给用户发送支付成功消息、赠送积分等后置操作。对这些非核心的后置流程,采用MQ异步处理,以此提升下单接口性能。

进程内,另开一个线程执行这些非核心流程;或先将非核心操作数据暂存在某种介质(DB表、redis等)中,然后采用定时任务定期扫描并执行这些操作。

2.2.2 并行处理多个请求

字面意思来看,就是当有多个外部请求进来时,可以让系统内部多个节点分别处理这些请求,或者节点内部做并行处理。比如:节点采用集群部署,并通过负载均衡策略,将用户请求分摊到不同的节点进行处理;节点内部采用线程池,通过另开线程来实现。

3 咋做?

直播间进入流程业务场景:用户首先访问直播商品详情页,然后购买此商品,紧接着再次访问详情页面时,会出现直播间入口,在进入直播间之前,会做一次权限校验,校验通过后,方才可以进入直播间与讲师进行互动:

外部请求:

  • 查询直播商品详情
  • 商品下单
  • 进入直播间前做用户权限校验。通过流量监控数据及日志分析,发现性能瓶颈在“直播商品详情加载”

直播商详这块,因为上游服务请求量超过下游服务承受吞吐量,导致大量RPC调用超时。反应问题:

  • 依赖的部分非核心接口没有加缓存、做降级,导致整个请求失败
  • 依赖的部分核心接口性能较差,导致后续请求一直被阻塞,直至超时异常返回
  • 下游服务提供的查询接口比较重量级,但上游服务仅需要返参中的部分字段,导致单次查询RT一直下不去
  • 上游调用方使用了错误的下游接口,比如上游调用方本来可以调用一次详细信息查询接口,便能获取所有需要的信息,可实际中,却先后调用了两种查信息的接口,才拿到完整的信息
  • 无状态查询接口没有加缓存,导致了频繁的RPC调用。

针对上述问题,主要从以下几点优化:

优化前,梳理整个调用链上,接口强弱依赖关系及每个接口RT

3.1 针对弱依赖接口

RPC调用超时时间设置策略

统计出弱依赖接口 TP99(RT较稳定的接口)/ TP95 (RT波动较大接口)的RT,设置它们的超时时间为 (1 + 50%) (TP99 或 TP95)

这里讲下为什么要这样设置超时时间:一般我们会设置超时时间为2s或3s,但每个接口的RT是不一样的,比如:接口A的RT稳定在100ms内,那么,如果超时时间是2s,假若接口A超时了,本次RT至少是2s,但如果超时时间设置为100ms,且我们加了1次重试,那么,本次请求的RT不会超过200ms,同时,重试时接口很大概率会正常返回结果。

缓存策略

给接口添加前置缓存。我们采用分布式缓存,缓存的更新策略是:采用了两个缓存,缓存A和缓存B(缓存A的失效时间为m分钟,缓存B为n分钟,且n>2m),首先从缓存A读数据,有则直接返回,没有则从B读数据,并在返回之前,异步启动一个更新线程,同时更新缓存A和缓存B。

降级策略

接口接入熔断降级机制,并对异常做捕获,返回默认值。

3.2 针对强依赖接口

RPC调用超时时间设置策略

统计出强依赖接口 TP99 的RT,设置它们的超时时间为 (1 + 50%) (TP99)

重试策略

根据接口RT波动性,基于dubbo的重试机制,设置重试次数为2或3次。

缓存策略

对于商品基础信息,考虑到“缓存预热”、“热点访问”等问题,接入了公司TMC(透明多级缓存), https://mp.weixin.qq.com/s/BnWtbetNq076iRRZfnGRrw

其他一些无状态查询信息,本地缓存。

3.3 商品详情信息聚合操作并行化

商品详情页面是一个聚合类信息展示窗口,它除了商品基础信息外,还包括A、B、C等内容(出于商业保密性,这里泛化内容名称),且这里的A、B、C和商品基础信息四者间是没有任何前后依赖关系的。当时我们将商品详情加载拆分为了4个子任务,并采用并行处理框架,对子任务做了并行化处理,并聚合返回,较大提升了接口RT性能。

3.4 查询类接口能力收拢

下游服务方提供稳定的原子化接口。在问题点(3)、(4)中有提到,上游调用方使用了下游不太合适的接口。由于历史原因,当前下游服务方中有特别多的查询类接口,且很多查询类接口在功能上都是重叠的。本次我们针对查询类接口,按照其返参字段使用场景的不同,提供了三种不同粒度的通用类原子化接口,之后所有的查询类需求,都会强制要求上游调用方从这三类接口中选择。这三类接口如下:

  • 粗粒度:返回最基本字段
  • 中粒度:返回经常使用的字段
  • 细粒度:返回详细信息

4 总结

产品功能是持续迭代的,性能优化也不是一蹴而就的事,大家在遇到性能问题时,可以参考本文提到的一些方法,做一些针对性的优化。同时,针对同一个节点,在不同的时刻,其优化点也可能不一样,比如:新功能刚上线时,查询性能的提升可能仅仅通过加索引的方式便能解决,但随着功能的不断叠加,后续的优化方向可能是“尽量走批量查询”、“加缓存”等方向。所以,性能优化还是要遵循“具体案例具体分析”这一基本原则。

关注我,紧跟本系列专栏文章,咱们下篇再续!

作者简介:魔都技术专家,多家大厂后端一线研发经验,在分布式系统、和大数据系统等方面有多年的研究和实践经验,拥有从零到一的大数据平台和基础架构研发经验,对分布式存储、数据平台架构、数据仓库等领域都有丰富实践经验。

各大技术社区头部专家博主。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。

负责:

  • 中央/分销预订系统性能优化
  • 活动&优惠券等营销中台建设
  • 交易平台及数据中台等架构和开发设计
  • 车联网核心平台-物联网连接平台、大数据平台架构设计及优化

目前主攻降低软件复杂性设计、构建高可用系统方向。

参考:

全部评论

相关推荐

投递米哈游等公司10个岗位
点赞 评论 收藏
转发
1 1 评论
分享
牛客网
牛客企业服务