现象

某一个周末我们的服务 oom了,一个比较重要的job 没有跑完,需要重跑,以为是偶然,重跑成功,因为是周末没有去定位原因

又一个工作日,它又oom了,重跑成功,持续观察,job 在oom之前竟然占用了30g左右(这里我们的任务一个数据量都在内存中计算,所以这里数据量大一点)

应用使用30g肯定是不正常的,怀疑内存泄漏了,怎么定位内存泄漏呢?

定位

搜了一下网上经常用到的工具是 go 的 pprof 火焰图,自己在本地跑了一下,因为数据量比较少,并没有发现什么,暂时放下了。

后续某个早上在公司工具里面打开了一下,发现有火焰图的工具,打开看了一下一个函数占用了 7224.46mb,占用了 7个g, 而且这个函数是已经跑完了,这个时候定位到那个函数了,和旁边同事说了一下,同事帮忙看了下邮件告警,每个下午都会有任务失败告警(任务失败会进行重试的); 这里怀疑是失败了, channel 没有关闭,导致 消费的go routine 没有回收。

举个例子看下代码:

package main

import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
) func main() {
readGroup, _ := errgroup.WithContext(context.Background())
consumeGroup, _ := errgroup.WithContext(context.Background()) var (
data = make(chan []int, 10)
) // 3个生产者往里面进行进行生产
readGroup.Go(func() error {
for i := 0; i < 3; i++ {
data <- []int{i}
}
return nil
}) readGroup.Go(func() error {
for i := 3; i < 6; i++ {
data <- []int{i}
}
return nil
}) readGroup.Go(func() (err error) {
for i := 6; i < 9; i++ {
// error
if i == 7 {
err = fmt.Errorf("error le")
return
}
data <- []int{i}
}
return nil
}) // 其中一个生产者遇到error 返回导致 channel 没有关闭,消费者没有退出 // 1个消费者进行消费 consumeGroup.Go(func() error {
for i := range data {
fmt.Println(i)
}
return nil
}) if err := readGroup.Wait(); err != nil {
fmt.Println(err)
return
} close(data) if err := consumeGroup.Wait(); err != nil {
fmt.Println(err)
return
} fmt.Println("end it")
}

这个case里面,readGroup 遇到error 直接退出了,channel并没有关闭,如果是常驻进程的程序,消费的go routine 并没有回收,就导致了内存泄漏

最简单的关闭修复

将 close 放到最上面的 defer close(data)

不过最好的还是生产者进行关闭,我们可以优化一下代码,把生产者的代码放到一个函数中,这样就可以让生产者去进行关闭的操作了

package main

import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
) func main() {
var (
data = make(chan []int, 10)
err error eg, _ = errgroup.WithContext(context.Background())
) eg.Go(func() (err error) {
defer close(data) err = readGroup(data) return
}) eg.Go(func() (err error) {
err = consumeGroup(data)
return
}) err = eg.Wait()
if err != nil {
return
} fmt.Println("end it")
} func consumeGroup(data chan []int) (err error) {
consumeGroup, _ := errgroup.WithContext(context.Background()) consumeGroup.Go(func() error {
for i := range data {
fmt.Println(i)
}
return nil
}) if err = consumeGroup.Wait(); err != nil {
fmt.Println(err)
return
} return
} func readGroup(data chan []int) (err error) {
readGroup, _ := errgroup.WithContext(context.Background()) // 3个生产者往里面进行进行生产
readGroup.Go(func() error {
for i := 0; i < 3; i++ {
data <- []int{i}
}
return nil
}) readGroup.Go(func() error {
for i := 3; i < 6; i++ {
data <- []int{i}
}
return nil
}) readGroup.Go(func() (err error) {
for i := 6; i < 9; i++ {
// error
if i == 7 {
err = fmt.Errorf("error le")
return
}
data <- []int{i}
}
return nil
}) if err = readGroup.Wait(); err != nil {
fmt.Println(err)
return
} return
}

修复

将生产者放在一个 goroutint 里面,最后如果遇到error的话 defer()的时候会把channel给关闭了

The Channel Closing Principle

One general principle of using Go channels is don't close a channel from the receiver side and don't close a channel if the channel has multiple concurrent senders. In other words, we should only close a channel in a sender goroutine if the sender is the only sender of the channel.

简单点:就是在生产者中进行channel的关闭

后续讨论和遇到的新问题

拆分代码函数的时候又遇到新的问题了,又一个切片数组我拆分函数的时候,我没有去接受切片函数的返回值,导致了切片发生扩容返回的是一个空切片,并没有修改掉原来的切片。之前以为在golang里面切片是引用类型,会自动改变其中的值最后查了一下,在go 里面都是值传递,可以修改其中的值其实是使用了指针修改了同一块地址中的值所以值发生了变化

总结

使用channel 的时候在生产者中进行关闭,思考一些遇到error的时候channel是否可以正常的关闭

go 中只有值传递,引用传递是修改了同一个指向内存地址中的值

参考文章:

如何优雅地关闭Go channel

Go语言参数传递是传值还是传引用

