# 我们做的是聚合支付系统,使用的是fasthttp 作为http server, http client 也是使用fasthttp
### 1. 第一个问题出现的场景是我们使用fasthttp client 请求微信支付时报了这个err ErrConnectionClosed
```
the server closed connection before returning the first response byte Make sure the server returns 'Connection: close' response header before closing the connection
```
![image.png](http://upload-images.jianshu.io/upload_images/5964227-96ef9323a9780a9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
由于是线上问题 情况紧急,查了一些资料后觉得使用fasthttp client 估计有坑,零时换成 go 原生 http client ,果然解决了问题。
那么问题来了,怎么会出现这中情况呢,这个错误的字面意思是 链接被对端关闭了,说到这里 就有一个要 注意了,fasthttp 默认使用的是 http1.1 golang 里面 默认是 keep-live 保持长链接的,但是对方关闭了链接我们 发包时 报的错误 应该是Connection reset by peer 呀,怎么是这么个玩意儿,只能抓包看看 啰, 结果发现 我们一个请求过去正常回包后,8秒过后 又收到一个FIN 包,此时就怀疑微信的 keep-live 时间了 ,那么 我就打印了一下 回包的head ,果然 keep-live 时间就是8s ,也就是说 8s 后微信 关闭了链接,不巧 我们的fasthttp 空闲等待时间是 15s 。还是没弄明白,按道理对端关闭了 ,client 端 应该是有感知的, 而且换成了 golang 原生 http client 就解决了问题,说明原生的client 是 有感知的,只能看代码了,结果发现了有意思的
![image.png](http://upload-images.jianshu.io/upload_images/5964227-c69e8c2ca3516a4f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
从截图可以看出来,这个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 没问题, 进一步来分析下,看代码
![image.png](http://upload-images.jianshu.io/upload_images/5964227-8e96471f7613ce43.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
可以看到两个 协程 在 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 接口 ,例如:
![image.png](http://upload-images.jianshu.io/upload_images/5964227-090d5753ce31507a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
实现思路 是这样的 按道理 就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
,可以包装一下 这个 函数 例如:
![image.png](http://upload-images.jianshu.io/upload_images/5964227-afb221e685b29143.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这样我们每个请求完毕之后,检测到stop 标志后 必定处理会关闭链接。
貌似 解决了,其实还有点漏洞,就是这个stop 标志的检测 是代码要运行到这里,如果设置了stop 标志代码,但此时这条链接上的没有请求过来怎么办(j假如我是10分钟发一个请求),那么只能等待 IdleConnTimeout 超时的时候关闭链接了,但是 我们关闭一个程序的时候 不希望等待那么久,可以看看 这段代码:
![image.png](http://upload-images.jianshu.io/upload_images/5964227-f520649fae37eea7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
listener 有一个maxWaitTime,就是程序无论有没有包在处理,到了这个时间程序 一定要退出,比如这个时间设置为 3秒,当然这个问题 也是有漏洞的万一 我们一个请求 处理耗时 不止3s 呢,岂不是不能完整处理这个请求, 当然我们业务一般一个请求一秒就处理完了。 不过这个问题确实存在,目前我还没想到一个好的办法彻底解决这个问题,如果你们有想法可以告诉我一下。
总结一下,这些主要是关于fasthttp 使用时要注意的问题,http协议 get post 虽然简单,但是要用好 还是要好好研究下, tcp 协议也要好好理解下,三次握手,四次挥手 还是有很多值得深究的,也很有意思。今天太冷就不多写了,写的有问题之处,还望多多指点。
有疑问加站长微信联系(非本文作者))