跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

c# SignalR 高并发连接数优化 SignalR 性能瓶颈

SignalR 默认连接数上不去是因服务器配置和底层限制所致,需调优Kestrel连接数、HTTP/2、TLS及Hub广播机制,并优化客户端重连策略。 SignalR 默认连接数为什么上不去? SignalR 在 IIS 或 Kestrel 上默认受限于服务器配置和 ASP.NET Core 的底层限制,不是代码写得“高级”就能突破的。常见现象是:压测时连接数卡在几百就报
ConnectionRefused
、
TimeoutException
或客户端反复重连失败——这往往不是 SignalR 自身问题,而是 Windows 句柄、线程池、HTTP/2 流控或 TLS 握手堆积导致的。 IIS 默认
maxConcurrentRequestsPerCPU
是 5000,但实际受
applicationPool → queueLength
(默认1000)和系统
MaxUserPort
(默认5000)拖累 Kestrel 默认不启用 HTTP/2,而大量短连接 + 频繁重连会快速耗尽端口和 TLS session cache
HubLifetimeManager
默认用内存实现,高并发下广播操作易成瓶颈,尤其
Clients.All.SendAsync
会同步阻塞整个 Hub 线程 必须改的 Kestrel 和 ASP.NET Core 配置项 别只盯着
Hub
类写法,真正起效的是宿主层调优。以下配置需在
Program.cs
中显式设置:
var builder = WebApplication.CreateBuilder(args); // 关键:Kestrel 连接与缓冲调优 builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.Limits.MaxConcurrentConnections = 100_000; // 显式放开 serverOptions.Limits.MaxConcurrentUpgradedConnections = 100_000; serverOptions.Limits.MaxRequestBodySize = null; // 若不用上传 serverOptions.Limits.MinRequestBodyDataRate = null; // 防止慢速攻击误杀 serverOptions.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(5); }); // 关键:ASP.NET Core 主机级并发控制 builder.Services.Configure(options => { options.AutomaticAuthentication = false; options.AuthenticationDisplayName = "Windows Authentication"; }); builder.Services.Configure(options => { options.MaxConnections = 100_000; options.MaxRequestBodySize = null; }); var app = builder.Build();
注意:
MaxConcurrentConnections
必须设为
null
或足够大数值,否则 Kestrel 内部连接队列会直接拒绝新连接;
MaxConcurrentUpgradedConnections
是 WebSocket 升级专用阈值,SignalR 默认走 WebSocket,此项常被忽略。 Hub 层避免广播阻塞的实操方式 高频消息场景下,
Clients.All.SendAsync
是性能杀手——它会把所有连接序列化一次、再逐个 await 发送,中间任意一个慢连接都会拖垮整批。替代方案不是“优化序列化”,而是绕过同步广播模型: 知网AI智能写作 知网AI智能写作,写文档、写报告如此简单 下载 用
Clients.AllExcept(...).SendAsync
减少目标集,但本质未解耦 改用
ITransportHeartbeat
+ 自定义
IHubLifetimeManager
实现异步批量投递(推荐) 更轻量做法:在 Hub 外启动独立后台服务(
BackgroundService
),将消息入队(如
Channel
),由消费者线程池分片推送 禁用自动 JSON 序列化缓存:
AddJsonProtocol(opt => opt.PayloadSerializerOptions.WriteIndented = false)
,减小 GC 压力 示例:用 Channel 实现非阻塞广播
public class BroadcastService : BackgroundService { private readonly ChannelReader _reader; private readonly IHubContext _hubContext; public BroadcastService(Channel channel, IHubContext hubContext) { _reader = channel.Reader; _hubContext = hubContext; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await foreach (var msg in _reader.ReadAllAsync(stoppingToken)) { // 不 await SendAsync,用 Fire-and-forget 避免阻塞 _ = _hubContext.Clients.All.SendAsync("ReceiveMessage", msg, CancellationToken.None); } } } 客户端连接复用与重连策略关键点 服务端再强,客户端乱建连也会拖垮整体。SignalR JS 客户端默认重试逻辑极易引发雪崩: 禁用默认自动重连:
new HubConnectionBuilder().withUrl(...).configureLogging(LogLevel.None).build()
,手动控制重连间隔 连接前检查
navigator.onLine
,避免离线时疯狂重试 服务端返回 429 时,客户端应退避重连(如指数退避),而非立即重试 Web Worker 中运行 SignalR 连接,防止 UI 线程阻塞影响心跳上报 .NET 客户端务必设置
HubConnectionBuilder.WithAutomaticReconnect()
并传入自定义策略,避免
RetryExponential
默认最大间隔仅 30 秒 最容易被忽略的是:WebSocket 连接本身不感 知网 络闪断,依赖心跳超时(默认 30 秒)才发现断连。生产环境建议将
KeepAliveInterval
设为 10–15 秒,
ClientTimeoutInterval
设为 30–45 秒,并在
OnCloseAsync
中清理资源。

相关文章