启用应用程序来处理预期的,暂时的失败时,它会尝试连接到由透明的重试操作了以前失败的期望,失败的原因是瞬时的服务或网络资源。这种模式可以提高应用程序的稳定性。
该通信的应用程序与在云中运行的元素必须是可能发生在这样的环境中的瞬时故障敏感。这些故障包括网络连接的过程中出现时,一个服务是忙碌的瞬时损失的组件和服务中,服务的临时不可用,或超时。
这些故障一般是自校正的,如果经过一个合适的延迟被重复触发一个故障的动作很可能是成功的。例如,数据库服务,它正在处理大量并发请求可以实现节流策略,暂时拒绝,直到它的工作量有所缓和任何进一步的请求。试图访问该数据库的应用程序可能无法连接,但如果它经过一个合适的延迟再次尝试它可能会成功。
在云中,瞬时故障的情况并不少见和应用应该被设计为优雅和透明地处理它们,减少的影响,这种故障可能对应用程序正在执行业务任务。
如果一个应用程序检测到故障时,它试图将请求发送到远程服务,它可以通过使用以下策略处理失败:
对于比较常见的短暂故障,重试期间,应选择以传播从应用程序中尽可能均匀的多个实例的请求。这可以减少繁忙的业务持续过载的可能性。如果一个应用程序的多个实例不断轰击与重试请求的服务,则可能需要该服务更长的时间来恢复。
如果请求仍然失败,应用程序可以等待进一步的时期,再次尝试。如果需要的话,这个过程可以重复而增加重试的延迟,直到请求的某一最大数目已经尝试都失败了。延迟时间可以逐步增加,或可使用的定时策略,如指数回退,取决于故障的性质和可能性,这将在这段时间内被校正。
图1示出了这种模式。如果尝试后的预定数量的请求不成功,应用程序应将故障为异常,并相应地处理它。
图1 - 使用重试模式中调用托管服务的操作
应用程序应该换所有试图访问远程服务,实现重试政策配套上面列出的策略之一的代码。发送到不同的服务请求会受到不同的政策,有的供应商提供封装这种方法库。这些库通常执行的政策是参数化的,而应用程序开发人员可以指定,如重试次数和重试之间的时间项的值。
在检测故障和重试失败的操作都应该记录这些故障的详细信息的应用程序的代码。这个信息可能是有用的运算符。如果一个服务被频繁报道为不可用或忙,往往是因为该服务已耗尽其资源。则可以减少与这些故障发生时通过换算出该服务的频率。例如,如果数据库服务正在不断超载,它可能是有利的分区数据库和负载分散到多个服务器。
注意: 微软 Azure 提供了重试模式的广泛支持。该模式与实践瞬态故障处理块允许应用程序通过一系列的重试策略来处理许多 Azure 服务瞬态故障。微软实体框架版本6提供了用于重新尝试数据库操作。此外,许多在 Azure Service Bus 和 Azure 存储的 API 透明地执行重试逻辑。
在决定如何实现这个模式时,您应考虑以下几点:
使用这种模式:
本实施例说明的重试模式的实现。该 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; }