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

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.hqwc.cn/news/87269.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win10家庭版远程桌面补丁_rdp wrapper

RDP Wrapper Library 就是可以帮你在 Windows 7、Windows 8、Windows 10 家庭版中打开远程桌面的工具。 1、把电脑上打开的安全软件与杀毒软件都关掉&#xff0c;因为这个远程桌面补丁会修改系统文件&#xff0c;所以安全软件可能会拦截。 2、下载RDP Wrapper Library补丁压缩…

windows安装mysql8.0.34的压缩包

文章目录 目录 文章目录 前言 一、下载安装包zip格式 二、使用步骤 总结 前言 一、下载安装包zip格式 MySQL :: Begin Your Download 二、使用步骤 解压缩之后在解压之后的目录里创建data和my.ini my.ini内容 # 设置mysql客户端连接服务端时默认使用的端口 port3306#默认…

Wlan——锐捷零漫游网络解决方案以及相关配置

目录 零漫游介绍 一代零漫游 二代单频率零漫游 二代双频率零漫游 锐捷零漫游方案总结 锐捷零漫游方案的配置 配置无线信号的信道 开启关闭5G零漫游 查看配置 零漫游介绍 普通的漫游和零漫游的区别 普通漫游 漫游是由一个AP到另一个AP或者一个射频卡到另一个射频卡的漫…

14-redis

一 Redis概述 1 为什么要用NoSQL 单机Mysql的美好年代 在90年代&#xff0c;一个网站的访问量一般都不大&#xff0c;用单个数据库完全可以 轻松应付。在那个时候&#xff0c;更多的都是静态网页&#xff0c;动态交互类型的网站不多。 遇到问题&#xff1a; 随着用户数的增长…

【Unity】【Amplify Shader Editor】ASE入门系列教程第二课 硬边溶解

黑色为0,白色为1 新建材质&#xff08;不受光照影响&#xff09; 拖入图片 设置 添加节点&#xff1a; 快捷键&#xff1a;K 组合通道&#xff1a;快捷键 V 完成图

设计模式入门笔记

1 设计模式简介 在IT这个行业&#xff0c;技术日新月异&#xff0c;可能你今年刚弄懂一个编程框架&#xff0c;明年它就不流行了。 然而即使在易变的IT世界也有很多几乎不变的知识&#xff0c;他们晦涩而重要&#xff0c;默默的将程序员划分为卓越与平庸两类。比如说&#xff…

不会用这个工具,你的Linux服务器就是个摆设!

大家好&#xff0c;我的网工朋友 在运维这一块&#xff0c;没有工具可谓是寸步难行。 一个好的Linux运维&#xff0c;为了提升自己的工作效率&#xff0c;免不得会找一些适合自己业务需求的工具&#xff0c;用起来工作效率高&#xff0c;工作幸福指数直线上升。 今天整理了几…

每日一题 57. 插入区间

读研了&#xff0c;开始用python刷题 今天的题目是力扣 每日一题 57. 插入区间 难度&#xff1a;中等 思路&#xff1a; 处理新区间起点&#xff0c;要么在两个老区间之间&#xff0c;要么被一个老区间包含处理新区间中点&#xff0c;同起点一样 我的代码如下 class Solut…

大数据处理平台的架构演进:从批处理到实时流处理

文章目录 批处理架构&#xff1a;实时流处理架构&#xff1a;混合架构&#xff1a; &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 ✨收录专栏&#xff1a;大数据系列 ✨文章内容&#xff1a;大数…

数据之美:探索数据可视化设计的奇妙世界

在信息时代的浪潮中&#xff0c;海量的数据正在影响着我们的生活和决策。然而&#xff0c;数据本身虽然有力量&#xff0c;但如何将其有机地呈现给我们&#xff0c;却成为了一个挑战。数据可视化设计应运而生&#xff0c;它不仅让枯燥的数字变得生动&#xff0c;还带来了一场视…

vue 简单实验 v-on html事件绑定

1.代码 <script src"https://unpkg.com/vuenext" rel"external nofollow" ></script> <div id"event-handling"><p>{{ message }}</p><button v-on:click"reverseMessage">反转 Message</but…

React 生命周期新旧对比

前言 React16.4版本之后使用了新的生命周期&#xff0c;它使用了一些新的生命周期钩子&#xff08;getDerivedStateFromProps、getSnapshotBeforeUpdate&#xff09;&#xff0c;并且即将废弃老版的3个生命周期钩子&#xff08;componentWillMount、componentWillReceiveProps…