Go http.Get不需要defer res.Body.Close()

news2024/10/5 14:25:36
前戏:

        go net/http包,必须要手动关闭嘛?非也。线上程序为啥协程数量直线上升,因为使用的姿势不对,请换个姿势。

干货:
  1.  手动关闭,释放资源 defer res.Body.Close()     (这是一个好习惯,要保持)
  2. 不想手动关闭,请读取完Body: io.ReadAll(res.Body)

以上两点,请任意选择一项即可。但是,如果一个都不喜欢,不想要,不想实现。那对不起,让你见识一下内存的飙升,协程数量的直线递增。


               客观请留步。。。以下为长篇踩雷故事,可忽略(摸鱼党忽略这一句,请继续往下看)

背景:惊心动魄,血压飙升,上图

 

 很清晰,一眼就能看出来。这量起来了,要赚钱了。呵呵~~

赶紧查问题吧,TCP连接这么高,没有释放,必然是没有关闭啊,pprof分析一下吧

 协程泄漏了。。。。。

问题:TCP连接过高+协程泄漏

最小复现代码:

func IsUploadSuccess(url string) bool {
	if len(url) == 0 {
		return false
	}

	res, err := http.Get(url)
	if err != nil {
		return false
	}

	if res.StatusCode != 0 && res.StatusCode != http.StatusOK {
		return false
	}

	if res.ContentLength <= 512 {
		return false
	}

	return true
}

       判断录音文件是否上传成功。ChatGPT 亲口告诉我:http.Get 不显示执行  defer res.Body.Close() 也行。我信了,程序也能跑起来,数据验证也正确,没问题,AI就是强大。

       但是,线上异常了OOM。。。。。

严查到底,深究原因:

 一、/net/http/transport.go:1750     

 这两个小可爱,长的真Loop

二、继续查0

/net/http/transport.go:2091

 这不就是死循环嘛,什么时候跳出呢?

三、继续查1

 

 

 pprof分析之后,发现协程泄漏就是 readLoop(),什么时候才跳出死循环,就是上边的三处。

找到这里,就真香大白了。和上边的干货总结一样:

  1. 手动关闭 defer res.Body.Close()
  2. 三处:bodyEOF。读取完毕之后,也跳出循环了。(沾花惹草,不负责,这还能行,分分钟让你下不了台)

/net/http/transport.go:2183

 


                                               回归业务,如何解决

解决:

  1.   主动关闭
  2. 读取完body (要负责,取了就要用,不要浪费)
func IsUploadSuccess(url string) bool {
	if len(url) == 0 {
		return false
	}

	res, err := http.Get(url)
	if err != nil {
		return false
	}

	/*
		// 方案一:手动关闭
		defer func() {
			if err := res.Body.Close(); err != nil {
				fmt.Printf("close err:%+v \r\n", err.Error())
			}
		}()
	*/

	// 方案二:读取body
	//_, _ = io.ReadAll(res.Body)

	if res.StatusCode != 0 && res.StatusCode != http.StatusOK {
		return false
	}

	if res.ContentLength <= 512 {
		return false
	}

	return true
}

其他优化:

        取了还不用,不如不取。

         http.Get(url) ==> http.Head(url)

复现代码:

package main

import (
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"runtime"
	"sync"
	"time"
)

func main() {

	fmt.Printf("start\r\n")
	GetMem()

	// 模拟请求10次
	for i := 0; i < 10; i++ {
		DoLogic(int32(i))

		//time.Sleep(time.Minute)
	}

	GetMem()

	fmt.Printf("end\r\n")
}

func DoLogic(s int32) {
	url := "https://www.baidu.com/"

	var i int32
	var wg sync.WaitGroup
	for {

		if i > 1000 {
			break
		}

		wg.Add(1)

		go func() {
			defer wg.Done()

			IsUploadSuccess(url)
		}()

		time.Sleep(time.Millisecond)

		i++
	}

	nums := runtime.NumGoroutine()
	fmt.Printf("进程:%d,NumGoroutine:%d\r\n", s, nums)

	GetMem()

	wg.Wait()

	return
}

