2017年多了1s你知道吗?是引发了故障,还是程序员背的黑锅

在新年第一天协调世界时(UTC)子夜,在Cloudflare的自定义RRDNS软件内部深处,一个数字本该在最糟糕的情况下总是为零,结果却变成负数。稍后,这个负值引起RRDNS“恐慌”。这个恐慌是使用Go语言的恢复特性而发现的。最终影响就是,为Cloudflare的一些托管网站提供的一些DNS解析以失败告终。

这个问题只影响了使用CNAME DNS记录的Cloudflare客户,只影响了Cloudflare的102个接入点(PoP)处的少量机器。高峰时段,向Cloudflare发出的DNS查询中大约只有0.2%受到了影响,向Cloudflare发出的所有HTTP请求中不到1%遇到了错误。

这个问题很快就被发现了。大多数受到影响的机器在90分钟内打上了补丁,等到UTC 06:45,修复程序已向全球用户发布。我们对客户受到了影响深表歉意,但是我们认为有必要叙述根源,让别人明白来龙去脉。

Cloudflare DNS方面先介绍一下

Cloudflare的客户使用我们的DNS服务,以便为其域名提供DNS查询的权威答案。他们需要告诉我们其原始Web服务器的IT地址,那样我们才能联系上服务器,处理非缓存请求。他们以双向方式做这项工作:要么输入与名称有关的IP地址(比如example.com的IP地址是192.0.2.123,并作为一条A记录来输入),要么输入CNAME(比如example.com是origin-server.example-hosting.biz)。

这张图显示的一个测试网站既有theburritobot.com的A记录,又有www.theburritobot.com的CNAME,直接指向Heroku。

2017年多了1s你知道吗?是引发了故障,还是程序员背的黑锅

客户使用CNAME这种选择时,Cloudflare偶尔要执行查询,使用DNS,查询原始服务器的实际IP地址。它是使用标准的递归DNS自动执行这项操作的。含有导致故障的那个软件错误的正是这个CNAME查询代码。

在内部,执行CNAME查询时,Cloudflare运行DNS解析器,查询来自互联网的DNS记录,以及RRDNS与这些解析器之间的对话,以便获得IP地址。RRDNS跟踪记录内部解析器的性能有多好,并对可能的解析器(我们每个接入点运行多个解析器,以确保冗余性)进行权重选择,选择性能最好的那个解析器。其中一些解析最后在数据结构中记录下了闰秒期间的一个负值。

权重选择代码在稍后被馈送到这个负数,因而引起了恐慌。负数是通过闰秒和平滑处理(smoothing)这两个因素出现在那里的。

程序员在时间方面的错误认识

影响了我们DNS服务的那个错误的根源出在时间不会倒退这一观念上。以我们为例,一些代码想当然地以为:在最糟糕的情况下,两个时间之间的时差总是为零。

