宕机纪实-extract from infoQ_Active Open Teamwork Perfect_百度空间

有一次,所有的一切都可以正常运转。那些日子里,有些偏执的家伙破坏了整个网络运行所基于的理论与实践。没错,我是在说防火墙出了问题。 顶顶顶顶

防火墙只不过是个特别的路由器。它将数据包从一些物理端口路由到另一些物理端口。在每个防火墙内部,一整套访问控制列表定义了允许连接的规则。这些规则看 起来就象“IP地址从192.0.2.0到192.0.2.24的这些计算机,可以向192.168.1.199的80端口发起连接”。当防火墙发现一个 进来的SYN数据包时,它会根据那些规则进行检查。这个数据包可能被批准(被路由到目标网络),也可能被拒绝(TCP重置数据包并发回),也可能被忽略 (扔掉数据包而且不做任何响应)。如果连接被批准,那么防火墙在它自己的内部表中做一个标记,例如“192.0.2.98:32770 已连接到192.168.1.199:80”。此后的数据包只要匹配这个标记,就可以在防火墙的网络中进行路由。

到目前为止一切顺利。那么这与那些早上5点把我叫醒的电话有什么联系呢?

问题关键在于防火墙内部建立的那个连接表。它是有限的,所以它不许无限期的连接,尽管TCP本身允许无限期连接。除了记录连接的端点之外,防火墙也记录了 “{zh1}到达数据包”的抵达时间。如果很长时间不在连接中传递数据包的话,防火墙会认为端点已经无效,并从列表中删除这个连接。TCP的设计却不这么高明。 也没有什么第三方来告诉网络的两个端点,连接已经被停掉了。尽管没有传递数据包,这两个端点还是认为它们之间的连接有效,并且可以一直持续下去。

打那儿开始,由于socket端口的半开状态,从任何一端的socket端口进行读写操作,都不会引起TCP重置或报错。相反,TCP/IP堆栈发送这些 数据包,等待ACK应答信号;如果等不到,就再次发送。忠实的堆栈一次又一次地重建连接,而防火墙不断地把发送过来的数据包扔到一边去,而且不返回 “ICMP目标不可达”这样的消息(攻击者可以通过欺骗源地址来侦测活动连接)。我所使用的Linux系统,运行在2.6内核上,它把自己 tcp_retries2的默认值设置为15,这样,在TCP/IP通知socket库之前就会有一个20分钟的时间间隔。我们当时使用的HP-UX服务 器的超时时限设置为30分钟。因此发生问题的应用所发出的socket写操作可以阻塞30分钟!socket读操作遇到的情况更加糟糕,它会被永远阻塞下 去!

当我反编译了资源池类以后,我发现它采取了后进先出策略。漫漫长夜之中,网站流量非常小,只需要从资源池中取出一个数据库连接就可以完成工作。下一个请求 会用同一个数据库连接。直到流量上来以前,另外三十九个连接全部处于空闲状态。在防火墙中空闲连接超时设置为一个小时,而且三十九个连接空闲时间也超过了 一个小时。

一旦流量开始升高,另外三十九个连接马上就锁定了。尽管剩余的一个连接还可以生成页面,但它迟早会被某一个线程占用,该线程阻塞在其余资源池的连接上。因此,{wy}好使的连接也被阻塞线程占用了,整个网站也就掛了。

Dead Connection 起效

找到解决方案

明白了故障发生的完整过程,我们就该找出解决方案了。我们可以让资源池在提供JDBC Connection前可检查它们的有效性;它可以通过执行类似 SELECT SYSDATE FROM DUAL这样简单的SQL查询来进行检查;可这会使请求处理线程挂起并导致服务器当掉。我们也可以让资源池追踪JDBC Connection的闲置时间,并抛弃那些超过一小时的JDBC 连接。可不幸的是,在这些连接中,有些是用来向数据库服务器发送数据包以通知它Session失效的。结果服务器同样就“挂了”。

于是我们开始采取一些非常复杂的措施,其中就包括创建“收割”线程,去查找那些驻留时间过久的连接,并在超时之前把它们干掉。庆幸的是,有一个机灵的 DBA想起一件事:Oracle有一个叫作“dead connection detection” 的特性,通过它你可以发现客户端的崩溃时间。将其参数设为“enabled”后,数据库服务器会周期性的发送一个ping数据包到客户端。如果客户端有响 应,数据库就知道它仍然是xx的。如果重试几次后客户端还没有响应的话,数据库就认为客户端已经崩溃,并释放该连接所掌握的所有资源。

我们不太关心客户端是否崩溃,ping数据包应该可以为当前连接重置防火墙的“{zh1}到达数据包”的抵达时间,以保证连接有效。“Dead connection detection”这一特性会保证连接是xx的,这就可以让我们睡个好觉了。

得到的教训是什么?

过去我们从没想过要去写一个单元测试,以模拟在TCP/IP协议下数据库调用被无限期挂起的行为。为什么要写呢?甚至更糟的是,这些容易出错的网络、服务 器和应用,有无数种方式产生类似的“超出规约”的故障。那我们可以做些什么呢?这是否意味着,在agile的世界中,人们错失了某个实践呢?是否存在某种 测试技术或代码实践可以让我们避免这类故障呢?

以前没有人能想象程序员会测试自己的代码。二十年前,这样的想法会让人笑掉大牙。而现在,单元测试却是大家所期望的,有时甚至是必须的。越来越多的人甚至 用Michael Feather'的定义来识别“遗留代码”,这个定义就是:没有单元测试的代码。也许,有人会发明一种测试技术,可以防止由于最基本的抽象层面发生问题而 导致的无数故障。

在那之前,我认为我们必须考虑架构问题,甚至在敏捷项目中也一样,以避免类似的错误再发生在我们身边。我们在功能性方面使用设计模式;同样的,为了保证灵 活性,我们也要应用设计模式。在《Release It》中,我创建了一套这样的“稳定性模式”。我希望这只是一个开端。

关于作者

致力于在全国范围内帮助开发人员减轻他们的痛苦。他向遇到的人共同分享他的热情和能量,来帮助他们提高,即使有些人并不情愿。Michael花了将近二十 年的时间,来学习如何成为一个关心艺术、质量和工艺的专业程序员。现在他已经是一个具有二十年经验的专业程序员和架构师。其间,他的工作领域涉及美国政府 机构、军事、银行、金融、农业和零售业。 除此之外,Michael常常自己开发一些系统。 这些来自现实世界的经验永远改变了他对软件架构和开发的看法。



郑重声明:资讯 【宕机纪实-extract from infoQ_Active Open Teamwork Perfect_百度空间】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——