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能在您提高应用的稳定性时帮助到你。