线上内存泄漏排查思路

“内存泄漏”是开发者最害怕的问题之一,尤其是在高并发、高负载的线上环境中。它往往不易察觉,却能悄悄吞噬系统的性能,最终导致应用崩溃或响应变慢。你是否曾在项目上线后遇到过性能下降或宕机的问题,而问题根源竟然是内存泄漏?本篇文章将带你深入分析线上内存泄漏的排查思路,帮你快速定位并解决这一隐患。

在现代互联网应用中,内存泄漏的排查已不再是一个简单的任务。线上系统通常复杂且动态,如何在不影响生产环境的情况下,定位并修复内存泄漏问题呢?你是不是也遇到过系统内存逐渐增加,甚至最终导致崩溃的情况?别着急,接下来我们一起探讨排查内存泄漏的有效方法。

内存泄漏的常见表现

  • 内存逐渐增加:应用程序在长时间运行后,占用的内存不断增加,且无法释放。
  • 频繁的 GC(垃圾回收):虽然 GC 不断回收,但堆内存依然无法被清理。
  • 系统卡顿或崩溃:内存泄漏严重时,系统可能因为内存耗尽导致性能下降、响应变慢,甚至宕机。

内存泄漏排查

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

思路

1.大概捋一下项目中有通过常驻内存操作实现业务逻辑的代码

图片

cpu火焰图

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

图片

 

内存调用

图片

上代码  (go2cache)

// 1. new一个go2cache的 server,这个对象负责将数据库的数据建立2层缓存(redis, memory),批量查内存优先级高
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存入内存的
    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) {



    // 重点来了~

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),
        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),

    ...
    for i := 0; i < config.Shards; i++ {

        tail:leftMarginIndex,
        head:leftMarginIndex,
        rightMargin:leftMarginIndex,
        verbose:verbose,
}

 

最终内存好用结果打印

图片

我们都是怎么使用的

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

图片

 

解决方式

方式一

// newMemoryCache 使用较小的内存做缓冲
func newMemoryCache(codec Codec) *memoryCache {
    config := bigcache.DefaultConfig(time.Second * 60)
    //config.Shards = 1024
    config.Shards = 10 // 改成系统默认的10个分片就行了,或者咱们的业务就别用内存了,直接对接redis
    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 M\n", m.Alloc/1024/1024)

        time.Sleep(time.Second * 7)
    }
}

图片

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

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

// ClearTimer 清除定时器

func (r *RoomWorkerBase) ClearTimer(key string) {
    g.Log().Debugf("ClearTimer Key: %s", key)
    timer, ok := r.timers[strings.ToUpper(key)]
    if !ok {
        return
    }
    timer.Stop()
    delete(r.timers, strings.ToUpper(key)) // TODO delete map 不会释放内存。只会打标记
}
room.Check()

go func() {
    defer func(room base.RoomWorkerItf) {
        serv.mutex.Lock()
        _ = cache.DelRoomWorkerId(rid, wid)
        delete(serv.rooms, room.GetWid()) // TODO delete map 只是打标记 不会真正释放内存
        serv.mutex.Unlock()
        serv.wg.Done()
    }(room)
    room.Run()
}()

serv.rooms[wid] = room

return room, nil

 

总结

go tool pprof --alloc_space http://127.0.0.1:6064/debug/pprof/heap 对所有内存对象的监控打印,其中包括被GC的

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掉的对象

top -pid 1123 //对具体的pid进行top命令监控

ps -ef | grep worker 等价于 ps -aux | grep nginx 根据进程名称查看进程的pid及启动信息

通过端口查出pid,进而查出进程的启动命令等信息

lsof -i :9527  //查看端口的pid
ps -ef | grep 12321 //根据pid 查出进程启动信息

随着应用规模的不断扩大,尤其是在微服务架构下,线上环境中的内存泄漏问题日益严重。为了保证服务的持续稳定,开发和运维团队需要密切配合,建立完善的内存监控和日志分析机制。现代化的监控工具和诊断工具,给我们提供了更高效的排查方式,但也要求我们在实践中不断积累经验和总结。

