前言
- 问题都是逐步暴露的,没有显现出来的问题不代表不存在,只是有更低级的问题先出现了而已。
- 特别是对于 service 来说,问题出现之后,必须要找到根因,找到根因之后,解决方案可以分布实施,否则所谓的中间临时方案很可能只是拆西墙补东墙,甚至可能是错的。做 service 不能抱侥幸心理,问题是一定会出现的。(所以真正的中间方案、临时方案一定是找到根因心里有完整的解决方案后的分步骤实现过程而已)
- 不同的阶段不同的措施,找到2/8点,四两拨千斤以最小的代价收获最大的效果。
- 做 service 的严谨性非常重要,前期并不会多花时间,后期反而会节省很多时间。
最先体现出来的问题:用户访问列表、用户信息等速度极慢
原因分析:
- 网络?
- 数据库访问?
- 每次访问数据库都发起了一次连接
- 当访问多张表时发起多次连接
改进方案:
- 修改数据库主从的位置
- 联合查询
- 存储过程
- 数据库连接池
2/8点:
- 将核心流程的数据库查询做存储过程
效果: - 用户的查询基本控制在 1s 以内了,1s以内的查询对于用户来说可能就基本无感了,因此该阶段暂告一段落
接着出现问题:日本量激增,造成了大量的访问错误(其实访问错误一直存在,只是大家没有关注)
分析:
- 基本还认为是网络问题、数据库问题造成访问速度慢,最终导致服务器性能不够
- 但是即使激增后的用户量,原理上来说也不可能将我们目前的服务资源消耗完,所以必须要进一步寻找根因
最严重的问题:
- IOS APP 在 1s 内发送大量的查询 userinfo 请求,导致服务启动太多的进程消耗完服务器资源
尝试的改进方案: - 将服务器上除 php 外的其他服务迁移到其他负载低的服务器
- 在 APIGateway 上对 userinfo 接口做 60s 的缓存,降低服务的压力(这样用户在一分钟内的频繁请求就会被 API 网关过滤)
效果:
- 负载降下来了,在问题出现的时候服务仍然基本处于可用的状态
为什么在 API 网关上做缓存?
- 依托于完善的公有云做接口限制,否则服务做限制仍然会消耗掉服务的进程资源
- 基于 API 网关做限制其实要求 接口是标准的 rest 风格,缓存都是针对 get 请求的,但是我们的接口并不是 rest,原则上做不了接口缓存。只是由于 userinfo 接口刚好携带有每用户唯一的鉴权头域,能实现方法级的缓存
问题:在 logging 上仍然有访问超时的现象发生
分析:
- 偶尔还是出现服务器资源被占完的情况,但是当前同时访问的用户数量其实并不多
- 分析日志后发现
- API 网关的缓存偶尔会失效,造成一部分压力还是透传到了 php 服务
- 但是透传后的压力应该不至于会造成服务器满载
- 所以需要从以上两个访问找原因
问题:
- APIGateway 的缓存有效期是 60s, 当超过 60s 之后,缓存失效,此时新来的请求还是会透传到后端服务,然后在重建数据,这个机制本身是没问题的。但是问题在于
- 第一个请求得到响应的时间太长,有的甚至达到接近 10s,在响应没有回来之前,缓存是无法重建的,所以10s内的并发压力全部透传到了后端服务
- 那么问题基本上指向了后端服务,为什么一个请求需要耗时那么久?
尝试的解决方案:
- 增加数据库连接池方案 proxysql,解决连接数据库慢的问题
- 修改 php-FPM 的配置,将初始进程数量调整到50个,最多的空闲进程调整为52个
- 对所有能做 redis 缓存的数据做 redis缓存
效果:
- 访问速度加快非常明显,但是在PC上做200个并发访问的时候,仍然有访问超时的现象出现(502,504)
分析:
- 发现 nginx 对频繁的访问会自动返回499,对应504
解决方案:
- 修改 nginx 的配置
效果:
- 504的问题解决了,但是还是会返回502的现象
分析:
- FPM的初始进程数量是50个,并发激增的时候创建进程比较耗时
解决方案:
- 将初始进程调整为 100个
效果:
- 响应时间明显缩短,但是还有少量 502 的现象
分析:
- FPM 原理上是一个消耗内存的框架,但是为什么现在是 CPU 先被占满呢?
- 打开日志查找慢响应的具体原因,发现
- BUG,有一个BUG会导致循环查询一个数据
- 其他的 php 服务没有使用数据库代理
- Php 服务之间走的是 curl 来访问
解决方案
- 解决BUG
- 所有的 php 服务都走数据库代理
- 要查询数据的时候直接访问代理,不走 curl(其实服务之间的访问走接口会更合理,但是php没有 UDS 等高性能服务)
效果
- 在三台 PC上发起测试,基本没有 502 出现,但是后面的请求访问延时仍然偏大
可能的原因:
- 开发测试环境的读数据库是在美国,与生产环境相反,生产环境的条件应该要比这个好
- 开发测试环境的数据库服务并不是独占资源,有可能数据库本身的访问会被影响
- 表设计优化(基于完整的用户故事设计表,而不是单个小需求)
- Mysql 查询方法优化
- Mysql 本身配置优化
- proxysql有更好的配置选项
- Php 服务还有继续优化的余地
- 服务器本身的配置优化
这个阶段的优化目标已经达到,所以暂时先不往下探索。
过程中我们还计划要做但是没有做的事情:
- 对所有的接口做参数的合法性校验
- 对所有的接口做调用频率的限制
- 做log的代理,用来将服务的错误日志发送到 GCP 的logging,同时将日志文件上传到 S3
- 做代码走读,梳理流程