hello,大家好,小幺鸡今天又和大家见面了,本期我们聊聊一次超时设置不当导致系统白屏的问题,该系统采用go语言基于gin框架开发,平时运行的妥妥的,在一次错误的操作后,导致了系统不可用。
一、问题背景
在优化项目配置初始化的过程中,由于配置采用yaml文件格式,修改了如下配置
认为10s中的s是多余的,于是去掉yaml中的s,结果发现系统无法响应,出现empty reply,curl请求结果如下图所示
二、排查思路
1、是不是端口没有监听
反复使用端口查看命令lsof(mac电脑), 能看到端口,使用telnet去连接端口,也是没问题的,因此确认程序是启动了。
2、查看报错日志是否有相关信息
查看日志文件没有任何内容写入,反复重启进行仍然没有内容写入,查看控制台也没看到任何输出,但控制台debug模式下输出路由了相关信息,实在找不到报错。
3、是不是访问地址哪里写错了
睁大眼睛反复看,怀疑是不是因眼睛出了问题,看花了,于是找来同事一起看,确认地址是没有错误的。
4、从代码执行过程进行分析
最后实在没办法了,只能一步步看代码,怀疑是不是endless(为了能平滑重启服务引入的module)的锅,具体过程见下文。
三、代码排查过程
找来找去实在没发现什么问题,感觉endless是不是哪里有bug,因此又去研究了下endless的原理,也没发现问题。
实在想不通,难道是配置初始化时route路由未能注入到endless,导致无法提供服务???因此想把路由输出来看看,
意外发现这里的单位居然是ns,纳秒,而 1秒 = 1,000,000,000 纳秒,发现这个之后就知道原来我们去掉了“s”影响的。那为什么没报错呢???
四、为什么没有任何响应并且没有任何报错
通过查看项目代码发现原来罪魁祸首不是endless,真正使用该参数的是net/http包,于是去net/http官网看了下,且复制了一段代码,作为调试用。
通过调试发现在处理请求的时候,会调用go/src/net/http/server.go中的serve方法,该方法会调用readRequest方法读取请求头
在readRequst方法中会调用time包的Add方法,将配置的超时参数ReadTimeout进行加工处理,
Add方法在go/src/time/time.go中,处理过程如下,我们可以看到经过d / 1e9(转换为s),被处理成0了。
在设置超时时间后,继续执行readRequest代码,发现在go/src/bufio/bufio.go中从socket读取请求头时触发了poll事件的TimeoutError
而在server方法中获取到该错误后,会调用isCommonNetReadError判断是否是“一般网络错误”
判断为net error
因此处理请求响应为空,don't reply.
五、其它说明
1、排查代码
package mainimport ( "fmt" "net/http")func main() { http.HandleFunc("/", HelloServer) server := &http.Server{ Addr: ":3397", ReadTimeout: 10, } server.ListenAndServe()}func HelloServer(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])}
2、关于poll事件socket是如何控制超时的,我们下期见。