线上内存泄漏排查思路

内存泄漏排查

背景了解:告知 线上 room_work 运行一段时间内存就会慢慢往上涨,8G内存吃掉了4G。。。

思路

  1. 大概捋一下项目中有通过常驻内存操作实现业务逻辑的代码
1. room_work 这是个根据rid进行转发到不同node的,深度使用内存存储rid的各种业务数据
2. 道具方面,干冰跟骰子移入了room_work后,也是使用自身定义的内存对象承载的业务
......

cpu火焰图看看

直接本地环境全部启动之后,开始使用三个手机进入dj房间,进行所有功能疯狂乱点,生成cup火焰图,但讲真看不出来啥,才发现应该才内存才对

企业微信截图_6e098d68-30f7-48cb-8728-5ba3603caa2f.png

内存调用瞅瞅

企业微信截图_8988d82a-6734-4e77-b0db-6f2ca81b9610.png

上代码(go2cache)


//1. new一个go2cache的 server,这个对象负责将数据库的数据建立2层缓存(redis,memory),批量查内存优先,单个查忽略内存只取  redis
func NewServer(redis string, codec Codec) *Server {
    memory := newMemoryCache(codec)  //内存子对象
    redisCacheServer := newRedisCache(redis, memory, codec)
    db := newDbCache(redisCacheServer, codec)
    return &Server{
        redis: redisCacheServer,
        db:    db,
        codec: codec,
    }
}

//2. 着重看内存子对象
func newMemoryCache(codec Codec) *memoryCache {
    config := bigcache.DefaultConfig(time.Second * 60)
    config.Shards = 1024     //1024个分片,bigcache这个组件,没了节约内存,将我们的数据是按byte存入全局的[]byte里面去的
    config.MaxEntrySize = 1024 * 16
    config.HardMaxCacheSize = 500
    cache, _ := bigcache.NewBigCache(config)
    return &memoryCache{
        cache: cache,
        codec: codec,
    }
}

func NewBigCache(config Config) (*BigCache, error) {
    return newBigCache(config, &systemClock{})
}


func newBigCache(config Config, clock clock) (*BigCache, error) {
.......
    for i := 0; i < config.Shards; i++ {
        cache.shards[i] = initNewShard(config, onRemove, clock)   //重点来了~
    }
    .......
}

func initNewShard(config Config, callback onRemoveCallback, clock clock) *cacheShard {
    bytesQueueInitialCapacity := config.initialShardSize() * config.MaxEntrySize
    maximumShardSizeInBytes := config.maximumShardSizeInBytes()
    if maximumShardSizeInBytes > 0 && bytesQueueInitialCapacity > maximumShardSizeInBytes {
        bytesQueueInitialCapacity = maximumShardSizeInBytes
    }
    return &cacheShard{
        hashmap:      make(map[uint64]uint32, config.initialShardSize()),
        hashmapStats: make(map[uint64]uint32, config.initialShardSize()),
        entries:      *queue.NewBytesQueue(bytesQueueInitialCapacity, maximumShardSizeInBytes, config.Verbose),   //重点
        entryBuffer:  make([]byte, config.MaxEntrySize+headersSizeInBytes),
        onRemove:     callback,

        isVerbose:    config.Verbose,
        logger:       newLogger(config.Logger),
        clock:        clock,
        lifeWindow:   uint64(config.LifeWindow.Seconds()),
        statsEnabled: config.StatsEnabled,
    }
}

// NewBytesQueue initialize new bytes queue.
// capacity is used in bytes array allocation
// When verbose flag is set then information about memory allocation are printed
func NewBytesQueue(capacity int, maxCapacity int, verbose bool) *BytesQueue {
    return &BytesQueue{
        array:        make([]byte, capacity),
        capacity:     capacity,
        maxCapacity:  maxCapacity,
        headerBuffer: make([]byte, binary.MaxVarintLen32),
        tail:         leftMarginIndex,
        head:         leftMarginIndex,
        rightMargin:  leftMarginIndex,
        verbose:      verbose,
    }
}

最终内存好用结果打印

