Go 第三方库引起的线上问题、如何在线线上环境进行调试定位问题以及golang开发中各种问题精华整理总结

news2024/11/16 9:23:43

Go 第三方库引起的线上问题、如何在线线上环境进行调试定位问题以及golang开发中各种问题精华整理总结。

01 前言

在使用 Go 语言进行 Web 开发时,我们往往会选择一些优秀的库来简化 HTTP 请求的处理。其中,go-resty 是一个被广泛使用的 HTTP 客户端。正是因为使用了 go-resty 库,才有今天想要分享的主题:go-resty 在处理 HTTP 307/308 重定向状态码时的问题及其解决方案。

02 HTTP 状态码

相信大家都听过 HTTP 302 状态码,但是否都了解过 HTTP 307/308 状态码。让我们分别看看 HTTP 302、307 和 308 状态码的定义及其之间的区别:

  1. HTTP 302:
    名称:Found 或 Temporary Redirect。
    描述:这是一个临时重定向,意味着请求的资源已经临时移动到了一个新的 URL。此状态码表示原始的请求方法应该保留使用于新的 URL。但在实际应用中,很多客户端,特别是浏览器,可能会将 POST 转换为 GET。
    应用:在实际应用中,很多时候 302 重定向被用于 URL 短链接、登录状态验证等场景。

  2. HTTP 307:
    名称:Temporary Redirect。
    描述:和 HTTP 302 类似,HTTP 307 也表示资源已经临时移动到另一个 URL。但与 302 的主要区别是,HTTP 307 明确规定客户端必须不改变请求方法进行重定向。例如,如果原始请求是 POST,那么重定向请求也必须是 POST。
    应用:由于 307 更加明确,如果需要确保请求方法在重定向时不被更改,应使用 307 而不是 302。

  3. HTTP 308:
    名称:Permanent Redirect。
    描述:与 HTTP 301 (Permanent Redirect) 类似,HTTP 308 表示请求的资源已经被永久移动到了新的 URL。但与 301 的区别在于,308 明确规定客户端必须保持原始请求的方法不变。例如,POST 请求在 308 重定向后仍应为 POST。
    应用:当资源永久移动且希望确保请求方法不被更改时,应使用 308。

主要区别:
永久性 vs. 临时性:302 和 307 是临时重定向,而 308 是永久重定向。
请求方法的保留:302 不保证原始请求方法在重定向后仍会被保留;307 和 308 保证在重定向后请求方法不变。

03 项目背景

由于后台服务多区域部署,需要涉及到2个以上的环境数据打通,当请求到达 A 区域时,发现数据在 B 区域,会使用 HTTP 307 进行临时重定向,跳转到 B 区域。

在这里插入图片描述
正常请求是上面这样子的,下面来看看 go-resty 引起的问题:
当多个请求同时访问区域 A 时,存在请求 A 需要 307 重定向,而请求 B 也同时访问了区域 A,此时,由于 go-resty 重用缓存区导致的重定向请求 A 拿到了 请求 B的数据。
在这里插入图片描述

04 issue

go-resty 最新版本是tag:v2.7.0,这个版本已经是2021年11月份发布的,而在2022年5月份,就有人在 github 上提出了这个 issue,并进行了修复,最终也合并到了 master 主分支了,然而至今并没有发布新版本,这就导致线上出了这个重定向问题。
修复错误重用缓冲区导致的重定向请求正文与源请求正文不匹配

https://github.com/go-resty/resty/pull/568
下面,我们来看看这个 mr 修复了哪里的代码
在这里插入图片描述
可以看到,原本直接通过 bytes.NewBuffer(r.bodyBuf.Bytes()) 直接返回了,修复的方式是通过 io.Copy 来复制一个新的缓存区,解决重用的问题。

最终提交的代码也编写了单元测试来验证结果是否准确:


func TestPostRedirectWithBody(t *testing.T) {
  ts := createPostServer(t)
  defer ts.Close()

  targetURL, _ := url.Parse(ts.URL)
  t.Log("ts.URL:", ts.URL)
  t.Log("targetURL.Host:", targetURL.Host)

  c := dc()
  wg := sync.WaitGroup{}
  for i := 0; i < 100; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      resp, err := c.R().
        SetBody([]byte(strconv.Itoa(newRnd().Int()))).
        Post(targetURL.String() + "/redirect-with-body")
      assertError(t, err)
      assertNotNil(t, resp)
    }()
  }
  wg.Wait()
}

