稳定性之于系统,就像健康之于人类,看起来重要不紧急,然而一旦失去,就追悔莫及。
稳定性是一切 0 前面的 1。
为什么要做这个专栏?
- 让无法解决的问题少一点点,让世界的确定性多一点点。
- 打造国内稳定性领域知识库,降低知识获取门槛。
加入我们
- GitHub 地址
- 在线文档站(阅读体验更佳)
- 钉钉群号
- 30000312(2群,推荐)
- 23179349(1群,已满)
- 如果你在本专栏有所收获,欢迎分享给身边的朋友,期待更多同学的加入!
框架目录
0. 故障案例
【必读】故障案例征集 & Demo 模板.md
【案例】Dubbo 稳定性:Nacos 注册中心可用性问题复盘
【案例】记一次线上内存报警排查过程
【案例】发现CMS_GC有点傻--应用A_FullGC问题排查
1. 事前防范
1.1 代码规约
1.2 变更管控
1.3 性能压测
1.4 混沌工程
混沌工程介绍与实践
1.5 风险预案
1.6 限流降级
流控降级最佳实践
1.7 业务隔离
2. 事中“止血”
2.1 监控告警
虾米SRE实践:监控体系升级之路
阿里云 ARMS 小程序监控进阶之路
饿了么监控系统 EMonitor 与 CAT 的对比
如何专业化监控一个Kubernetes集群
2021 Gartner APM 魔力象限解读
OPLG:新一代云原生可观测最佳实践
2.2 异常巡检
2.3 流量调度
2.4 资损防控
数据一致性检测应用场景与最佳实践
3. 事后诊断
3.1 系统诊断
So Hot?快给 CPU 降降温
3.2 JVM 诊断
3.2.1 异常诊断
OutOfMemoryError 常见原因及解决方法
StackOverFlowError 常见原因及解决方法
NoSuchMethodError 常见原因及解决方法
3.2.2 性能优化
Java应用CPU&JVM内存性能调优
3.2.3 线程诊断
线程池满
死锁
3.2.4 GC 诊断
咱们从头到尾说一次Java垃圾回收
3.3 组件诊断
Dubbo 常见错误及解决方法
Nacos 常见问题及解决方法
Spring Boot 常见错误及解决方法
SchedulerX 常见问题及解决方法
3.4 在线诊断
Arthas
3.5 链路追踪
【剖析 SOFARPC 框架】之 SOFARPC 链路追踪剖析
如何检测Web服务请求丢失问题
让可观察性带上导航,快速发现和定位业务问题:OpenTracing上写入业务信息
开源自建/托管与商业化自研Trace,如何选择?
前后端、多语言、跨云部署,全链路追踪到底有多难?
链路分析 K.O “五大经典问题”
链路追踪(Tracing)其实很简单——分布式链路追踪的起源
链路追踪(Tracing)其实很简单——分布式链路追踪的诞生
链路追踪(Tracing)其实很简单——分布式链路追踪的应用与兴起
链路追踪(Tracing)其实很简单——分布式链路追踪的挑战与限制
链路追踪(Tracing)其实很简单——请求轨迹回溯
链路追踪(Tracing)其实很简单——多维链路筛选
链路追踪(Tracing)其实很简单——链路实时分析、监控与告警
链路追踪(Tracing)其实很简单——链路拓扑
链路追踪(Tracing)其实很简单——链路功能进阶指南
链路追踪(Tracing)其实很简单——链路成本进阶指南
链路追踪(Tracing)其实很简单——链路诊断1分钟定位错慢根因
链路追踪(Tracing)其实很简单——全量存储? No! 按需存储? YES!.md
3.6 RootCause
系统黄金指标之延迟(Latency)指标的故障诊断
版本迭代
- 2024-12-20
* [Java应用CPU&JVM内存性能调优](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/jvm/tuning/Java应用CPU&JVM内存性能调优.md)@涯海
- 2024-12-16
* [链路追踪(Tracing)其实很简单——链路诊断1分钟定位错慢根因](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——链路诊断1分钟定位错慢根因.md)@涯海
- 2022-11-03
* [【案例】发现CMS_GC有点傻--应用A_FullGC问题排查](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/case/【案例】发现CMS_GC有点傻--应用A_FullGC问题排查.md)@佐井
- 2022-10-26
* [链路追踪(Tracing)其实很简单——链路成本进阶指南](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——链路成本进阶指南.md)@涯海
* [链路追踪(Tracing)其实很简单——链路功能进阶指南](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——链路功能进阶指南.md)@涯海
* [链路追踪(Tracing)其实很简单——链路拓扑](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——链路拓扑.md)@涯海
* [链路追踪(Tracing)其实很简单——链路实时分析、监控与告警](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——链路实时分析_监控与告警.md)@涯海
- 2022-07-14
* [链路追踪(Tracing)其实很简单——多维链路筛选](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——多维链路筛选.md)@涯海
* [链路追踪(Tracing)其实很简单——请求轨迹回溯](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——请求轨迹回溯.md)@涯海
* [链路追踪(Tracing)其实很简单——分布式链路追踪的挑战与限制](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——分布式链路追踪的挑战与限制.md)@涯海
* [链路追踪(Tracing)其实很简单——分布式链路追踪的应用与兴起](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——分布式链路追踪的应用与兴起.md)@涯海
* [链路追踪(Tracing)其实很简单——分布式链路追踪的诞生](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——分布式链路追踪的诞生.md)@涯海
* [链路追踪(Tracing)其实很简单——分布式链路追踪的起源](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——分布式链路追踪的起源.md)@涯海
- 2022-04-15
* [OPLG:新一代云原生可观测最佳实践](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/processing/monitor/OPLG_新一代云原生可观测最佳实践.md)@涯海
- 2021-11-17
* [【必读】故障案例征集 & Demo 模板](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/case/【必读】故障案例征集&Demo模板.md)@涯海
- 2021-11-08
* [链路分析 K.O “五大经典问题”](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路分析K.O“五大经典问题”.md)@涯海
- 2021-09-23
* [前后端、多语言、跨云部署,全链路追踪到底有多难?](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/前后端_多语言_跨云部署_全链路追踪到底有多难.md)@涯海
- 2021-08-27
* [开源自建/托管与商业化自研Trace,如何选择?](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/开源自建_托管与商业化自研Trace_如何选择.md)@涯海
- 2021-05-27
* [链路追踪(Tracing)其实很简单——全量存储? No! 按需存储? YES!.md](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——全量存储No按需存储YES.md)@涯海
* [如何专业化监控一个Kubernetes集群](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/processing/monitor/如何专业化监控一个Kubernetes集群.md)@佳旭
* [2021 Gartner APM 魔力象限解读](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/processing/monitor/2021_Gartner_APM魔力象限解读.md)@西杰
- 2019-12-26
* [SchedulerX 常见问题及解决方法](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/plugin/scheduling/SchedulerX常见问题及解决方法.md)@学仁
* [【案例】Dubbo 稳定性:Nacos 注册中心可用性问题复盘](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/case/【案例】Dubbo稳定性_Nacos注册中心可用性问题复盘.md)@岛风
* [让可观察性带上导航,快速发现和定位业务问题:OpenTracing上写入业务信息](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/让可观察性带上导航_快速发现和定位业务问题_OpenTracing上写入业务信息.md)@竹影
- 2019-11-07
* [链路追踪(Tracing)其实很简单——单链路诊断](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——单链路诊断.md)@涯海
* [Spring Boot 常见错误及解决方法](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/plugin/microservice/SpringBoot常见错误及解决方法.md)@洛夜
* [【案例】记一次线上内存报警排查过程](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/case/【案例】记一次线上内存报警排查过程.md)@神帅
* [饿了么监控系统 EMonitor 与 CAT 的对比](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/processing/monitor/饿了么监控系统EMonitor与CAT的对比.md)@李刚
- 2019-09-19
* [Nacos常见问题及解决方法](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/plugin/slb/Nacos常见问题及解决方法.md)@敦谷
* [数据一致性检测应用场景与最佳实践](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/processing/lostprevention/数据一致性检测应用场景与最佳实践.md)@龙多
* [链路追踪(Tracing)其实很简单——初识](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/链路追踪其实很简单——初识.md)@涯海
- 2019-09-05
* [系统黄金指标之延迟(Latency)指标的故障诊断](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/rootcause/系统黄金指标之延迟指标的故障诊断.md)@绍宽
* [【剖析 SOFARPC 框架】之 SOFARPC 链路追踪剖析](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/剖析SOFARPC框架之SOFARPC链路追踪剖析.md)@畅为/碧远/卓与
* [阿里云ARMS小程序监控进阶之路](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/processing/monitor/阿里云ARMS小程序监控进阶之路.md)@慕扉
- 2019-08-22
* [So Hot?快给 CPU 降降温](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/system/cpu/SoHot_快给CPU降降温.md)@涯海
* [虾米SRE实践:监控体系升级之路](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/processing/monitor/虾米SRE实践_监控体系升级之路.md)@全琮
* [混沌工程介绍与实践](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/prevention/resilience/混沌工程介绍与实践.md)@穹谷
* [如何检测Web服务请求丢失问题](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/tracing/如何检测Web服务请求丢失问题.md)@竹影
- 2019-08-08
* [NoSuchMethodError 常见原因及解决方法](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/jvm/exception/系统稳定性——NoSuchMethodError常见原因及解决方法.md)@涯海
* [咱们从头到尾说一次Java垃圾回收](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/jvm/gc/咱们从头到尾说一次垃圾回收.md)@率鸽
* [流控降级最佳实践](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/prevention/resilience/流控降级最佳实践.md) @宿何
- 2019-07-26
* [OutOfMemoryError 常见原因及解决方法](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/jvm/exception/系统稳定性——OutOfMemoryError常见原因及解决方法.md)@涯海
* [StackOverFlowError 常见原因及解决方法](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/jvm/exception/系统稳定性——StackOverFlowError常见原因及解决方法.md)@涯海
* [Dubbo 常见错误及解决方法](https://raw.githubusercontent.com/StabilityMan/StabilityGuide/HEAD/docs/diagnosis/plugin/rpc/系统稳定性——Dubbo常见错误及解决方法.md)@空冥
专栏建设
- 目标用户:稳定性相关的从业人员、技术决策者、爱好者等。
- 参与形式:欢迎一切有想法或兴趣的同学一起共建,可以自由选择参与内容编写、渠道宣传、社区维护等活动。
- 写作原则:通俗易懂,让读者有所收获,尽量避免介绍公网无法获取的内部产品或工具。
- 内容格式:Markdown & PDF 格式,易于传播、分享与共建。
- 内容管理:文档即代码,通过 Git 管理;代码目录与内容框架目录保持一致。会定期 Review 代码/目录结构。
- 内容编写:建议内容原创或再创作。为了保障文章质量,不建议直接转载。
- 问题列表:所有人有任何问题和建议都可以通过 Issue 的形式提交。
目录提纲
- 目录拆分尽量遵循 MCME 原则(互相独立,完全穷尽)。
- 每个专题尽量保证 3 篇及以上的优质文章。
- 内容目录一定要与代码目录保持结构一致。
文档提纲(仅供参考)
- 文档类型
* 原创,注明作者与创作时间。
* 转载,取得原作者许可,注明出处,注意许可协议风险。
- 一句话标题,提纲挈领地表达要解决的问题。
* 标题应该表达的是问题的核心线索,而不是深层次原因。因为用户排查问题时只能看到表象。比如 SQL 慢是表象,没有创建索引是原因。
* 可以在标题后的第一段做一些补充,对该问题做一个概括性描述,整体结构按照总-分-总模式。
- 问题的现象是什么?
- 问题产生的原因是什么?
* 如果有前置的背景知识,可以科普一下,让读者更容易理解。
- 怎么去排查,会用到哪些工具?
* 详细介绍排查思路与路径,最好是小白式操作,事无巨细,能够完美复现。
* 在本模块可以介绍一些开源或云产品的使用,不要涉及内部产品,另外产品介绍不要带有太明显的主观偏向性。
- 解决后的效果如何?
* 前后效果对比。
* 简要的问题复盘总结,文档收尾。
- 推荐阅读/产品链接/公众号/交流群等。
友情链接
【必读】故障案例征集&Demo模板
【必读】故障案例征集 & Demo 模板
案例是最生动的理论,为了更好的指导生产环境实践,诚邀大家共建 【StabilityGuide 故障案例库】,你的分享能够帮助到他人,而他们的分享也能在最危急的时候给你指导。我为人人,人人为我,让我们一起加入分享!
故障案例写作模板(推荐)
- 标题:统一以【案例】开头,直观体现出问题的现象或结论,比如【案例】记一次线上内存不足告警排查过程
- 内容:
* 问题现象描述:客观描述系统问题表象,不加主观判断和分析性结论。
* 问题排查过程:按时间线逐步讲解,图文结合效果更好。
* 问题根因定位:归纳问题产生的根本原因,如果没有定位到根因可以明确说明。
* 解决方案:通常分为短期解决方案和长期解决方案。
* 最终效果:与解决方案前后呼应。
- 扩展
* 个人思考:比如对问题的延伸思考,对相关技术的深度探索。
* 推荐工具/阅读:对相关的技术、工具和其他文章进行分享。
故障案例 Demo
【案例】Dubbo稳定性 Nacos注册中心可用性问题复盘
【案例】Dubbo 稳定性:Nacos 注册中心可用性问题复盘
作者:徐靖峰(岛风) 创作日期:2019-12-02 专栏地址:【稳定大于一切】 PDF 格式:【案例】Dubbo 稳定性:Nacos 注册中心可用性问题复盘
问题描述
上周四晚刚回到家,就接到了软负载同学的电话,说是客户线上出了故障,我一听”故障“两个字,立马追问是什么情况,经过整理,还原出线上问题的原貌:
客户使用了 Dubbo,注册中心使用的是 Nacos,在下午开始不断有调用报错,查看日志,发现了 Nacos 心跳请求返回 502
2019-11-15 03:02:41.973 [com.alibaba.nacos.client.naming454] -ERROR [com.alibaba.nacos.naming.beat.sender] request xx.xx.xx.xx failed.
com.alibaba.nacos.api.exception.NacosException: failed to req API: xx.xx.xx.xx:8848/nacos/v1/ns/instance/beat. code:502 msg:
此时还没有大范围的报错。随后,用户对部分机器进行了重启,开始出现大规模的 Nacos 连接不上的报错,并且调用开始出现大量 no provider 的报错。
问题分析
Nacos 出现心跳报错,一般会有两种可能:
- 用户机器出现问题,如网络不通
- Nacos Server 宕机
但由于是大面积报错,所以很快定位到是 Nacos Server 本身出了问题:由于磁盘老旧导致 IO 效率急剧下降,Nacos Server 无法响应客户端的请求,客户端直接接收到 502 错误响应。这个事件本身并不复杂,是一起注册中心磁盘故障引发的血案,但从这起事件,却可以窥探到很多高可用的问题,下面来跟大家一起聊聊这当中的细节。
问题复现
Dubbo 版本:2.7.4
Nacos 版本:1.1.4
复现目标:在本地模拟 Nacos Server 宕机,检查 Dubbo 的调用是否会受到影响。
复现步骤:
- 本地启动 Nacos Server、Provider、Consumer,触发 Consumer 调用 Provider
- kill -9 Nacos Server,模拟 Nacos Server 宕机,触发 Consumer 调用 Provider
- 重启 Consumer,触发 Consumer 调用 Provider
期望:
3 个步骤均可以调用成功
实际结果:
1、2 调用成功,3 调用失败
问题成功复现,重启 Consumer 之后,没有调用成功,客户恰好遇到了这个问题。大家可能对这其中的细节还是有一些疑问,我设想了一些疑惑点,来和大家一起进行探讨。
为什么 Nacos 宕机后,仍然可以调用成功
我们都知道,一般聊到 Dubbo,有三个角色是必须要聊到的:服务提供者、服务消费者、注册中心。他们的关系不用我赘述,可以从下面的连通性列表得到一个比较全面的认识:
- 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
- 服务提供者向注册中心注册其提供的服务,此时间不包含网络开销
- 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,此时间包含网络开销
- 注册中心,服务提供者,服务消费者三者之间均为长连接
- 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
- 注册中心宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
- 注册中心可选的,服务消费者可以直连服务提供者
重点关注倒数第二条,Dubbo 其实在内存中缓存了一份提供者列表,这样可以方便地在每次调用时,直接从本地内存拿地址做负载均衡,而不避免每次调用都访问注册中心。只有当服务提供者节点发生上下线时,才会推送到本地,进行更新。所以,Nacos 宕机后,Dubbo 仍然可以调用成功。
Nacos 宕机不影响服务调用,为什么日志中仍然有调用报错
宕机期间,已有的服务提供者节点可能突然下线,但由于注册中心无法通知给消费者,所以客户端调用到下线的 IP 就会出现报错。
对于此类问题,Dubbo 也可以进行兜底
- Dubbo 会在连接级别进行心跳检测,当 channel 本身不可用时,即使没有注册中心通知,也会对其进行断连,并设置定时器,当该连接恢复后,再恢复其可用性
- 在阿里云商业版的 Dubbo -- EDAS 中,提供了「离群摘除」功能,可以在调用层面即时摘除部分有问题的节点,保证服务的可用性。
为什么期望 Consumer 重启之后,调用成功
Nacos Server 宕机后,Consumer 依旧可以调用成功,这个大家应该都比较清楚。但是为什么期望 Consumer 重启之后,依旧调用成功,有些人可能就会有疑问了,注册中心都宕机了,重启之后一定连不上,理应调用失败,怎么会期望成功呢?这就要涉及到 Nacos 的本地缓存了。
Nacos 本地缓存的作用:当应用与服务注册中心发生网络分区或服务注册中心完全宕机后,应用进行了重启操作,内存里没有数据,此时应用可以通过读取本地缓存文件的数据来获取到最后一次订阅到的内容。
例如在 Dubbo 应用中定义了如下服务:
<dubbo:service interface="com.alibaba.edas.xml.DemoService" group="DUBBO" version="1.0.0" ref="demoService" />
可以在本机的 /home/${user}/nacos/naming/ 下看到各个命名空间发布的所有服务的信息,其内容格式如下:
{"metadata":{},"dom":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","cacheMillis":10000,"useSpecifiedURL":false,"hosts":[{"valid":true,"marked":false,"metadata":{"side":"provider","methods":"sayHello","release":"2.7.4","deprecated":"false","dubbo":"2.0.2","pid":"5275","interface":"com.alibaba.edas.xml.DemoService","version":"1.0.0","generic":"false","revision":"1.0.0","path":"com.alibaba.edas.xml.DemoService","protocol":"dubbo","dynamic":"true","category":"providers","anyhost":"true","bean.name":"com.alibaba.edas.xml.DemoService","group":"DUBBO","timestamp":"1575355563302"},"instanceId":"30.5.122.3#20880#DEFAULT#DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","port":20880,"healthy":true,"ip":"30.5.122.3","clusterName":"DEFAULT","weight":1.0,"ephemeral":true,"serviceName":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","enabled":true}],"name":"DEFAULT_GROUP@@providers:com.alibaba.edas.xml.DemoService:1.0.0:DUBBO","checksum":"69c4eb7e03c03d4b18df129829a486a","lastRefTime":1575355563862,"env":"","clusters":""}
为什么期望重启后调用成功?因为经过检查,发现线上出现问题的机器上,缓存文件一切正常。虽然 Nacos Server 宕机了,本地的缓存文件依旧可以作为一个兜底,所以期望调用成功。
为什么 Consumer 重启后,没有按照预期加载本地缓存文件
缓存文件正常,问题只有可能出现在读取缓存文件的逻辑上。
- 可能是 nacos-client 出了问题
- 可能是 Dubbo 的 nacos-registry 出了问题
一番排查,在 Nacos 研发的协助下,找到了 naocs-client 的一个参数: namingLoadCacheAtStart ,该配置参数控制启动时是否加载缓存文件,默认值为 false。也就是说,使用 nacos-client,默认是不会加载本地缓存文件的。终于定位到线上问题的原因了:需要手动开启加载本地缓存,才能让 Nacos 加载本地缓存文件。
该参数设置为 true 和 false 的利弊:
- 设置为 true,认为可用性 & 稳定性优先,宁愿接受可能出错的数据,也不能因为没有数据导致调用完全出错
- 设置为 false,则认为 Server 的可用性较高,更能够接受没有数据,也不能接受错误的数据
无论是 true 还是 false,都是对一些极端情况的兜底,而不是常态。对于注册发现场景,设置成 true,可能更合适一点,这样可以利用 Nacos 的本地缓存文件做一个兜底。
Dubbo 传递注册中心参数
Dubbo 中使用统一 URL 模型进行参数的传递,当我们需要在配置文件传递注册中心相关的配置参数时,可以通过键值对的形式进行拼接,当我们想要在 Dubbo 中开启加载注册中心缓存的开关时,可以如下配置:
<dubbo:registry address="nacos://127.0.0.1:8848?namingLoadCacheAtStart=true"/>
遗憾的是,最新版本的 Dubbo 只传递了部分参数给 Nacos Server,即使用户配置了 namingLoadCacheAtStart 也不会被服务端识别,进而无法加载本地缓存。我在本地修改了 Dubbo 2.7.5-SNAPSHOT,传递上述参数后,可以使得 1、2、3 三个阶段都调用成功,证明了 namingLoadCacheAtStart 的确可以使得 Dubbo 加载本地缓存文件。该问题将会在 Dubbo 2.7.5 得到修复,届时 Dubbo 中使用 Nacos 的稳定性将会得到提升。
问题总结
该线上问题反映出了 Nacos 注册中心可用性对 Dubbo 应用的影响,以及系统在某个组件宕机时,整体系统需要进行的一些兜底逻辑,不至于因为某个组件导致整个系统的瘫痪。
总结下现有代码的缺陷以及一些最佳实践:
- Dubbo 传递注册中心参数给 Nacos 时,只能够识别部分参数,这会导致用户的部分配置失效,在接下来的版本会进行修复。
- nacos-client 加载本地缓存文件的开关等影响到系统稳定性的参数最好设计成 -D 启动参数,或者环境变量参数,这样方便发现问题,及时止血。例如此次的事件,有缺陷的 Dubbo 代码仅仅依赖于参数的传递,无法加载本地缓存文件,而如果有 -D 参数,可以强行开始加载缓存,大大降低了问题的影响面。
namingLoadCacheAtStart是否默认开启,还需要根据场景具体确定,但 nacos-server 宕机等极端场景下,开启该参数,可以尽可能地降低问题的影响面。顺带一提,Nacos 本身还提供了一个本地灾备文件,与本地缓存文件有一些差异,有兴趣的朋友也可以去了解一下。
推荐链接
加入我们
【稳定大于一切】打造国内稳定性领域知识库,让无法解决的问题少一点点,让世界的确定性多一点点。
- GitHub 地址
- 钉钉群号:
* 30000312(2群,推荐)
* 23179349(1群,已满)
- 如果阅读本文有所收获,欢迎分享给身边的朋友,期待更多同学的加入!
【案例】记一次线上内存报警排查过程
【案例】记一次线上内存报警排查过程
作者:樊春帅(神帅) 创作日期:2019-08-14 专栏地址:【稳定大于一切】 PDF 格式:【案例】记一次线上内存报警排查过程
今天风和日丽,刚到公司,看看博客,微信&钉钉消息。突然发现报警群里有很多报警说 xx.xx.16.28 机器的内存不够,报警信息如下:
[故障]: 集团线上-xx中心-xx部-研发部-HR和工作台
告警地址: x.x.16.28 监控取值: 869.46 MB 告警等级: Warning 告警信息: x.x.16.28 内存剩余小于 900M 告警时间: 2019-10-31 09:50:23 持续时间:1h 0m
开始时间大概是从昨天晚上11点多开始的,而且持续到今天上午10点多,事出有因必有妖,下面看一下排查思路和排查过程。
1. 查一下 xx.xx.16.28 的内存使用情况
2. 排查最近是否有新上线服务,导致内存紧张
通过 rpcservice list 与 ps -ef | tomcat 两个命令发现业务服务有 7 个,进程存活时间较长,不太可能有新服务上线,同时根据另一台 xx.xx.16.29 机器的服务部署情况也验证了没有新上线服务。
3. 排查是否有 Java 服务在持续 FGC
使用 top 命令查一下,发现 9 个 java 服务,7 个业务服务,2 个日志进程服务。使用 jstat -gcutil pid 2000 命令一一排查,发现 GC 情况正常,没有服务有持续的 YGC 或 FGC 情况存在。
4. 排查异常占用内存的 Java 服务
由于有 7 个业务服务,直觉告诉我 dwf 服务应该比 RPC 服务占用的内存少,这一步走错了两个方向,浪费了一些时间。
- 以为 Web 服务占用内存较大,比 RPC 服务还高,但是发现不是
- 以为其中一个日志进程服务(flume)占用内存较大,发现另一台 xx.xx.16.29 的日志进程服务占用的内存跟出问题的这一台机器是一样的
5. top 命令对比 xx.xx.16.28/xx.xx.16.29 两台服务器
发现其中肯定有同一个 Java 进程占用的内存比另一个 Java 进程占用的内存高。如下图所示:


6. 排查内存占用
由于之前排查过程中跟踪过出问题的这一台的服务情况,但是肉眼没有看出来,通过内存占用对比(top命令 ,然后 shift + M)对比占用内存最高的几个进程,现在很明显两台机器中有一个服务肯定有问题。
7. 通过对比可以发现有个服务是有问题的


8. 结合之前已经截图的现场可以发现
xx.xx.16.28 的 corehr_job 服务占用内存是 12.3%,xx.xx.16.29 的 corehr_job 服务占用内存是 6.3%,很明显的,到这里我们已经揪出有问题的服务了。下面继续追查为啥不一样,先透个底,有预感觉得是由于 corehr_job 中的一些定时任务执行之后没有释放内存导致的。看一下这个服务的堆内存占用内存比例大小,如下图:


9. 现在要看看这两个机器的同一个服务堆内存到底有什么对象


很明显我们可以看到 xx.xx.16.28 中的这个有问题的服务堆内存占用的对象比另一个正常的多,由于很小心的保留了现场我们可以分析一下,为啥有占用呢? 由于老年代占用 76%,没有达到FGC的阈值,导致大量对象在年轻代,老年代驻留,下面尝试一下触发 FGC。
10. 使用命令触发FGC
sudo djava jmap -histo:live 28895 执行这个命令可能引发一次 FGC,然后释放内存,执行完之后确实触发了一次FGC。

11. 再次进行 top (shift+m)
发现内存占用依然没有解决,也就是说虽然触发了FGC,但是应用程序已经申请的内存是不会释放的,笑哭~

分析到此结束,根据现场保留,排查数据和线索可以有以下应对方案和措施:
- 已知引起原因,目前已重启该问题服务,内存紧张报警解除。
- 提工单进行服务器升配(不止升级有问题的这一台,还有另一台),机智~
- 排查获取大数据量的 job,增加对象回收的逻辑比如用完之后 clear(),设置为 null 之类的。
这里引申出几个问题:
- java 应用程序申请的内存触发FGC之后会返回给操作系统吗?
- 使用CMS垃圾回收算法的情况下触发FGC的条件是什么?
- 有什么方法可以让应用触发FGC之后将内存归还给操作系统?
此外,根据涯海的总结我们可以得出出现此类问题的一些原因。针对这个案例,一个长时间运行的 Java 程序,如果在没有变更的情况下出现系统内存不足。通常可以分为以下几种情况:
- 如果是突然不足,一般是请求了一个超大的对象(数组)。
- 预期外的持续流量脉冲。
- 如果是内存余量缓慢减少,通过是内存泄漏(大量引用对象未释放),可以重点检查下数据库连接/文件资源/本地缓存等资源的释放情况。
参考:https://www.cnblogs.com/seifon/p/11228224.html
加入我们
【稳定大于一切】打造国内稳定性领域知识库,让无法解决的问题少一点点,让世界的确定性多一点点。
- GitHub 地址
- 钉钉群号:
* 30000312(2群,推荐)
* 23179349(1群,已满)
- 如果阅读本文有所收获,欢迎分享给身边的朋友,期待更多同学的加入!
【案例】发现CMS GC有点傻 应用A FullGC问题排查
【案例】发现 CMS GC 有点傻 —— 应用A Full GC 问题排查
作者:李成武(佐井) 创作日期:2022-11-03 专栏地址:【稳定大于一切】
背景
今年5月底开始,应用A机器陆续开始出现单机 Full GC,经过连续几天的排查也没有找到根因,临时解决办法是把 4c8g 的机器升配到 4c16g,缓解内存压力。
9月份开始,16g 的机器偶尔还是出现单机 Full GC,由于 16g 内存的机器整体性能还可以,一般单机 Full GC 一次后机器就恢复正常,很难再抓到现场。但是单机抖动对业务的影响还是非常大,需要彻底解决。
问题排查思路
在5月份的时候,我们那个 4c8g 的机器挺不过 Full GC 压力直接被打死了,我们拿到了不少当时的 dump,但是通过分析也没有找到内存泄漏的点。而且9月份开始的 Full GC 几秒钟后就恢复了,很难抓取现场。所以换个思路:
- 在 Full GC 发生的前后分析下堆的使用情况,看看内存到底是怎么被填满的,是逐渐填满的还是突然填满的。
- 怀疑在 Full GC 前有大对象内存分配,在还没来得及执行 CMS GC 的时候把堆撑爆了,触发了 Full GC,要尝试把大对象分配找出来。
先看看GC前后内存统计
背景知识:PrintFLSStatistics
-XX:PrintFLSStatistics=n
PrintFLSStatistics是用来统计内存分配情况和碎片统计的信息的,网上关于 PrintFLSStatistics 介绍比较少,相关细节需要看下源码,我们用的是 JDK8,可以看这个源码: shenandoah-jdk8u/compactibleFreeListSpace.cpp at master · openjdk/shenandoah-jdk8u

源码显示当设置 PrintFLSStatistics > 0 的时候,会在 GC 的开始(prologue)和结束(epilogue)打印 FreeList 统计信息,FreeList 是空闲内存的索引,堆内存分配的时候需要通过 FreeList 查询合适的空间。由于 FreeList 里面查找的空闲块大小很难与申请的大小一致,申请后剩余的内存还会还回到 FreeList,这个过程会导致 FreeList 的空间不断别切割成更片段,造成内存不连续而无法使用,内存碎片就是这么产生的。
- 当 PrintFLSStatistics > 0 时,首先打印的是 BinaryTreeDictionary 信息,这个信息就是可用的连续内存。
- 当 PrintFLSStatistics > 1 时,会打印 IndexedFreeLists 信息,这里面包含FreeList所有的碎片信息。

应用A单机gc日志还原
下面我们看下应用A xx.xx.243.44 这个机器在 Full GC 时候的统计日志。
我们先看 GC 开始前(Before GC):
2022-10-13T10:13:15.350+0800: 1358193.250: [GC (Allocation Failure) Before GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 16005
Max Chunk Size: 16005
Number of Blocks: 1
Av. Block Size: 16005
Tree Height: 1
Statistics for IndexedFreeLists:
--------------------------------
Total Free Space: 165963191
Max Chunk Size: 254
Number of Blocks: 32379033
Av. Block Size: 5
free=165979196 frag=1.0000
第一行 Allocation Failure 表示没有更多的空闲内存分配了(主意这里需要的是连续的空间),我们可以看到在IndexedFreeLists 统计下面的信息,剩余空间 Total Free Space: 165963191 = 165963191 8 / 1024 / 1024 / 1024 = 1.24GB,frag=1.0000 表示这个 1.24GB 空间 100% 的碎片率,最大的连续空间才 Max Chunk Size: 254 = 254 8 = 2k,平均空间大小 Av. Block Size: 5 = 5 * 8 = 40bit,真是碎的一地鸡毛啊。 再看上面日志的 BinaryTreeDictionary,在 Full GC 前 Total Free Space: 16005 = 16005 * 8 / 1024 = 125k,也就说只要分配比125k大,内存就爆了。BinaryTreeDictionary (我们简单理解是多个大块的连续内存)是随着内存的分配不断减少的,Number of Blocks 是连续空间块的个数,也一样是随着内存分配不断减少。下图是在 Full GC 前几次BinaryTreeDictionary 统计信息,总大小和可用块数都在不断减少。

我们知道碎片信息在 CMS GC 是无法避免的,只能通过 Full GC回收。 Monitoring CMS free list BinaryTreeDictionary statistics

我们看下Full GC后(After GC)的内存统计情况:
CMS: Large block 0x00000006bddd5a90
: 4994842K->1529558K(6291456K), 6.2918770 secs] 8195071K->1529558K(9961472K), [Metaspace: 437386K->436770K(1476608K)]After GC:
Statistics for BinaryTreeDictionary:
------------------------------------
Total Free Space: 609506478
Max Chunk Size: 609506478
Number of Blocks: 1
Av. Block Size: 609506478
Tree Height: 1
Statistics for IndexedFreeLists:
--------------------------------
Total Free Space: 0
Max Chunk Size: 0
Number of Blocks: 0
free=609506478 frag=0.0000
, 7.2442236 secs] [Times: user=7.42 sys=0.00, real=7.25 secs]
Full GC 重建了世界的秩序,Total Free Space: 609506478,Number of Blocks: 1 表示整个空闲堆被整理成了1块连续的空间,大小=609506478 * 8 / 1024 / 1024 / 1024 = 4.5GB
IndexedFreeLists 显示,碎片全被干掉了,frag=0.0000,世界清静了,同时整个 GC 耗时 7.25 secs。对于 10G 的大堆,7秒的 Full GC 时间也没什么好惊讶的。
应用Afull gc原因
好了,现在我们通过上面日志分析,解释下应用A Full GC 的原因,先贴上应用A的内存和 GC 参数:
-server -Xmx10g -Xms10g -Xmn4g -Xss512k -XX:MetaspaceSize=512M
-XX:CMSInitiatingOccupancyFraction=80
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
整个堆10G,new generation=4g,old generation=6g,CMSInitiatingOccupancyFraction=80 当老年代使用超过80%的时候还是CMS,就是说,剩余空间小于 6g * (1-80%) = 1.2G的时候才会触发 CMS GC,不幸的是通过我们上面的分析: 剩余空间 Total Free Space: 165963191 = 165963191 * 8 / 1024 / 1024 / 1024 = 1.24GB 明明1.24GB都是碎片,还认为有剩余的空间不进行 CMS GC,由于最后一次内存申请超过125k,雪崩了触发了STW的 Full GC(Allocation Failure),你说这算法是不是很傻?
总结一下:
应用A Full GC 的原因是碎片太大,超过了 OldGen * (1-CMSInitiatingOccupancyFraction),这个算法傻的地方是没有看内存的碎片率
所以这里两个Action:
- CMSInitiatingOccupancyFraction=70,可以缓解。
- 另外,在 G1 中是可以通过 Minor GC 消除碎片的,JVM 团队同学推荐用 JDK11 的 G1,优化的比较成熟。
看看大对象分配
虽然通过上面的分析,即使没有大对象分配,也证明了会导致 Full GC。不过为了放心,索性把大对象分配的猜想也验证一下。
背景知识:打印大对象内存分配堆栈
-XX:ArrayAllocationWarningSize=20480k
ArrayAllocationWarningSize参数是阿里ajdk特有的参数,在 Dragonwell 8.1.1 后的版本支持。
增加了参数ArrayAllocationWarningSize,默认值为512M。当分配的对象大小超过该值的时候,标准输出里会显示分配的堆栈。该参数可以通过jinfo动态修改
这里需要注意一下,ArrayAllocationWarningSize 的信息只会打印到标准输出里,所以在我们线上应用需要修改下启动脚本,把 Jetty 或 Tomcat 的标准输出重定向到一个文件,不然数据就丢失了,比如以 Jetty 为例:

应用A 实验数据
实验机器:应用A xx.xx.243.44
最近正好封网没有发布,经过5天的测试,在机器上发现的唯一大对象来自com.aliyun.rc.service.cache.ProductLocationWatcher.queryFullLocationTree(ProductLocationWatcher.java:176),分配了30MB。详细日志如下:
==WARNING== allocating large array--thread_id[0x00007fd907e8e000]--thread_name[ProductLocationWatcher-1666232342702]--array_size[30669952 byt
es]--array_length[15334967 elememts]
os_prio=0 tid=0x00007fd907e8e000 nid=0x1fbe7 runnable
at com.alibaba.fastjson.serializer.SerializeWriter.expandCapacity(SerializeWriter.java:306)
at com.alibaba.fastjson.serializer.SerializeWriter.writeLong(SerializeWriter.java:757)
at com.alibaba.fastjson.serializer.DateCodec.write(DateCodec.java:228)
at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:333)
at com.alibaba.fastjson.serializer.ASMSerializer_11_LocationAttribute.writeNormal(Unknown Source)
at com.alibaba.fastjson.serializer.ASMSerializer_11_LocationAttribute.write(Unknown Source)
at com.alibaba.fastjson.serializer.CollectionCodec.write(CollectionCodec.java:101)
at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:333)
at com.alibaba.fastjson.serializer.ASMSerializer_10_Location.writeNormal(Unknown Source)
at com.alibaba.fastjson.serializer.ASMSerializer_10_Location.write(Unknown Source)
at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:333)
at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:311)
at com.alibaba.fastjson.serializer.ASMSerializer_9_ListResult.writeNormal(Unknown Source)
at com.alibaba.fastjson.serializer.ASMSerializer_9_ListResult.write(Unknown Source)
at com.alibaba.fastjson.serializer.JSONSerializer.writeWithFieldName(JSONSerializer.java:333)
at com.alibaba.fastjson.serializer.ASMSerializer_7_RpcLogExt.writeNormal(Unknown Source)
at com.alibaba.fastjson.serializer.ASMSerializer_7_RpcLogExt.write(Unknown Source)
at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:285)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:758)
at com.alibaba.fastjson.JSON.toJSONString(JSON.java:692)
at com.aliyun.ecs.common.framework.trace.AbstractRpcLogEntryPointExt.toFullJsonString(AbstractRpcLogEntryPointExt.java:119)
at com.aliyun.ecs.common.framework.trace.AbstractRpcLogEntryPointExt.processAndSaveRpcLog(AbstractRpcLogEntryPointExt.java:95)
at com.aliyun.ecs.common.framework.trace.CommonRpcLogFilter.invoke(CommonRpcLogFilter.java:135)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.aliyun.ecs.common.framework.sentinel.filter.CustomizeSentinelDubboConsumerFilter.invoke(CustomizeSentinelDubboConsumerFilter.ja
va:50)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:53)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.csp.sentinel.adapter.dubbo.DubboAppContextFilter.invoke(DubboAppContextFilter.java:41)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:48)
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)
at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:74)
at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53)
at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77)
at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:226)
at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72)
at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:52)
at com.alibaba.dubbo.common.bytecode.proxy0.queryFullLocationTree(proxy0.java)
at com.aliyun.rc.service.cache.xxx.queryFullLocationTree(ProductLocationWatcher.java:176)
at com.aliyun.rc.service.cache.xxx.refresh(ProductLocationWatcher.java:107)
at com.aliyun.rc.service.cache.xxx.run(ProductLocationWatcher.java:59)
at java.lang.Thread.run(Thread.java:858)

再也没有其他大对象分配了。
所以后面的Action:
- 这个ArrayAllocationWarningSize=20480k 参数常态化部署到所有机器上。
- 针对应用的标准输出的大对象进行监控和报警。
最后,也证明了最近的 Full GC 不是大对象引起的,至此,问题基本结案了。
加入我们
【稳定大于一切】打造国内稳定性领域知识库,让无法解决的问题少一点点,让世界的确定性多一点点。
- GitHub 专栏地址:https://github.com/StabilityMan/StabilityGuide
- 钉钉交流群号:30000312
- 如果阅读本文有所收获,欢迎分享给身边的朋友,期待更多同学的加入!