m3dbnode oom
oom时排查
- 内存火焰图: 80G内存
- bytes_pool_get_on_empty qps 很高
- db read qps增长 80%
- node cpu kernel 暴涨
看图论断
- m3dbnode 内存oom过程很短,很激烈:总工夫不超过7分钟
- 内存从27G增长到250G
- 节点sys态cpu暴涨:因为大量的mem_alloca sys_call
- 内存增长曲线和db_read_qps曲线和bytes_pool_get_on_empty曲线高度吻合
- 内存火焰图: 27G的rpc 40G的pool.(*objectPool).tryFill
查看代码,追踪火焰图中这个tryFill
内存分配器
目标很简略:本人治理内存,防止频繁的mem_allocate sys_call 晋升速度,空间换工夫
外围构造
- 初始化时调用init 向池中注入
type objectPool struct { opts ObjectPoolOptions values chan interface{} alloc Allocator size int refillLowWatermark int refillHighWatermark int filling int32 initialized int32 dice int32 metrics objectPoolMetrics}
for i := 0; i < cap(p.values); i++ { p.values <- p.alloc() }
从池中获取对象时
- 池中还有残余则间接获取否则走各自的alloc调配
- 同时设置bytes_pool_get_on_empty
func (p *objectPool) Get() interface{} { if atomic.LoadInt32(&p.initialized) != 1 { fn := p.opts.OnPoolAccessErrorFn() fn(errPoolGetBeforeInitialized) return p.alloc() } var v interface{} select { case v = <-p.values: default: v = p.alloc() p.metrics.getOnEmpty.Inc(1) } p.trySetGauges() return v}
同时判断池水位,是否加油
if p.refillLowWatermark > 0 && len(p.values) <= p.refillLowWatermark { p.tryFill() }
加油过程
- 用CompareAndSwapInt32做并发管制标记位
- 加油加到refillHighWatermark
func (p *objectPool) tryFill() { if !atomic.CompareAndSwapInt32(&p.filling, 0, 1) { return } go func() { defer atomic.StoreInt32(&p.filling, 0) for len(p.values) < p.refillHighWatermark { select { case p.values <- p.alloc(): default: return } } }()}
默认池参数
defaultRefillLowWaterMark = 0.3 defaultRefillHighWaterMark = 0.6
总结思考
- 默认池低水位为什么不是0:因为 从水位判断到tryFill两头的并发申请使得最初tryFill开始时低水位可能低于0.3
- 火焰图中的tryFill耗费了40G内存不是一次性的,类比右侧thriftrpc27,属于累加内存耗费值
- 一次性的内存耗费必定没有这么多:每次加油时内存耗费低于初始化
- 所以能够失去论断,oom是因为在过后byte_pool频繁的get耗费,而后tryFill频繁的加油导致内存调配
- 所以根本原因还是查问导致的
解决办法:限度query资源耗费爱护db
批改m3coordinator参数