部署至AWS ECS后SignalR出现重复消息问题排查求助
SignalR部署AWS ECS后消息重复问题排查
问题背景
本地运行正常的单Hub SignalR项目,部署到AWS ECS一段时间后,所有返回消息出现重复。消息处理器的CloudWatch日志显示仅触发一次消息发送,但前端收到重复消息。
实现与配置细节
- 禁用粘性会话,仅启用WebSocket传输层,前端设置
SkipNegotiation = true - 负载均衡器下仅配置1个ECS任务
相关代码片段
Program.cs
builder.Services.AddSingleton<IConnectionMultiplexer>(sp => RedisConnection.Connect(redisConfig)); builder.Services.AddSingleton<IRedisClient, RedisClient>(); var signalRBuilder = builder.Services.AddSignalR(); signalRBuilder.AddStackExchangeRedis(c => { c.Configuration = RedisConnection.GetConfigurationOptions(redisConfig); c.Configuration.ChannelPrefix = ""; }); var app = builder.Build(); app.MapHub<TestHub>("/testhub", options => { options.Transports = HttpTransportType.WebSockets; }); var redisClient = app.Services.GetService<IRedisClient>(); var messageHandlers = new List<IBaseMessageHandler> { ActivatorUtilities.CreateInstance<TestMessageHandler>(app.Services) }; foreach (var messageHandler in messageHandlers) { redisClient?.SubscribeToRedisChannel(messageHandler.GetChannelName(), (IRedisMessageHandler)messageHandler); } app.Run();
TestMessageHandler.cs
public class TestMessageHandler : IBaseMessageHandler, IRedisMessageHandler { private readonly IHubContext<TestHub> _hubContext; private readonly ILogger<TestMessageHandler> _logger; public TestMessageHandler(IHubContext<TestHub> hubContext, ILogger<TestMessageHandler> logger) { _hubContext = hubContext; _logger = logger; } public string GetChannelName() { return typeof(TestMessage).Name; } public void Handle(RedisValue message) { ContextualMessage<TestMessage> contextualMessage; try { contextualMessage = JsonConvert.DeserializeObject<ContextualMessage<TestMessage>>(message.ToString()); } catch { _logger.LogError("Unable to deserialize message: {message}", message.ToString()); throw; } _logger.LogDebug("send message to testGroup"); _hubContext .Clients .Group("testGroup") .SendAsync(GetChannelName(), contextualMessage.Message); } }
TestHub.cs
public class TestHub : Hub { private readonly ILogger<TestHub> logger; public TestHub(ILogger<TestHub> logger) { this.logger = logger; logger.LogDebug("TestHub construct"); } public override async Task OnConnectedAsync() { logger.LogDebug("Connected"); await AddGroups(); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception exception) { logger.LogDebug("Disconnected"); await base.OnDisconnectedAsync(exception); } private async Task AddGroups() { var group = "testGroup"; await Groups.AddToGroupAsync(Context.ConnectionId, group); } }
消息发送代码
await _redisClient.SendNotification(message.Message.GetType().Name, message2);
排查方向
1. 客户端连接重复
- 检查前端WebSocket连接逻辑:确认是否存在重复建立连接的情况(比如页面路由切换时未销毁旧连接、重连逻辑异常触发多次连接)
- 查看ECS中SignalR的
Connected日志:统计同一客户端的连接次数,确认是否单个客户端建立了多个连接 - 验证前端
SkipNegotiation=true配置:WebSocket直连模式下,是否因错误重连逻辑导致重复连接
2. Redis订阅重复
- 检查Redis订阅逻辑:确认
SubscribeToRedisChannel是否被重复调用(比如ECS任务重启后旧订阅未取消,新实例又添加订阅) - 执行Redis命令
PUBSUB NUMSUB {channelName}:查看目标频道的订阅者数量,正常情况下1个ECS任务应该只有1个订阅者 - 确认
IConnectionMultiplexer生命周期:确保是单例实例,避免创建多个Redis连接导致重复订阅
3. SignalR组管理异常
- 检查用户入组逻辑:确认同一连接是否被多次加入
testGroup(比如OnConnectedAsync被重复触发、AddGroups方法被多次调用) - 查看Redis中的组存储:通过Redis Key查看组内连接数,确认单个客户端连接是否被多次添加到组中
- 验证
OnDisconnectedAsync逻辑:确认连接断开时是否正确从组中移除连接,避免旧连接残留导致重复发送
4. AWS ECS/负载均衡器配置异常
- 检查负载均衡器健康检查:是否因健康检查逻辑导致WebSocket连接中断后重连,进而产生重复连接
- 查看ECS任务重启记录:是否存在任务异常重启,导致旧进程未完全退出,仍持有Redis订阅和SignalR连接
- 验证WebSocket超时配置:AWS ALB默认WebSocket超时为70秒,是否因超时导致前端自动重连,旧连接未被正确清理
5. SignalR Redis背板频道前缀配置
当前配置c.Configuration.ChannelPrefix = "",可能导致不同环境或服务的SignalR消息互相干扰。建议设置唯一频道前缀(比如c.Configuration.ChannelPrefix = "ecs-test-"),避免跨实例/环境的消息广播
内容的提问来源于stack exchange,提问作者kalinowski
相关产品推荐
相关产品推荐

