1 分钟阅读

简介(未完成)

源代码级的debug

系统级的debug

基本现象及通常原因

从用户角度看

  1. 数据错误
  2. 页面刷不出来

    • 请求超时

从报警角度看

  1. tomcat 线程池打满
  2. 大量异常

    • 本身异常
    • 依赖服务异常
  3. 大量gc
  4. mysql 大量超时

    通常原因是 耗时查询的sql 过多

  5. redis 大量超时

    通常原因是网卡被打满,包括入宽打满和出宽打满。前者因为大量写入(通常是代码逻辑错误),后者通常因为服务负载较大,需要扩容。

排查故障的基本知识

jvm 基本组成,gc的原因分析 等

曾经作为一个coder,笔者虽然很热心的学习了这些知识,但实际用的少,

完备的系统感知能力

感知什么?

一个示例。

完备的监控,笔者曾碰到一次事故,tomcat 线程池满了,可以判断出来某个请求的处理很慢。报警系统同时反馈redis 连接池连接获取超时,进而可以定位是redis 的原因。然后运维说,redis 网卡打满了,但尚未定位是入宽打满还是出宽打满,我当时觉得自己的系统缓存用的少,应该是别人的原因,因此回去睡觉了。但后来 确实我这边 大量的写入导致的。

我自己的代码肯定是有问题,锅甩不掉。换个较角度看,如果 监控系统 监控redis 的带宽,并设定阈值报警,同时能够分析入宽来源方的数据量,则可以极大的减少定位问题的时间,并找到相关的开发解决问题。

此外,从中我们还可以看到:

  1. 从运维的角度看,redis 资源的分配 应设定几条基本原则,其中重要的原则之一就是方便定位问题。
  2. 大部分时候 我们还是在受限的资源下做事情,监控/报警系统不完备或者没有,这都不是出现事故的理由。还以上述问题 为例,仅以完成代码功能来说,业务 ==> db 即可。为了挺高性能:业务 ==> 缓存 ==> db。 缓存是共用的,那么为了减少依赖提高可靠性。可以: 业务 ==> local cache ==> 缓存 ==> db。 同时根据业务做好降级熔断措施。
  3. 所以,为了提高一段代码,功能实现、高性能、可靠性、可扩展性,你的脑子里一共有几个维度? 根据项目的发展阶段,各个维度的重要性如何取舍?

案例分析

缓存穿透的例子

现象

  1. 报警层面,大量的请求 timeout
  2. 日志层面,大量的Broken pipe
  3. 监控层面,数据库连接池耗尽
  4. 运维反馈,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 猛增时,建立连接也需要 增加时间,会更加恶化上表的时间进度。

自己先前的误区及教训

  1. 过于迷信请求合并,认为请求合并就可以解决 缓存穿透问题。
  2. dba 会定期排查慢请求,自己的sql 不是慢请求,觉得不是慢请求就没事。但实际上,高并发的请求 同样会 增大mysql 的负载,进而影响mysql 上的其它业务。单是查询请求还好,若是update/delete(会对行加锁,影响其它对该行数据读写的sql) 突然的高并发 对mysql 的影响会更大。高并发和慢请求一样危险。
  3. 两个高qps 的业务 不适合部署在一个数据库、redis 甚至服务器中。应能够掌握 所有服务的 并发强度,并有针对的隔离其依赖的资源,两个高并发的服务 尽量不要依赖同一个redis、db、甚至服务器。
  4. 对于高并发项目,要谨慎控制对外界资源的依赖。比如,我一看报警,第一反应是增加连接池,然后增加了服务器,但在原因未查明之前,这些操作立刻增加了数据库负载,进而影响到了共用mysql的其它服务。
  5. 高并发的服务,重启不可随意,最好是分批重启,尤其是依赖本地缓存的服务。

系统实现

  1. 系统层面的三高

    • 设计计划 找多个人过一下
  2. 代码层面上

    1. 对while循环、递归等保持警惕
    2. 感觉这块儿会出问题,就通常会出问题
    3. 测试类、代码review 等层面上 提高代码的正确性

tips

  1. 如果普遍报问题,那可能是代码逻辑问题。如果偶尔error log,那么基本是跟输入参数有关系,此时排查错误需要 打印更多的输入信息。

留下评论