via:
https://medium.com/better-programming/common-go-pitfalls-a92197cd96d2作者:Tyler Finethy
四哥水平有限,如有翻译或理解错误,烦请帮忙指出,感谢!
我喜欢 Go 语言有几个原因:
- 语言本身极其简洁(只有 25 个关键字);
- 能轻而易举地实现交叉编译;
- 天然支持创建可靠的 HTTP 服务器;
从根本上来讲,Go 是一种 boring 的语言,可能这就是为什么可以用它来开发一些诸如 Docker 和 Kubernetes 等很棒的项目,像 Cloudflare 等具有高性能和弹性要求的公司也正在使用它。
尽管上手很容易,但是有很多细节还是值得关注。如果你在不清楚的情况下编写代码,很可能会导致各种稀奇古怪的问题,并且很难发现和纠正错误。
下面会给大家列举一些常见错误,是在 review 生产代码时发现的。希望你再遇到相同问题时能轻松地解决。
HTTP 超时时间
HTTP 超时时间,其实在点击已经跟大家讨论过这个问题。但仍然值得再提一提,因为好的解决方案总是需要更多的时间思考的。
使用默认的 HTTP 客户端可以发出 HTTP 请求,为了说明问题,下面是一个使用 GET 请求访问 google.com 的例子:
正如文章指出的,默认的 HTTP 客户端没有设置超时时间,这意味着请求有可能会被长时间挂起(ps:具体原因可以查看原文)
所以,解决这个问题最好的办法是什么呢?
&http.Client{Timeout: time.Minute},给 HTTP 客户端定义一个合理的超时时间。你也可以考虑给 HTTP 请求加上 context,这样做有几个好处:
- 有能力取消正在进行的 HTTP 请求;
- 为一些特殊请求指定超时时间;
第 2 个好处显得尤为重要,比如你知道有几个请求需要耗时很长时间,超过 1 个小时。但是你又不想每个请求都设置这么长的超时时间,你就可以只针对特殊请求设置比较长的超时时间。
上面的例子中,如果加上 context 代码会像下面这样:
请求时间如果超过了超时时间,c.Do() 调用就会返回 DeadlineExceeded 错误,可以很容易地处理错误或者重试。
数据库连接
我参与的每一个 Go 项目几乎都会出现数据库连接问题。我认为对刚入门 Go 语言的新手来说,有个难以绕过去的点,sql.DB 对象是并发安全的连接池,而不是单个数据库连接。这意味着连接使用完之后如果没有返还给进程池,会轻易导致连接数耗尽,甚至最后导致应用程序宕掉。
例如,数据库连接池包含打开和空闲连接,分别是通过下面这些选项设置的:
- SetConnMaxLifetime,连接可以重用的最长时间;
- SetMaxIdleConns,最大的空闲连接数量;
- SetMaxOpenConns,最大的打开连接数量;
需要注意的是,即使你的最大打开连接数设置成 200,如果连接使用完不返还连接池,应用程序也有可能会耗尽数据库能接受的最大连接数,最后导致宕机、重启服务。你需要检查数据库设置,以确保正确设置了这些参数。
如果数据库没有设置这些参数,应用程序将轻而易举地耗尽数据库能接受的连接数。
让我们回到进程池的问题上,查询数据库之后,很多开发人员会忘记关闭 *sql.Rows 对象,这就会导致超出最大连接数限制,并导致死锁或者高延迟。下面给大家展示下类似的代码片段:
相信你也注意到,正如能在 HTTP 请求上添加 context 一样,我们也可以在数据库查询时添加超时时间的 context。这没什么问题。
正如上面讨论的,我们需要关闭 rows 对象将连接返还给进程池,防止连接数超出。
如果在函数或者包之间传递数据库连接,尤其难以发现这一点。
goroutine 或者内存泄漏
最后一个要讨论的常见问题是 goroutine 泄漏,一般这个问题难以发现,但通常是由开发人员的错误引起的。
使用 channel 时通常会发生这种问题,比如:
如果我们不关闭通道 c 或者 someFunc() 不返回错误,我们初始化的 goroutine 将会挂起直到程序终止。
我们不可能找出每一个导致 goroutine 泄漏的 地方,我通常采用两种方法来检测和消除它们。
第一种方法是在单元测试方法里使用探测器,比如使用 Uber 开源的 goleak 库,就像下面这个例子一样:
这段代码就会验证,在代码优美关闭 30s 之后是否还有多余的 goroutine 在运行。
另一种方法是在应用程序的运行实例上使用 Go profiler,并查看存活的 goroutine 数量。其中一种方法就是使用 net/http/pprof 库,并查看生成的火焰图。
就像下面这样使用它:
上面这段代码,pprof 占用 6060 端口,对于特别严重的泄漏,如果你刷新将会看到协程数量在增多;对于更多的一些微小泄漏问题,则需要查看 profile 发现具体的问题,profile 页面就像下面这样:
如果你的应用程序是空闲的,但是你又看见大数据量的 goroutine,这说明程序已经有问题了。确认泄漏位置之后,我仍然建议在单元测试中使用探测器,以确保解决问题。
总结
希望上面讨论的这些常见错误,如果以后你也遇到,可以帮助你更快地识别并完美地解决问题。
这是持续翻译的第 14/100 篇优质文章。
如果你有想交流的话题,欢迎留言。
如果我的文章对你有所帮助,点赞、转发都是一种支持!