05 总结

当线上环境出现 307 重定向请求且并发量急剧变大时,同一时间出现碰撞的概率就变大了,就出现了串数据的情况,这是非常严重的问题。


总结Golang编程时常见的错误和问题:

unrecognized import path "golang.org/x/…
golang 在 github 上建立了一个镜像库,下载github上的镜像库放入GOPATH下即可

mkdir -p $GOPATH/src/golang.org/x
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/sync.git
git clone https://github.com/golang/crypto.git
git clone https://github.com/golang/sys.git

go包管理代理网址无法访问:proxy.golang.org
解决:

go env -w GOPROXY=https://goproxy.cn

verifying module: invalid GOSUMDB: malformed verifier id
解决:

关闭包验证

go env -w GOSUMDB=off

设置go env -w GOPROXY=***warning: go env -w GOPROXY=… does not override conflicting OS environment variable

解决方案:

unset GOPROXY

再重新设置即可

类型不匹配
在 Golang 中,类型的匹配是至关重要的。关于 Golang 类型匹配的一个经典错误是在比较两个不同类型的变量。例如,将字符串类型的变量与整数类型的变量进行比较。要避免这种错误,应该始终确保比较的是相同类型的变量。或者,在进行类型转换时,需确保变量类型符合要求。

空指针错误
在 Golang 中,指向空值的指针是非常常见的错误。这通常在代码中的资源管理方面出现问题,如打开和关闭文件,连接到数据库等。当处理指针变量时,应该始终确保其指向一个有效的对象。此外,对于指针变量的使用应该小心谨慎,以避免出现在编程过程中不同的指针指向同一个对象的情况。

内存分配错误
内存分配是计算机程序所必需的一项任务,但是在 Golang 中,这个任务可能会出现问题。最常见的错误是未能释放内存。在使用 Golang 中的 new() 或 make() 函数的时候,应该注意释放内存,以确保程序不会因为内存泄漏而崩溃。

大规模并发处理问题
在 Golang 中,大规模的并发处理是其一个显著的特点,但这也带来了一些问题。并发程序的错误处理比串行程序更为棘手,因为开发者需要在多个并发执行的地方确定错误。在开发 Golang 并发程序时,应该设计清晰的错误管理方案,并充分利用 Golang 提供的资源管理功能进行并发控制。

死锁
死锁是一个常见的 Golang 错误。大多数死锁发生在程序未能释放锁或加锁太多的情况下。更糟糕的是,用户可能需要重新启动程序来解决死锁的问题。为避免死锁,可以使用并发控制的技术确保子程序无法同时访问相同的资源。

文件处理问题
在 Golang 中,文件处理是经常进行的任务。常见的文件处理错误包括文件无法打开,读取和写入错误。为避免这些错误,应该选择一个完全合适的文件操作函数,并检查文件路径是否正确。同时,应该在解析文件时检查文件的格式,以确保文件中的数据已正确转换。

数组和切片的越界问题
在 Golang 中,数组和切片的越界访问是另一个可能的错误。由于数组和切片的索引是从 0 开始计数的,所以如果开发者在索引时错误地写入负数、超出边界的正数或者不同于整数的索引,就可能出现越界错误。为避免这个问题,开发者可以使用循环来遍历数组和切片,并始终确保数组和切片的索引值是有效的。

总结

以上是 Golang 中常见的错误。在 Golang 的学习过程中,这些错误可能会使新手们不知所措,但只要有足够的耐心和学习态度,并遵循最佳实践,就可以轻松地避免这些错误。


最近线上有个服务突然卡死了,看起来是进程启动了,但是请求接口则反应连接不上,看错误日志也没有什么异常信息,对于这种线上服务卡死的问题,一直没有丰富经验来排查。看网上一些人各自gdb,systemtap之类的工具调试线上问题,很是羡慕。

我先是用 strace -p pid 来看问题,发现有时候又处于wait futex,有时候有正常的刷刷刷一大堆信息。按照我之前的经验,出现wait futex,一般是等待channel出现,如果一直处于wait futex,则说明进程卡死在某个channel上了。但是现在这个卡死的进程并没有一直处于wait futex,说明可能不是卡死在某个channel上,至少还有其他goroutine在正常活动中。

