通过将ASP.NET Core SignalR与Redis结合使用来实现Hub的冗余备份

首先

调查ASP.NET Core SignalR的冗余机制。

ASP.NET Core SignalR概述
参考:https://learn.microsoft.com/ja-jp/aspnet/core/signalr/introduction

ASP.NET Core SignalR是一个用于构建实时Web功能的开发框架。通过SignalR,可以使用ASP.NET Core构建具有实时双向通信功能的应用程序。SignalR提供了简化了实时Web应用程序开发过程的高级函数库和API,使得开发者可以方便地实现实时数据推送、群组通信和服务端到客户端的通信等功能。详细信息请查阅上述参考链接。

题目

image.png

回答:在SignalR的背板中使用Redis。

经过调查,发现可以通过在SignalR的背板中使用Redis来实现对HubServer的冗余。

ASP.NET Core SignalR的托管和扩展参考,请参考以下链接:https://learn.microsoft.com/ja-jp/aspnet/core/signalr/scale

网络配置经过重新评估后

image.png
image.png

试一试

为了这个目的,我要创建一个测试环境并进行尝试。

image.png

测试代码

客户端每秒向中央服务器发送消息”你好。”和自己的进程ID。
中央服务器将收到的消息原封不动地发送回客户端。
这只是一个简单的代码。

客户端使用测试代码

项目模板:控制台应用程序 (.NET 6)
项目名称:SignalRClientApplication
NuGet 添加:Microsoft.AspNetCore.SignalR.Client (6.0.22)

using Microsoft.AspNetCore.SignalR.Client;

namespace SignalRClientApplication
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            while (true)
            {
                try
                {
                    // 接続先URL入力
                    WriteLine("Input HubServer URL : ", ConsoleColor.Yellow);
                    var url = Console.ReadLine();
                    if (string.IsNullOrEmpty(url)) continue;

                    // 接続情報作成
                    WriteLine($"HubServer Connecting...");
                    var connection = new HubConnectionBuilder()
                        .WithUrl(url)
                        .Build();

                    // 接続開始
                    await connection.StartAsync();
                    WriteLine($"HubServer Connected!");

                    // Clientのメソッド登録
                    connection.On<string, string>("ClientMethod", (user, message) =>
                    {
                        WriteLine($"Recv ClientMethod : {message} from {user}{(user == Environment.ProcessId.ToString() ? "(my)" : "(other)")}", ConsoleColor.Blue);
                    });

                    // 接続中
                    while (true)
                    {
                        if (connection.State != HubConnectionState.Connected)
                        {
                            WriteLine($"HubServer DisConnected {url}");
                            break; // 再接続へ
                        }

                        // Hubのメソッド呼び出し
                        await connection.InvokeAsync("HubMethod", Environment.ProcessId.ToString(), "Hello.");

                        await Task.Delay(1000);
                    }
                }
                catch (Exception ex)
                {
                    WriteLine(ex.Message, ConsoleColor.Red);
                }
            };
        }

        // コンソール画面に色分け表示して表示
        private static readonly object lockobj = new();
        private static void WriteLine(string value, ConsoleColor ForegroundColor = ConsoleColor.Gray)
        {
            lock (lockobj)
            {
                Console.ForegroundColor = ForegroundColor;
                Console.WriteLine(value);
                Console.ResetColor();
            }
        }
    }
}

使用HubServer的测试代码

项目模板:ASP.NET Core Web 应用程序 (.NET 6)
项目名称:SignalRHubServerWebApplication
添加NuGet包:Microsoft.AspNetCore.SignalR.StackExchangeRedis (6.0.22)

using StackExchange.Redis;

namespace SignalRHubServerWebApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddRazorPages();
            builder.Services.AddSignalR().AddStackExchangeRedis(configure =>
            {
                configure.Configuration = new ConfigurationOptions
                {
                    ChannelPrefix = "MyApp",
                    EndPoints =
                    {
                        { "192.168.56.106", 6379 } // Redis
                    },
                    AbortOnConnectFail = false,
                };
            });

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            //app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.MapRazorPages();
            app.MapHub<HubServer>("/hub");

            app.Run("http://*:5000");
        }
    }
}
using Microsoft.AspNetCore.SignalR;

namespace SignalRHubServerWebApplication
{
    public class HubServer : Hub
    {
        public async Task HubMethod(string user, string message)
        {
            // Clientのメソッド呼び出し
            await Clients.All.SendAsync("ClientMethod", user, message);
        }
    }
}

