You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

部署至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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 21:37:18