河南专业网站建设创新中心,国外短视频软件,易语言做试用点击网站,河南省信息网Go 第三方库引起的线上问题、如何在线线上环境进行调试定位问题以及golang开发中各种问题精华整理总结。
01 前言
在使用 Go 语言进行 Web 开发时#xff0c;我们往往会选择一些优秀的库来简化 HTTP 请求的处理。其中#xff0c;go-resty 是一个被广泛使用的 HTTP 客户端。…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 状态码的定义及其之间的区别 HTTP 302: 名称Found 或 Temporary Redirect。 描述这是一个临时重定向意味着请求的资源已经临时移动到了一个新的 URL。此状态码表示原始的请求方法应该保留使用于新的 URL。但在实际应用中很多客户端特别是浏览器可能会将 POST 转换为 GET。 应用在实际应用中很多时候 302 重定向被用于 URL 短链接、登录状态验证等场景。 HTTP 307: 名称Temporary Redirect。 描述和 HTTP 302 类似HTTP 307 也表示资源已经临时移动到另一个 URL。但与 302 的主要区别是HTTP 307 明确规定客户端必须不改变请求方法进行重定向。例如如果原始请求是 POST那么重定向请求也必须是 POST。 应用由于 307 更加明确如果需要确保请求方法在重定向时不被更改应使用 307 而不是 302。 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 最新版本是tagv2.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.gitgo包管理代理网址无法访问proxy.golang.org 解决
go env -w GOPROXYhttps://goproxy.cnverifying module: invalid GOSUMDB: malformed verifier id 解决
关闭包验证
go env -w GOSUMDBoff设置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 的学习过程中这些错误可能会使新手们不知所措但只要有足够的耐心和学习态度并遵循最佳实践就可以轻松地避免这些错误。 最近线上有个服务突然卡死了看起来是进程启动了但是请求接口则反应连接不上看错误日志也没有什么异常信息对于这种线上服务卡死的问题一直没有丰富经验来排查。看网上一些人各自gdbsystemtap之类的工具调试线上问题很是羡慕。
我先是用 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/dlvlatest 这样子会安装 dlv 到你的GOPATH目录下里面会生成一个dlv的可执行文件。然后将这个dlv上传到服务器上就可以使用了。
当然也可以先拉取代码然后在代码目录执行 go build生成可执行文件。
attach和coredump 有了dlv之后第一步就是要让dlv可以接触上卡死的进程也就是使用 attach指令把dlv附着在卡死的进程上。
首先使用 ps -ef | grep xxxx 查看卡死的进程的pid假设卡死的进程pid1234然后执行 ./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和我是不一样的这个要怎么解决呢
终极解决办法编译参数加上 trimpathgo 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 GOPATH−asmflags−trimpathGOPATH 这样子编译的代码路径就会去掉你本人的文件夹路径使用相对于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 把这个配置永久保存下来。