使用 fasthttp 时要注意的两个点

Bulesxz · · 10776 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

# 我们做的是聚合支付系统,使用的是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 协议也要好好理解下,三次握手,四次挥手 还是有很多值得深究的,也很有意思。今天太冷就不多写了,写的有问题之处,还望多多指点。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

10776 次点击  ∙  2 赞  
加入收藏 微博
被以下专栏收入,发现更多相似内容
4 回复  |  直到 2017-12-17 05:59:56
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传