Ocelot + Consul + SignalR 粘性会话
Ocelot + Consul + SignalR 粘性会话
一、核心结论(速览)
| 问题 | 答案 |
|---|---|
| 是否需要 Hub 返回 Cookie? | ❌ 不需要 |
| Cookie 由谁生成? | ✅ Ocelot 网关 |
| Cookie 值是什么? | ✅ Ocelot 生成的随机 GUID |
| 实例标识来源? | ✅ Consul 返回的 Host:Port |
| 下游服务是否感知? | ❌ 完全无感知 |
| SignalR 是否还需要 Redis? | ✅ 多实例广播仍需 |
二、底层工作原理
Ocelot 的 CookieStickySessions 负载均衡器在网关层完全接管了会话亲和性,其执行流程如下:
- 首次请求:客户端不带粘性 Cookie,Ocelot 向 Consul 查询
ServiceName对应的健康实例列表(如192.168.1.10:5000,192.168.1.11:5000)。 - 实例选择:Ocelot 按内置策略(通常是 RoundRobin)选中一个实例(如
192.168.1.10:5000)。 - 映射存储:Ocelot 生成一个随机 GUID(如
abc123),并在内存字典中建立映射:{ "abc123" -> "192.168.1.10:5000" }。 - 下发 Cookie:网关在 HTTP 响应头中写入
Set-Cookie: Ocelot_SignalR_Affinity=abc123。 - 后续请求:浏览器自动携带该 Cookie,Ocelot 查字典找到对应实例并固定转发,确保 SignalR 的
/negotiate和 WebSocket 升级落在同一台机器。
关键点:Cookie 的值是 Ocelot 的随机 Key,绑定的目标是 Consul 的具体节点地址,而非服务名。
三、完整配置文件
1. ocelot.json(网关配置)
{
"Routes": [
{
"UpstreamPathTemplate": "/hubs/chat/{*any}",
"UpstreamHttpMethod": ["Get", "Post", "Put", "Delete"],
"DownstreamPathTemplate": "/hubs/chat/{*any}",
"DownstreamScheme": "http",
"ServiceName": "signalr-service",
"LoadBalancerOptions": {
"Type": "CookieStickySessions",
"Key": "Ocelot_SignalR_Affinity",
"Expiry": 3600000
}
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "consul",
"Port": 8500,
"Type": "Consul"
},
"BaseUrl": "http://localhost:5000"
}
}
2. Program.cs(Ocelot 网关启动项)
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOcelot();
var app = builder.Build();
app.UseWebSockets(); // 必须启用 WebSocket
await app.UseOcelot();
app.Run();
3. SignalR 客户端(JavaScript)
⚠️ 必须配置 withCredentials: true,否则浏览器不会保存/发送网关下发的 Cookie。
const connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:5000/hubs/chat", {
withCredentials: true
})
.withAutomaticReconnect()
.build();
async function start() {
try {
await connection.start();
console.log("SignalR Connected.");
} catch (err) {
console.error(err);
setTimeout(start, 5000);
}
};
connection.onclose(async () => { await start(); });
start();
4. SignalR 服务端(Redis Backplane)
即使有了 Sticky Session,为了跨实例广播消息,仍需配置 Redis。
// Program.cs of SignalR Service
builder.Services.AddSignalR()
.AddStackExchangeRedis("redis:6379", options =>
{
options.Configuration.ChannelPrefix = "MyApp:SignalR";
});
四、Consul 服务注册示例
确保 Consul 中注册的地址是 Ocelot 能直接访问的 IP 或容器名。
{
"ID": "signalr-node-1",
"Name": "signalr-service",
"Address": "signalr-svc-1",
"Port": 5000,
"Check": {
"HTTP": "http://signalr-svc-1:5000/health",
"Interval": "10s"
}
}
五、注意事项
| 场景 | 说明 |
|---|---|
| Ocelot 多实例部署 | ⚠️ Ocelot 的粘性字典存在本机内存,若前面有 LB,需在 Nginx/K8s 再做一层 Sticky。 |
| 跨域 (CORS) | 网关和 SignalR 服务必须配置 AllowCredentials(),且不能设置 AllowAnyOrigin()。 |
| 实例重启/下线 | 若 Consul 摘除实例,Ocelot 内存映射失效,客户端需重连并获取新 Cookie。 |
| LongPolling | 同样依赖 Cookie 粘性,确保每次轮询请求都带上 Cookie。 |
| Docker/K8s | 确保 DownstreamHostAndPorts 或 Consul 注册的是容器网络可达地址。 |
六、一句话总结
SignalR 的会话粘性完全由 Ocelot 网关通过
Set-Cookie实现,基于 Consul 返回的具体实例地址进行绑定,Hub 无需编写任何 Cookie 逻辑,只需配合 Redis Backplane 即可在微服务下稳定运行。
留待后查,同时方便他人
联系我:ivesbao@163.com
联系我:ivesbao@163.com

浙公网安备 33010602011771号