func GetMem() {
	var mem runtime.MemStats
	runtime.ReadMemStats(&mem)

	allocatedMib := bytesToMib(mem.Alloc)
	totalAllocatedMib := bytesToMib(mem.TotalAlloc)
	heapInUseMib := bytesToMib(mem.HeapAlloc)
	heapIdleMib := bytesToMib(mem.HeapIdle)
	heapReleasedMib := bytesToMib(mem.HeapReleased)

	fmt.Printf("Allocated memory: %.2f MiB\r\n", allocatedMib)
	fmt.Printf("Total allocated memory: %.2f MiB\r\n", totalAllocatedMib)
	fmt.Printf("Heap memory in use: %.2f MiB\r\n", heapInUseMib)
	fmt.Printf("Heap memory idle: %.2f MiB\r\n", heapIdleMib)
	fmt.Printf("Heap memory released to OS: %.2f MiB\r\n", heapReleasedMib)
}

func bytesToMib(bytes uint64) float64 {
	return float64(bytes) / (1024 * 1024)
}

func IsUploadSuccess(url string) bool {
	if len(url) == 0 {
		return false
	}

	res, err := http.Get(url)
	if err != nil {
		return false
	}

	/*
		// 方案一:手动关闭
		defer func() {
			if err := res.Body.Close(); err != nil {
				fmt.Printf("close err:%+v \r\n", err.Error())
			}
		}()
	*/

	// 方案二:读取body
	//_, _ = io.ReadAll(res.Body)

	if res.StatusCode != 0 && res.StatusCode != http.StatusOK {
		return false
	}

	if res.ContentLength <= 512 {
		return false
	}

	return true
}

【end】

我为人人,人人为我,美美与共,天下大同。

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode[剑指Offer51]数组中的逆序对

难度&#xff1a;Hard 题目&#xff1a; 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。 示例 1: 输入: [7,5,6,4] 输出: 5 限制&#xff1a; 0 < 数组…

C++【STL】queue和deque 容器详解

C【STL】queue和deque 容器详解 一级目录 二级目录 三级目录 1. 什么是queue容器&#xff1f; Queue是一种先讲先出( First In First Out,FIFO )的数据结构&#xff0c;它有两个出口。 queue模版类的定义在头文件中。 include 定义queue对象的示例代码如下∶ queue<i…

【华为c# OD机考参考答案】01---IPv4地址转换成整数

题目 1、题目 01---IPv4地址转换成整数2、解图思路 1、IP地址转为二进制 2、二进制转十进制 3、注意事项 1、IP地址的范围判断 2、空字符串判断 3、非法字符判断 4、考点 1、string的split 、convert等相关用法 2、正则表达式 3、进制转换 4、理解32位整数的意思 5、代码 判…

2023华为OD统一考试(B卷)题库清单(持续收录中)以及考点说明

目录 专栏导读2023 B卷 “新加题”&#xff08;100分值&#xff09;2023Q2 100分2023Q2 200分2023Q1 100分2023Q1 200分2022Q4 100分2022Q4 200分牛客练习题 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 刷的越多&…

【实用工具】-Git+GitLab

1. Git介绍 1.1 什么是Git&#xff1f; Git是一个分布式版本控制系统&#xff0c;用于跟踪和管理项目代码的变化。它是由Linus Torvalds于2005年创建的&#xff0c;旨在帮助开发者更好地协作、追踪代码的更改&#xff0c;并轻松地回滚到之前的版本。 Git相比传统的集中式版本…

网络安全 Day17-计算机网络知识02

计算机网络知识02 1. 交换机2. 网络知识名词3. ARP3.1 ARP通讯原理3.2 arp欺骗3.3 ARP欺骗与预防3.4 排查ARP病毒 4. 路由器 1. 交换机 什么是交换机 实现一个网络内多台主机之间通讯的设备用于电信号转发和放大的网络设备 常见的交换机&#xff1a;以太网交换机&#xff0c;电…

【Java】SpringBoot下写一个全局捕获异常的怎么实现?

文章目录 前言一、全局异常处理方式一1.1 自定义全局异常类1.2 手动抛出异常1.3 测试打印 二、全局异常处理方式二2.1 定义基础接口类2.2 定义枚举类2.3 自定义异常类2.4 自定义数据传输2.5 自定义全局异常处理2.6 测试代码 总结 前言 在日常项目开发中&#xff0c;异常是常见…

Nginx 用户指南:安装、配置和基本使用

前言 本用户指南将帮助您了解如何安装、配置和基本使用 Nginx。Nginx 是一款高性能的开源 Web 服务器和反向代理服务器&#xff0c;具有强大的性能和灵活性&#xff0c;可以满足各种 Web 服务器需求。 安装 Nginx a. 在 Ubuntu 上安装&#xff1a; $ sudo apt update $ sud…

