关于7月18号停止服务的解释

[原文发表地址] Explanation of July 18th outage

[原文发表时间] 2014/7/31

首先要说一句对不起,我花了一周半的时间才写好这篇博客。

在7月18号周五的一段时间内,多数的VS的认证都被中断了。所有服务大约中断了90分钟。幸运的是那段时间只有少部分人在使用,因此受到影响的客户的要比预想的少很多,但是我知道这只是对那些受到影响的人的小小的安慰而已。

我主要想表达的是我们从这次事故中学习到一些东西,从而能使我们的服务能更出色,并且分享这次的事件,希望能够让一其它人避免类似的错误。

发生了什么?

这次断电的根本原因是因为在SQL云端的一个数据库变得很慢。实际上我不知道为什么会这样,因此我猜测那个不是真正的根本原因,但是对我来说,这个很接近了。我相信SQL Azure 团队追踪的是根本原因的一部分-这件事情并没有对他们产生影响。数据库会时不时变得很慢,在过去的一年多的时间,SQL Azure 做的已经相当不错了。

具体的情况是Visual Studio中(在IDE)在“调用服务平台”(一种平常的服务实例管理身份,用户配置文件,许可证之类的)建立获得通知有关更新漫游设置的连接。共享平台服务会调用的Azure服务总线,它会调用状况不佳的SQL Azure数据库。

这个运行缓慢Azure数据库引起的调用共享平台服务(SPS)会累积直到在SPS中所有的操作完成之后,同时,因为同样依赖于SPS,TFS的操作也会被阻塞住。最终的结果是在线的VS服务停掉了,直到我们人工断开与Azure 服务总线的链接,同时清理它自身的日志。

我们可以从这件事当中学习到很多。这其中的一些我已经知道了原因,还有一些我还并不知道,但是不管我清楚不清楚它们的根本原因,这都是一个有趣的、具有启发作用的错误。

**更新**在最开始的10分钟,我的团队的好多人已经联系我说这个根本原因可能是应为Azure 数据库引起的。实际上,我的这篇文章想说的是不管根本原因是什么。在复杂的服务当中肯定会发生一些瞬时的错误,重要的是你的反应是什么。所以,不管这个是什么引起的,这次断电的”根本原因”是我们没有正确的处理瞬时错误,并且让它发展成为一个总的服务断电的事故。我同样说过,关于SB/Azure 数据库我可能说错了。我尽量避免说一些关于其他服务发生的事情,因为这是一件很危险的事情。我不会花时间去确认并纠正任何的错误,因为,这不是要进行一个相关的讨论。这篇文章不是关于是什么引起的这次的事故,这篇文章是关于我们如何应对类似的事情,和我们将会采取什么样的措施来在将来更好的解决类似的问题。

不要让一个可有可无的功能影响你主要的任务

第一个并且最重要的教训是”不要让一个可有可无的功能影响你主要的任务“。在服务中有一个概念是所有的服务应该是低耦合并具有容错性。一个服务停掉了不应该引起大量的失败出现,引起其他的服务失败也应该是因为功能部分完全依赖的失败的组件不可用。像谷歌和必应在这个方面就做的非常好,他们是有成千的服务器,任何一个服务器都有可能停掉,但是你根本觉察不到,因为大多的用户体验还是像原来好的时候一样。

这次特殊的事情是因为Visual Studio 漫游设置功能体验失败。如果我们有一个正确的方法遏制这次事件,漫游设置就不会在90分钟内尝试同步,其他的东西也应该顺畅工作,这看起来不是一个多严重的问题。然而事实是,整个服务都停掉了。

在我们这次事件当中,我们所有的服务都是在其它服务当中处理当前失败,但是因为在一个关键的服务中该错误最终被存储在一个已经干枯的线程库中。当到那个点之后,所有的服务都不能进行任何工作。

更小的服务器会更好

 

