2

Web 应用高并发的思考与实践

 3 years ago
source link: https://cdc.tencent.com/2021/03/15/web-%e5%ba%94%e7%94%a8%e9%ab%98%e5%b9%b6%e5%8f%91%e7%9a%84%e6%80%9d%e8%80%83%e4%b8%8e%e5%ae%9e%e8%b7%b5/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Web 应用高并发的思考与实践 – 腾讯CDC

Web 应用高并发的思考与实践

Jeter 2021.03.15

2020 年初,突如其来的疫情使各行各业都进入紧张状态,互联网产品也各尽其能投入到战“疫”中。健康信息收集是抗疫过程中最基础的工作之一,腾讯问卷平台结合其信息采集、统计分析的特性,快速迭代出“健康打卡”“复学码”等功能。

然而,健康码关系着用户能否正常进出社区、公司或校园,计算的时效性和准确性必须有保证。加之用户量大、打卡时间点集中,在开发时间和资源都紧张的情况下,我们是如何应对访问高并发、数据量陡增,从而保障系统的稳定?

图 1

本文结合腾讯问卷后台的优化和改造工作,记录过程中的思考,整理提升 Web 应用负载能力的关注点。作为一个刚接触海量用户场景的小白,希望借此跟大家交流探讨。

图 2

实践围绕图2的四个点,从被动到主动,从发现到治理。这几个方法远远谈不上全面,但可能是短时间内性价比最高的实践。

1. 理解和关注高并发

1.1 What & Why

“高并发(High Concurrency)通常是指,通过设计保证系统能够同时并行处理很多请求”……Web 应用需求变更快,需要持续改进、持续运营以应对潜在的用户量突增。

Web 应用负载能力不足会有怎样的表现?

比如打开页面持续 loading/白屏,需要等待或者重新进入,给用户带来很差的使用体验。

系统崩溃可能表现在服务无响应、组件不可用、用户数据丢失等。在问卷系统中假设用户提交的数据丢失,健康码会因为中断打卡由绿变红,给用户带来困扰,同时也给开发运维带来修复的工作量。

所幸通过项目组的合力改造,系统的抗压能力提升了一个数量级。但也引发了新的思考,假如我们的系统要支持更高的并发,如何复盘当前的工作,以及准备下一步的工作?

图3借用《海量运维、运营规划之道》一书中的负载与架构演进图:

图 3

作者认为,Web 架构的生命周期要求我们要杜绝过度设计,又要保证前瞻性。而在实践中我也形成了一种认知,高并发是相对而不是绝对的,在业务每个阶段都需要有敏捷支持相对于前一阶段更高负载的能力,做性价比最高的事情。

1.2 Who

作为应用开发我也曾陷入这样的误区,认为高并发是架构设计师、运维工程师才需要考虑的事情,实践中发现项目组的各个角色都可以为高并发能力做点什么。

  • 产品策划和交互设计

当一个功能被设计出来时,就可以预见是否存在高并发瓶颈,通过一些产品策略或者交互方式的转变,往往能缓解问题。

图 4

比如在“健康打卡”这个功能点,以往设计了在每天早上七点推送提醒,无形中给系统制造了高并发的场景,实际上推送的时间点可以交给客户设置、并且分散到不同时间段,从而合理利用机器资源。

即使架构师设计了良好的分布式方案、运维工程师保障了系统能稳定扩容,负载的瓶颈还会是被开发人员“制造”出来(更别提在我们的业务每个人都身兼数职)。后续的案例也可以体现开发在支持高并发过程中的职责。

2. 理解和关注业务

2.1 When

什么时候需要重点关注业务的并发能力?最好的时间点当然是在设计之初,但往往我们是中途加入项目,下表中这几个时机可以作为参考:

软件熵这里可以做一个简单的科普,熵是一个来自物理学的概念,指的是某个系统中的 “无序” 的总量,当软件中的无序增长时,我们称之为“软件腐烂”。

图5中举了个例子,问卷中有个打卡统计功能,在开发的初期只需要展示谁没有打卡,重实现轻设计。在逐渐新增交叉分析、筛选等功能,以及在修过几次统计不准确的 Bug 后,打卡统计越来越耗性能,以致于在并发量高时耗尽计算资源而挂掉。

图 5

