乐趣区

关于prometheus:m3dbnode-oom追踪和内存分配器代码查看

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 参数

退出移动版