云计算设计模式(十八)——重试模式
启用应用程序来处理预期的,临时的失败时。它会尝试连接到由透明的重试操作了曾经失败的期望,失败的原因是瞬时的服务或网络资源。这样的模式能够提高应用程序的稳定性。
背景和问题
该通信的应用程序与在云中执行的元素必须是可能发生在这种环境中的瞬时故障敏感。这些故障包含网络连接的过程中出现时,一个服务是忙碌的瞬时损失的组件和服务中,服务的暂时不可用。或超时。
这些故障通常是自校正的,假设经过一个合适的延迟被反复触发一个故障的动作非常可能是成功的。
比如,数据库服务,它正在处理大量并发请求能够实现节流策略,临时拒绝。直到它的工作量有所缓和不论什么进一步的请求。
试图訪问该数据库的应用程序可能无法连接,但假设它经过一个合适的延迟再次尝试它可能会成功。
解决方式
在云中。瞬时故障的情况并不少见和应用应该被设计为优雅和透明地处理它们,降低的影响,这样的故障可能对应用程序正在运行业务任务。
假设一个应用程序检測到故障时,它试图将请求发送到远程服务,它能够通过使用下面策略处理失败:
•假设故障指示故障不是瞬时的或不太成功,假设反复(比如,所造成的无效提供凭据的认证失败是不可能成功的,不管是多少次未遂)。应用程序应中止操作和报告一个合适的异常。
•假设报道的详细故障是不平常的或罕见的,这可能是由于反常的情况,如网络数据包成为损坏,同一时候它被发送。在这样的情况下。应用程序能够再次马上重试失败的请求,由于同样的故障是不可能被反复和请求将可能是成功的。
•假设故障是由一种更加普遍的连接,或“忙”的失败,网络或服务可能须要在短期内同一时候连接问题纠正或工作的积压被清除。应用程序应该等待请求重试前一个合适的时间。
对于比較常见的短暂故障,重试期间。应选择以传播从应用程序中尽可能均匀的多个实例的请求。这能够降低繁忙的业务持续过载的可能性。假设一个应用程序的多个实例不断轰击与重试请求的服务。则可能须要该服务更长的时间来恢复。
假设请求仍然失败。应用程序能够等待进一步的时期,再次尝试。假设须要的话,这个过程能够反复而添加重试的延迟。直到请求的某一最大数目已经尝试都失败了。延迟时间能够逐步添加。或可使用的定时策略,如指数回退。取决于故障的性质和可能性,这将在这段时间内被校正。
图1示出了这样的模式。
假设尝试后的预定数量的请求不成功,应用程序应将故障为异常,并对应地处理它。
图1 - 使用重试模式中调用托管服务的操作
应用程序应该换全部试图訪问远程服务,实现重试政策配套上面列出的策略之中的一个的代码。
发送到不同的服务请求会受到不同的政策。有的供应商提供封装这样的方法库。
这些库通常运行的政策是參数化的,而应用程序开发者能够指定,如重试次数和重试之间的时间项的值。
在检測故障和重试失败的操作都应该记录这些故障的具体信息的应用程序的代码。这个信息可能是实用的运算符。
假设一个服务被频繁报道为不可用或忙,往往是由于该服务已耗尽其资源。则能够降低与这些故障发生时通过换算出该服务的频率。比如,假设数据库服务正在不断超载,它可能是有利的分区数据库和负载分散到多个server。
注意:
微软Azure提供了重试模式的广泛支持。
该模式与实践瞬态故障处理块同意应用程序通过一系列的重试策略来处理很多Azure服务瞬态故障。微软实体框架版本号6提供了用于又一次尝试数据库操作。此外。很多在Azure
Service Bus和Azure存储的API透明地运行重试逻辑。
问题和注意事项
在决定怎样实现这个模式时。您应考虑下面几点:
•重试政策应进行调整,以满足应用和故障性质的业务需求。它可能是更好一些非关键操作失败快而不是重试几次。并影响应用程序的吞吐量。
比如,在试图訪问远程服务的交互式Web应用程序,这可能是更好的重试之后用重试之间仅仅有一个短的延迟的数量较少失败,并显示一个适当的消息给用户(比如,“请稍后“)。再次尝试阻止应用程序变得反应迟钝。对于批处理应用程序,它能够是更合适的。以添加重试尝试的次数与尝试之间的指数添加的延迟。
•与尝试之间最小的延迟和大量的重试的高攻击重试的政策,可能会进一步减少正在接近执行或容量的占用。
此重试策略也可能会影响应用程序的响应,假设它被不断地在尝试执行失败的操作,而不是做实用功。
•假设后一个显著次数的重试请求仍然失败,则可能是更好的应用程序。以防止进一步的请求将要在同样的资源为一个周期。并简单地马上报告故障。当期限届满后,该应用程序能够临时同意通过一个或多个请求,看看他们是否成功。对于这一策略的具体信息,请參阅断路器格局。
•在由它实现了一个重试策略可能须要为幂等的应用程序调用的服务的操作。比如,发送到服务的请求能够被接收和处理成功,可是,因为瞬时故障,它可能无法发送响应,指示该处理已完毕。然后在应用程序的重试逻辑可能试图反复上没有接收到所述第一请求的假定该请求。
•一个请求到服务失败可能因为各种原因而提出不同的异常,依据故障的性质。一些例外可指示故障,能够很迅速地得到解决,而还有一些可能表明该故障持续时间更长。
可能是故意的重试策略,调整基于所述异常的类型的重试尝试之间的时间。
•考虑怎样重试的操作是事务的一部分,会影响总体交易的一致性。这可能是实用的微调对于事务性操作的重试政策,最大限度地取得成功的机会,并降低须要撤消全部交易步骤。
•确保全部重试代码是全然针对各种故障条件下进行測试。
检查它不会严重影响应用程序的性能或可靠性,导致在服务和资源的过度负荷,或产生竞态条件或瓶颈。
•实现仅仅在一个失败的操作的全方面了解重试逻辑。比如,假设包括的重试策略任务调用还有一个任务还包括一个重试策略,这个额外的重试的层可加长的延迟的处理。它可能是更好的配置的低级任务失败高速并报告失败返回调用它的任务的原因。
然后这个更高级别的任务能够决定怎样处理是依据它自己的策略失效。
•记录全部的连接故障。提示了重试。使潜在的问题与该应用程序,服务或资源能够被识别是非常重要的。
•研究是最有可能发生于一个服务或资源发现,假设它们有可能是持久或终端的故障。
假设是这种话,它可能是更好地处理该故障为异常。该应用程序能够报告或记录该异常。然后试图通过调用还有一个服务,持续或者(假设有一个可用的),或通过提供降级功能。关于怎样检測和处理持久故障的很多其它信息,请參阅断路器格局。
何时使用这个模式
使用这样的模式:
•当一个应用程序可能会经历短暂的故障,由于它与远程服务进行交互。或訪问远程资源。
这些故障估计将是短暂的,并反复了曾经没有可以成功的兴许尝试的请求。
这样的模式可能不适合:
•当故障非常可能是持久的。由于这可能会影响应用程序的响应性。该应用程序能够简单地是浪费时间和资源试图反复请求是最有可能失败。
•对于处理故障是不因瞬时故障。如在应用程序的业务逻辑引起错误的内部的异常。
•作为一种替代解决系统中的可扩展性问题。假设一个应用程序有频繁的“忙”的故障,这是通常指示被訪问的服务或资源应对应加大。
样例
本实施例说明的重试模式的实现。
该OperationWithBasicRetryAsync方法。例如以下所看到的,通过TransientOperationAsync方法异步调用外部服务(该方法的细节将特定于服务,并从样本代码被省略)。
private int retryCount = 3; ... public async Task OperationWithBasicRetryAsync() { int currentRetry = 0; for (; ;) { try { // Calling external service. await TransientOperationAsync(); // Return or break. break; } catch (Exception ex) { Trace.TraceError("Operation Exception"); currentRetry++; // Check if the exception thrown was a transient exception // based on the logic in the error detection strategy. // Determine whether to retry the operation, as well as how // long to wait, based on the retry strategy. if (currentRetry > this.retryCount || !IsTransient(ex)) { // If this is not a transient error // or we should not retry re-throw the exception. throw; } } // Wait to retry the operation. // Consider calculating an exponential delay here and // using a strategy best suited for the operation and fault. Await.Task.Delay(); } } // Async method that wraps a call to a remote service (details not shown). private async Task TransientOperationAsync() { ... }
调用此方法的声明被包裹在一个循环一个try/
catch块中封装。假设调用TransientOperationAsync方法成功,没有抛出异常的for循环退出。
假设TransientOperationAsync方法失败,catch块检查为失败的原因,而且假设它被觉得是一个瞬时错误代码等待一个短暂的延时,然后重试该操作。
在for循环还跟踪该操作已经尝试的次数,而且假设代码失败三次异常被觉得是更持久。
假设该异常是不是临时的。或者是长久的,catch处理抛出的异常。
此异常退出for循环。应捕获调用该OperationWithBasicRetryAsync方法的代码。
该IsTransient方法。例如以下所看到的,检查是否有特定的一组是相关的。当中所述代码执行的环境的异常。
一过异常的定义能够依据被訪问的资源,并在其上执行的操作环境的不同而不同。
private bool IsTransient(Exception ex) { // Determine if the exception is transient. // In some cases this may be as simple as checking the exception type, in other // cases it may be necessary to inspect other properties of the exception. if (ex is OperationTransientException) return true; var webException = ex as WebException; if (webException != null) { // If the web exception contains one of the following status values // it may be transient. return new[] {WebExceptionStatus.ConnectionClosed, WebExceptionStatus.Timeout, WebExceptionStatus.RequestCanceled }. Contains(webException.Status); } // Additional exception checking logic goes here. return false; }