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

来源:这里教程网 时间:2026-02-21 17:38:44 作者:

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<IISOptions>(options =>
{
    options.AutomaticAuthentication = false;
    options.AuthenticationDisplayName = "Windows Authentication";
});
builder.Services.Configure<HttpSysOptions>(options =>
{
    options.MaxConnections = 100_000;
    options.MaxRequestBodySize = null;
});
var app = builder.Build();

注意:

MaxConcurrentConnections
必须设为
null
或足够大数值,否则 Kestrel 内部连接队列会直接拒绝新连接;
MaxConcurrentUpgradedConnections
是 WebSocket 升级专用阈值,SignalR 默认走 WebSocket,此项常被忽略。

Hub 层避免广播阻塞的实操方式

高频消息场景下,

Clients.All.SendAsync
是性能杀手——它会把所有连接序列化一次、再逐个 await 发送,中间任意一个慢连接都会拖垮整批。替代方案不是“优化序列化”,而是绕过同步广播模型:

Clients.AllExcept(...).SendAsync
减少目标集,但本质未解耦
改用
ITransportHeartbeat
+ 自定义
IHubLifetimeManager
实现异步批量投递(推荐)
更轻量做法:在 Hub 外启动独立后台服务(
BackgroundService
),将消息入队(如
Channel<t></t>
),由消费者线程池分片推送
禁用自动 JSON 序列化缓存:
AddJsonProtocol(opt => opt.PayloadSerializerOptions.WriteIndented = false)
,减小 GC 压力

示例:用 Channel 实现非阻塞广播

public class BroadcastService : BackgroundService
{
    private readonly ChannelReader<object> _reader;
    private readonly IHubContext<ChatHub> _hubContext;
    public BroadcastService(Channel<object> channel, IHubContext<ChatHub> 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
中清理资源。

相关推荐