首頁

目前文章總數:157 篇

  

最後更新:2024年 12月 07日

0067. SignalR 橫向擴展部署 Server - Azure SignalR Service Backplane 解決方案

日期:2024年 06月 09日

標籤: C# Nginx SignalR Microsoft Azure Asp.net Core Web MVC

摘要:C# 學習筆記


應用所需:1. Visual Studio 2022 以上,支援.net Core 6 WebSite
     2. 有 Azure 雲服務帳號
解決問題:1. 當使用SignalR時,解決 SLB + Session Sticky(黏著),導致每個 Server 無法互通問題
範例檔案:Azure SignalR Service 代碼
基本介紹:本篇分為四大部分。
第一部分:Azure SignalR Service 介紹
第二部分:建立 Azure SignalR Service 服務
第三部分:專案代碼實現說明
第四部分:Demo成果






第一部分:Azure SignalR Service 介紹

Step 1:前言

上篇上篇 完成 SLB + Session Sticky 配置,最後遺留下 2台Web機器的 SignalR 無法互通問題。
此問題MSDN提出第3個建議解法,常態性解決 Server 實際部署受到環境影響的問題

※假設使用 SignalR Server 註冊到另一台 SignalR Server 會無法通過 SLB + Session Sticky 配置。
  因為 SLB 會動態分配到 Server,有可能自己連到自己造成 Crash

我們目標是啟用 Azure Signalr Service Backplane 模式,管理所有連接的 SignalR Server,當需要推播時,由 Azure Signalr Service Server 完成推播給所有用戶的方法

Step 2:補充使用原因

SignalR Scaleout with Azure Service Bus 適合早期的代碼,現在新版 .net Core 已經建議使用 Azure SignalR Service

Step 3:Azure SignalR Service 介紹

Azure SignalR Service官方簡介:

在 Web 應用程式中建置即時通訊,快速又輕鬆

有了 Azure SignalR Service,您就不需要兼任即時通訊大師!
因為在 Web 應用程式中新增即時通訊就和佈建服務一樣簡單!


簡單說,使用 Asp.net Core 如果使用 SignalR + Azure ,此服務已經整合大部分需要解決的問題,包含(只列幾項,詳細清參考連結):

1. 跨平台
2. 自動負載平衡
3. 各種程式語言支持
4. 最佳化傳輸方式
5. 高可用


Step 4:收費方式

基本上,如果小型開發或者測試,可以用免費版的,20000次訊息的推播
如果是大型產品使用 SLA 是考量的重要指標

N/A 服務商想停機就停機
99.9 % 服務商一年停機時間不超過 9 小時
99.95 % 服務商一年停機時間不超過 5 小時



Step 5:官方建構

可以參照官方的說明進行,但代碼與設定還需要自行摸索,本篇已完善補齊一些實做細節 官方建構文件



第二部分:建立 Azure SignalR Service 服務

Step 1:建立資源

進入 Azure 儀錶板後 -> 選擇左側 Menu -> 建立資源

Step 2:搜尋資源

1. 輸入 SignalR
2. 點擊 Azure SignalR Service



Step 3:創建資源

點擊 Create 開始進行設定

Step 4:設定 - 1

1. 地區依照自己的所在位置,會影響時區
2. 選擇變更



Step 5:設定 - 2

選擇變更後 -> 選擇 Free_F1 方案 (依照自己所需)

Step 6:設定 - 3

1. 補完所有資訊
2. 檢閱 + 建立



Step 7:設定最後確認

最後檢查沒問題就選擇建立
※如果誤選擇付費方案,建立後,就立刻計費了

Step 8:建立完成

等待一下,就會出現建立完成訊息



第三部分:專案代碼實現說明

Step 1:範例專案架構


1. Web資源檔 將 SignalR 8.0.0.js 下載,讓 .cshtml 引用,使前端網站可以註冊 SignalR 服務
2. 初始化配置 依賴注入 Azure SignalR Service 等配置
3. 配置 每個 Server 為了辨識,增加自己的代號、使用的 Port 號、Azure SignalR Service 連線位置
4. SignalR Hub Web 伺服器實現 SignalR ,並且提供前端 Publish 與後端 Subscribe
5. 前端頁面 提供聊天室註冊 SignalR 並且可發送訊息,與接收訊息功能



Step 2:專案引用套件

Asp.net Core 的專案,需要安裝 Microsoft.Azure.SignalR 套件


Step 3:Web資源檔

可從微軟給定的CDN SignalR 8.0.0 的位置下載,並放進自己的專案中,可在不對外,並且只能在內部網路的狀況下使用 SignalR
SignalR 8.0.0 下載

Step 4:初始化配置 - 1

在 Program.cs 初始化配置中,將 Azure 連線設定

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

// 1. 添加 Azure Signalr Service 設定
var azureConnection = builder.Configuration["Azure:SignalR:ConnectionString"];
builder.Services.AddSignalR().AddAzureSignalR(options =>
{
    // 2. 這邊要參考 Azure 上的連線字串
    options.ConnectionString = azureConnection;
});

var app = builder.Build();

...... 略


app.UseEndpoints(endpoints =>
{
    //3. 配置 SignalR 路由
    endpoints.MapHub<UpdateHub>("UpdateHub");
});

...... 略

app.Run();



Step 5:初始化配置 - 2

Azure 連線設定,應從儀表表中取得設定連結
※範例代碼會移除此連結,要自行取得自己的


