dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.32k stars 9.97k forks source link

SignalR: Distributed HubConnectionStore #42011

Closed evman182 closed 2 years ago

evman182 commented 2 years ago

Is there an existing issue for this?

Is your feature request related to a problem? Please describe the problem.

The dependency on the HubConnectionStore and its use of an in-memory dictionary as it's data store means that any load balanced SignalR endpoints must use sticky sessions, which in certain environments can be difficult to setup.

Describe the solution you'd like

I'd like to see a configurable option for the HubConnectionStore, allowing the use of a plugin-able distributed store, such as Redis.

Additional context

I'm willing to work on this myself, but am wondering if there's a technical reason that the connection store has been limited to just an in-memory option so far.

DamianEdwards commented 2 years ago

Have you looked at the existing Redis backplane for SignalR? https://docs.microsoft.com/en-us/aspnet/core/signalr/redis-backplane?view=aspnetcore-6.0

evman182 commented 2 years ago

@DamianEdwards My understanding is the Redis backplane allows distribution for the messages, but that the connections themselves are still stored in-memory on the server.

DamianEdwards commented 2 years ago

Ah OK, misunderstood. RE connection state my understanding is we've long avoided making that pluggable due to the complexities that can arise once it's not a synchronous/atomic in-memory operation, e.g. interactions with downstream APIs that aren't async. Fundamentally the system is designed around the idea of events/messages rather than state, but I completely understand the motivation for the request.

All that said, I'm not sure about the latest state of the world here. I know there's been recent thinking about the idea of using something like Orleans to implement distributed versions of some of the backing primitives. @davidfowl?

davidfowl commented 2 years ago

We don't have any plans to write a distributed HubConnectionStore , it's a simple data structure to aid in writing your a backplane. Why not just use something like entity framework or orleans to build distributed storage?

evman182 commented 2 years ago

I just wanted to make sure there wasn't a reason that the connection store couldn't be distributed before going down that path.

davidfowl commented 2 years ago

Can you elaborate on the scenario? What are you trying to implement?

evman182 commented 2 years ago

Just using SignalR with multiple servers behind a load balancer without having to use Sticky Sessions

davidfowl commented 2 years ago

Turning off transport negotiation and forcing websockets is a way to accomplish that. A distributed HubConnectionStore wouldn't help here, the stickiness requirement is between the negotiate request and the initial connection. Distributed state could help here but it's at a much lower level.

evman182 commented 2 years ago

For the moment we're not able to use websockets, and we're relying on HTTP polling, and so yea, the issue is during the negotiation handshake. For the moment our solution has been to only run a single instance of the hub.

davidfowl commented 2 years ago

And your environment doesn't support sticky sessions?

evman182 commented 2 years ago

Short answer: no it doesn't

davidfowl commented 2 years ago

Got it, SignalR can't work with long polling or server sent events without sticky sessions.

ghost commented 2 years ago

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

evman182 commented 2 years ago

I understand that long polling doesn't currently work without sticky sessions. What I'm wondering is if there's any technical reason that it has to be that way (other than that it's a constraint of the current implementation).

davidfowl commented 2 years ago

Yes we made a architectural decision to treat the any connection like a persistent socket and to avoid baking in the idea of a backplane or distribute storage at this layer for performance reasons.

The virtual connection abstraction must act like a stream from a single source.

I’m theory you could build a new transport layer that routes bytes to the right server no matter where the request lands using various technologies, but it would be inefficient and less reliable than a direct connection.

This isn’t something we would build but if you wanted some pointers on how to do so, we could help.

davidfowl commented 2 years ago

Actually I just remembered the other reason why this is so hard, connection lifetime management. If the connection can exists across multiple servers, it's more difficult to know when it's disconnected. This is the other reason we didn't do this.

ASP.NET Core SignalR is split into 2 distinct protocols:

They are separate so the hub protocol can be expressed on top of any streaming transport. What you're asking for here is an implementation of the transport protocol that can send messages to the right server.

In your environment, can the compute instances talk to each other?