首頁

目前文章總數:254 篇

  

最後更新:2026年 10月 17日

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 儀錶板可以發現:服務也有產生資料流