线上内存泄漏是一种渐进式的隐性问题,往往难以在早期察觉。然而,通过合理的工具和排查思路,我们完全可以在生产环境中高效定位并解决问题。内存管理是开发者不可忽视的一项基础能力,而通过监控和分析,提升系统的稳定性和健壮性,才是最终目标。

“内存泄漏是性能杀手,排查它,正是高效开发的必修课。把内存管理做好,才能让系统走得更远。”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/958385.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Redis】在ubuntu上安装Redis

文章目录 提权搜索软件包安装修改配置文件ip保护模式配置密码 重新启动服务器使用 redis 自带的客户端来连接服务器 提权 先切换到 root 用户,su 命令切换到 root. 搜索软件包 使用 apt 命令来搜索 redis 相关的软件包 apt search redis 安装 使用 apt 命令安装 redisapt …

人形机器人,自动驾驶“老炮”创业第二站

造一台人形机器人&#xff0c;或许正在成为2025年最炙手可热的事情。 从去年第四季度开始&#xff0c;伴随着大模型应用的深入&#xff0c;具身智能概念被点燃&#xff0c;其中最鲜明的一个特点是&#xff0c;大量自动驾驶大佬的转行加入。 随便说几个比较有分量的&#xff0…

《SwinIR:使用Swin-Transformer图像恢复》学习笔记

paper&#xff1a;2108.10257 GitHub&#xff1a;GitHub - JingyunLiang/SwinIR&#xff1a; SwinIR&#xff1a; 使用 Swin Transformer 进行图像修复 &#xff08;官方仓库&#xff09; 目录 摘要 1、Introduction 2、Related Work 2.1 图像修复 2.2 视觉Transformer…

力扣hot100-->滑动窗口、贪心

你好呀&#xff0c;欢迎来到 Dong雨 的技术小栈 &#x1f331; 在这里&#xff0c;我们一同探索代码的奥秘&#xff0c;感受技术的魅力 ✨。 &#x1f449; 我的小世界&#xff1a;Dong雨 &#x1f4cc; 分享我的学习旅程 &#x1f6e0;️ 提供贴心的实用工具 &#x1f4a1; 记…

Top 30的AI应用产品出海经验分享

榜单说明 本文基于对AI图片产品的分类和流量分析&#xff0c;旨在洞察AI图片应用的出海趋势。以下是分类和收录标准&#xff1a; 分类标准 将AI图片产品分为三大类&#xff1a;图片生成、图片编辑和平面设计。 图片生成&#xff1a;以基于大模型生成图片并展示结果&#xff0…

Hive之加载csv格式数据到hive

场景&#xff1a; 今天接了一个需求&#xff0c;将测试环境的hive数据导入到正式环境中。但是不需要整个流程的迁移&#xff0c;只需要迁移ads表 解决方案&#xff1a; 拿到这个需求首先想到两个方案&#xff1a; 1、将数据通过insert into语句导出&#xff0c;然后运行脚本 …

73,【5】BUUCTF WEB [网鼎杯 2020 玄武组]SSRFMe(未解出)

进入靶场 又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码又是代码 <?php // 检查 URL 是否为内部 IP 地址 function check_inner_ip($url) {// 使用正则表达式检查 URL 格式是否以 http、https、gopher 或 d…

如何实现各种类型的进度条

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了浮动按钮相关的内容&#xff0c;,本章回中将介绍进度条相关的Widget,闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 进度条是常用的组件之一&#xff0c;它主要用来显示某种动作的完成进度。Flu…

复位信号的同步与释放(同步复位、异步复位、异步复位同步释放)

文章目录 背景前言一、复位信号的同步与释放1.1 同步复位1.1.1 综述1.1.2 优缺点 1.2 recovery time和removal time1.3 异步复位1.3.1 综述1.3.2 优缺点 1.4 同步复位 与 异步复位1.5 异步复位、同步释放1.5.1 总述1.5.2 机理1.5.3 复位网络 二、思考与补充2.1 复…

欢迎来到linux大陆!本次试炼地点——秩序“权限”圣殿

一篇关于权限的学习笔记~ 1、权限的概念2、权限管理2.1 角色的不同分类2.2 文件权限原理剖析2.2.1 熟悉指令2.2.2 普通用户只能更改自己的文件权限&#xff0c;但是sudo提权可以更改其他文件权限2.2.3 没有权限&#xff0c;系统拒绝访问2.2.4 权限匹配2.2.5 root用户不受任何限…