进程没有卡死在wait futex,但是又不能监听端口处理请求,说明要么是在启动阶段,还未到监听端口这一步就出现死锁卡死了,要么就是监听端口的那部分代码出问题了。

以前我只用过strace -p 来判断是否死锁了,更早之前还试过用gdb调试python服务,但是go的服务,该用什么工具,还不太了解,理论上用strace和gdb也能排查到一些问题的,但是这两个工具都无法识别goroutine,调试起来很难。

后来同事介绍了 dlv,也就是delve这款工具,专门为go语言打造的调试工具,可以识别goroutine等。搜了下官方文档和网上的各自介绍文章,写得都不太实用,好多文章就是玩具级别的介绍,实际生产环境中使用很多细节都没说明。但是只能硬着头皮看一点说明介绍就试用一下,不过这种边解决问题边学习的方法,效率还是很高的。最终也是依靠dlv这款工具定位到了死锁之处以及引起问题所在。

安装:
参照官方文档,先执行

go install github.com/go-delve/delve/cmd/dlv@latest  

这样子会安装 dlv 到你的GOPATH目录下,里面会生成一个dlv的可执行文件。然后将这个dlv上传到服务器上就可以使用了。

当然也可以先拉取代码,然后在代码目录执行 go build,生成可执行文件。

attach和coredump:
有了dlv之后,第一步就是要让dlv可以接触上卡死的进程,也就是使用 attach指令把dlv附着在卡死的进程上。

首先,使用 ps -ef | grep xxxx 查看卡死的进程的pid,假设卡死的进程pid=1234,然后执行 ./dlv attach 1234 就 可以附着到卡死的进程上了。如果进程是使用非当前用户启动的话,则要加上 sudo 才行。

连接上进程之后,不管三七二十一,要赶紧导出coredump,因为一旦出现什么问题,导致进行死掉或者卡死状态解除了,就无法继续排查调试了。有了coredump,则可以一直慢慢来调试。

在dlv里,执行 dump ~/sample.core , 就会把当前整个进程的状态都记录下来,那样子就可以下载coredump文件,在自己的开发机上逐步调试问题所在,又同时不影响线上服务。

在自己的开发机上查看coredump文件,还需要提供和线上运行程序一致的二进制文件。比如你线上运行的是 v0.22 版本,要么你就直接从线上下载这个二进制文件,要么你可以把代码切换到v0.22,然后进行编译,同时编译参数不能有任何的变化。只有保证二进制文件与coredump对应的完全一致,才可以使用。

假设二进制文件名问:sample_bin,则在开发机上执行 dlv core sample_bin sample.core 就能加载到coredump文件进行排查问题了。

attach和coredump的区别,主要是attach是附着到一个运行中的实例中,是可以执行函数调用,设置断点等操作的,而coredump就只能静态的去查看栈帧、内存等信息了。因此,如果线上出现了类似卡死的故障,先是保存好coredump,如果可以的话,就直接在线上attach调试,这样子会更容易排查问题。

排查:

排查故障,主要用到的就几个指令:
bt,查看栈帧
list,查看当前栈帧运行到的代码
grs,查看当前所有的goroutine列表
gr 4,切换到第四个groutine
print xxx,打印当前栈帧中的变量值
args,打印函数参数值
locals,打印当前栈帧的local 变量

总结处理过程:

grs 查看goroutine列表
优先切换到 gr 1
执行bt指令看栈帧,查找到业务相关代码
frame xxx,切换到指定栈帧
list 查看当前栈帧运行到的代码
print xxx 查看变量值

遇到的问题:
线上调试的时候,想执行 list 查看当前栈帧的代码,出现了类似 Command failed: open /path/to/the/mainfile.go: no such file or directory 的错误。即使下载了coredump文件和二进制到自己的开发机上进行调试,也会出现这个问题,因为二进制是另一个同事编译的,Go的编译默认会带上代码的完整路径,而同事的GOPATH和我是不一样的,这个要怎么解决呢?

终极解决办法,编译参数加上 trimpath:go build -gcflags=-trimpath= G O P A T H − a s m f l a g s = − t r i m p a t h = GOPATH -asmflags=-trimpath= GOPATHasmflags=trimpath=GOPATH 这样子编译的代码路径,就会去掉你本人的文件夹路径,使用相对于GOPATH的路径了,别人在你编译的二进制上获取的coredump,也可以正常调试了。