所有的问题都是建立在历史问题的基础上,往往这个时候对问题的处理就没有第一个版本那么完美。实践的经验是代码开始腐烂的位置往往也是存在高并发风险的位置。这时候就需要尽早关注。

2.2 Where

“设计一个大型高并发网站的架构,首先必须以了解业务特点作为出发点,架构的目的是支撑业务”……

Web 应用的架构往往并不复杂,在项目中我们主要关注两个方面:

1)流量链路

图 6

2)业务架构

  • 组件的选型是否合理
  • 依赖的服务是否稳定

图 7

图7粗略展示了腾讯问卷的技术栈,实践中其中某一个环节出问题都可能导致负载提升不上去,后文也将围绕各个环节来做分析。

1.1 监控

监控是为了第一时间发现、定位并记录异常。当前腾讯问卷系统虽谈不上立体化,但也在不断完善,保证了从业务层、系统层、网络层都有迹可循。

图 8

1.1.1 监控的指标选择

监控时我们往往最关注两个内容:时间和行为(路径),例如在应用层,左图记录了任务执行的耗时,右图记录了在任务执行过程中经历了哪些事件,二者结合便于判断性能的瓶颈、或者 bug 潜在的位置。同理对前端、对接入层也用了类似的指标。

图 9

1.1.2 监控的呈现

接下来是把指标可视化,将关键日志进行聚合,利用 Dashboard 呈现。

图 10

1.1.3 监控的监控

在异常爆发时,自建的监控系统在也会接收大量的上报,如果监控系统自己都挂掉了,对业务的监控也就失去意义,因此对监控系统也需要做保护,例如对错误上报做限流、对监控做额外的健康探测等。

1.2 压测

1.2.1 预测

  • 预估可能存在的瓶颈位

各个位置的带宽是否跑满?

组件的 CPU 或者内存使用率是过高?

业务机的 CPU 或者是磁盘 IO 是否跑满?

需求角度出发:实际总用户量可能是多少?同时会有多少人使用(打卡)功能?

现状角度出发:想达到的目标是当前负载能力的 5 倍?10 倍?

1.2.2 实施

我们常会使用 apache bench(简称ab) 测试接口,但到业务场景中,单接口的压测有局限性。在测试同学的带领下我们尝试了 Locust。

  • 如何模拟用户并发

首先要理解一个用户同时发起一万次请求,跟一万个用户同时发起一次请求,对系统造成的压力是不一样的。实测过程中我们预留了一千万个 ID 用于模拟真实用户,同时避免测试数据难以清理。

图 11
  • 如何模拟行为并发

这里先介绍一个用户使用问卷进行打卡的流程:打开问卷链接->登录->答题->提交答案。也就是说一个打卡场景实际涉及多个请求,在做压测的时候我们尽可能还原。在写完每个接口的测试方法后,Locust 可以使用 TaskSet 进行任务组合,并且支持权重配比。这个比例正好可以从我们的流量监控获得。

图 12

1.2.3 定位瓶颈

压测的目的是在于找到系统的瓶颈,一定是要确定系统某个方面达到瓶颈了,压力测试才算是基本完成…

  • 纵向:按流量链路逐层“断点” 回顾系统的链路,流量从前端到接入层、再到应用层、缓存层、存储层,每个环节都有被“撑爆”风险。只有逐层排查和改造,才能了解到链路中潜在的瓶颈位。

案例 1. 流量被前端拦住,并没有到后台

图 13

案例 2. 缓存层带宽不足

图 14
  • 横向:对照实验 经过逐层压测,系统的性能瓶颈定位到 MySQL 上,这也符合我们对业务的认知,因为系统重度依赖数据库并且缺乏针对性的优化。但是数据库的读写来源有接口服务和任务队列,我们采用了对照实验,调整 fpm worker 与 queue worker 数量比例,表格记录每一轮测试的效果,进而判断是哪个影响更大

1.2.4 压测小结

压力测试不仅是孤立测试各个软硬件的性能指标,而是要尽可能模拟真实的业务场景,从而充分评估系统上线后可能发生的情况,同时也为我们后续制定问题应对措施、制定改造计划提供了依据。

2.1 扩容

当用户量开始增多,现有系统无法处理那么大访问流量,最快速的响应方案是扩容。但是你的系统真的能快速扩容吗?

2.1.1 扩容的方式

软件上,通过调参能否充分利用硬件资源

硬件上,是否有升配的能力

