Course contentsShow
← C#
Lesson 1495 of 1,90813. Real-time Communication with SignalRFree lesson

Global Connection State Management

Synchronize active user counts and connectivity status across distributed nodes.

In this lesson, you will learn how to maintain a consistent "active user count" and connectivity status across a distributed set of SignalR server nodes.

The Distributed State Challenge

In a simple application, you might track connected users using a static ConcurrentDictionary inside your Hub. However, in a distributed system, your users are spread across multiple server instances. If Node A doesn't know who is connected to Node B, your "Global Active User" count will be fragmented and inaccurate.

To solve this, we move the source of truth for connection state outside the individual process memory and into a shared, high-speed distributed cache like Redis.

Synchronizing State with Redis

When a client connects to any node, that node must publish the event to a shared store. Instead of relying on local memory, we use a Backplane (or a distributed cache pattern) to track the lifecycle of each ConnectionId.

Consider this logic for tracking counts across a cluster:

public override async Task OnConnectedAsync()
{
    // Increment the global counter in Redis
    await _distributedCache.IncrementAsync("global-user-count");
    
    // Register this connection's metadata in a distributed hash
    await _distributedCache.HashSetAsync("active-connections", Context.ConnectionId, User.Identity.Name);
    
    await base.OnConnectedAsync();
}

public override async Task OnDisconnectedAsync(Exception? exception)
{
    // Decrement the global counter
    await _distributedCache.DecrementAsync("global-user-count");
    
    // Remove the connection mapping
    await _distributedCache.HashDeleteAsync("active-connections", Context.ConnectionId);
    
    await base.OnDisconnectedAsync(exception);
}

Ensuring Consistency

While this handles the "up-to-date" aspect, you must also handle server failures. If a node crashes, OnDisconnectedAsync might not trigger. To achieve true robustness, use a Heartbeat pattern:

  1. The client sends a periodic "keep-alive" ping to the Hub.
  2. The Hub updates a "last-seen" timestamp in Redis with a Time-To-Live (TTL).
  3. A background service periodically sweeps the cache to remove entries where the TTL has expired, effectively cleaning up orphaned connections from dead nodes.

By centralizing the state in a distributed store, you decouple your connection tracking from the lifecycle of any single server instance.

Key Takeaway: Global connection state in distributed SignalR systems requires moving from local memory to an external, shared store like Redis, coupled with heartbeat mechanisms to handle unexpected node failures.