如果编译前没有加上这个指令 ,则可以在 dlv 里执行 : config substitute-path otherPath mypath。

如果这个path弄错了,怎么清除弄错的配置呢?直接执行 config substitute-path otherPath 即可

注意 mypath一定要填绝大路径,使用 ~/ 这样子的路径会有问题。

config配置完之后,如果需要永久的,可以执行 config -save,这样子配置会默认保存在 ~/.config/dlv 目录里面。

不过另一个问题也难以解决,服务器上一般是不会带上Go代码及代码块的,所以在服务器上进行调试的时候,list指令就用不上了,很难排查问题。最终还是只能用coredump在开发机上排查调试?

其实也可以开启调试服务,线上开启,在开发机上用dlv连接线上开启的服务,就可以进行调试了。不过这个也挺麻烦的。
还遇到一个问题,是print变量的时候,如果是字符串变量,好像默认只打印前64个字符,这明显不够,可以通过设置:config max-string-len 99999 让打印的字符串长度加长。此时可以执行 config -save 把这个配置永久保存下来。

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

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

相关文章

还在为Compose组件管理苦恼?Jetpack Compose的版本控制清单(BOM)扫除你的苦恼

还在为Compose组件管理苦恼&#xff1f;Jetpack Compose的版本控制清单&#xff08;BOM&#xff09;扫除你的苦恼 Jetpack Compose通过简化和提高效率&#xff0c;彻底改革了Android UI开发。其中一个有助于此的功能是Jetpack Compose版本控制清单&#xff08;BOM&#xff09;…

vue2 组件组成部分,组件通信,进阶语法

一、学习目标 1.组件的三大组成部分&#xff08;结构/样式/逻辑&#xff09; ​ scoped解决样式冲突/data是一个函数 2.组件通信 组件通信语法父传子子传父非父子通信&#xff08;扩展&#xff09; 4.进阶语法 v-model原理v-model应用于组件sync修饰符ref和$refs$nextTic…

3、当今的企业架构分析

3、当今的企业架构分析 3、分库分表水平拆分&#xff08;MySQL集群&#xff09; 因为一个数据库装不下了&#xff0c;需要分库分表&#xff0c;读写分离&#xff0c;主从复制&#xff0c;主节点M与从节点s组成了一个数据库的集群&#xff0c;组成了一个小的单元&#xff0c;前端…

Axure RP软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Axure RP是Axure公司开发的一款原型设计工具&#xff0c;广泛应用于产品设计和UI/UX设计领域。以下是Axure RP软件的主要特点和功能&#xff1a; 原型设计&#xff1a;Axure RP提供了丰富的界面元素和交互元素&#xff0c;用户…

django的简易的图书管理系统jsp书店进销存源代码MySQL

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 django的简易的图书管理系统 系统有1权限&#xff1a…

使用WebDriver采样器将JMeter与Selenium集成

第一步&#xff1a; 在JMeter中添加Selenium / WebDriver插件 第二步&#xff1a; 创建一条测试计划–添加线程组 添加配置元素 - jpgc - WebDriver Sampler 添加配置元素 - jpgc - Chrome Driver Config 并且添加监听器查看结果树 第三步&#xff1a; 下载 chromedriver…

时序预测 | MATLAB实现Attention-GRU时间序列预测(注意力机制融合门控循环单元,TPA-GRU)

时序预测 | MATLAB实现Attention-GRU时间序列预测----注意力机制融合门控循环单元&#xff0c;即TPA-GRU&#xff0c;时间注意力机制结合门控循环单元 目录 时序预测 | MATLAB实现Attention-GRU时间序列预测----注意力机制融合门控循环单元&#xff0c;即TPA-GRU&#xff0c;时…

煤矿皮带运输智能监控算法 opencv

煤矿皮带运输智能监控算法通过opencvpython深度学习算法网络模型&#xff0c;煤矿皮带运输智能监控算法实时监测皮带运输过程中的各种异常情况&#xff0c;如跑偏、撕裂、堆料异常等&#xff0c;一旦检测到异常情况&#xff0c;立即发出告警并采取相应的措施&#xff0c;以保障…

搭建开发环境-WSL+Ubuntu(一键搭建开发环境)

