goroutine 泄漏和避免泄漏实战示例

目录
  • goroutine 泄漏和避免泄漏的最佳实践
  • 什么是goroutine泄漏?
    • 原因分析
    • 伪代码
    • 有什么方法可以解决这个问题?

goroutine 泄漏和避免泄漏的最佳实践

Go的奇妙之处在于,我们可以使用goroutines和channel轻松地执行并发任务。如果在生产环境中使用goroutines和channel,但是不了解它们的行为方式,会造成一些严重的影响。

好吧,我们就面临着这样的影响,我们在goroutines中出现了泄漏,导致应用服务器随着时间的推移而膨胀,消耗了大量的CPU和频繁的GC,影响了多个服务的SLA。

从本文中可以看到什么

理解什么是goroutine泄露。 理解goroutine泄漏的多种方式。 详细了解造成goroutine泄露的一个真实场景。 我们是如何找到goroutine泄漏原因? 阻止goroutine泄漏的最佳实践是什么?

正如你在上面所附的指标中所看到的,goroutines开始随着时间的推移成倍地飙升。唯一的一次下降是当我们的一个正在运行的实例被AWS调度走,新的实例被启动,或者有一个新的版本,杀死了现有的容器并产生了新的容器。

如果你观察GC暂停的时间,它会随着活动的goroutine的数量不断增加。GC暂停的次数越多,CPU利用率就越高,响应时间也越来越长。

什么是goroutine泄漏?

goroutine泄漏是指客户端生成一个goroutine来做一些异步任务,并在任务完成后将一些数据写入一个channel,但是

没有监听程序消耗该channel的数据写入。

在上述情况下,代码成功地完成了执行,好像根本就没有问题。但这里发生的情况是,会有一个没有被管理的goroutine驻留在内存中,占用CPU和RAM。

原因分析

主要原因是第3行,我们正在向一个通道写入数据,但根据Go原则,一个未缓冲的通道会阻止向通道的写入,直到消费者从该channel取走信息。

所以在这种情况下,第4行的返回将永远不会被执行,并且newgoroutine函数在整个应用程序生命周期中都被卡住,因为这个channel没有消费者。

在goroutine启动和channel监听器之间有一些条件逻辑。

在这个案例中,有一个小小的改进。我们有一个消费者从dataChan中消费数据,但是从我们生成goroutine开始,到我们开始从通道中消费数据之前,有大量的应用程序代码驻留在那里,这些代码可以在一些处理错误|DB错误|无指针异常|panic的情况下退出主函数,由于这些原因,channel的数据可能从未被执行。

这就是一个goroutine看似正常,实际可能导致泄漏的情况。

我们不能在应用处理之前将channel中的值提前消费,因为消费者会阻止剩下业务逻辑的处理,直到它收到数据,从而消除了并发任务的执行。

发送完成立刻返回 以上两种情况是当goroutine因为没有channle的消费者而被阻塞,或者消费者从channel中消费数据的代码块被跳过。

当我们把一个channel传递给goroutine去消费时,当发送者向通道发送数据时出现了问题,这是否也是同样的情况?

好吧,95%的goroutine泄露都是因为这3种情况中的一种,在我们的案例中,是由于情景-2。我们在GoIbibo-Makemytrip的工作是折扣和便利费服务。

当客户应用一个促销代码时,我们有一套规则要执行,以找出正确的折扣。我们有另一个微服务,我们称之为实时动态折扣器(DD),它试图根据一些算法(黑盒子)来计算折扣。

这个动态折扣是一个A/B实验,只有10%的用户会参与其中。只有当我们的静态规则中存在有效的折扣时,我们才会覆盖DD折扣。

伪代码

我们只有在处理完静态规则后才需要DD的响应。所以来自ddChan的消费将在最后进行。

如果静态规则的评估有问题|如果没有满足请求的有效规则|如果用户应用了一些假的促销活动,我们从ddChan中消耗数据的代码将无法到达,这导致loadDDDiscount函数成为一个无法控制的goroutine。

有什么方法可以解决这个问题?

方法-1 方法 -> 从我们启动goroutine开始,到我们从退出channel的消耗数据为止,我们识别每一个错误条件,并在每一个返回语句前放置一个接收者,以解除对生成的goroutine的封锁。

陷阱 -> 我们必须手动找到所有的边缘情况,并且在将来,如果我们必须处理更多的错误情况,我们需要记住在返回之前我们需要消耗哪些channel的数据。

方法-2 方法 -> 与其在每个错误的情况下放置一个接收者,为什么不设置一个可以从channel中接收数据的延迟函数。

陷阱 -- 在成功的情况下,数据将在处理完静态规则后从通道中读取。因此,如果我们在defer函数中开始接收通道中的数据,那么在成功的情况下就会阻塞主goroutine。

