メインコンテンツ

デプロイされたアーカイブでのアプリケーション状態の管理

R2026b

この例では、MATLAB® Production Server™ にデプロイされたアプリケーション アーカイブ内の永続データを管理する方法を示します。MATLAB Production Server MATLAB 関数実行のための RESTful API と JSON を使用して、MATLAB アプリの 1 つ以上のインスタンスをサーバーにデプロイされたアーカイブに接続します。

MATLAB Production Server ワーカーはステートレスです。永続性により、サーバーにデプロイされた MATLAB コードへの複数回の呼び出し間でデータをキャッシュし、状態を維持するためのメカニズムが提供されます。複数のワーカーが、キャッシュされたデータにアクセスできます。

メモ

サーバーへの呼び出し間では MATLAB ワークスペース変数は共有されませんが、リクエストの処理中に行われた特定の環境レベルの変更は、そのリクエストを処理したワーカー プロセス内に保持されることがあります。たとえば、デプロイされた関数が javaaddpath (MATLAB) を使用して Java 動的クラスパスを変更した場合、同じワーカーにルーティングされた後続のリクエストには、変更されたクラスパスが引き継がれます。リクエストからワーカーへのルーティングは保証されておらず、ワーカーは定期的に再起動されるため、この動作には依存しないでください。デプロイされた関数で Java 動的クラスパスや同様のプロセスレベルの状態を変更する場合は、クリーンな状態であることを前提とせず、各呼び出しの開始時に現在の環境を確認するように関数を設計してください。

この例では、2 つのワークフローについて説明します。

  1. アプリケーションをサーバーにデプロイする前に、MATLAB デスクトップ環境でその機能をテストするためのテスト ワークフロー。

  2. アクティブな MATLAB Production Server インスタンスを使用してアーカイブをデプロイするデプロイ ワークフロー。

永続性の使用方法を示すため、この例では、都市間の最短経路を求める巡回セールスマン問題を使用します。この実装では、永続的な MATLAB graph (MATLAB) オブジェクトをデータ キャッシュに格納します。都市がグラフのノードを形成し、都市間の距離がグラフのエッジに関連付けられた重みとなります。この例では、グラフは完全グラフです。テスト ワークフローでは、経路探索関数のローカル バージョンを使用します。デプロイ ワークフローでは、アーカイブにパッケージ化されてサーバーにデプロイされた経路探索関数を使用します。MATLAB アプリは、経路探索関数を呼び出します。これらの関数は、キャッシュからグラフ データを読み取り、キャッシュにグラフ データを書き込みます。

この例のコードは $MPS_INSTALL/client/matlab/examples/persistence/TravelingSalesman にあります。ここで、$MPS_INSTALL は MATLAB Production Server がインストールされている場所です。

[Production Server コンパイラ] アプリで作成したデプロイ可能なアーカイブをホストするには、アーカイブの作成に使用した MATLAB のバージョンと互換性のあるバージョンの MATLAB Runtime がインストールされている必要があります。詳細については、MATLAB Production Server でサポートされる MATLAB Runtime バージョンを参照してください。