golang channel 未关闭导致的内存泄漏的更多相关文章

  1. but has failed to stop it. This is very likely to create a memory leak(c3p0在Spring管理中,连接未关闭导致的内存溢出)

    以下是错误日志信息: 严重: The web application [/news] registered the JDBC driver [com.mysql.jdbc.Driver] but fa ...

  2. 在Activity中使用Thread导致的内存泄漏

    https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/%E5%9C%A8Activity%E4%B8%AD%E4 ...

  3. static关键字所导致的内存泄漏问题

    大家都知道内存泄漏和内存溢出是不一样的,内存泄漏所导致的越来越多的内存得不到回收的失手,最终就有可能导致内存溢出,下面说一下使用staitc属性所导致的内存泄漏的问题. 在dalvik虚拟机中,sta ...

  4. vue自定义指令导致的内存泄漏问题解决

    vue的自定义指令是一个比较容易引起内存泄漏的地方,原因就在于指令通常给元素绑定了事件,但是如果忘记了解绑,就会产生内存泄漏的问题. 看下面代码: directives: { scroll: { in ...

  5. 使用block的时候,导致的内存泄漏

    明确,只要在block里边用到我们自己的东西,成员变量,self之类的,我们都需要将其拿出来,把它做成弱指针以便之后进行释放. 在ZPShareViewController这个控制器中,由如下代码: ...

  6. 精华阅读第 13 期 |常见的八种导致 APP 内存泄漏的问题

    本期是移动开发精英俱乐部的第13期文章,都是以技术为主,所以这里就不过多的进行赘述了,我们直接看干货内容吧!本文系ITOM管理平台OneAPM整理. 实际项目中的MVVM(积木)模式–序章 导读:开篇 ...

  7. 一个驱动导致的内存泄漏问题的分析过程(meminfo->pmap->slabtop->alloc_calls)

    关键词:sqllite.meminfo.slabinfo.alloc_calls.nand.SUnreclaim等等. 下面记录一个由于驱动导致的内存泄漏问题分析过程. 首先介绍问题背景,在一款嵌入式 ...

  8. 使用HandyJSON导致的内存泄漏问题相关解决方法

    在移动开发中,与服务器打交道是不可避免的,从服务器拿到的接口数据最终都会被我们解析成模型,现在比较常见的数据传输格式是json格式,对json格式的解析可以使用原生的解析方式,也可以使用第三方的,我们 ...

  9. iOS - Block产生Memory Leaks循环引用导致的内存泄漏以及解决方案

    在ARC(自动引用技术)前,Objective-c都是手动来分配释放 释放 计数内存,其过程非常复杂. ARC技术推出后,貌似世界和平了很多,但是其实ARC并不等同于Java或者C#中的垃圾回收,AR ...

  10. android Context 持有导致的内存泄漏

    Context使用场景 为了防止Activity,Service等这样的Context泄漏于一些生命周期更长的对象,可以使用生命周期更长的ApplicationContext,但是不是所有的Conte ...

随机推荐

  1. 容易忽视的细节:Log4j 配置导致的零点接口严重超时

    作者:vivo 互联网服务器团队- Jiang Ye 本文详细的记录了一次0点接口严重超时的问题排查经历.本文以作者自身视角极具代入感的描绘了从问题定位到具体的问题排查过程,并通过根因分析并最终解决问 ...

  2. 这可能是最全面的Redis面试八股文了

    Redis连环40问,绝对够全! Redis是什么? Redis(Remote Dictionary Server)是一个使用 C 语言编写的,高性能非关系型的键值对数据库.与传统数据库不同的是,Re ...

  3. Kubernetes(K8S) kubesphere 安装

    安装KubeSphere最好的方法就是参考官方文档,而且官方文档是中文的. 官网地址:https://kubesphere.com.cn/ https://github.com/kubesphere/ ...

  4. Python分支结构之if语句

    程序结构 程序三种结构 顺序 循环 分支 分支结构 分支结构基本语法 if 条件表达式: 语句1 语句2 语句3 ...... 条件表达式就是计算结果必须为布尔值的表达式 表达式后面的冒号不能少 注意 ...

  5. NC20279 [SCOI2010]序列操作

    题目链接 题目 题目描述 lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成 ...

  6. 2022-03-03:课程表 III。 这里有 n 门不同的在线课程,按从 1 到 n 编号。给你一个数组 courses ,其中 courses[i] = [durationi, lastDayi]

    2022-03-03:课程表 III. 这里有 n 门不同的在线课程,按从 1 到 n 编号.给你一个数组 courses ,其中 courses[i] = [durationi, lastDayi] ...

  7. Windows server 2012 安装ad域

    Windows server 2012 安装ad域   安装ad域(active directory)服务的作用:存储目录数据并管理域之间的通信,包括用户登录处理,身份验证和目录搜索等. 1.使用ad ...

  8. AcWing 243. 一个简单的整数问题2-(区间修改,区间查询)

    给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一: C l r d,表示把 A[l],A[l+1],-,A[r]都加上 d. Q l r,表示询问数列中第 l∼r个数的和. ...

  9. 二进制部署k8s集群

    部署k8s有多种方式,本章我们采取二进制的部署方式来部署k8s集群,二进制部署麻烦点,但是可以在我们通过部署各个组件的时候,也通知能让我们更好的深入了解组件之间的关联,也利于后期维护 主机环境 系统: ...

  10. 手把手教你如何在 Linux 上源码安装最新版本 R

    如果你使用的 Linux 系统 GCC 版本太低,又没有 root 权限(即使有 root 权限又担心升级 GCC 带来的风险):同时你又不想额外多安装多一个 Anaconda 或者 Minicond ...