RRDNS是用Go编写的,使用Go的time.Now函数来获得时间。遗憾的是,这个函数并不保证单调性(monotonicity)。Go目前并不提供单调的时间源(详情请参阅https://github.com/golang/go/issues/12914)。

在评估用于CNAME查询的上游DNS解析器的性能时,RRDNS含有下列代码:

// Update upstream sRTT on UDP queries, penalize it if it fails

if !start.IsZero {

rtt := time.Now.Sub(start)

if success && rcode != dns.RcodeServerFailure {

s.updateRTT(rtt)

} else {

// The penalty should be a multiple of actual timeout

// as we don't know when the good message was supposed to arrive,

// but it should not put server to backoff instantly

s.updateRTT(TimeoutPenalty * s.timeout)

}

}

在上述代码中,如果time.Now早于start(这是由调用早些时候的time.Now()来设定的),rtt可能是负数。

如果时间往前进,这个代码可顺畅运行。遗憾的是,我们将解析器的速度调整得非常快,这意味着它们在几毫秒内回答是很正常的事。如果解析发生时,时间后退一秒,所谓的解析时间会是负值。

RRDNS根本不为每个解析器保留单一的度量指标,它拿来许多度量指标后,对它们进行平滑处理。所以,单一的度量指标不会引起RRDNS认为解析器在负时间内工作,但是经过几个度量指标后,经过平滑处理的值最终会变成负值。

当RRDNS选择上游解析CNAME时,它使用一种权重选择算法。代码拿来上游时间值后,将它们馈送到Go的 rand.Int63n函数。如果变量是负值,rand.Int63n就会立即恐慌。这就是造成RRDNS恐慌的根源。

(另外,程序员在时间方面还有其他的许多错误认识)。

只需一个字符的修复程序

使用非单调性时钟源时要防范的一个地方就是,始终要检查两个时间戮之间的差异是不是负数。要是出现这种情况,除非时钟停止倒回,否则就不可能准确地确定时间差。

在这个补丁中,我们让RRDNS忘记目前的上游性能,如果时间向后跳过,让它再次规范化。这可以防止负数泄露给服务器的选择代码,从而避免在试图联系上游服务器之前导致抛出错误信息。

我们采用的修复办法防止服务器选择代码记录负值。重启所有的RRDNS服务器,然后修复这个问题再次出现的毛病。

时间表

下面是闰秒错误方面的事件的完整时间表:

2017-01-01 00:00 UTC 影响启动

2017-01-01 00:10 UTC 上报给工程师

2017-01-01 00:34 UTC 确认问题

2017-01-01 00:55 UTC 缓解措施部署到一个金丝雀代码上,并加以核实

2017-01-01 01:03 UTC 缓解措施部署到金丝雀接入点上,并加以核实

2017-01-01 01:23 UTC 修复程序部署到大多数受影响的接入点

2017-01-01 01:45 UTC 修复程序部署到主要的接入点

2017-01-01 01:48 UTC 修复程序部署到每个地方

2017-01-01 02:50 UTC 修复程序部署到大多数受影响的接入点

2017-01-01 06:45 UTC 影响已消除

这张图显示了每个Cloudflare接入点的错误率(一些接入点受到的影响比其他接入点要大),修复程序部署后,错误率迅速下降。我们将修复程序优先部署在错误最多的那些地方。

2017年多了1s你知道吗?是引发了故障,还是程序员背的黑锅

结束语

我们为我们的客户受到这个错误的影响而表示歉意,我们在检查所有代码,确保时间间隔没有出现其他对闰秒敏感的地方。

本文为头条号作者发布,不代表今日头条立场。

您可以选择一种方式赞助本站

支付宝扫一扫赞助

微信钱包扫描赞助

发表评论

gravatar

:?::razz::sad::evil::!::smile::oops::grin::eek::shock::???::cool::lol::mad::twisted::roll::wink::idea::arrow::neutral::cry::mrgreen:

目前评论:12   其中:访客  11   博主  1

  1. avatar 丁春华 2

    完全看不懂这篇文章,好深奥啊,整蒙圈了。

  2. avatar 成航先森 5

    元旦放假前公司有警告,让我注意下,但是公司业务没有受到影响、

  3. avatar 米粒博客 4

    多3秒都没什么影响,还是一样的过去了,3秒能干啥呢

  4. avatar Roogle 0

    如果没用cloudflare加速应该没多大影响吧

  5. avatar 山野愚人居 0

    都是1秒惹的事!

  6. avatar 贼靠谱,微钱客 2

    很高深的样子

  7. avatar 热腾网 5

    没有影响到。都是网站业务,无所谓。

  8. avatar 明月登楼的博客 4

    不错,过来点个赞支持一下先!

  9. avatar 马超金博客 1

    过来看看,支持下

  10. avatar Koolight 5

    标题党,文章脱离中心,差评!

  11. avatar boke112导航 1

    看新闻知道有这么一回事,但是没有亲身感受到它的厉害

评论加载中...