手順 1: 永続性関数を使用する MATLAB コードの作成

  1. 永続データを初期化する関数を作成します。

    都市と距離のグラフがデータ キャッシュに存在するかどうかを確認する関数を作成します。グラフが存在しない場合は、距離データを含む Excel® スプレッドシートからグラフを作成し、キャッシュに書き込みます。この書き込み操作を一度に実行できる MATLAB Production Server ワーカーは 1 つだけであるため、同期ロックを使用して、データの初期化が 1 回だけ実行されるようにします。

    mps.cache.connect を使用して、距離データを格納するキャッシュに接続するか、キャッシュが存在しない場合は作成します。書き込み操作の間、mps.sync.mutex を使用してミューテックスのロックを取得します。データがキャッシュに書き込まれたら、ロックを解放します。

    loadDistanceData 関数を使用して距離データを初期化します。

    function tf = loadDistanceData(connectionName, cacheName)
        c = mps.cache.connect(cacheName,'Connection',connectionName);    
        tries = 0;
    
        while isKey(c,'Distances') == false && tries < 6
            lk = mps.sync.mutex('DistanceData','Connection',connectionName);       
            if acquire(lk,10)
                if isKey(c,'Distances') == false
                    g = initDistanceData('Distances.xlsx');
                    c.Distances = g;
                end
                release(lk);
            end
            tries = tries + 1;
        end   
        tf = isKey(c,'Distances');
    end
  2. 永続データを読み取る関数を作成します。

    データ キャッシュから距離データのグラフを読み取る関数を作成します。キャッシュからのデータ読み取りは冪等な操作であるため、同期ロックを使用する必要はありません。mps.cache.connect を使用してキャッシュに接続し、その後グラフを取得します。

    キャッシュからグラフを読み取り、listDestinations 関数を使用して cell 配列に変換します。

    findRoute 関数を使用して、可能な最短経路を計算します。最近傍アルゴリズムを使用し、指定された都市から開始して、すべての都市を訪問するまで、その時点で最も近い次の都市を繰り返し訪問します。

    function destinations = listDestinations()
        c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad');   
        if loadDistanceData('ScratchPad','TravelingSalesman') == false
            error('Failed to load distance data. Cannot continue.');
        end
        
        g = c.Distances;
        destinations = table2array(g.Nodes);
    end
    function [route,distance] = findRoute(start,destinations)
        c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad');    
        if loadDistanceData('ScratchPad','TravelingSalesman') == false
            error('Failed to load distance data. Cannot continue.');
        end
            
        g = c.Distances;    
        route = {start};
        distance = 0;
        current = start;
        
        while ~isempty(destinations)
            minDistance = Inf;
            nextSegment = {};        
            for n = 1:numel(destinations)
                [p,d] = shortestpath(g,current,destinations{n});
                if d < minDistance
                    nextSegment = p(2:end);
                    minDistance = d;
                end
            end
    
            current = nextSegment{end};
            distance = distance + minDistance;
            destinations = setdiff(destinations,current);        
            route = [ route nextSegment ];
        end
    end
  3. 永続データを変更する関数を作成します。

    新しい都市を追加する関数を作成します。都市を追加すると、データ キャッシュに保存されているグラフが変更されます。この操作ではキャッシュへの書き込みが必要になるため、ロックにはステップ 1 で説明した mps.sync.mutex 関数を使用します。都市を追加した後、すべての都市の組み合わせについて都市間の距離が既知であることを確認し、グラフが引き続き完全グラフであることを確認します。

    addDestination 関数を使用して都市を追加します。都市を追加すると、新しいグラフ ノード name とともに、このノードをグラフ内の既存のすべてのノードに接続する新しいエッジが追加されます。新しく追加されたエッジの重みは、ベクトル distances で指定されます。destinations は、グラフ内の他の都市の名前を含む文字ベクトルの cell 配列です。

    function count = addDestination(name, destinations, distances)  
        count = 0;
        c = mps.cache.connect('TravelingSalesman','Connection','ScratchPad');   
        if loadDistanceData('ScratchPad','TravelingSalesman') == false
            error('Failed to load distance data. Cannot continue.');
        end
        
        lk = mps.sync.mutex('DistanceData','Connection','ScratchPad');  
        if acquire(lk,10)
            g = c.Distances;      
            newDestinations = setdiff(g.Nodes.Name, destinations);       
            if ~isempty(newDestinations)
                error('MPS:Example:TSP:MissingDestinations', ...
                      'Add distances for missing destinations: %s', ...
                    strjoin(newDestinations,', '));
            end
            
            src = repmat({name},1,numel(destinations));
            g = addedge(g, src, destinations, distances);
            c.Distances = g;
            release(lk);
            count = numnodes(g);
        end   
    end
    

  4. 経路探索関数を呼び出す MATLAB アプリを作成します。

    ステップ 2 とステップ 3 で説明した関数を、それぞれ対応するプロキシ関数でラップする MATLAB アプリを作成します。このアプリでは、ホストとポートを指定できます。テストでは、ホストが空白で、ポートの値が 0 の場合に、経路探索関数のローカル バージョンを呼び出します。デプロイ ワークフローでは、指定したホストとポートで稼働しているサーバー上のデプロイ済み関数を呼び出します。webwrite (MATLAB) 関数を使用して、HTTP POST リクエストをサーバーに送信します。

    アプリの作成方法の詳細については、App Designer を使用した簡単なアプリの作成と実行 (MATLAB)を参照してください。

    findRoute、addDestination、listDestination 関数に対して、それぞれプロキシ関数 findRouteProxy、addDestinationProxy、listDestinationProxy を作成します。

    function destinations = listDestinationsProxy(app)
        if isempty(app.HostEditField.Value) && ...
                app.PortEditField.Value <= 0
            destinations = listDestinations();
            return;
        end
            
        listDestinations_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw');
        listDestinations_HOST = app.HostEditField.Value;
        listDestinations_PORT = app.PortEditField.Value;
        noInputJSON = '{ "rhs": [], "nargout": 1 }';
        destinations_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/listDestinations', ...
            listDestinations_HOST,listDestinations_PORT), noInputJSON, listDestinations_OPTIONS);
        if iscolumn(destinations_JSON), destinations_JSON = destinations_JSON'; end
        destinations_RESPONSE = mps.json.decoderesponse(destinations_JSON);
        if isstruct(destinations_RESPONSE)
            error(destinations_RESPONSE.id,destinations_RESPONSE.message);
        else
            if nargout > 0, destinations = destinations_RESPONSE{1}; end
        end        
    end
    function [route,distance] = findRouteProxy(app,start,destinations)
        if isempty(app.HostEditField.Value) && ...
                app.PortEditField.Value <= 0
            [route,distance] = findRoute(start,destinations);
            return;
        end  
        findRoute_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw');
        findRoute_HOST = app.HostEditField.Value;
        findRoute_PORT = app.PortEditField.Value;
        start_destinations_DATA = {};
        if nargin > 0, start_destinations_DATA = [ start_destinations_DATA { start } ]; end
        if nargin > 1, start_destinations_DATA = [ start_destinations_DATA { destinations } ]; end
        route_distance_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/findRoute', ...
                findRoute_HOST,findRoute_PORT), ...
                mps.json.encoderequest(start_destinations_DATA,'nargout',nargout), findRoute_OPTIONS);
        if iscolumn(route_distance_JSON), route_distance_JSON = route_distance_JSON'; end
        route_distance_RESPONSE = mps.json.decoderesponse(route_distance_JSON);
        if isstruct(route_distance_RESPONSE)
            error(route_distance_RESPONSE.id,route_distance_RESPONSE.message);
        else
            if nargout > 0, route = route_distance_RESPONSE{1}; end
            if nargout > 1, distance = route_distance_RESPONSE{2}; end
        end
    end
    function count = addDestinationProxy(app, name, destinations,distances)
        if isempty(app.HostEditField.Value) && ...
                app.PortEditField.Value <= 0
            count = addDestination(name, destinations,distances);
            return;
        end
                        
        addDestination_OPTIONS = weboptions('MediaType','application/json','Timeout',60,'ContentType','raw');
        addDestination_HOST = app.HostEditField.Value;
        addDestination_PORT = app.PortEditField.Value;
        name_destinations_distances_DATA = {};
        if nargin > 0, name_destinations_distances_DATA = [ name_destinations_distances_DATA { name } ]; end
        if nargin > 1, name_destinations_distances_DATA = [ name_destinations_distances_DATA { destinations } ]; end
        if nargin > 2, name_destinations_distances_DATA = [ name_destinations_distances_DATA { distances } ]; end
        count_JSON = webwrite(sprintf('http://%s:%d/TravelingSalesman/addDestination', ...
            addDestination_HOST,addDestination_PORT), ...
            mps.json.encoderequest(name_destinations_distances_DATA,'nargout',nargout), addDestination_OPTIONS);
        if iscolumn(count_JSON), count_JSON = count_JSON'; end
        count_RESPONSE = mps.json.decoderesponse(count_JSON);
        if isstruct(count_RESPONSE)
            error(count_RESPONSE.id,count_RESPONSE.message);
        else
            if nargout > 0, count = count_RESPONSE{1}; end
        end
    end