业务服务器加机器

组件集群加节点/分片

2.1.2 怎样支持扩容

对于老业务,我们往往要先解决怎样支持扩容的问题。

  • 解耦 业务中依赖了大量的公司内部服务,但由于网络策略或安全因素,它们影响了业务的扩容能力。实践中我们把这些依赖剥离出来,通过代理等形式,形成了局部单体、整体可扩容的架构。
  • 上云 以往的单体应用架构制约了扩容能力:比如需要在应用层加一台物理机,需要从装系统开始做起;给 MongoDB 加一个节点,需要大量的运维操作。借助云服务的灵活性可以大大提高系统的扩容能力。

腾讯问卷上云主要包括以下三个方面:

18.png

在上云过程中多多少少会踩坑,这里暂不展开讨论,后续再开新篇深度记录。

2.1.3 扩容还需要注意什么

  • 依赖的组件是否会扛不住
  • 依赖的远程服务是否会因为扩容被拖垮

在压测过程中可能会在测试环境 Mock 掉一些远程请求,导致在线上扩容业务服务后,问题才暴露出来,需要引以为戒。

图 15

显示了在并发量逐步上升的过程中,QPS 出现下降再上升的情况,排查发现是依赖的远程服务容量不足导致的性能下降,所以在压测过程中不能仅关注系统本身。

2.2 优化

扩容往往只是一种缓解措施,从运维的角度提升了系统的负载能力,但随之而来的是服务器成本的上升,我们需要通过改造和优化使资源的利用更合理。

2.2.1 关注代码逻辑

为了功能快速上线,我们往往忽略了代码里耗时的方法和逻辑,当大量请求涌入,慢执行就会阻塞后续的请求,拖慢系统的其他接口,甚至造成雪崩。

CPU 密集型的比如 web 应用常会防止 xss 攻击,一般会用正则过滤标签,在面对大文本、高并发、或者是循环的时候,都会大量占用 CPU,使业务服务器的压力上升。

IO 密集型的比如上传下载文件时过度扫描目录,任务的大部分时间都在等待 IO 操作完成,响应时间变长,这时候业务服务器的 CPU 使用率不高,而磁盘读写压力大。

对于PHP接口通过记录和查询 fpm_slow_log 可以分析耗时的方法。同时我们把慢执行数量进行监控,在新功能上线时需要密切关注。

无论是在生产环境还是压测过程,造成 MySQL 压力的很多情况是大量的慢查询,通过查 slow_log 或者从腾讯云管理后台可以进行慢查询分析。

很多资料都会提到使用索引来提升查询速度(先抛开I/O、锁这些不讲),然而即使有索引也可能踩坑。比如 “隐式类型转换” 的问题在腾讯问卷系统中就存在。

图 16

MySQL 会根据需要自动将数字转换为字符串,比如 openid 为 varchar 类型,当使用 where openid=123456 的查找时,实际进行了全表扫描,解决方案是查询前在代码中做转换,或者使用 CAST 方法转换。

2.2.2 合理使用组件

  • 是否有性能更高的替代

专业的事情交给专业的人干,在组件选型同样适用。在改造过程中,Elasticsearch 组件的引入,减少了在 MySQL 做搜索或聚合,提高了前面案例提到的打卡统计的计算能力。

  • 是否有更低成本的替代

Web 应用在使用缓存时,往往不注意容量和带宽的成本,回顾前面的压力测试,我们发现将大量的问卷静态数据存在 Redis 后,Redis 很快成为负载瓶颈,实际上只需要稍加改造,就可以找到合适的存储替代。

四、小结与展望

本文整理了 Web 应用高并发改造过程中的一些关注点,实际上每个加粗的小点都应该展开来更深入地探讨,提供对比方案和详细效果。但作为开发的我往往感觉到,自己不缺乏解决单个问题的能力,而缺乏系统性思维和全局观,本文也算是为高并发实践记录一些思路和方向,后续应继续补充完善。

回顾这段时间的改造效果,在使用资源不到两倍的情况下实现了5-10倍的负载能力,并且使继续扩容成为可能。虽然我们用效率最高的手段,让系统顺利支持了疫情期间的并发量,但如果要实现十倍甚至百倍的负载能力,当前的工作必定是不够的,或许更完善的分布式架构、或者当前火热的微服务架构,是我们系统下一步的考虑方向。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK