七叶笔记 » golang编程 » Go 语言:使用 fasthttp 时要注意的两个点

Go 语言:使用 fasthttp 时要注意的两个点

我们做的是聚合支付系统,使用的是fasthttp 作为http server, http client 也是使用fasthttp

1. 第一个问题出现的场景是我们使用fasthttp client 请求微信支付时报了这个err ErrConnectionClosed

由于是线上问题 情况紧急,查了一些资料后觉得使用fasthttp client 估计有坑,零时换成 go 原生 http client ,果然解决了问题。

那么问题来了,怎么会出现这中情况呢,这个错误的字面意思是 链接被对端关闭了,说到这里 就有一个要 注意了,fasthttp 默认使用的是 http1.1 golang 里面 默认是 keep-live 保持长链接的,但是对方关闭了链接我们 发包时 报的错误 应该是reset 呀,怎么是这么个玩意儿,只能抓包看看 啰, 结果发现 我们一个请求过去正常回包后,8秒过后 又收到一个FIN 包,此时就怀疑微信的 keep-live 时间了 ,那么 我就打印了一下 回包的head ,果然 keep-live 时间就是8s ,也就是说 8s 后微信 关闭了链接,不巧 我们的fasthttp 空闲等待时间是 15s 。还是没弄明白,按道理对端关闭了 ,client 端 应该是有感知的, 而且换成了 golang 原生 http client 就解决了问题,说明原生的client 是 有感知的,只能看代码了,结果发现了有意思的

从截图可以看出来,这个fasthttp client write 和 read是 同步的

如果第一个请求 之后 8s 之内不再请求 ,那么我们是不会再进行read 操作的,那么就无法感知 已经 被 server 端关闭了。此时问题又来了, 8s 后请求 对端已经发了 FIN 我们再次write 应该是 Connection reset by peer 错误吧,其实不对 ,这里就涉及 tcp 的 四次挥手了, server 发了FIN 包 只能说明 server 不再 write 了 ,但是 server 是可以再read 的 ,也就是说 client 是可以再 wirte 的,刚好错误就是在第二次 请求 的read 操作时报出的。这里证明了错误的产生原因。

但是有 一个疑问 没解决,为什么用 原生 http client 没问题, 进一步来分析下,看代码

可以看到两个 协程 在 loop write read,所以对 server 端 FIN 是 及时响应的,也就是client 及时也关闭了链接。

问题找到了如何解决呢,两个方法:

  1. 简单粗暴,直接 设置 head 请求头 keep-live close ,变成短链接,但是这个有损性能。

  2. 既然 微信 keep-live 是 8s 那么 只要 设置 IdleConnTimeout 小于 8s 就行了 比如 7s , 这样就可以先于 微信 关闭连接了,不会让新的请求 使用 对端 发了 FIN 的链接 。

第二个问题 fasthttp 优雅关闭关不掉

我们使用了 fasthttp 作为 http server . 为了保证 业务完整性,做了一个 优雅关闭功能 ,其实优雅关闭思路很简单:

收到 signal QUIT 后 首先close 调 监听 端口, 然后等所有的链接上的业务处理完毕后 退出程序 就OK 了 , 那么如果 保证 所有链接处理完毕呢, 计数器!!! 新建一个链接时 +1 关闭一个链接时 -1 , 具体实现 其实就是实现 net .Listener 的 accept 和 close 接口 ,例如:

实现思路 是这样的 按道理 就OK 了,其实不是的,我是这么测试的:用一个 golang client for 循环 每隔5秒 发一个 请求, 然后在server 端 发送kill signal QUIT 结果发现程序一直没有退出, 怎么回事呢 ,除非 链接数 一直没有 减到 0 ,后来打印日志发现 确实没有,但是没道理啊, 一个请求处理了应该 close 呀, 此时 又想起了 http1.1 默认 keep-live 我每隔5秒 发一个请求,而设置的 server 最大空闲等待时间为15分钟, 也就是虽然 listener 关闭了 不会再有新的链接进来,但是 正在跑的这个链接永远不会主动关闭,因为每隔五秒 就有一个请求

怎么解决呢 ? 想到一个方法,就是设置一个标志判断是否程序 被关闭,当收到了signal QUIT 之后 就把正在处理的 链接的 resonse head 设置 connection close ,如何设置呢, 因为 我们我们处理的请求 都是在 一个 fasthttp.RequestHandler

,可以包装一下 这个 函数 例如:

这样我们每个请求完毕之后,检测到stop 标志后 必定处理会关闭链接。

貌似 解决了,其实还有点漏洞,就是这个stop 标志的检测 是代码要运行到这里,如果设置了stop 标志代码,但此时这条链接上的没有请求过来怎么办(j假如我是10分钟发一个请求),那么只能等待 IdleConnTimeout 超时的时候关闭链接了,但是 我们关闭一个程序的时候 不希望等待那么久,可以看看 这段代码:

listener 有一个maxWaitTime,就是程序无论有没有包在处理,到了这个时间程序 一定要退出,比如这个时间设置为 3秒,当然这个问题 也是有漏洞的万一 我们一个请求 处理耗时 不止3s 呢,岂不是不能完整处理这个请求, 当然我们业务一般一个请求一秒就处理完了。 不过这个问题确实存在,目前我还没想到一个好的办法彻底解决这个问题,如果你们有想法可以告诉我一下。

总结一下,这些主要是关于fasthttp 使用时要注意的问题,http协议 get post 虽然简单,但是要用好 还是要好好研究下, tcp 协议也要好好理解下,三次握手,四次挥手 还是有很多值得深究的,也很有意思。今天太冷就不多写了,写的有问题之处,还望多多指点。


本文由 Bulesxz 发布在 Go 语言中文网

相关文章