Golang 入门教程:第三部分 - 避免常见错误

发表时间: 2017-12-17 20:34

聪明的你,用golang写后端服务,各种使用channel和goroutine,把java要用线程池干的事儿用携程都搞定了。服务线下运行一切正常,压测,单元测,联调统统通过。你露出得意的微笑,一键发布到生产环境,欣喜的发现服务崩溃了。

为什么服务会崩溃呢?

channel死锁

死锁是golang里边最常见的一类问题,我们从java和c/c++转过来的gopher在编程时候会特别注意mutex,semaphore,atomic等等的使用,反复检查会不会造成死锁。但是我们有可能会忽略掉另一个死锁大元凶:channel。

channel简直就是用来死锁的。

unbuffered channel读和写都是blocking的,也就是说读一个没人写的channel和写一个没人读的channel都会造成死锁。死锁状态下的goroutine会永远等待这个channel operation返回。这么做的goroutine多了,直接就会造成内存泄漏,服务器崩溃

buffered channel不会吗?

也会的。buffered channel写满了之后再写就会死给你看。空的channel没人的情况下去读也死给你看。

怎么避免呢?

这在官方的 effective go里边就有介绍。

第三方调用

我们往往不能天真的认为我们调用的上游服务不会挂,我们也不能淳朴的认为我们的网络环境永远是可靠的。往往一个小波动就会使得我们好多goroutine临时的卡在第三方api调用上。比如正常状况下我们期待所有goroutine20ms都会完成。但是突然网络抖动,或者第三方服务稳定性出了一点小问题,我们的goroutine都要2秒钟才会完成。

可能有人会觉得,不就是慢了一点吗,有什么大关系。

同志们,关系非常大。本来goroutine 20ms就退出的情况下,我们可能同时也就是几千个goroutine。如果goroutine生命周期变长,我们就会瞬间攒下来100*几千个goroutine,如果不加以限制的话,我们的服务就会瞬间崩溃。

所以别人的问题,可能会导致我们自己服务的失败。

怎么办?

使用circuit breaker。

我们想这样保护我们的服务:

首先设定SLA (service level agreement)

  1. 平均响应时间

  2. 容错率

  3. max qps

我们需要monitor第三方服务的调用出错率。当出错率高于某个阈值(超时也是一种错误),我们需要暂时对服务调用这个操作直接报错,这样调用的goroutine就可以迅速的退出。

我们还需要不断的尝试,看看调用状况是不是变回良好可用的状态,如果是,我们就得恢复正常的调用机制。并且重新开始计算出错率。

这个pattern其实就是大家常说的熔断器(circuit breaker)

熔断器的一个golang实现:afex/hystrix-go

怎么观测我们的服务承受的压力?

普罗米修斯:Prometheus

数据狗:Modern monitoring & analytics

NewRelic: Digital Performance Monitoring and Management | New Relic

如何debug到底goroutine卡在什么地方?

pprof : golang.org/pkg/net/http

这篇文章粗浅无比的谈了一下golang服务如何防止,检测,排查goroutine过多引起的在线故障。意在抛砖引玉。聪明的你,我们一起学习,一起深入讨论。

希望我们露出得意微笑部署golang服务的时候,可以更加自信,更加成竹在胸。