Azure signalr単位の自動スケーリング



文脈
こんにちは!私はプログラマーとソフトウェアアーキテクトです.私はどのように私はプラットフォーム上の需要に基づいてAzure signalrユニットの自動スケールを実装するコストを最適化することができます教えてください.
Azure Signalrについての詳細情報を読む必要がある場合は、official websiteに行ってください

Azure Signalrサービスの問題点は?
を、短い答えは、それは需要によって接続単位を使用することは不可能です.
長い答えや私の説明は、Azure Signalrサービスは、偉大なリアルタイムのアプリケーションを作成することができます素晴らしい管理ツールであり、もちろん、それはあなたに高い可用性を保護する基盤インフラを提供しています.それは完璧!右?)いいえ.

Azure Signalrサービスは、ユニットを通してそのプール接続を管理します.つの単位は1000の同時接続を表します、これはあなたがあなたのアプリケーションの同時接続の数を知っているならば、あなたは必要な単位を選ぶことができます.たとえば、私たちが4000人の同時ユーザーを持っているならば、あなたは4つの単位の代わりに5つの単位でなぜAzureで5つの単位を契約しなければなりませんか?Azureは、スケーリングのために選択する単位オプションを制限しました
https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-howto-scale-signalr
あなたが4000の同時ユーザーを持っているならば、あなたは5台をセットアップしなければなりません、しかし、将来的にあなたのユーザーが6000に増加するならば、あなたは手動で構成を10の単位に変えなければなりません!はい、手動で自動的にそれを行うためのメカニズムがないので.

誰かが解決した
スタッフォードウィリアムズは私のガイドであった近い解決をしました.あなたは彼が何をしたか知りたいですか?それを読むために彼の に行ってください.
blog
ヘイ!こちらが解決策です

デザイン
これは私の考えです.

Azureポータル内のAzure Signalrサービス設定ページはアラートオプションを含む監視セクションを提供します.私は最大の接続acountメトリック(MCAM)に基づいてアラートのセットを作成することを決めた、それは私がMCAMが1 K、2 K、5 K、10 K、20 K、50 Kを超えるときに知ることができます.したがって、各アラートはAzure関数を呼び出すことができるアクショングループを実行できます.


作成されたアクショングループは、AGコールAzure関数と呼ばれ、以前に作成されたFnScaleUnitという名前のAzure関数を呼び出します.



なぜAzure関数?
OK、Azure関数は私のカウント接続マネージャのように動作します.このアイデアは、Azure REST APIを使用して、決定されたモーメント内の最大接続ACPANを知っていて、Azure signalrインスタンスを持たなければならない単位の理想的な数を計算することです.AZ関数が単位数を持つとき、Sigulrインスタンス内で単位値を変更するためにAzure REST APIへの要求を行います.また、このプロセスを上下に計算する必要があります.簡単、右?では、もっと技術的な詳細を続けていきましょう.

実装

OAuth 2認証
最初に、Azure REST APIはOAuHT 2トークンを通して認証方法を必要とします.Azureアクティブディレクトリ内に新しいアプリケーションを登録する必要があります.このアプリケーションを使用すると、OAuth 2エンドポイントを使用して1つのトークンを取得することができます.エンドポイントは次のようになります.https://login.microsoftonline.com/{your-tenant-id}/oauth2/token

Important: It's required to integrate the Active Directory Application with SignalR Instance. It can do adding the application to the Azure SignalR Instance. For this, you must go to the SignalR Instance setup page, in the Access control (IAM) option, in its Role assigments tab, and to choose our application.




