Holmes 原理浅析
前言
对于系统的性能尖刺问题,我们通常使用Go官方内置的pprof
包进行分析,但是难点是对于一闪而过的“尖刺”,
开发人员很难及时地保存现场:当你收到告警信息,从被窝中爬起来,打开电脑,链接VPN,系统说不定都已经重启三四趟了。
MOSN社区的Holmes是一个基于golang实现的,轻量级性能监控系统,当应用的性能指标
发生了异常波动时,holmes
会在第一时间保留现场,让你第二天上班可以一边从容地喝着枸杞茶,一边追查问题的根因。
本文将介绍如何使用 holmes
对您的应用进行监控,并简单分析了holmes
的实现原理。
Quick Start
使用holmes
的方式十分简单,只需要在您的系统初始化逻辑内添加以下代码:
// 配置规则
h, _ := holmes.New(
holmes.WithCollectInterval("5s"), // 指标采集时间间隔
holmes.WithDumpPath("/tmp"), // profile保存路径
holmes.WithCPUDump(10, 25, 80, 2 * time.Minute), // 配置CPU的性能监控规则
holmes.WithMemDump(30, 25, 80, 2 * time.Minute),// 配置Heap Memory 性能监控规则
holmes.WithGCHeapDump(10, 20, 40, 2 * time.Minute), // 配置基于GC周期的Heap Memory 性能监控规则
holmes.WithGoroutineDump(500, 25, 20000, 100*1000, 2 * time.Minute), //配置Goroutine数量的监控规则
)
// enable all
h.EnableCPUDump().
EnableGoroutineDump().
EnableMemDump().
EnableGCHeapDump().Start()
类似于holmes.WithGoroutineDump(min, diff, abs,max,2 * time.Minute)
的API
含义为:
-
当goroutine指标满足以下条件时,将会触发dump操作。
current_goroutine_num
>10
&¤t_goroutine_num
<100*1000
&¤t_goroutine_num
>125%
*previous_average_goroutine_num
||current_goroutine_num
>2000
. -
当goroutine数大于
max
时,holmes
会跳过本次dump操作,因为当goroutine
数过大时,goroutine dump
操作成本很高。 -
2 * time.Minute
是两次dump操作之间最小时间间隔,避免频繁profiling对性能产生的影响。
更多使用案例点击这里。
Profile Types
holmes
支持以下五种Profile类型,用户可以按需配置。
- mem: 内存分配
- cpu: cpu使用率
- thread: 线程数
- goroutine: 协程数
- gcHeap: 基于GC周期监控的内存分配
指标采集
mem
, cpu
, thread
, goroutine
这四种类型是根据用户配置的CollectInterval
,每隔一段时间采集一次应用当前的性能指标,
而gcHeap
时基于GC
周期采集性能指标。本小节会分析一下两种指标。
根据CollectInterval
周期采集
holmes
每隔一段时间采集应用各项指标,并使用一个固定大小的循环链表来存储它们。
根据GC
周期采集
在一些场景下,我们无法通过定时的memory dump
保留到现场, 比如应用在一个CollectInterval
周期内分配了大量内存,
又快速回收了它们,此时holmes
在周期前后的采集到内存使用率没有产生过大波动,与实际情况不符。
为了解决这种情况,holmes开发了基于GC周期的
Profile
类型,它会在堆内存使用率飙高的前后两个GC周期内各dump
一次profile
,然后开发人员可以使用pprof --base
命令去对比
两个时刻堆内存之间的差异。 具体实现介绍。
根据GC
周期采集到的数据也会放在循环列表中。
规则判断
本小节介绍holmes
是如何根据规则判断系统出现异常的。
阈值含义
每个Profile
都可以配置min
,diff
,abs
,coolDown
四个指标,含义如下:
- 当前指标小于
min
时,不视为异常。 - 当前指标大于
(100+diff)
*100%
*历史指标,说明系统此时产生了波动,视为异常。 - 当前指标大于
abs
(绝对值),视为异常。
cpu
和goroutine
这两个profile
类型提供max
参数配置,基于以下考虑。
cpu
的profiling
操作大约会有5%的性能损耗, 所以当在cpu
过高时,不应当进行profiling
操作,否则会拖垮系统。- 当
goroutine
数过大时,goroutine dump
操作成本很高,会进行STW操作,从而拖垮系统。
Warming up
当holmes
启动时,会根据CollectInterval
周期采集十次各项指标,在这期间内采集到的指标只会存入循环链表中,不会进行规则判断。
扩展功能
除了基本的监控之外,holmes
还提供了一些扩展功能。
事件上报
您可以通过实现Reporter
来实现以下功能:
- 发送告警信息,当
holmes
触发Dump
操作时。 - 将
Profiles
上传到其他地方,以防实例被销毁,从而导致profile丢失,或进行分析。
type ReporterImpl struct{}
func (r *ReporterImple) Report(pType string, buf []byte, reason string, eventID string) error{
// do something
}
......
r := &ReporterImpl{} // a implement of holmes.ProfileReporter Interface.
h, _ := holmes.New(
holmes.WithProfileReporter(reporter),
holmes.WithDumpPath("/tmp"),
holmes.WithLogger(holmes.NewFileLog("/tmp/holmes.log", mlog.INFO)),
holmes.WithBinaryDump(),
holmes.WithMemoryLimit(100*1024*1024), // 100MB
holmes.WithGCHeapDump(10, 20, 40, time.Minute),
)
动态配置
您可以通过Set
方法在应用运行时更新holmes的配置。它的使用十分简单,和初始化时的New
方法一样。
有些配置时不支持动态更改的,比如Core数,如果在系统运行期间更改这个参数,会导致CPU使用率产生巨大 波动,从而触发Dump操作。
h.Set(
WithCollectInterval("2s"),
WithGoroutineDump(10, 10, 50, 90, time.Minute))
配置中心支持
利用Set
方法,您可以轻松地对接自己公司的配置中心,比如,将holmes
作为数据面,配置中心作为控制面。
并对接告警系统(邮件/短信等),搭建一套简单的监控系统。
具体架构如下:
总结
本文简单地介绍了Holmes
的使用方法与原理。希望holmes
能在您提高应用的稳定性时帮助到你。