增强系统稳定性的基本方法

保证系统稳定性,是系统研发的底板思维。

系统部署到客户那里,最基本最重要的要求就是系统稳定性。系统稳定性差,那么系统功能再丰富界面再美观,也无法补偿稳定性差给客户带来的负面影响和损失。

保证系统稳定性,是系统研发的底板思维。

稳定性问题

  • 系统频繁重启
  • 系统直接崩溃
  • 系统严重阻塞,无法响应和处理请求,基本不可用

根因分析及思路

导致系统出现稳定性问题的两大原因:

  • 流量过大,远超过系统能够承载负荷(反过来说是系统性能不足);
  • 系统内部存在死循环或死锁,锁住了线程,使得无法向前推进。

大数据量的源头:

  • 大量瞬时请求:比如大量 agent 同时发送规则拉取请求。
  • 大量瞬时消息:比如配置了宽泛规则导致瞬时大量告警上报。
  • 批量级联变更:比如批量变更主机 IP、业务组导致的级联更新风暴。
  • 异常时的全表更新:比如根据 MD5 更新文件上传任务表,如果发生异常时, MD5 值为空,就可能导致全表更新。
  • 弱条件查询或更新。

思路:

  • 避免或减少大流量的产生(尽量避免大量级联变更,比如快照信息没必要更新);
  • 控制大流量的流入(限流);
  • 弱化不稳定依赖的影响(降级);
  • 降低高负荷运行的持续时间(动态控制速率);
  • 检查并消除死循环或死锁(复杂业务逻辑、加锁逻辑检查)。

稳定性风险分析与检查

稳定性风险分析包括四要素:

  • 风险类别: 存在哪些导致服务不稳定的风险情形 ?
  • 风险表征: 通过什么特征来识别这些风险 ?
  • 风险根因: 引起这些风险的根本原因是什么 ?
  • 风险对策: 若风险发生,如何及时提前处理,将问题掐灭在摇篮期间 ?

稳定性风险检查需要找出影响系统稳定性的因素:

  • 接口是否会面对大流量?是否有限流措施?
  • 消息处理是否有削峰措施?
  • 是否有潜在的大数据量的表更新?
  • 是否有大对象的非受控的持续创建?
  • 对于系统的各种非强依赖项,是否有降级措施,保证基本功能始终可用?
  • 是否存在弱查询条件,导致一次加载大量数据到内存里?
  • 发生异常时是否可能有全表更新的隐患?
  • 是否存在死循环的可能?
  • 是否存在死锁的可能?

入侵APP 的稳定性检查

  1. 大量 agent 拉取规则的接口稳定性问题:采用异步任务方案。
  2. 瞬时告警流量问题: 直接对接告警流量的接口已加限流措施,kafka 可以进行削峰。如果 kafka 消息堆积,可以通过告警过滤表达式快速丢弃指定的告警。
  3. 批量级联变更: 目前主要是主机 IP/主机名批量变更 导致告警表中的主机 IP/ 主机名变更 ;业务组批量变更导致告警表等的业务组ID变更。已经做了分批处理,限制只处理一个月的数据。
  4. 异常时全表更新: 主要是文件上传失败时。已确认上传失败的时候 sha256 都有值,更新是按照这两个值进行的,应该没问题。
  5. 死循环: 目前只有构建有序进程树时存在较复杂的 while 循环,之前已经遇到过 pid = ppid ,已经做了处理,遇到这种情况打日志,并 break。
  6. 死锁: 大多数分布式锁都是针对定时任务,不存在两个锁申请。 病毒检测需要多个子任务锁,子任务锁有任务创建锁和检测锁。任务创建锁是 type + md5, 检测说是 taskType + sha256。由于子任务创建顺序是不固定的,而进程检测有很多重复情形,可能存在 某个检测 A 先申请了 avira 任务锁,再申请 tav 任务锁, 某个检测 B 先申请了 tav 任务锁,再申请 avira 任务锁。这两个检测的数据是完全一样的。不过病毒检测不是在同一个流程使用多把锁。A 在申请 tav 锁的时候,已经释放了 avira 锁,同理 B 在申请 tav 任务锁时,已经释放了 avira 锁。因此不会有死锁。
  7. 更新加防御式检查: 如果用于更新的条件来自外部系统,那么更新时必须加防御式检查(不要信任外部的数据)。登录事件/检测消息消费更新加防御式判空检查,因为更新条件是来自大数据或引擎检测的结果数据,不能完全信任。如果有问题,可能会导致大量错误数据。
  8. 弱条件查询检查。
  9. 不必要的强依赖检查(比如定时任务依赖 redis)。
  10. 内存问题。

具体方法与措施

  1. 代码健壮性。做好数据兼容工作。比如,不能因为某个字段问题导致整个告警详情展示不出来,或者因为某个告警有脏数据导致整个告警列表都展示不出来。
  2. 削峰。对于大流量消息进行削峰,保持在系统能够处理的范围内;
  3. 限流。对于瞬时大流量接口请求,控制系统流入量。
  4. 降级。弱化依赖的影响,避免依赖服务跪了的级联影响。
  5. 异步。如果接口比较耗时,可以将同步改成异步任务,异步处理,快速响应。
  6. 减少依赖。尽可能减少不必要的外部依赖。
  7. 分批,分页,流处理。使用游标或 stream 查询数据库,分批处理数据。
  8. 避免在方法里动态创建线程或线程池。
  9. 异常更新避免全表更新。异常情形进行查询或更新时,尤其要注意条件是否可能为空,避免全表扫或全表更新。
  10. 弱条件分页查询。有弱条件时,要使用分页来查询大量数据。
  11. 重试补偿机制:基础服务的某次处理失败不可避免,因此要有自动的重试补偿。比如大流量写入 ES 导致 ES 失败,则要有重试补偿。对于 HBase 和 API 访问相同。
  12. 避免消息堆积和阻塞: 消息消费型应用要避免消息堆积和阻塞,尽可能自动过滤掉脏数据。
  13. 避免死循环。检查是否有复杂的 while 循环,for 循环,提交 AI review 下。
  14. 避免死锁。检查是否有复杂的加锁逻辑。
  15. 非核心应用的访问限制。如果两个应用(一个核心,一个非核心)同时依赖某个中间件的服务,那么非核心应用的访问应当加以限制。比如订单详情和订单导出都访问 HBase 主表服务,由于订单详情的影响范围很大,因此要对订单导出访问 HBase 服务加以限制(从访问频率和访问数据量)。
  16. 监控:密切关注业务监控(峰值 QPS,RT 突涨、同比环比)、异常监控、服务器监控、Java 监控、消息堆积报警等。

参考文章


posted @ 2023-07-01 22:57  琴水玉  阅读(292)  评论(0编辑  收藏  举报