简介(未完成)
源代码级的debug
系统级的debug
基本现象及通常原因
从用户角度看
- 数据错误
-
页面刷不出来
- 请求超时
从报警角度看
- tomcat 线程池打满
-
大量异常
- 本身异常
- 依赖服务异常
- 大量gc
-
mysql 大量超时
通常原因是 耗时查询的sql 过多
-
redis 大量超时
通常原因是网卡被打满,包括入宽打满和出宽打满。前者因为大量写入(通常是代码逻辑错误),后者通常因为服务负载较大,需要扩容。
排查故障的基本知识
jvm 基本组成,gc的原因分析 等
曾经作为一个coder,笔者虽然很热心的学习了这些知识,但实际用的少,
完备的系统感知能力
感知什么?
一个示例。
完备的监控,笔者曾碰到一次事故,tomcat 线程池满了,可以判断出来某个请求的处理很慢。报警系统同时反馈redis 连接池连接获取超时,进而可以定位是redis 的原因。然后运维说,redis 网卡打满了,但尚未定位是入宽打满还是出宽打满,我当时觉得自己的系统缓存用的少,应该是别人的原因,因此回去睡觉了。但后来 确实我这边 大量的写入导致的。
我自己的代码肯定是有问题,锅甩不掉。换个较角度看,如果 监控系统 监控redis 的带宽,并设定阈值报警,同时能够分析入宽来源方的数据量,则可以极大的减少定位问题的时间,并找到相关的开发解决问题。
此外,从中我们还可以看到:
- 从运维的角度看,redis 资源的分配 应设定几条基本原则,其中重要的原则之一就是方便定位问题。
- 大部分时候 我们还是在受限的资源下做事情,监控/报警系统不完备或者没有,这都不是出现事故的理由。还以上述问题 为例,仅以完成代码功能来说,业务 ==> db 即可。为了挺高性能:业务 ==> 缓存 ==> db。 缓存是共用的,那么为了减少依赖提高可靠性。可以: 业务 ==> local cache ==> 缓存 ==> db。 同时根据业务做好降级熔断措施。
- 所以,为了提高一段代码,功能实现、高性能、可靠性、可扩展性,你的脑子里一共有几个维度? 根据项目的发展阶段,各个维度的重要性如何取舍?
案例分析
缓存穿透的例子
现象
- 报警层面,大量的请求 timeout
- 日志层面,大量的Broken pipe
- 监控层面,数据库连接池耗尽
- 运维反馈,mysql并发太高
问题原因
一个qps 很高的业务,但因为数据量很少,我只加了本地缓存(guava cache),重启的时候,本地缓存失效,大量的qps 直接穿透去访问数据库了。虽然guava cache 有请求合并,但只针对了相同的key 做了合并,如果业务的key 都很不一样,那其实还是缓存穿透。这时看着报警也没办法,随着本地缓存 数据逐渐覆盖所有的key, 本地缓存就可以处理,较少访问数据库,数据库负载才会降下来。
公司内的nginx 一般设置了2s 断开连接,假设web 服务是2000qps,一共有50个数据库连接池,每个sql 处理耗费10ms,来回网络通信耗费10ms,即一个sql的处理耗费20ms,一个 guava cache loader 执行2个sql,综合起来假设业务从数据库拿回数据并处理一共耗费 50ms。假设从t0 开始处理请求
正在处理的请求编号 | 等待请求数 | 第一个批次到t0时间偏移 | 第二个批次到t0+1s时间偏移 |
---|---|---|---|
1~50 | 1950 | 50ms | |
51~100 | 1900 | 100ms | |
… | … | … | … |
951~1000 | 1000 + 2000 | 1000ms | 0m |
1001~1050 | 950 + 2000 | 1050ms | 50ms |
… | … | … | |
1951~2000 | 0 + 2000 + 2000 | 2000ms | 1000ms |
… | … | … | |
2951~3000 | 0 + 1000 + 2000 | 2000ms |
可以看到,第一个批次在2s 内处理了所有请求。但对于第二批次的后1000个 请求来说,即便拿到数据库响应,因时间已超过2秒,web服务与nginx的连接被nginx 断开,web 服务写回数据时会抛出Broken pipe异常。也就是当一个请求要50ms 才能处理完毕时,其qps 只能是(1000ms / 数据请求与处理耗费50ms * jdbc连接池连接数)以下,才不会累积,进而对后续的请求处理造成影响。
一般情况下,web 项目都配置有jdbc 连接池,在qps 较低时只会维持一个较低的连接数。qps 猛增时,建立连接也需要 增加时间,会更加恶化上表的时间进度。
自己先前的误区及教训
- 过于迷信请求合并,认为请求合并就可以解决 缓存穿透问题。
- dba 会定期排查慢请求,自己的sql 不是慢请求,觉得不是慢请求就没事。但实际上,高并发的请求 同样会 增大mysql 的负载,进而影响mysql 上的其它业务。单是查询请求还好,若是update/delete(会对行加锁,影响其它对该行数据读写的sql) 突然的高并发 对mysql 的影响会更大。高并发和慢请求一样危险。
- 两个高qps 的业务 不适合部署在一个数据库、redis 甚至服务器中。应能够掌握 所有服务的 并发强度,并有针对的隔离其依赖的资源,两个高并发的服务 尽量不要依赖同一个redis、db、甚至服务器。
- 对于高并发项目,要谨慎控制对外界资源的依赖。比如,我一看报警,第一反应是增加连接池,然后增加了服务器,但在原因未查明之前,这些操作立刻增加了数据库负载,进而影响到了共用mysql的其它服务。
- 高并发的服务,重启不可随意,最好是分批重启,尤其是依赖本地缓存的服务。
系统实现
-
系统层面的三高
- 设计计划 找多个人过一下
-
代码层面上
- 对while循环、递归等保持警惕
- 感觉这块儿会出问题,就通常会出问题
- 测试类、代码review 等层面上 提高代码的正确性
tips
- 如果普遍报问题,那可能是代码逻辑问题。如果偶尔error log,那么基本是跟输入参数有关系,此时排查错误需要 打印更多的输入信息。