Azure SignalR 服务常见问题故障排除指南
本文提供有关客户可能会遇到的一些常见问题的故障排除指南。
- 客户端
ERR_CONNECTION_
- 414 URI 太长
- 413 有效负载太大
- 访问令牌不得长于 4 K。 413 请求实体太大
对于 HTTP/2,单个标头的最大长度为 4 K。因此,如果使用浏览器访问 Azure 服务,则会出现有关此限制的“ERR_CONNECTION_
”错误。
对于 HTTP/1.1 或 C# 客户端,最大 URI 长度为 12 K,最大标头长度 16 K。
使用 SDK 1.0.6 或更高版本时,/negotiate
会在生成的访问令牌大于 4 K 时引发“413 Payload Too Large
”错误。
默认情况下,在生成针对 ASRS(Azure SignalR Service,即 Azure SignalR 服务)的 JWT 访问令牌时,会包括 context.User.Claims
中的声明,这样,这些声明会被保留,并可以在客户端连接到 Hub
时从 ASRS 传递到 Hub
。
在某些情况下,会使用 context.User.Claims
来存储应用服务器的大量信息,其中的大多数信息不是供 Hub
使用,而是供其他组件使用。
生成的访问令牌通过网络传递。对于 WebSocket/SSE 连接,访问令牌通过查询字符串传递。 因此,我们建议仅当 Hub 需要时才通过 ASRS 将必需的声明从客户端传递给应用服务器,这是最佳做法。
可以通过 ClaimsProvider
在访问令牌中自定义传递给 ASRS 的声明。
以下代码适用于 ASP.NET Core:
services.AddSignalR()
.AddAzureSignalR(options =>
{
// pick up necessary claims
options.ClaimsProvider = context => context.User.Claims.Where(...);
});
以下代码适用于 ASP.NET:
services.MapAzureSignalR(GetType().FullName, options =>
{
// pick up necessary claims
options.ClaimsProvider = context.Authentication?.User.Claims.Where(...);
});
- ASP.NET 的“无可用服务器”错误 #279
- ASP.NET 的“连接未处于活动状态,无法将数据发送到服务。”错误 #324
- “向
https://<API endpoint>
发出 HTTP 请求时出错。 此错误可能是由于未在 HTTPS 用例中正确使用 HTTP.SYS 配置服务器证书所致。 客户端与服务器之间的安全绑定不匹配也可能造成此错误。”
出于安全考虑,Azure 服务仅支持 TLS 1.2。 使用 .NET Framework 时,TLS 1.2 可能不是默认协议。 因此,无法成功建立与 ASRS 的服务器连接。
如果可以在本地重现此错误,请取消选中“仅我的代码”,引发所有 CLR 异常,并在本地调试应用服务器以查看引发的具体异常。
取消选中“仅我的代码”
引发 CLR 异常
请查看调试应用服务器端代码时引发的异常:
对于 ASP.NET 错误,还可以将以下代码添加到
Startup.cs
,以便启用详细的跟踪并查看日志中的错误。app.MapAzureSignalR(this.GetType().FullName); // Make sure this switch is called after MapAzureSignalR GlobalHost.TraceManager.Switch.Level = SourceLevels.Information;
将以下代码添加到 Startup:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
检查客户端请求是否有多个 hub
查询字符串。 hub
是保留的查询参数。如果服务检测到查询中有多个 hub
,则会返回 400 错误。
JWT 令牌生存期的默认值目前为一 (1) 小时。
对于 ASP.NET Core SignalR,它在使用 WebSocket 传输类型时是正常的。
对于 ASP.NET Core SignalR 的其他传输类型(SSE 和长轮询),默认生存期意味着默认情况下连接最多可以保持一小时。
对于 ASP.NET SignalR,客户端会不时地将 /ping
“保持连接”请求发送给服务。当 /ping
失败时,客户端会中止连接,且不再重新连接。 对于 ASP.NET SignalR,默认令牌生存期会使连接最多持续一小时,而无论传输类型是哪一种。
出于安全考虑,不建议延长 TTL。 建议在发生此类 401 错误时,添加客户端的重新连接逻辑以重启连接。 客户端在重启连接时,会与应用服务器协商以再次获取 JWT 令牌并获取续订的令牌。
请查看此文,了解如何重启客户端连接。
对于 SignalR 持久性连接,它首先与 Azure SignalR 服务执行 /negotiate
,然后建立与 Azure SignalR 服务的实际连接。
- 按照如何查看传出请求操作,以获取从客户端到服务的请求。
- 出现 404 时,请检查请求的 URL。 如果 URL 是针对你的 Web 应用,并且类似于
{your_web_app}/hubs/{hubName}
,则请检查客户端SkipNegotiation
是否为true
。 客户端会在首次与应用服务器协商时接收重定向 URL。 使用 Azure SignalR 时,客户端不得跳过协商。 - 如果在调用
/negotiate
后过了五 (5) 秒以上才处理连接请求,则可能会发生另一个 404 错误。 如果对服务请求的响应较慢,请检查客户端请求的时间戳,并向我们提出问题。
对于 ASP.NET SignalR,当客户端连接断开时,它会使用相同的 connectionId
重新连接三次,然后才停止连接。 如果连接断开是由于网络间歇性问题,则可以使用 /reconnect
。/reconnect
可以成功地重新建立持久性连接。 在其他情况下,例如,客户端连接因为路由的服务器连接而断开时,或者 SignalR 服务发生一些内部错误(如实例重启/故障转移/部署错误)时, 该连接将不再存在,因此 /reconnect
会返回 404
。 它是 /reconnect
的预期行为,三次重试后连接会停止。 建议在连接停止时使用连接重启逻辑。
存在两种情况。
对于免费实例,并发连接计数限制为 20。对于标准实例,每个单位的并发连接计数限制为 1K,这意味着 100 个单位允许 100 K 个并发连接。
连接包括客户端连接和服务器连接。 请查看此文,了解如何进行连接计数。
当同一时间内客户端协商请求过多时,可能会受到限制。 此限制与单位计数相关,即单位计数越高,上限越高。 此外,我们建议先经过一段随机的延迟再重新连接;有关重试示例,请查看此文。
如果没有与 Azure SignalR 服务的服务器连接,则会报告此错误。
启用服务器端跟踪,以便在服务器尝试连接到 Azure SignalR 服务时查明错误详情。
ASP.NET Core SignalR 的服务器端日志记录与在 ASP.NET Core Framework 中提供的基于 ILogger
的日志记录集成。 你可以使用 ConfigureLogging
来启用服务器端日志记录,示例用法如下:
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole();
logging.AddDebug();
})
Azure SignalR 的记录器类别始终以 Microsoft.Azure.SignalR
开头。 若要从 Azure SignalR 启用详细日志,请在 appsettings.json 文件中将前面的前缀配置为 Debug
级别,如下所示:
{
"Logging": {
"LogLevel": {
...
"Microsoft.Azure.SignalR": "Debug",
...
}
}
}
使用 >= 1.0.0
的 SDK 版本时,可以通过将以下内容添加到 web.config
来启用跟踪:(详细信息)
<system.diagnostics>
<sources>
<source name="Microsoft.Azure.SignalR" switchName="SignalRSwitch">
<listeners>
<add name="ASRS" />
</listeners>
</source>
</sources>
<!-- Sets the trace verbosity level -->
<switches>
<add name="SignalRSwitch" value="Information" />
</switches>
<!-- Specifies the trace writer for output -->
<sharedListeners>
<add name="ASRS" type="System.Diagnostics.TextWriterTraceListener" initializeData="asrs.log.txt" />
</sharedListeners>
<trace autoflush="true" />
</system.diagnostics>
当客户端连接到 Azure SignalR 时,客户端与 Azure SignalR 之间的持久性连接有时可能会因不同的原因而断开。 此部分介绍导致此类连接断开的几种可能性,并提供一些有关如何确定根本原因的指导。
The remote party closed the WebSocket connection without completing the close handshake
Service timeout. 30000.00ms elapsed without receiving a message from service.
{"type":7,"error":"Connection closed with an error."}
{"type":7,"error":"Internal server error."}
客户端连接可能会在各种情况下断开:
- 当
Hub
引发传入请求的异常时 - 当客户端路由到的服务器连接断开时,请参阅下一部分,了解有关服务器连接断开的详细信息
- 当客户端与 SignalR 服务之间发生网络连接问题时
- 当 SignalR 服务有一些内部错误(如实例重启错误、故障转移错误、部署错误等)时
- 打开应用服务器端日志以查看是否发生了异常
- 检查应用服务器端事件日志以查看应用服务器是否已重启
- 创建一个将提交给我们的问题,提供时间范围,并通过电子邮件向我们发送资源名称
客户端连接使用不当可能会导致这个问题。 如果用户忘记停止/释放 SignalR 客户端,则连接会保持打开状态。
在 Azure SignalR 的指标中,客户端连接计数会不断增加,持续很长时间。
SignalR 客户端连接的 DisposeAsync
从未被调用,因此该连接保持打开状态。
检查 SignalR 客户端是否从未关闭。
检查是否关闭了连接。 手动调用 HubConnection.DisposeAsync()
,以便在使用连接后停止连接。
例如:
var connection = new HubConnectionBuilder()
.WithUrl(...)
.Build();
try
{
await connection.StartAsync();
// Do your stuff
await connection.StopAsync();
}
finally
{
await connection.DisposeAsync();
}
当有人在 Azure 函数方法中建立 SignalR 客户端连接,而不是使其成为函数类的静态成员时,通常会出现此问题。 你可能只期望建立一个客户端连接,但却看到指标中的客户端连接计数不断增加。 所有这些连接只有在 Azure Function 或 Azure SignalR 服务重启后才会断开。 出现此行为的原因如下:Azure Function 会为每个请求创建一个客户端连接,而如果你不在函数方法中停止客户端连接,客户端会让到 Azure SignalR 服务的连接保持活动状态。
- 如果在 Azure 函数中使用 SignalR 客户端或将 SignalR 客户端用作单一实例,请记得关闭客户端连接。
- 无需在 Azure 函数中使用 SignalR 客户端,你可以在其他任何位置创建 SignalR 客户端,并使用适用于 Azure SignalR 服务的 Azure Functions 绑定,以便通过协商将客户端连接到 Azure SignalR。 此外,还可以利用绑定来发送消息。 可在此处找到用于协商客户端和发送消息的示例。 可在此处找到更多信息。
- 在 Azure 函数中使用 SignalR 客户端时,可能会有更适合你的方案的体系结构。 检查是否设计了正确的无服务器体系结构。 可以参阅使用 Azure SignalR 服务和 Azure Functions 的实时应用。
当应用服务器在后台启动时,Azure SDK 就会开始启动到远程 Azure SignalR 的服务器连接。 如 Azure SignalR 服务内部情况所述,Azure SignalR 会将传入客户端流量路由到这些服务器连接。 断开服务器连接后,它所处理的所有客户端连接也会关闭。
应用服务器与 SignalR 服务之间的连接是持久性连接,因此可能会遇到网络连接问题。 在服务器 SDK 中,我们对服务器连接实施“始终进行重新连接”策略。 我们还建议用户使用随机延迟时间向客户端添加连续重新连接逻辑,避免向服务器同时发送大规模的请求,这是最佳做法。
我们会定期发布 Azure SignalR 服务的新版本,有时还有 Azure 范围的修补或升级,我们的依赖服务偶尔也会出现中断。 这些事件可能会导致服务中断很短的时间,但只要客户端存在断开连接/重新连接机制,此影响就会很小,就像任何客户端导致的断开连接-重新连接一样。
此部分介绍导致服务器连接断开的几种可能性,并提供一些有关如何确定根本原因的指导。
[Error]Connection "..." to the service was dropped
The remote party closed the WebSocket connection without completing the close handshake
Service timeout. 30000.00ms elapsed without receiving a message from service.
服务器-服务连接通过 ASRS(Azure SignalR Service,Azure SignalR 服务)关闭。
服务器端的 CPU 使用率较高或线程池不足可能会导致 ping 超时。
对于 ASP.NET SignalR,SDK 1.6.0 中已修复一个已知问题。 将 SDK 升级到最新版本。
如果服务器的资源枯竭,则意味着没有任何线程在处理消息。 所有线程在特定方法中均未响应。
通常,在异步方法中,通过同步或 Task.Result
/Task.Wait()
异步会导致这种情况。
请参阅 ASP.NET Core 性能最佳做法。
详细了解线程池资源枯竭。
检查线程计数。 如果当时没有出现高峰,请执行以下步骤:
如果使用的是 Azure 应用服务,请检查指标中的线程计数。 检查
Max
聚合:如果使用的是 .NET Framework,可以在服务器 VM 的性能监视器中找到指标。
如果在容器中使用 .NET Core,请参阅在容器中收集诊断数据。
还可以使用代码来检测线程池资源枯竭:
public class ThreadPoolStarvationDetector : EventListener
{
private const int EventIdForThreadPoolWorkerThreadAdjustmentAdjustment = 55;
private const uint ReasonForStarvation = 6;
private readonly ILogger<ThreadPoolStarvationDetector> _logger;
public ThreadPoolStarvationDetector(ILogger<ThreadPoolStarvationDetector> logger)
{
_logger = logger;
}
protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name == "Microsoft-Windows-DotNETRuntime")
{
EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// See: https://learn.microsoft.com/dotnet/framework/performance/thread-pool-etw-events#threadpoolworkerthreadadjustmentadjustment
if (eventData.EventId == EventIdForThreadPoolWorkerThreadAdjustmentAdjustment &&
eventData.Payload[2] as uint? == ReasonForStarvation)
{
_logger.LogWarning("Thread pool starvation detected!");
}
}
}
将此代码添加到服务中:
service.AddSingleton<ThreadPoolStarvationDetector>();
然后,在服务器连接由于 ping 超时断开时检查日志。
若要找到线程池资源枯竭的根本原因,请执行以下操作:
- 打开应用服务器端日志以查看是否有异常情况发生。
- 检查应用服务器端事件日志以查看应用服务器是否已重启。
- 创建问题。 提供时间范围,并通过电子邮件将资源名称告诉我们。
以 ASP.NET Core 的为例(ASP.NET 的类似):
从浏览器:以 Chrome 为例,可以使用 F12 打开控制台窗口,并切换到“网络”选项卡。可能需要使用 F5 刷新页面,以从头开始捕获网络。
从 C# 客户端:
可以使用 Fiddler 查看本地 Web 流量。 从 Fiddler 4.5 开始,支持 WebSocket 流量。
下面是示例代码,其中包含可以与“始终重试”策略配合使用的重启连接逻辑:
在本指南中,你了解了如何处理常见问题。 你还可以了解更多常用的故障排除方法。