Redis构建

简单地写个备忘录。

# テストには邪魔なファイアウォール停止
> sudo systemctl stop firewalld
> sudo systemctl disable firewalld

# Redisインストール
> sudo dnf install redis
全てy

# localhostのみ接続許可になっているので bind から始まる行を見つけて変更する
> sudo vi /etc/redis/redis.conf
# bind 127.0.0.1 -::1
bind * -::*

> sudo systemctl start redis

构建HubServer

准备两台。

# テストには邪魔なファイアウォール停止
> sudo systemctl stop firewalld
> sudo systemctl disable firewalld

# .NET 6ランタイム
> sudo dnf install aspnetcore-runtime-6.0
全てy

# HostServer用テストコードをpublishしてリモートにコピー、実行
> dotnet SignalRHubServerWebApplication.dll
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /root/publish/

客户建立

客戶使用自己的個人電腦。

考试

image.png
> dotnet SignalRClientApplication.dll
Input HubServer URL [e.g. http://localhost:5005/hub] :
http://192.168.56.107:5000/hub
HubServer Connecting...
HubServer Connected!
> dotnet SignalRClientApplication.dll
Input HubServer URL [e.g. http://localhost:5005/hub] :
http://192.168.56.108:5000/hub
HubServer Connecting...
HubServer Connected!

连接已建立。

Recv ClientMethod : Hello. from 1816(my)
Recv ClientMethod : Hello. from 23380(other)
Recv ClientMethod : Hello. from 1816(my)
Recv ClientMethod : Hello. from 23380(other)
:
Recv ClientMethod : Hello. from 23380(my)
Recv ClientMethod : Hello. from 1816(other)
Recv ClientMethod : Hello. from 23380(my)
Recv ClientMethod : Hello. from 1816(other)
:

可以看到“Hello.”已经传播到了所有客户端。

image.png

客户B检测到HubServer B的停止并断开连接。

HubServer DisConnected http://192.168.56.108:5000/hub
Input HubServer URL :

客户A正常运行,但无法接收来自客户B的消息。

Recv ClientMethod : Hello. from 1816(my)
Recv ClientMethod : Hello. from 1816(my)
Recv ClientMethod : Hello. from 1816(my)
Recv ClientMethod : Hello. from 1816(my)
:
image.png

客户A再次收到客户B的信息。

Recv ClientMethod : Hello. from 1816(my)
Recv ClientMethod : Hello. from 23380(other)
Recv ClientMethod : Hello. from 1816(my)
Recv ClientMethod : Hello. from 23380(other)
:

B客户端也接收到了A客户端的消息。

Input HubServer URL :
http://192.168.56.107:5000/hub
HubServer Connecting...
HubServer Connected!
Recv ClientMethod : Hello. from 23380(my)
Recv ClientMethod : Hello. from 1816(other)
:
image.png

客户A也接收来自客户C的消息。

Recv ClientMethod : Hello. from 1816(my)
Recv ClientMethod : Hello. from 23380(other)
Recv ClientMethod : Hello. from 24432(other)
:

客户B也接收来自客户C的消息。

Recv ClientMethod : Hello. from 23380(my)
Recv ClientMethod : Hello. from 24432(other)
Recv ClientMethod : Hello. from 1816(other)
:

客户C收到来自客户A / B的信息。

Input HubServer URL :
http://192.168.56.108:5000/hub
HubServer Connecting...
HubServer Connected!
Recv ClientMethod : Hello. from 24432(my)
Recv ClientMethod : Hello. from 1816(other)
Recv ClientMethod : Hello. from 23380(other)
:

我确认了HubServer的冗余备份正正常运行。

Redis的冗余备份

image.png

不过,但本文章不讨论Redis的冗余性。
你可以通过Google搜索找到有价值的信息……。

Redis的冗余化
https://www.sraoss.co.jp/tech-blog/redis/redis-ha/

将Redis配置为冗余化架构(部署故障转移环境-第二部分)

如果使用AWS,使用AWS ElastiCache for Redis或Amazon MemoryDB for Redis,可以更容易地实现冗余。
如果使用Azure,使用Azure SignalR Service可以更容易地实现冗余。

以上。
这就是上述的全部内容。
总之,就是这样。
综上所述。
简言之。

bannerAds