手順 2: テスト ワークフローでの例の実行

MATLAB デスクトップ環境でサンプル コードをテストします。そのためには、$MPS_INSTALL/client/matlab/examples/persistence/TravelingSalesman にあるすべてのファイルを、/tmp/persistence_example など、システム上の書き込み可能なフォルダーにコピーします。MATLAB デスクトップを起動し、cd (MATLAB) コマンドを使用して現在の作業ディレクトリを /tmp/persistence_example に設定します。

テスト目的では、mps.cache.control 関数を使用して、MATLAB デスクトップから永続性サービスを制御します。この関数は、ローカル永続性サービスのライフサイクルを管理する mps.cache.Controller オブジェクトを返します。

  1. Redis™ 永続性プロバイダーを使用するローカル永続性サービス用の mps.cache.Controller オブジェクトを作成します。

    >> ctrl = mps.cache.control('ScratchPad', 'Redis', 'Port', 8675);

    このコントローラーがアクティブになると、ScratchPad という名前の接続が有効になります。接続名は、キャッシュを永続性サービス内の保存場所に関連付けます。mps.cache.connect 関数でデータ キャッシュを作成するには、接続名が必要です。MATLAB Production Server 管理者は、キャッシュ構成ファイル mps_cache_config で接続名を設定します。詳細については、Redis を使用するためのサーバーの構成を参照してください。MATLAB デスクトップ セッションで同じ接続名を使用することで、コードを変更せずに開発からテストを経て本番環境へ移行できます。

  2. start を使用して永続性サービスを開始します。

    >> start(ctrl);
  3. 永続性サービスを使用する TravelingSalesman 経路探索アプリを開始します。

    >> TravelingSalesman

    アプリは、[ホスト] と [ポート] に既定値が設定された状態で開始します。

    [Load Cities] をクリックして、都市のリストを読み込みます。[開始] メニューを使用して出発地点を設定し、[>>] および [<<] ボタンを使用して、訪問する都市を選択または選択解除します。[Compute Path] をクリックして、すべての都市を訪問する経路を表示します。

    Traveling Salesman app displaying the shortest route

  4. アプリを閉じるときは、stop を使用して永続性サービスを停止します。永続性サービスを停止すると、そのサービスによって保存されているデータが削除されます。

    >> stop(ctrl);