Spring 定时任务:@Scheduled 注解四大参数解析

本文主要介绍了在 Spring 框架中使用Scheduled注解实现定时任务的方法&#xff0c;重点讲解了fixedRate、fixedDelay、cron和initialDelay这四个参数的用法&#xff0c;并通过实例代码进行了详细说明。 1. fixedRate 参数 参数含义 fixedRate指定任务固定时间间隔执行。如设…

使用频谱仪:测量宽带信号的功率

marker默认只测一个频率点的功率&#xff0c;当测试宽带信号&#xff0c;如20MHz&#xff0c;不能直接使用marker来测量功率。 有2种方式&#xff1a; 宽带信号需要使用Measure-> channel power 来测量。 meas setup integ BW&#xff1a;500mhz Freq&#xff1a;中心频…

postman请求参数化

postman界面介绍 一、使用环境变量(Environment Variables)进行参数化 1、在请求中使用环境变量 在请求的url、请求头(Headers)、请求体(Body)等部分都可以使用环境变量。 URL 部分示例 点击 Postman 界面右上角的 “眼睛” 图标(Environment Quick Look)打开环境管理…

优选算法——哈希表

目录 1. 哈希表简介 2. 两数之和 3. 判定是否为字符重排 4. 存在重复元素 5. 字母异位词分组 1. 哈希表简介 2. 两数之和 题目链接&#xff1a;1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 题目展示&#xff1a; 题目分析&#xff1a; 大家来看上面的图&…

【C语言学习】:C语言补充:转义字符,<<,>>操作符,IDE

&#x1f381;个人主页&#xff1a;我们的五年https://blog.csdn.net/djdjiejsn?typeblog &#x1f50d;系列专栏&#xff1a;C课程学习https://blog.csdn.net/djdjiejsn/category_12617142.html &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 C语言学…

Cesium特效——城市白模的科技动效的各种效果

最终效果图如下&#xff1a; 实现方法&#xff1a; 步骤一&#xff1a;使用cesiumlib生产白模&#xff0c;格式为3dtiles 注意事项&#xff1a;采用其他方式可能导致白模贴地&#xff0c;从而导致不能实现该效果&#xff0c;例如把步骤二的服务地址改为Cesium Sandcastle 里的…

仿 RabbitMQ 的消息队列3(实战项目)

七. 消息存储设计 上一篇博客已经将消息统计文件的读写代码实现了&#xff0c;下一步我们将实现创建队列文件和目录。 实现创建队列文件和目录 初始化 0\t0 这样的初始值. //创建队列对应的文件和目录&#xff1a;public void createQueueFile(String queueName) throws IO…

无人机 PX4 飞控 | PX4源码添加自定义参数方法并用QGC显示与调整

无人机 PX4 飞控 | PX4源码添加自定义参数方法并用QGC显示与调整 0 前言 之前文章添加了一个自定义的模块&#xff0c;本篇文章在之前的自定义模块中&#xff0c;添加两个自定义参数 使用QGC显示出来&#xff0c;并通过QGC调整参数值&#xff0c;代码实现参数更新 新增的参…

【真机调试】前端开发:移动端特殊手机型号有问题,如何在电脑上进行调试?

目录 前言一、怎么设置成开发者模式&#xff1f;二、真机调试基本步骤&#xff1f; &#x1f680;写在最后 前言 edge浏览器 edge://inspect/#devices 谷歌浏览器&#xff08;开tizi&#xff09; chrome://inspect 一、怎么设置成开发者模式&#xff1f; Android 设备 打开设…

2024年第十五届蓝桥杯青少组国赛(c++)真题—快速分解质因数

快速分解质因数 完整题目和在线测评可点击下方链接前往&#xff1a; 快速分解质因数_C_少儿编程题库学习中心-嗨信奥https://www.hixinao.com/tiku/cpp/show-3781.htmlhttps://www.hixinao.com/tiku/cpp/show-3781.html 若如其他赛事真题可自行前往题库中心查找&#xff0c;题…