当请求未在配置的时间内完成时,发送和接收操作可能会超时。 大多数超时都是可恢复的,客户端库会自动重试。 当超时传递到你的应用程序代码时,要么所有重试机会都已耗尽,要么该操作的耗时超过了配置的单次尝试超时时间。
本文介绍超时的含义、如何配置控制此行为的重试选项,以及如何避免导致可避免超时的客户端模式。 它是故障排除指南和服务总线 异常参考文档的补充。
超时的含义
根据操作和主机环境的不同,超时可能表现为 TimeoutException、OperationCanceledException,或其 为 Reason 的 ServiceTimeout。
指示 ServiceTimeout 服务未在预期时间内响应请求。 原因可能位于连接两侧:
- 客户端 - 应用程序无法及时访问服务(网络路径、DNS、代理、防火墙)、客户端过载(线程池不足、CPU 过高),或者配置的尝试超时时间太短,无法执行此操作。
- 服务端 - 服务暂时异常或更大范围的故障。 当服务出现故障时,故障通常跨多个操作关联,并在Azure门户中的命名空间资源运行状况页中可见。
使用 IsTransient 上的 ServiceBusException 属性来区分可恢复的故障。 当 IsTransient 为 true 时,客户端已应用重试策略,并且每次尝试都失败。 持续出现的瞬时超时通常表明存在连接问题,或者重试配置设置过小。
配置重试选项
根据客户端上配置的重试选项,客户端库会重试暂时性故障,包括超时。 在 .NET 库中,通过 ServiceBusClientOptions 设置 ServiceBusRetryOptions。
var options = new ServiceBusClientOptions
{
RetryOptions = new ServiceBusRetryOptions
{
Mode = ServiceBusRetryMode.Exponential,
MaxRetries = 3,
Delay = TimeSpan.FromSeconds(0.8),
MaxDelay = TimeSpan.FromSeconds(60),
TryTimeout = TimeSpan.FromSeconds(60)
}
};
var client = new ServiceBusClient(fullyQualifiedNamespace, credential, options);
| 选项 | 它控制的内容 | 默认 |
|---|---|---|
Mode |
退避是 Fixed 还是 Exponential |
Exponential |
MaxRetries |
暂时性故障的最大重试尝试次数 | 3 |
Delay |
尝试之间的基本延迟 | 0.8 秒 |
MaxDelay |
回退延迟的上限 | 60 秒 |
TryTimeout |
单次尝试超时前的最长持续时间 | 60 秒 |
请对照 ServiceBusRetryOptions 参考检查当前默认值,因为这些默认值可能会因库版本不同而发生变化。 Java、JavaScript、Python 和 Go 客户端库中存在等效的重试选项。
调整工作负载的值
- 如果操作确实需要超过 60 秒,请增加
TryTimeout— 例如,从具有较长maxWaitTime的低流量队列接收消息,或者在高延迟网络上进行操作。 较大的尝试超时可减少过早超时,但也会延长真正无法访问的服务出现错误之前的时间。 -
增加
MaxRetries或MaxDelay以承受更长时间的瞬态情况,但代价是故障检测会变慢。 - 请注意锁定时长。 如果
Reason为ServiceBusy(节流),客户端将进行 10 秒退避。 如果实体的锁定持续时间短于退让时间,则等待期间可能会丢失消息或会话锁。 请参阅 限制。
避免使用会导致超时的客户端模式
许多超时升级是由应用程序模式而不是服务引起的。 首先解决这些问题。
不要阻止异步调用(同步过度同步)
在异步 服务总线 API 上调用 .Result、.Wait() 或 .GetAwaiter().GetResult() 会阻塞一个线程池线程。 在高负载下,此操作会导致线程池饥饿:用于处理已完成 I/O 的回调无法执行,因此即使服务运行正常,操作也会超时。 症状表现为超时,并且会随着并发增加而加重,在负载下降时消失。
请改为以端到端方式使用 async/await:
// Avoid
ServiceBusReceivedMessage message = receiver.ReceiveMessageAsync().GetAwaiter().GetResult();
// Prefer
ServiceBusReceivedMessage message = await receiver.ReceiveMessageAsync();
将客户端作为单例重用
每个新的 ServiceBusClient 都会打开一个新的 AMQP 连接。 为每条消息或每个请求创建一个客户端会耗尽套接字,并导致连接超时。 在应用程序的生存期内创建一个客户端并重复使用它。 有关详细信息,请参阅 套接字耗尽错误。
合理设置预取和并发度
较高的预提取计数,或较高的 MaxConcurrentCalls 与缓慢的消息处理相结合,可能会导致锁超时和表现为超时的处理延迟。 将预取与您的处理速率相匹配,并在处理确实需要的时间超过锁定时长时续订锁定。 请参阅 预提取消息 和 性能最佳做法。
验证网络路径
在连接时发生超时时,确认客户端可以访问服务:
- AMQP 端口 5671 和 5672 已开放,或者通过 443 端口使用 WebSocket 传输。
- DNS 解析命名空间,包括任何专用终结点和专用 DNS 区域配置。
- 没有代理或防火墙正在截获连接。
有关分步网络检查,请参阅 连接、证书或超时问题。
在代码中处理超时情况
针对 Reason 进行筛选,这样你就可以专门处理超时,而不是以相同的方式捕获所有故障:
try
{
await sender.SendMessageAsync(message);
}
catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.ServiceTimeout)
{
// The client already retried. Log for correlation, then decide whether
// to retry at the application level or surface the failure.
}
确定是客户端还是服务
在将超时视为服务问题上报之前,请先排除客户端因素:
- 检查Azure门户中的命名空间资源运行状况页,了解受影响的时间范围。
- 检查这些超时是否与客户端 CPU 使用率高或高并发相关。
- 启用客户端日志,以查看是否发生了重试以及底层的 AMQP 错误是什么。 请参阅 日志记录和诊断。
- 确认来自客户端主机的网络路径和 DNS 解析。
如果资源运行状况将命名空间显示为正常,并且超时与客户端加载或配置相关,则问题很可能在客户端上。 如果资源健康状况在整个时间窗口内都显示为降级,则更可能是服务端导致的。