手順 3: デプロイ ワークフローでの例の実行

デプロイ ワークフローでこの例を実行するには、$MPS_INSTALL/client/matlab/examples/persistence/TravelingSalesman にあるすべてのファイルを、/tmp/persistence_example など、システム上の書き込み可能なフォルダーにコピーします。MATLAB デスクトップを起動し、MATLAB cd (MATLAB) コマンドを使用して現在の作業ディレクトリを /tmp/persistence_example に設定します。

デプロイ ワークフローでは、MATLAB デスクトップ環境の外部で永続性サービスのライフサイクルを管理し、サーバーにデプロイされたアーカイブにパッケージ化されている経路探索関数を呼び出します。

  1. MATLAB Production Server インスタンスを作成します。

    mps-new を使用して、システム コマンド ラインからサーバーを作成します。詳細については、コマンド ラインを使用したサーバー インスタンスの作成を参照してください。サーバー環境をまだセットアップしていない場合、詳細については、mps-setup を参照してください。

    フォルダー tmp に新しいサーバー server_1 を作成します。

    mps-new /tmp/server_1

    または、MATLAB Production Server ダッシュボードを使用してサーバーを作成します。詳細については、MATLAB Production Server Admin Portal のセットアップとログインを参照してください。

  2. 永続性サービス接続を作成します。

    デプロイ可能なアーカイブには、ScratchPad という名前の永続性サービス接続が必要です。ダッシュボードを使用して ScratchPad 接続を作成するか、サンプル ディレクトリからファイル mps_cache_config をサーバー インスタンスの config ディレクトリにコピーします。config ディレクトリに mps_cache_config ファイルが既に存在する場合は、そのファイルを編集して、サンプルの mps_cache_config で指定されている ScratchPad 接続を追加します。

  3. Production Server コンパイラ アプリを使用して、デプロイ可能なアーカイブを作成し、サーバーにデプロイします。

    1. [Production Server アーカイブ コンパイラ] を開きます。

      • MATLAB ツールストリップ: [アプリ] タブの [アプリケーションのデプロイ] で、[Production Server アーカイブ コンパイラ] をクリックします。

      • MATLAB コマンド プロンプト: 「productionServerArchiveCompiler」と入力します。

    2. [エクスポートする関数] フィールドに、findRoute.m、listDestinations.m、addDestination.m を追加します。

    3. アーカイブの名前を TravelingSalesman に変更します。

    4. [カスタム要件] で、Distances.xlsx を追加します。

    5. [パッケージ化] をクリックします。

    6. デプロイ可能なアーカイブ TravelingSalesman.ctf がプロジェクトの出力フォルダーに生成されます。ホストするために、TravelingSalesman.ctf ファイルをサーバーの auto_deploy フォルダーにコピーします。この例では /tmp/server_1/auto_deploy です。

  4. サーバー インスタンスを開始します。

    mps-start を使用して、システム コマンド ラインからサーバーを開始します。

    mps-start -C /tmp/server_1
    または、ダッシュボードを使用してサーバーを開始します。

  5. 永続性サービスを開始します。

    mps-cache を使用して、システム コマンド ラインから永続性サービスを開始します。

    mps-cache start -C /tmp/server_1 --connection ScratchPad
    または、ダッシュボードを使用して永続性サービスを開始し、アタッチします。

  6. アプリをテストします。

    永続性サービスを使用する TravelingSalesman 経路探索アプリを開始します。

    >> TravelingSalesman

    アプリは、[ホスト] と [ポート] に空の値が設定された状態で開始します。server_name/config にあるサーバー構成ファイル main_config を参照して、MATLAB Production Server インスタンスのホスト値とポート値を取得します。この例では、構成ファイルは /tmp/server_1/config にあります。アプリにホストとポートの値を入力します。

    [Load Cities] をクリックして、都市のリストを読み込みます。[開始] メニューを使用して出発地点を設定し、[>>] および [<<] ボタンを使用して、訪問する都市を選択または選択解除します。[Compute Path] をクリックして、すべての都市を訪問する経路を表示します。

    Traveling Salesman app displaying the shortest route

テスト環境のワークフローとデプロイ環境のワークフローの結果は同じです。

参考

(MATLAB Compiler SDK) | (MATLAB Compiler SDK) | (MATLAB Compiler SDK) | (MATLAB Compiler SDK) | (MATLAB Compiler SDK) | (MATLAB Compiler SDK) | (MATLAB Compiler SDK)

トピック