.NET6环境下如何使用SQL Server实现SignalR横向扩展?
SQL Server作为.NET6 SignalR横向扩展背板的实现方案
首先明确:完全可行,虽然官方没有提供适配ASP.NET Core(.NET6)的Microsoft.AspNetSignalR.SqlServer包(该包仅针对.NET Framework),但有两种可靠的实现方式:
方法一:使用社区维护的SQL Server SignalR背板包
社区已针对ASP.NET Core SignalR推出适配SQL Server的背板包,步骤简单直接:
- 安装NuGet包
dotnet add package AspNetCore.SignalR.SqlServer
- 在
Program.cs中配置SignalR使用SQL Server背板
var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR() .AddSqlServer(options => { options.ConnectionString = builder.Configuration.GetConnectionString("SignalRBackplane"); // 可选配置:自定义表名、Schema,默认会自动创建所需表结构 options.TableName = "SignalRMessageQueue"; options.SchemaName = "signalr"; }); var app = builder.Build(); app.MapHub<ChatHub>("/chathub"); // 替换成你的Hub类 app.Run();
- 数据库准备
包会自动在首次运行时创建消息队列表;若需手动初始化,可查看NuGet包的内容文件获取内置SQL脚本,确保应用账户拥有数据库读写及DDL权限(自动建表时需要)。
方法二:自定义SQL Server消息总线(适合定制需求)
如果不想依赖第三方包,可自行实现IMessageBus接口,基于SQL Server实现服务器间消息传递:
- 创建消息存储表
CREATE TABLE dbo.SignalRMessages ( Id INT IDENTITY(1,1) PRIMARY KEY, Payload NVARCHAR(MAX) NOT NULL, CreatedAt DATETIME2 DEFAULT GETUTCDATE() NOT NULL, Processed BIT DEFAULT 0 NOT NULL )
- 实现
IMessageBus核心逻辑
public class SqlSignalRMessageBus : IMessageBus { private readonly string _connString; private readonly ILogger<SqlSignalRMessageBus> _logger; private readonly Timer _pollingTimer; public SqlSignalRMessageBus(IConfiguration config, ILogger<SqlSignalRMessageBus> logger) { _connString = config.GetConnectionString("SignalRBackplane"); _logger = logger; // 启动轮询,读取未处理的消息 _pollingTimer = new Timer(PollForMessages, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(500)); } public Task PublishAsync(Message message, CancellationToken cancellationToken = default) { // 将消息序列化后写入数据库 var payload = JsonSerializer.Serialize(message); using var conn = new SqlConnection(_connString); conn.Open(); var cmd = new SqlCommand( "INSERT INTO SignalRMessages (Payload) VALUES (@payload)", conn); cmd.Parameters.AddWithValue("@payload", payload); return cmd.ExecuteNonQueryAsync(cancellationToken); } private void PollForMessages(object state) { // 读取并处理未处理的消息,用UPDLOCK+READPAST避免并发冲突 using var conn = new SqlConnection(_connString); conn.Open(); var cmd = new SqlCommand( "SELECT TOP 10 Id, Payload FROM SignalRMessages WHERE Processed = 0 WITH (UPDLOCK, READPAST)", conn); using var reader = cmd.ExecuteReader(); while (reader.Read()) { var id = reader.GetInt32(0); var payload = reader.GetString(1); var message = JsonSerializer.Deserialize<Message>(payload); // 对接SignalR的消息分发逻辑,将消息广播给当前实例的连接 // ... // 标记消息为已处理 using var updateCmd = new SqlCommand( "UPDATE SignalRMessages SET Processed = 1 WHERE Id = @id", conn); updateCmd.Parameters.AddWithValue("@id", id); updateCmd.ExecuteNonQuery(); } } // 实现IMessageBus的其他接口方法,此处省略... }
- 注册自定义消息总线
builder.Services.AddSignalR() .AddMessageBus(sp => { var config = sp.GetRequiredService<IConfiguration>(); var logger = sp.GetRequiredService<ILogger<SqlSignalRMessageBus>>(); return new SqlSignalRMessageBus(config, logger); });
关键注意事项
- 性能:SQL Server作为消息背板的吞吐量和延迟表现不如Redis,高并发场景优先考虑官方推荐的Redis方案。
- 消息清理:定期清理已处理的旧消息,可通过SQL代理作业或应用定时任务实现,避免表数据膨胀。
- 并发控制:自定义实现时需使用
UPDLOCK、READPAST等锁机制,避免消息重复处理。
内容的提问来源于stack exchange,提问作者Anand
相关产品推荐
相关产品推荐