(转载)神经网络遗传算法函数极值寻优(matlab实现)

本博客的完整代码获取&#xff1a; https://www.mathworks.com/academia/books/book106283.html 1案例背景 对于未知的非线性函数,仅通过函数的输入输出数据难以准确寻找函数极值。这类问题可以通过神经网络结合遗传算法求解,利用神经网络的非线性拟合能力和遗传算法的非线性…

【前端动画】科技感扫描效果 css动画animation

扫描出现动画 参考了网友写的二维码扫描 <template><div><div class"scan-content"><img style"width: 2rem;height: 2rem;" src"../../assets/images/eye.png" alt"" /><div class"line">…

四个现实中的商品样例,帮助你理解如何使用css【前端CSS入门样例】

实现商品列表 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>商品列表图片</title><style>.row > img {width: 15%;}</style></head><body><div class"row"><img sr…

【NOSQL】MongoDB

MongoDB MongoDB简介体系结构Linux系统中的安装启动和连接&#xff08;1&#xff09;先到官网下载压缩包——>解压——>重命名新建几个目录&#xff0c;分别用来存储数据和日志&#xff1a;新建并修改配置文件官网下载MongoDB Compass MongoDB简介 MongoDB是一个开源、高…

从零开始学习 Java:简单易懂的入门指南(二)

Java基础语法 1. 注释1.1使用的技巧1.2注意点 2. 关键字2.1 概念2.2 第一个关键字class 3. 字面量3.1区分技巧 4. 变量4.1 什么是变量&#xff1f;4.2 变量的定义格式4.2.1 格式详解4.2.2 常用的数据类型4.2.3 变量的注意事项 4.3 变量的练习 5. 数据类型5.1 Java语言数据类型的…

CentOS7系统MBR、GRUB2、内核启动流程报错问题

目录 &#x1f969;Linux启动流程 &#x1f969;MBR修复 &#x1f36d;1、模拟损坏 &#x1f36d;2、重启测试 &#x1f36d;3、修复MBR &#x1f36d;4、测试系统 &#x1f969;GRUB2修复 &#x1f36d;1、模拟损坏 &#x1f36d;2、修复GRUB2 &#x1f36d;3、测试系统 &…

【C++】string类模拟实现

&#x1f680; 作者简介&#xff1a;一名在后端领域学习&#xff0c;并渴望能够学有所成的追梦人。 &#x1f681; 个人主页&#xff1a;不 良 &#x1f525; 系列专栏&#xff1a;&#x1f6f8;C &#x1f6f9;Linux &#x1f4d5; 学习格言&#xff1a;博观而约取&#xff0…

基于 Docker + Nginx + Gitlab-runner 实现前端自动化部署流程

本篇会用到Docker&#xff0c;Gitlab-runner等相关工具&#xff0c;如果对其不是特别了解&#xff0c;可以参考下相关文档&#xff1a; GitLab RunnerDocker 快速入门CI/CD&#xff1a;持续集成/持续部署 在早期部署前端项目时&#xff0c;我们通常会通过ftp把前端代码直接传…

AcWing 239. 奇偶游戏—带边权并查集、带扩展域并查集解法

AcWing 239. 奇偶游戏—带边权并查集、扩展域 问题带边权并查集解法扩展域解法并查集所要掌握的知识技能如下图所示 问题 题目链接: AcWing 239. 奇偶游戏 问题描述 分析 这道题比较有意思&#xff0c;可以由前缀和的思想来解决&#xff0c;[l,r]为偶数&#xff0c;说明[0,l…

CentOS8.5 安装时配置镜像源

CentOS8.5 安装时配置镜像源 阿里云镜像地址 http://mirrors.aliyun.com/centos/8-stream/BaseOS/x86_64/os/ 安装时录入镜像源 点击Done等待… 搞定

day30-Auto Text Effect(自动文本吐字效果)

50 天学习 50 个项目 - HTMLCSS and JavaScript day30-Auto Text Effect&#xff08;自动文本吐字效果&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewpo…

玩转 PI 系列-如何在 Rockchip Arm 开发板上安装 Docker Tailscale K3s Cilium?

概述 618 买了几个便宜的 Purple PI OH 开发板 (500 块多一点买了 3 个&#x1f911;), 这个开发板类似树莓派&#xff0c;是基于 Rockchip&#xff08;瑞芯微&#xff09; 的 rx3566 arm64 芯片。如下&#xff1a; 买来是用作家庭服务器或家庭实验室的。主要考虑就是&#xf…