概述 所谓工欲善其事必先利其器&#xff0c;搭环境往往是开发过程中卡出很多初学者的拦路虎。 对于很多老鸟来说&#xff0c;很多东西都已经习惯成自然&#xff0c;也就没有刻意和初学者说。但对于很多初学者&#xff0c;却是受益良多。 这个系列&#xff0c;先从操作系统开始…

MySQL数据库——约束(概述、约束演示、外键约束、删除/更新行为)

目录 概述 约束演示 表格创建 验证 图形化界面约束 外键约束 概念 语法 外键删除/更新行为 总结 概述 概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中数据。目的&#xff1a;保证数据库中数据的正确、有效性和完整性。分类&#xff1…

有哪些常人不知道的赚钱方法?

在目前互联网时期&#xff0c;如果说还有什么挣钱方法让别人不知道&#xff0c;那一定是赚大钱或者违法乱纪的挣钱渠道。但是对于普通人而言&#xff0c;确实是有一些挣钱的方法不被其了解&#xff0c;我这里就介绍几个&#xff0c;供大家一起探讨。 第一个&#xff1a;知识变…

TCP/IP五层模型、封装和分用

1.网络通信基础2.协议分层OSI七层协议模型TCP/IP五层/四层协议模型【重点】 3. 封装&分用 1.网络通信基础 IP地址&#xff1a;表示计算机的位置&#xff0c;分源IP和目标IP&#xff1b;举个例子&#xff1a;买快递&#xff0c;商家从上海发货&#xff0c;上海就是源IP&…

爱奇艺数据湖实战 - 基于数据湖的日志平台架构演进

01 背景 为了满足公司内日志实时查询分析的需求&#xff0c;爱奇艺大数据团队自研了Venus日志服务平台&#xff0c;负责爱奇艺各服务日志的采集、存储、处理、分析等场景。早期采用基于ElasticSearch的存储分析架构&#xff0c;随着数据规模的不断扩大&#xff0c;出现了成本高…

C语言基础之——指针(上)

前言&#xff1a;小伙伴们又见面啦&#xff01;本期内容&#xff0c;博主将展开讲解有关C语言中指针的上半部分基础知识&#xff0c;一起学习起来叭&#xff01;&#xff01;&#xff01; 目录 一.什么是指针 二.指针类型 1.指针的解引用 2.指针-整数 三.野指针 1.野指针…

Day46|动态规划part08:139.单词拆分、多重背包理论基础、背包问题总结

139. 单词拆分 leetcode链接&#xff1a;力扣题目链接 视频链接&#xff1a;你的背包如何装满&#xff1f;| LeetCode&#xff1a;139.单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。注意&#xff1a;不要…

【Linux】目录结构、路径

目录 1. 目录结构 1.1 基本概念 1.2 具体的目录结构 2. 路径 2.1 绝对路径和相对路径 2.2 特殊路径符 1. 目录结构 1.1 基本概念 Linux的目录结构是一个树形结构。 Windows系统可以拥有多个盘符&#xff0c;如 C盘、D盘、E盘。Linux没有盘符这个概念&#xff0c;只有一…

树莓派自带的GPIO串口输出及输出乱码问题解决方案

可以使用树莓派的UART0进行串口输出&#xff0c;具体连接方法如图所示&#xff1a; 连接后可以使用如下代码发送串口数据&#xff1a; import serial import time# 串口初始化 ser serial.Serial(/dev/serial0, 9600, timeout1) # /dev/serial0 是树莓派上默认的串口设备# 发…

图书馆项目Java阅览室管理系统jsp源代码MySQL

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 图书馆项目 系统有1权限&#xff1a;管理员 用所技术…

Scikit-Learn中的特征选择和特征提取详解

概要 机器学习在现代技术中扮演着越来越重要的角色。不论是在商业界还是科学领域&#xff0c;机器学习都被广泛地应用。在机器学习的过程中&#xff0c;我们需要从原始数据中提取出有用的特征&#xff0c;以便训练出好的模型。但是&#xff0c;如何选择最佳的特征是一个关键问…

eslint和prettier格式化冲突

下载插件 ESLint 和 Prettier ESLint 进入setting.json中 setting.json中配置 {"editor.tabSize": 2,"editor.linkedEditing": true,"security.workspace.trust.untrustedFiles": "open","git.autofetch": true,"…