方法-3 没有完美的方法。在上述所有场景中,我们创建了一个无缓冲的通道,阻止发送者向该通道发送数据,直到接收者收到数据。这里的主要问题是我们不确定由于我们的应用处理,接收方是否会被执行。那么,简单的解决方案是创建一个上限为1的缓冲通道。有了这个,即使没有消费者,或者消费者代码没有达到,发送者也不会被阻止写一次数据。 图片 陷阱 -> 绝对是零。这与非缓冲通道的工作原理完全相同,但为我们提供了一个额外的能力,即发送者在发送数据时不会受到阻碍,而消费者可以在任何时候消费它,而且生成的goroutine也不会等待消费者的到来。 我们用第三种方法将变化带入生产环境,你可以看到显著的影响。

图片 以前是线性增长的goroutine数量,现在下降到150个,我们的GC暂停频率也是如此。

整个事情中最痛苦的部分是,如何找到代码中存在goroutine泄漏的部分?

所以我的方法是这样的。

当服务器启动时,使用debug.SetGCPercent(-1)禁用垃圾收集器。 现在运行代码中每一个使用Go程序的流程(Dev Env)。 在每个API的入口处,打印在开始和执行API之前和之后运行的goroutines的数量。

func ApplyPromo() {
   fmt.Println(runtime.NumGoroutine())
   defer fmt.Println(runtime.NumGoroutine()
   // Process your application logic
}

现在,如果一个服务在前后返回不同的Goroutines数量,那么这个逻辑就存在泄漏。

我们有近20个API和大约35-40个地方使用了goroutines以改善并发性。幸运的是,我能够在前3次迭代中找出泄漏问题,并发现了这个存在泄漏的逻辑。

以上就是goroutine 泄漏和避免泄漏实战示例的详细内容,更多关于goroutine 泄漏避免泄漏的资料请关注我们其它相关文章!

(0)

相关推荐

  • Go语言CSP并发模型goroutine channel底层实现原理

    目录 Go的CSP并发模型(goroutine + channel) 1.goroutine goroutine的优点: 2.channel 无缓存channel 有缓存channel 3.Go并发模型的底层实现原理 4.一个CSP例子 参考Go的CSP并发模型实现:M, P, G Go语言是为并发而生的语言,Go语言是为数不多的在语言层面实现并发的语言. 并发(concurrency):多个任务在同一段时间内运行. 并行(parallellism):多个任务在同一时刻运行. Go的CSP并发模

  • Go语言使用goroutine及通道实现并发详解

    目录 使用通道接收数据 阻塞接收数据 非阻塞接收数据 接收任意数据,忽略掉接收的数据 循环接收数据 使用通道接收数据 在上一篇文章中介绍了通道以及使用通道发送数据,本篇接着了解通道的基本内容,如何使用通道接收数据: 通道的接收同样使用"<-"操作符: 使用通道接收数据的特性如下: 通道的发送和接收操作在不同的两个goroutine间进行,由于通道中的数据在没有接收方接收时会持续阻塞,所以通道的接收必定在另外一个goroutine中进行: 如果在接收方接收时,通道中没有发送方发送数

  • 一文初探 Goroutine 与 channel基本用法

    目录 前言 Goroutine 基本用法 channel channel 的基本操作 带缓冲 channel 与无缓冲 channel 声明 channel 的只发送类型和只接收类型 channel 的关闭 小结 前言 本文介绍的内容是 Go 并发模块的两个重要角色 → goroutine 与 channel.如果本文对你有帮助,不妨点个赞,如果你是 Go 语言初学者,不妨点个关注,一起成长一起进步,如果本文有错误的地方,欢迎指出! Go 语言的 CSP 并发模型的实现包含两个主要组成部分:一个

  • Go语言Goroutinue和管道效率详解

    目录 goroutinue基本介绍 进程和线程说明 并发和并行 同步和异步 Go协程和Go主线程 go协程特点 goroutinue基本使用 实验代码 效果图 执行流程图 goroutinue的调度模型 MPG MPG运行状态1 MPG运行状态2 管道(channel) 不同协程之间如何通讯 全局变量加锁同步缺陷 管道基本介绍 管道基本使用 声明和定义 管道关闭和遍历 关闭 遍历 管道注意事项 综合案例 goroutinue基本介绍 进程和线程说明 进程介绍程序在操作系统中的一次执行过程,是系统

  • GoRoutines高性能同时进行多个Api调用实现

    目录 正文 原始调用 高性能调用 正文 Golang是高效的,非常高效.这种效率在很大程度上要归功于它在处理并发性问题时的独特抽象.例如,Java将其线程映射为操作系统线程,而Go使用自己的goroutines调度器将其轻量级goroutines从操作系统线程中进一步抽象出来.简而言之,Golang在使用操作系统线程方面非常节俭:如果一个goroutine被阻塞了,Go的调度器会在它的位置上切换另一个goroutine,以尽可能地保持线程的忙碌.由于每个CPU核心处理的线程数量有限(而且产生新的

  • goroutine 泄漏和避免泄漏实战示例

    目录 goroutine 泄漏和避免泄漏的最佳实践 什么是goroutine泄漏? 原因分析 伪代码 有什么方法可以解决这个问题? goroutine 泄漏和避免泄漏的最佳实践 Go的奇妙之处在于,我们可以使用goroutines和channel轻松地执行并发任务.如果在生产环境中使用goroutines和channel,但是不了解它们的行为方式,会造成一些严重的影响. 好吧,我们就面临着这样的影响,我们在goroutines中出现了泄漏,导致应用服务器随着时间的推移而膨胀,消耗了大量的CPU和

  • c# 实现语音聊天的实战示例

    一.语音聊天说专业点就是即时语音,是一种基于网络的快速传递语音信息的技术,普遍应用于各类社交软件中,优势主要有以下几点: (1)时效性:视频直播会因为带宽问题有时出现延迟高的问题,而语音直播相对来说会好很多,延迟低,并且能够第·一时间与听众互动,时效性强. (2)隐私性:这一点体现在何处,如主播不想暴露自己的长相,或者进行问题回答是,没有视频的话会让主播感到更安心,所以语音直播隐私性更强. (3)内容质量高:因为语音直播不靠"颜值"只有好的内容才能够吸引用户,所以语音直播相对来说内容质

  • Opencv 图片的OCR识别的实战示例

    一.图片变换 0.导入模块 导入相关函数,遇到报错的话,直接pip install 函数名. import numpy as np import argparse import cv2 参数初始化 ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", required = True, help = "Path to the image to be scanned") arg

  • Redis 抽奖大转盘的实战示例

    目录 1. 项目介绍 2. 项目演示 3. 表结构 4. 项目搭建 4.1 依赖 4.2 YML配置 4.3 代码生成 4.4 Redis 配置 4.5 常量管理 4.6 业务代码 4.7 总结 5. 项目地址 1. 项目介绍 这是一个基于Spring boot + Mybatis Plus + Redis 的简单案例. 主要是将活动内容.奖品信息.记录信息等缓存到Redis中,然后所有的抽奖过程全部从Redis中做数据的操作. 大致内容很简单,具体操作下面慢慢分析. 2. 项目演示 话不多说,

  • Spring WebClient实战示例

    目录 WebClient实战 服务端性能对比 Spring WebFlux Spring MVC 客户端性能比较 webclient resttemplate(不带连接池) resttemplate(带连接池) webclient连接池 webclient 的HTTP API 小结 WebClient实战 本文代码地址:https://github.com/bigbirditedu/webclient Spring Webflux 是 Spring Framework 5.0 的新特性,是随着当

  • SpringBoot Security从入门到实战示例教程

    目录 前言 入门 测试接口 增加依赖 自定义配置 配置密码加密方式 配置AuthenticationManagerBuilder 认证用户.角色权限 配置HttpSecurity Url访问权限 自定义successHandler 自定义failureHandler 自定义未认证处理 自定义权限不足处理 自定义注销登录 前后端分离场景 提供登录接口 自定义认证过滤器 鉴权 1.注解鉴权 2.自定义Bean动态鉴权 3.扩展默认方法自定义扩展根对象SecurityExpressionRoot 登出

  • vue3 vite异步组件及路由懒加载实战示例

    目录 引言 一.前言 1-1.三点变化: 1-2.引入辅助函数defineAsyncComponent的原因: 二.Vue 2.x与Vue 3.x定义比较 2-1.异步组件/路由定义比较 2-2.声明方式比较 2-3.异步组件加载函数返回比较 三.Vue3实践 3-1.路由懒加载实现 3-2.异步组件实现 四.总结 引言 在 Vue2 中,异步组件和路由懒加载处理使用 import 就可以很轻松实现.但是在Vue 3.x 中异步组件的使用与 Vue 2.x 完全不同了.本文就详细讲讲vue3中异

  • node强缓存和协商缓存实战示例

    目录 前言 什么是浏览器缓存 优点 强缓存 Expires Cache-Control 协商缓存 ETag.If-None-Match node实践 koa启动服务 创建项目 koa代码 原生koa实现简易静态资源服务 强缓存验证 设置Expire 协商缓存验证 小结 总结 前言 浏览器缓存是性能优化非常重要的一个方案,合理地使用缓存可以提高用户体验,还能节省服务器的开销.掌握好缓存的原理和并合理地使用无论对前端还是运维都是相当重要的. 什么是浏览器缓存 浏览器缓存(http 缓存) 是指浏览器

  • Vue3中Vuex状态管理学习实战示例详解

    目录 引言 一.目录结构 二.版本依赖 三.配置Vuex 四.使用Vuex 引言 Vuex 是 Vue 全家桶重要组成之一,专为 Vue.js 应用程序开发的 状态管理模式 + 库 ,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 一.目录结构 demo/ package.json vite.config.js index.html public/ src/ api/ assets/ common/ components/ store/ index.

  • Dapr+NestJs编写Pub及Sub装饰器实战示例

    目录 系列 Dapr JavaScript SDK 安装 结构 实战 Demo 源码 准备环境和项目结构 注入 Dapr 赖项 配置 Dapr 组件(rabbitMQ) API/Gateway 服务 内部监听微服务 @DaprPubSubscribe 装饰器 运行应用程序 Dapr 是一个可移植的.事件驱动的运行时,它使任何开发人员能够轻松构建出弹性的.无状态和有状态的应用程序,并可运行在云平台或边缘计算中,它同时也支持多种编程语言和开发框架.Dapr 确保开发人员专注于编写业务逻辑,不必分神解

随机推荐