ASP.NET Core SignalR应用水平扩展时如何跨实例校验用户连接状态
水平扩展下SignalR用户在线状态校验与脏数据处理方案
核心问题说明
你当前遇到的两个核心问题本质是分布式场景下的状态一致性问题:
- 静态列表
public static readonly List<string> Connected = new();是进程内存储,水平扩容后每个实例独立维护自己的列表,跨实例不可见,必然失效 - 持久化数据库存储连接映射没有失效机制,服务实例异常宕机时不会触发
OnDisconnectedAsync回调,无法主动清理无效连接,产生脏数据
最优解决方案(基于已有的Redis背板实现,无需额外引入组件)
因为你已经用Redis做SignalR背板,直接复用该Redis实例实现带自动过期的连接状态存储即可:
- 存储设计
- 每个连接对应Redis字符串键:
signalr:conn:{connectionId},值存储格式为{用户ID}|{当前服务实例唯一标识},设置90秒滑动过期时间(为SignalR默认30秒KeepAlive间隔的3倍,可根据自己的KeepAlive配置调整为2~3倍) - 每个用户对应Redis集合键:
signalr:user:{userId}:conns,存储该用户所有有效连接ID
- 每个连接对应Redis字符串键:
- 回调逻辑改造
- 重写
OnConnectedAsync:用户连接成功时,写入上述两个Redis键,将当前连接ID加入对应用户的连接集合 - 重写
OnDisconnectedAsync:用户正常断开时,从用户连接集合中移除当前连接ID,同时删除对应连接的Redis键
- 重写
- 续期逻辑实现
每个SignalR服务实例启动一个后台定时任务,间隔30秒遍历当前实例持有的所有有效SignalR连接,批量给这些连接对应的signalr:conn:{connectionId}键续期90秒 - 脏数据自动清理
服务实例异常宕机后,该实例持有的所有连接对应的Redis键不会被续期,90秒后自动过期失效。每次查询用户在线状态前,先清理该用户连接集合中对应的signalr:conn:{connectionId}已不存在的无效连接ID即可
在线状态校验逻辑
校验用户是否在线时,查询对应signalr:user:{userId}:conns集合的有效连接数,只要存在至少1个有效连接,即可判定为在线。
如果需要极高的状态准确性,可在查询时增加主动探测逻辑:向该用户的所有有效连接发送心跳探测消息,客户端收到后立即回包,10秒内未收到回包的连接直接判定为失效并清理。
兜底方案(基于数据库存储的适配改造)
如果你必须使用数据库存储连接映射,可按如下逻辑改造避免脏数据:
- 给连接映射表增加
last_heartbeat(最后心跳时间)、server_instance_id(所属服务实例标识)字段 - 每个服务实例每隔30秒批量更新自身所有有效连接的
last_heartbeat为当前时间 - 部署全局定时任务,每隔1分钟删除所有
last_heartbeat超过90秒的记录即可
内容的提问来源于stack exchange,提问作者JumpAttacker
相关产品推荐
相关产品推荐