这个问题的一部分原因是因为主要的服务器像是我们的认证服务器和一些不重要的服务器(漫游服务器)共享了同一个容易耗尽的资源(资源池)。另外的一些主要的服务器被尽可能的分解成小的工作单元。这些工作单元运行的时候可能会有一些常见的故障,所有的互动应履行“防御性编程”的做法。如果我们的认证服务器出现故障,那么我们的服务关闭。但漫游设置服务器就不会停掉。在过去的18月左右的时间当中我们一直在努力逐步重构与VS联机成一组松耦合的服务器。事实上,大约一年前,在现在的SPS被从TFS中分离出来成一个单独的服务。总而言之,今天我们有15个左右的独立服务器。显然,我们还需要更多的:)

你应该重试多少次?

另外服务当中一些存在已久的规则会视那些短暂的失败是“正常的”。每一个服务器都会消耗另外的服务器的时候对丢包现象,瞬时的延迟,背压流量控制等等都是很宽容的。这其中的早期技术是当你使用的服务失败的时候重试一下。这样看起来没什么问题。有趣的是我们运行在一个一组级联重试的情况,具体的情况是:

Visual Studio->SPS->服务总线->Azure 数据库

当Azure数据库故障时服务总线重试3次,服务总线故障时SPS重试2次,SPS故障时VS重试3次。3*2*3=18 次。所以,每一个Visual Studio 客户端打开之后,在这段时间内,SQL Azure数据库会推送18次尝试。因为这个问题,数据库运行缓慢(导致时间超市30秒左右),18次尝试*30秒=每个9分钟。

在堆栈中所有的调用堆积起来,并且直线上升,直到最后,线程池已满,没有更多的请求能被处理。

事实证明,SQL Azure 对使用者来说是非常容易沟通的,不管如何,重试是值得的。SB并不重视这一点,也不传达给使用者。SPS同样也不会。因此,我学到的新的规则是,依据错误来仔细检查服务是很重要的,而不要去判断服务和服务之间失败重练次数以及服务之前连接是否合理 . 如果已经做了,每个链接只是链接30秒而不是9分钟,这样的话,情况会好很多的。

彻底解决阻塞还要等待很长时间

想象一下,SPS保存了当前有多少并发呼叫进入服务器的总线数,直到那个是一个低优先级的服务,并且呼叫都是同步进行的,线程池是有限的,它决定呼叫并发数超过某个阈值(为了方便讨论各种情况,假设是30),在阻塞缓解一些之前,它就会拒绝所有新的申请呼叫。

有些设置不是漫游的呼叫会被很快的拒绝,但是我们从来没有用尽线程来时高优先级的服务继续良好地运行。假设客户端设置成尝试一些非常罕见的间隔重新连接时,同时假设底层数据已经被清理掉,系统会自己进行修复。

线程,线程,更多的线程

我敢肯定,没有别人帮忙指出这次事件的根本原因之一是跨服务器的呼叫时同步进行的,我是不可能弄清楚的。它们本应该是异步进行的,因此不会占用一个线程,所以线程池是不会枯竭的。这个是非常合理的,但是我这不是我现在最重要的事情。你们总会消耗一些内存资源,即便是异步呼叫。这个资源可能很大,但并不是取之不尽的。我上面列出的技术不管是对同步通信或者是异步通信都是非常有价值的,同时能够防止其他的不好的事情,像是对一个本已经十分脆弱的数据库进行过多重试操作等。

所以,这是一个很好的想法,但是我并不认为这是一个好的解决方案。

所以,像我们之前发布的一系列的如何改进“基础设施”文章,将帮助我们找到一种更加可靠的服务方式。所有的软件都可能会因为不知名的原因挂掉。关键的是要检查每一个故障点,追踪所有故障的根本原因,归纳经验教训,并建立更好的防御措施。

对于因为我们的原因引起的中断我感到十分抱歉,我不能保证类似的事情不会再发生。*但是*在几个星期(我们进行了一些防御的措施)调查修复之后,这样的情况就不会因为同样的原因再次发生。

感谢您一如既往的加入到我们的行列中来进行这次的发布,并且给予我们充分的理解。希望这些经验教训可以对您和您自己的开发工作提供一定的参考价值。

Brian