考慮
また、次のエンドポイントを使用します.
  • は、OAuthトークンを得ます:GET https://login.microsoftonline.com/{your-tenant-id}/oauth2/token
  • signalrインスタンスについてメトリクスを取得します.
  • 単位の値を更新します.GET https://management.azure.com/subscriptions/${subscription}/resourceGroups/${resourceGroup}/providers/Microsoft.SignalRService/SignalR/${nameSignalRInstance}/providers/microsoft.insights/metrics?api-version=2018-01-01&$filter=Endpoint eq 'server' or Endpoint eq 'client' .
  • つのベストプラクティスは、アプリの機能では、AzureキーVaultから秘密を統合するには、この方法をGoogleでチェックしてください.

    azure機能を紹介しましょう.
    前に、Bussinessロジックの最初のビューとしてシーケンス図をお見せします.
  • signalrインスタンス内の警告はActivedです.
  • この警告はアクショングループを実行し、azure関数fnscalerunitを呼び出します.
  • Azure 2はトークンを得るためにOAuth 2エンドポイントへのリクエストを行います.
  • Active Directoryトークンを持つ
  • 関数は、Sigulrインスタンスのメトリクスを取得するためにAzure REST APIを消費します.
  • 関数はユニットの理想的な数を計算します.
  • 関数は、インスタンスのユニット数を更新するAzure REST APIへの要求を送信します.
  • それは動く!
  • そして最後に、コード.
    const fetch = require('node-fetch');
    
    //signalR service data from a keyvault through a env variable.
    const subscription = process.env["subscription"];
    const resourceGroup = process.env["resourceGroup"];
    const nameSignalRInstance = process.env["nameSignalRInstance"];
    
    //service principal data from a keyvault through a env variable.
    const clientId = process.env["key-vault-clientId"];
    const clientSecret = process.env["key-vault-secret"];
    const oAuthTokenEndpoint = process.env["key-vault-oAuthUrl"];
    
    const numberConnectionsPerUnit = 1000;
    
    //This Array represents the ranges allowed by Azure for a SignalR Instance
    const signalRUnitRanges = [
        {
            initialUnit: 0,
            finalUnit: 1
        },
        {
            initialUnit: 1,
            finalUnit: 2
        },
        {
            initialUnit: 2,
            finalUnit: 5
        },
        {
            initialUnit: 5,
            finalUnit: 10
        },
        {
            initialUnit: 10,
            finalUnit: 20
        },
        {
            initialUnit: 20,
            finalUnit: 50
        },
        {
            initialUnit: 50,
            finalUnit: 100
        }
    ];
    
    let token = '';
    let metrics = []; 
    let contextGlobal;
    
    //Main function
    module.exports = async function (context, req) {
        context.log('Start the evaluation of units');
        contextGlobal = context;
        try {
            token = await GetToken();
            metrics = await GetMetrics();
            const idealUnit = GetIdealUnit();
            context.log.info(`ideal Unit ${idealUnit}`);
            let resultUpdate = UpdateUnits(idealUnit);
            context.log.info(`The result was successfully`);
        } catch(error) {
            context.log.error( error );
        }
        context.res = {
            status: 200
        };
    }
    
    //updates the capacity of the SignalR Service.
    async function UpdateUnits( unit ) {
    
        var details = {
            'sku': {
               'name': 'Standard_S1',
               'tier': 'Standard',
               'capacity': unit
            },
            'location': 'eastus2'
        };
    
        return await fetch(`https://management.azure.com/subscriptions/${subscription}/resourceGroups/${resourceGroup}/providers/Microsoft.SignalRService/signalR/${nameSignalRInstance}?api-version=2020-05-01`,
        {
            method: 'PUT',
            headers: {
                'Authorization': `Bearer ${token}`,
                'Content-Type': 'application/json'
            },
            body:  JSON.stringify(details)
        }).then( (response ) => {
            if(response.ok) {
                return response.json();
            } else {
                throw new Error(`HttpStatus: ${response.status} Message: GetToken Failed: reason: ${response.statusText}`);
            }
        });
    }
    
    //Calculates what is the ideal unit value based on the last current number of connections.
    function GetIdealUnit() {
        let clientMetrics = metrics[0];
        let serverMetrics = metrics[1];
        let currentMaxClientConnections = clientMetrics[clientMetrics.length - 1].maximum;
        let currentMaxServerConnections = serverMetrics[serverMetrics.length - 1].maximum;
        let currentTotalConnections = currentMaxClientConnections + currentMaxServerConnections ;
    
        contextGlobal.log.info(`currentTotalConnections: ${currentTotalConnections}`);
        let unitCalculated = currentTotalConnections / numberConnectionsPerUnit;
        contextGlobal.log.info(`unitCalculated: ${unitCalculated}`);
    
        for( let index = 0; index < signalRUnitRanges.length ; index++) {
    
            let currentUnitRange = signalRUnitRanges[index];
    
            if( unitCalculated >= currentUnitRange.initialUnit && unitCalculated < currentUnitRange.finalUnit) {
                console.log(`${unitCalculated} is between ${ currentUnitRange.initialUnit } and ${currentUnitRange.finalUnit} units`);
                return currentUnitRange.finalUnit;
            }
        }
    }
    
    //Allows obtaining a valid active directory token of Azure.
    async function GetToken() {
        var details = {
            'grant_type': 'client_credentials',
            'client_id': clientId,
            'client_secret': clientSecret,
            'resource': 'https://management.azure.com/'
        };
    
        var formBody = [];
        for (var property in details) {
            var encodedKey = encodeURIComponent(property);
            var encodedValue = encodeURIComponent(details[property]);
            formBody.push(encodedKey + "=" + encodedValue);
        }
        formBody = formBody.join("&");
    
        return await fetch( oAuthTokenEndpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
            },
            body: formBody
        })
        .then( (response) => {
            if(response.ok) {
                return response.json();
            } else {
                throw new Error(`HttpStatus: ${response.status} Message: GetToken Failed: reason: ${response.statusText}`);
            }
        })
        .then( data => data.access_token);
    
    }
    //Obtains the SignalR metrics in a time-periodic
    async function GetMetrics() {
        return await fetch(`https://management.azure.com/subscriptions/${subscription}/resourceGroups/${resourceGroup}/providers/Microsoft.SignalRService/SignalR/${nameSignalRInstance}/providers/microsoft.insights/metrics?api-version=2018-01-01&$filter=Endpoint eq 'server' or Endpoint eq 'client'`, {
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${token}`
            }
        }).then((response) => {
                if (response.ok) {
                    return response.json();
                } else {
                    console.log(response);
                    throw new Error('HttpStatus: ' + response.status + ' Message:' + response.statusText);
                }
            })
            .then(data => {
                let clientMetrics = data.value[0].timeseries[0].data;
                let serverMetrics = data.value[0].timeseries[1].data;
                return [ clientMetrics, serverMetrics];
            });
    }
    
    
    待ってください、しかし、あなたは考えています、しかし、この機能は多くの責任を持ちます、私はそれを知っています、そして、それはよりよくありえます、しかし、それは我々のケース使用のために働きます.

    結果
    このソリューションは、スケールアップし、自動的にスケールアウトし、サブスクリプションでお金を節約することができます.
    私のケースでは、私は通常の1日の仕事の間に35 Kの同時接続をすることができます、そして、夕方に、単位は1 kまで落ちます.それは私の振る舞いです.

    ありがとう、そして、この実装があなたのために働くことを願っています.