第九章 忙音模式
此模式着重描述当云服务对程序请求返回忙音而非成功响应时,应用程序应如何应对。
此模式反映的是客户端而非服务方的视角。客户端通过程序向服务发出请求,但服务返回忙音。客户端负责正确解读忙音,并进行适当次数的重试。如果在重试期间仍然出现忙音,客户端则将服务视为不可用。
拨打电话偶尔会遇到忙音。通常的应对方法是重试,重试后通常可以成功接通电话。
类似地,调用服务偶尔会返回失败代码,表明云服务当前无法满足请求。通常的应对方法是重试,重试后通常可以成功调用服务。
云服务无法满足请求的主要原因是其繁忙。有时,服务“繁忙”的时间可能只有几百毫秒,甚至一两秒。智能重试策略有助于处理忙音信号,而不会影响用户体验或使繁忙的服务不堪重负。
背景
忙音模式可以有效应对以下挑战:
- 您的应用程序使用云平台服务,但这些服务无法保证每次都能成功响应。
此模式适用于访问所有类型的云平台服务,例如管理服务、数据服务等等。
更一般地说,此模式可以应用于通过网络访问服务或资源的应用程序,无论这些服务或资源是否位于云端。在所有这些情况下,都应该预料到会出现周期性的瞬态故障。一个常见的非云示例是,当 Web 浏览器无法完全加载网站时,只需刷新页面或重试即可解决问题。
云的重要性
如多租户和通用硬件入门(第 8 章)中所述,使用云服务的应用程序会遇到周期性的瞬态故障,从而导致忙音响应。如果应用程序未能对这些忙音做出适当的响应,用户体验将会受到影响,并且应用程序会遇到难以诊断或重现的错误。预先考虑并规划好忙音的应用程序可以做出适当的响应。
这种模式对于即使在本地环境中运行的稳健型应用程序也十分适用,但由于此类故障在本地环境中的发生频率远低于云端,因此在历史上并未得到充分重视。
影响
可用性、可扩展性、用户体验
机制
使用忙音模式来检测和处理应用程序(在本例中为客户端)访问云服务时发生的正常瞬态故障。瞬态故障是指并非客户端故障导致的短暂故障。事实上,如果客户端在几毫秒后重新发出相同的请求,通常会成功。
瞬态故障是预期事件,而非异常事件,类似于拨打电话时遇到忙音。
虽然网络连接问题有时可能是导致瞬时故障的原因,但我们将重点关注服务边界处的瞬时故障,即请求到达云服务但服务未能立即响应的情况。这种模式适用于任何可以通过编程方式访问的云服务,例如关系数据库、NoSQL 数据库、存储服务和管理服务。
瞬态故障导致忙音
云服务请求失败的原因有很多:请求账户过于激进、所有租户的整体活动激增,或者云服务硬件故障。无论何种原因,服务都会主动管理资源访问,努力平衡所有租户的体验,甚至会根据活动高峰、工作负载变化和内部硬件故障动态地进行自我配置。
云服务存在一些限制;请咨询您的云服务供应商以获取相关文档。限制示例包括每秒可执行的最大服务操作数、每秒可传输的数据量以及单次操作可传输的数据量。
在前两个示例中,即每秒操作数和每秒传输的数据量,即使没有单个服务操作出现故障,多个操作的累积量也可能超过限制。相比之下,第三个示例(单次操作可传输的数据量)则有所不同。如果超出此限制,则并非由于累积效应,而是一个无效操作,该操作应始终被拒绝。由于无效操作始终会失败,因此它与瞬态故障不同,并且在此模式下将不再考虑。
云服务中的限制可能由单个客户端超出,也可能由多个客户端共同超出。当您使用某项服务超过其允许的最大吞吐量时,该服务会检测到此情况,并对您的访问进行限速。限速是服务的一种自我保护机制,用于限制或降低使用速度,有时会延迟响应,有时会拒绝应用程序的全部或部分请求。应用程序需要自行重试任何被服务拒绝的请求。
多个客户端即使各自未超过允许的最大吞吐量,集体也可能超过限速限制。即使没有单个客户端出现故障,但总需求无法得到满足。在这种情况下,服务也会对一个或多个已连接的客户端进行限速。第二种情况被称为嘈杂邻居问题,即您恰好与其他租户正在使用同一服务实例(或虚拟机),而其他租户恰好非常繁忙。即使从技术上讲您没有任何过错,您也可能被限速。服务非常繁忙,需要对某些用户进行限速,而有时这个“某些用户”就是您。
云服务具有动态性;由大量“嘈杂邻居”(指用户流量过大)引起的流量高峰可能在几毫秒后即可解决。而由多个活跃客户端(每个客户端都符合速率限制)造成的持续拥塞,则应由云平台完善的资源监控和管理功能来处理。资源监控应能检测到问题并加以解决,例如将部分负载分散到其他服务器。
云服务也会遭遇内部故障,例如磁盘驱动器故障。虽然服务会自动进行自我修复,例如故障转移到健康的磁盘驱动器、将流量重定向到健康的节点,并启动故障磁盘上数据的复制(通常会为此情况准备三个副本),但可能无法立即完成。在恢复过程中,服务的容量会降低,服务调用更容易被拒绝或超时。
识别忙音
对于通过 HTTP 访问的云服务,瞬时故障表现为服务拒绝请求,通常会返回相应的 HTTP 状态码,例如:503 服务不可用。对于通过 TCP 访问的关系数据库服务,数据库连接可能会关闭。其他短暂的服务中断可能会导致不同的错误代码,但处理方法类似。请参阅云服务文档以获取指导,但您应该能够清楚地判断何时遇到瞬时故障,文档中也可能规定了最佳响应方式。处理(并记录)意外的状态码。
应对忙线信号
一旦检测到忙线信号,最基本的应对措施就是重试。对于 HTTP 服务,这意味着重新发出请求。对于通过 TCP 访问的数据库,这可能需要重新建立数据库连接,然后再重新发出查询。
如果服务再次失败,您的应用程序应该如何响应?这取决于具体情况。一些可以考虑的响应措施包括:
- 立即重试(无延迟)。
- 延迟后重试(固定或随机延迟)。
- 逐渐增加重试延迟(线性或指数退避),并设置最大延迟。
- 在应用程序中抛出异常。
访问云服务需要经过网络,这本身就会引入一定的延迟(通过公共互联网访问时延迟较长,在数据中心内部访问时延迟较短)。如果故障很少发生,并且您所访问的服务的文档没有推荐其他方法,那么立即重试是合适的。
当服务限制请求时,短时间内可能会有多个客户端请求被拒绝。如果所有这些客户端同时快速重试,服务可能需要再次拒绝其中许多请求。延迟重试 的方法可以让服务有时间清理队列或重新平衡请求。如果延迟时间是随机的(例如 50 到 250 毫秒),则客户端对繁忙服务的重试次数会更加分散,从而提高所有客户端的成功率。
最温和的重试方法是逐渐增加延迟重试。如果服务遇到暂时性问题,不要通过频繁重试请求使问题恶化,而是随着时间的推移逐渐降低重试频率。重试会在一定延迟后进行;如果需要进一步重试,则每次重试前都会增加延迟时间。延迟时间可以按固定值增加(线性退避),也可以每次增加一倍(指数退避)。
无论采用何种重试策略,都应限制重试次数并限制退避次数。过于频繁的重试可能会降低性能,并给已接近容量极限的系统带来过大负担。记录重试次数有助于分析,从而识别过度重试的区域。
经过一定次数的延迟、退避和重试后,如果服务仍然没有响应,则应放弃尝试。这样做既是为了使服务能够恢复,也是为了避免应用程序被锁定。应用程序代码通常通过抛出异常来表明其无法完成任务(例如存储某些数据)。应用程序中的其他代码将以适合应用程序的方式处理该异常。这种异常处理机制需要编写到每个云原生应用程序中。
用户体验影响
处理瞬态故障有时会影响用户体验。虽然每个应用程序的具体处理方法各不相同,但有一些通用准则可供参考。
重试策略的选择和最大重试次数应取决于是否存在正在等待结果的交互式用户,或者这是否是批量操作。对于批量操作,采用指数退避算法并设置较高的重试次数限制可能更为合理,这样既能让服务有时间从活动高峰中恢复,又能充分利用没有交互式用户的优势。
如果存在正在等待的交互式用户,则应考虑在短时间内进行多次重试,然后再告知用户“系统目前繁忙,请稍后再试”。社交网络服务 Twitter 就以这种行为而闻名。您可以参考以队列为中心的工作流程模式(第 3 章)来了解如何将耗时的工作与用户界面解耦。
当服务在合理的时间或重试次数内仍未成功时,您的应用程序应采取相应措施。尽管令人不满意,但有时将信息反馈给用户也是一种合理的方法,例如“服务器繁忙,您的更新将在十秒后重试”。(这类似于 Google Mail 和 Quora 在其网页用户界面中处理临时网络连接问题的方式。)
记录并减少忙音
记录忙音有助于了解故障模式。由于调试和管理分布式云应用程序本身就极具挑战性,因此在云环境中可靠地跟踪和处理瞬态故障至关重要。
分析忙音日志可以带来一些改进措施,从而减少未来的忙音。例如,分析可能显示,访问云数据库服务时忙音的出现频率较高。请记住,云虽然给人一种资源无限的错觉,但这并不意味着每个资源都拥有无限的容量。要获得更多容量,您必须配置更多实例。当一个数据库实例不足以满足需求时,可能就需要应用数据库分片模式(参见第7章)。
测试
在非云环境中测试云应用程序很常见。在开发或测试环境中运行的代码,尤其是在低于生产环境的负载或使用专用硬件的情况下,可能不会遇到云端常见的瞬态故障。务必在尽可能接近生产环境的环境中进行测试和负载测试。
示例:在 Windows Azure 上构建 PoP
照片页面 (PoP) 应用程序(在前言中已介绍)使用了多项 Windows Azure 云服务。其中两项分别是用于存储帐户配置文件信息的 Windows Azure SQL 数据库和用于存储照片的 Windows Azure 存储 Blob。由于这些是云服务,PoP 代码需要能够处理瞬态故障。
Windows Azure 平台包含可用于各种编程环境(例如 C#、Java、Node.js、Python 和移动设备)的软件库。这些库通过自动检测瞬态故障并最多重试三次,简化了对 Blob 的访问。此外,还有许多其他预定义的重试行为可用,也可以创建自定义重试策略。大多数应用程序不需要默认行为之外的任何功能。
访问 Windows Azure SQL 数据库的应用程序使用与访问 SQL Server 相同的 TCP 协议。但是,需要一种更强大的瞬态故障检测和重试逻辑方法。这可以通过一个名为“瞬态故障处理应用程序块”(也称为 Topaz)的库来实现。与之前提到的用于 Blob 访问的库中的重试支持类似,Topaz 也预定义了一些重试行为,同时还提供了丰富的自定义模型。在编写数据库访问代码时,利用 Topaz 功能的最简单方法是将标准数据库对象替换为 Topaz 自带的、能够感知瞬态故障的等效对象。例如,使用 ReliableSqlConnection 对象替换标准的 .NET SqlConnection 对象。
某些 SQL 数据库瞬态故障是由于限流造成的,并通过返回一个众所周知的错误代码来通知您。有时,SQL 数据库会断开您的数据库连接,需要您重新连接。
虽然本文重点介绍 Topaz 与 SQL 数据库的结合使用,但它也支持 Windows Azure 存储、Windows Azure 缓存和 Windows Azure 服务总线,包括您在需要时可以使用的高级重试功能。
总结
处理瞬态故障对于构建可靠的云原生应用程序至关重要。使用忙碌信号模式,您的应用程序可以检测并妥善处理瞬态故障。此外,您可以针对批处理或交互式用户场景调整您的处理方法。
如果应用程序运行在非云硬件上或负载过轻,则可能难以测试其对瞬态故障的响应。