企业微信截图_16082c41-06dd-4bbf-afa7-1f2bc003238c.png

找我们都是怎么使用的

经过分析发现 /Flock-Server/rpc/server/internal/room/worker/base/base.go@Init 方法会在每次rid new一个work的时候被初始化一次, 了解下房间业务,发现房间是一个街区一个房间的,所以。。。

企业微信截图_08818812-8644-46e8-ab4a-93ccd84b3747.png

解决方式

方式一

//newMemoryCache 使用较小的内存做缓冲
func newMemoryCache(codec Codec) *memoryCache {
    config := bigcache.DefaultConfig(time.Second * 60)
    //config.Shards = 1024
       config.Shards = 10// 改成系统默认的10个分片就行了,或者咱们的业务就别用内存了,直接对接redis, bigcache这个组件默认的1024估计是出于全局只要new一个考虑,而我们吧bigcache当子组件使用的时候,忘记这一茬了。。。
    config.MaxEntrySize = 1024 * 16 //16KB
    config.HardMaxCacheSize = 500
    cache, _ := bigcache.NewBigCache(config)
    return &memoryCache{
        cache: cache,
        codec: codec,
    }
}

然后把
func (r *RoomWorkerBase) Init() {
    r.Ctx = context.TODO()
    r.ServerCache = go2cache.NewServer(consts.RedisRoom, cache.RoomCodec{})  //想办法把这个对象编程单例
    r.I18n = i18n.NewI18n()
    r.I18n.SetLanguage("en")

    r.msgCh = make(chan interface{}, 500)
    r.stopCh = make(chan interface{})
    r.SyncHdl = make(map[reflect.Type]SyncHandler)
    r.ASyncHdl = make(map[reflect.Type]AsyncHandler)
    r.SyncPbHdl = make(map[protoreflect.Descriptor]SyncProtoHandler)
    r.ASyncPbHdl = make(map[protoreflect.Descriptor]AsyncProtoHandler)
    r.CmdHdl = make(map[string]CmdHandler)
    r.timers = make(map[string]*time.Timer)

    r.CommonCache = cache.NewRoomCommon()

    r.RegisterHandlers()
}

方式二

直接把内存的二级缓存拿掉,让go2cache直接对接redis, 这种方式代码业务方无感知,只需要该go2cache内部

来个demo重现看看

func main() {

    var m runtime.MemStats

    asas := map[string]interface{}{}
    for i := 0; i < 20; i++ {
        asas[fmt.Sprintf("%d", i)] = go2cache.NewServer(consts.RedisRoom, RoomCodec{})

        fmt.Println(fmt.Sprintf("第 %d: 次循环n", i))
        runtime.ReadMemStats(&m)
        fmt.Printf("%d Mn", m.Alloc/1024/1024)

        time.Sleep(time.Second * 7)

    }

}
企业微信截图_876f20c1-9d82-4624-993b-d1c8e09a03c9.png

对其他全局对象使用的一些思考

golang里面的map充当全局对象使用的时候, 要时刻提醒自己,这个map在被delete的时候,内存不会被gc的,只会被打tag,需要定时迁移新的map,
才能是老的map里面被tag的内存对象被回收。。。

企业微信截图_f7e090be-4947-4ef7-a02c-c2f93c6bbade.png
企业微信截图_02967465-cf10-4728-92eb-1a67863cbf15.png

总结

  1. go tool pprof --alloc_space http://127.0.0.1:6064/debug/pprof/heap 对所有内存对象的监控打印,其中包括被GC的
  2. go tool pprof http://127.0.0.1:6064/debug/pprof/heap 等价与 go tool pprof --inuse_space http://127.0.0.1:6064/debug/pprof/heap 对活跃内存对象打印,不包活会被GC掉的对象
  3. top -pid 1123 //对具体的pid进行top命令监控
  4. ps -ef | grep worker 等价于 ps -aux | grep nginx 根据进程名称查看进程的pid及启动信息
  5. 通过端口查出pid,进而查出进程的启动命令等信息
lsof -i :9527   //查看端口的pid
ps -ef | grep 12321  //根据pid 查出进程启动信息
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/12491,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?