Step 6:配置

appsettings.json 增加 SiteNumber、ConnectionStrings 完成辨識 Server 與 Azure SingnalR Service 連線字串的工作

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "SiteNumber": "2",
  "Site": "WebStie Port: XXXX",
  "AllowedHosts": "*",
  "Azure": {
    "SignalR": {
      "ConnectionString": "你的Azure SignalR Service 的連線字串"
    }
  }
}



Step 7:SignalR Hub

依序將 1. ~ 4. 項 SignalR 的推播
※可以將 4. 步驟替換自己真實資料的保存

public class UpdateHub : Microsoft.AspNetCore.SignalR.Hub
{
    private readonly IConfiguration _configure;
    private static string _Site = string.Empty;
    private int _siteNumber = 0;

    public UpdateHub()
    {
        _configure = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                                              .AddJsonFile("appsettings.json")
                                              .Build();
        _Site = _configure.GetValue("Site", string.Empty);

        //1. 求出自己站點編號的 2 ^ (SiteNumber-1) 值 EX: 編號1=1 / 編號2=2 / 編號3=4
        _siteNumber = (int)Math.Pow(2, (_configure.GetValue("SiteNumber", 1) - 1));
    }
    /// <summary>
    /// 2. 建立連接時,將歷史訊息回傳
    /// </summary>
    /// <returns></returns>
    public override async Task OnConnectedAsync()
    {
        var getData = FakeHistoryMessage();
        foreach (var message in getData)
        {
            await Clients.Caller.SendAsync("ReceiveMessage", message);
        }
        await base.OnConnectedAsync();
    }

    //事件名稱SendUpdate 行為:回傳message
    public async Task SendUpdate(string message)
    {
        await Clients.All.SendAsync("SendUpdate", message);
    }

    /// <summary>
    /// 3. 接收前端傳送訊息
    /// </summary>                
    public async Task SendMessage(string user, string message)
    {
        var dateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        var combineMessage = $@"[站點{_siteNumber} {dateTime}] {user}: {message}";
        await Clients.All.SendAsync("ReceiveMessage", combineMessage);
    }

    /// <summary>
    /// 4. 偽造歷史資料 ※實務資料源可為 Redis / Mysql / SqlServer / MongoDB .....
    /// </summary>        
    private List<string> FakeHistoryMessage()
    {
        return new List<string>()
        {
            "[站點1 2024-4-29 05:32:35] Louis: 1",
            "[站點2 2024-4-29 05:32:35] MilkTeaGreen: 2",
            "[站點1 2024-4-29 05:33:17] Louis: 3",
            "[站點2 2024-4-29 05:33:52] MilkTeaGreen: 4",
            "[站點1 2024-4-29 05:34:12] Louis: 5",
            "[站點2 2024-4-29 05:34:17] MilkTeaGreen: 6",
        };
    }
}



Step 8:前端頁面

前端實現基本的 SignalR 連線工作,在 “ReciveMessage” 訂閱工作中

<script>
    // 3. 預設頁面值
    var updateContainer = document.getElementById("updateContainer");
    updateContainer.innerHTML = `<p>New update: 初始化 </p>`;

    // 4. 創建 SignalR 連接
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("UpdateHub", { accessTokenFactory: () => "I am jwtToken" })
        .build();

    // 5. 監聽 SendUpdate 事件
    connection.on("SendUpdate", (message) => {
        const updateContainer = document.getElementById("updateContainer");
        updateContainer.innerHTML += `<p>${message.message}</p>`;
    });

    // 6. 訂閱可接收訊息
    connection.on("ReceiveMessage", function (message) {            
        const updateContainer = document.getElementById("updateContainer");
        updateContainer.innerHTML += `<p>${message}</p>`;            
    });

    // 7. 啟動
    connection.start()
        .then(() => {
            console.log("連接 SignalR 成功");
        })
        .catch((error) => {
            console.log("錯誤訊息:" + error);
        });

    // 8. 發送訊息到Hub 伺服器上
    function sendMessage() {
        var user = document.getElementById("userInput").value;
        var message = document.getElementById("messageInput").value;
        connection.invoke("SendMessage", user, message).catch(function (err) {
            console.error("Error invoking SendMessage: " + err.toString());
        });
    }
</script>





第三部分:Demo成果

Step 1:部署代碼

將範例代碼部署到2個 Web 資料夾中

Step 2:調整配置 - Web1

Web1的 appsetting.json 如下

Step 3:調整配置 - Web2

Web2的 appsetting.json 如下

Step 4:啟動Web

對 Web1 輸入指令

dotnet SingalRWebsiteUseAzureSignalRServiceBackPlateExample.dll --urls=http://localhost:6001


對 Web2 輸入指令

dotnet SingalRWebsiteUseAzureSignalRServiceBackPlateExample.dll --urls=http://localhost:6002



Step 5:開啟 Nginx

啟動 Nginx
這邊的配置是基於上一篇 SLB + Session Sticky

Step 6:開啟聊天室 - 完成

進入配置好的首頁,並且開啟兩個分頁(第1個分頁連 web1 ; 第2個分頁連 web2)
輸入一些簡單文字,可以發現完成 SLB + Session Sticky 下的 SignalR 聊天室互動
未來要部署多個機器時,只要改一下 appsetting.json 的代號,即可部署完成


檢查 Azure 儀錶板可以發現:服務也有產生資料流