RoadRunnerアプリケーションサーバーの詳細設定

サーバーコマンド

RoadRunnerアプリケーションは、PHPアプリケーションのルートディレクトリから簡単なコマンドを実行することで起動できます。

$ rr serve

カスタム設定ファイルを使用してRoadRunnerを起動することも可能です。

$ rr serve -c ./app/.rr.yaml

すべてのRoadRunnerサービスを再読み込みします。

$ rr reset

このコマンドをIDEのファイルウォッチャーとして追加できます。

特定のプラグインのみをリセットします。

$ rr reset http

golang pprofサーバーをデバッグモードで実行します。

$ rr serve -d -c .rr.yaml

すべてのアクティブなワーカーのステータスをインタラクティブモードで表示します。

$ rr workers -i
Workers of [http]:
+---------+-----------+---------+---------+-----------------+
|  プロセスID  |  ステータス  |  実行回数  |  メモリ使用量  |     作成日時     |
+---------+-----------+---------+---------+-----------------+
|   10240  |  準備完了   |  55,120  |  28 MB   |  3週間前        |
|   10247  |  準備完了   |  55,115  |  28 MB   |  3週間前        |
|   10254  |  準備完了   |  55,108  |  28 MB   |  3週間前        |
|   10261  |  準備完了   |  55,122  |  28 MB   |  3週間前        |
+---------+-----------+---------+---------+-----------------+

ログ記録

RoadRunnerは、各プラグインのログを個別に制御する機能を提供します。

グローバル設定

ログ記録をグローバルに設定するには、`logs`設定セクションを使用します。

logs:
  mode: production
  output: stderr

開発モードを使用します。これは開発モード(DPanicLevelログでパニックを発生させる)、コンソールエンコーダを使用し、標準エラーに出力し、サンプリングを無効にします。スタックトレースは、WarnLevel以上のログに自動的に含まれます。

logs:
  mode: development

個別のファイルに出力します。

logs:
  mode: production
  output: file.log

コンソールに優しい出力を使用するには:

logs:
  encoding: console # デフォルト値

特定のログレベルのメッセージを抑制するには:

logs:
  encoding: console # デフォルト値
  level: info

チャンネル

さらに、`channels`セクションを使用して、各プラグインのログメッセージを個別に設定できます。

logs:
  encoding: console # デフォルト値
  level: info
  channels:
    server.mode: none # サーバーログを無効化。`off`も使用可能。
    http:
      mode: production
      output: http.log

要約:

  1. レベル:`panic`、`error`、`warn`、`info`、`debug`。デフォルト:`debug`。
  2. エンコーディング:`console`、`json`。デフォルト:`console`。
  3. モード:`production`、`development`、`raw`。デフォルト:`development`。
  4. 出力:`file.log`または`stderr`、`stdout`。デフォルトは`stderr`。
  5. エラー出力:`err_file.log`または`stderr`、`stdout`。デフォルトは`stderr`。

独自のZapLogger拡張を自由に登録してください。

自動リロード

RoadRunnerは、PHPファイルの変更を自動的に検出し、接続されたサービスを再読み込みできます。この方法により、手動で`max_jobs: 1`を設定するか、サーバーを手動でリセットすることなくアプリケーションを開発できます。

設定

httpサービスの自動リロードを有効にするには:

reload:
  # 同期間隔
  interval: 1s
  # 同期するグローバルパターン
  patterns: [ ".php" ]
  # 同期対象のサービスリスト
  services:
    http:
      # ファイルパターンを追加するための再帰的検索
      recursive: true
      # 無視するフォルダー
      ignore: [ "vendor" ]
      # サービス固有のファイルパターンを同期
      patterns: [ ".php", ".go", ".md" ]
      # 同期するディレクトリ。recursiveがtrueの場合、
      # 再帰的同期は`dirs`セクションのディレクトリにのみ適用されます
      dirs: [ "." ]

パフォーマンス

`reload`コンポーネントはアプリケーションサーバーのパフォーマンスに影響を与えます。開発モードでのみ使用してください。将来、このプラグインをネイティブのOS機能を使用した通知イベントベースのものに書き直す計画があります。

本番環境での使用

RoadRunnerを本番環境で実行する場合、いくつかのヒントと推奨事項を認識する必要があります。

ステータスとメモリ

ステータスとメモリは、異なるワーカーインスタンス間で共有されるのではなく、単一のワーカーインスタンス内で共有されます。単一のワーカーが通常複数のリクエストを処理するため、注意が必要です:

  • すべてのディスクリプタを確実に閉じる(特に致命的な例外が発生した場合)。
  • (オプション)メモリを低く保ちたい場合は、各実行後に`gc_collect_cycles`を呼び出すことを検討してください(これはアプリケーションの速度を若干遅くします)。
  • メモリリークを監視する - 使用するコンポーネントに注意深くする必要があります。メモリリークが発生した場合、ワーカーは再起動されますが、アプリケーションを正しく設計することでこの問題を完全に回避するのは難しくありません。
  • ステータスの汚染(メモリ内のグローバル変数やユーザーデータのキャッシュなど)を避ける。
  • データベース接続やパイプ/ソケットは潜在的な障害点です。簡単な対処法は、各反復後にすべての接続を閉じることです。ただし、これは最高のパフォーマンスを提供するソリューションではないことに注意してください。

設定

  • RPCサービスで0.0.0.0をリッスンしないようにしてください(Docker内でない限り)。
  • より高いパフォーマンスを得るためにワーカーにパイプ接続を使用します(Unixソケットは少し遅いです)。
  • プールタイムアウトを好みの値に調整します。
  • ワーカー数 = システムのCPUスレッド数。アプリケーションがIOバウンドでない限り、この数を試行的に選択してください。
  • アプリケーションの安定性に問題が発生した場合は、`max_jobs`をワーカーに使用することを検討してください。
  • RoadRunnerはKeep-Alive接続を使用することで40%のパフォーマンス向上を実現します。
  • メモリ制限を`max_memory_usage`より少なくとも10-20%下げて設定します。
  • RoadRunnerワーカーはCLIから実行されるため、`opcache.enable_cli=1`を通じて設定する必要があります。
  • クラウド環境でrrを実行する場合、ヘルスチェックエンドポイントを使用していることを確認してください。
  • Linuxベースのシステムから特定のユーザーでワーカープロセスを起動するには、`user`設定オプションを使用します。

Linux上でRRサーバーをデーモンとして実行する

RRリポジトリには、rr.server systemdユニットファイルが含まれています。このファイルの構造は次のとおりです:

[Unit]
Description=High-performance PHP application server

[Service]
Type=simple
ExecStart=/usr/local/bin/roadrunner serve -c <path/to/.rr.yaml>
Restart=always
RestartSec=30

[Install]
WantedBy=default.target

ユーザーがすべきことは、`ExecStart`を独自のオプションで更新することだけです。これを行うには、正しい`roadrunner`バイナリパス、必要なフラグ、および.rr.yamlファイルのパスを設定します。通常、このようなユーザーユニットファイルは`.config/systemd/user/`にあります。RRの場合、`.config/systemd/user/rr.service`になる可能性があります。有効にするには、次のコマンドを使用します:`systemctl enable --user rr.service`および`systemctl start rr.service`。これで完了です。これで、roadrunnerはサーバー上でデーモンとして実行されるはずです。

systemdユニットファイルの詳細については、こちらをご覧ください。

アプリケーションメトリクス

RoadRunnerサーバーには、Prometheusベースの組み込みメトリクスサーバーが含まれています。

メトリクスの有効化

メトリクスを有効にするには、設定に`metrics`セクションを追加します:

metrics:
  address: localhost:2112

これにより、`http://localhost:2112/metrics`URLを使用してPrometheusメトリクスにアクセスできます。

メトリクス拡張をインストールしてください:

$ composer require spiral/roadrunner-metrics

アプリケーションメトリクス

サーバーへのRPC接続を使用して、アプリケーション固有のメトリクスを公開することもできます。まず、設定ファイルでメトリクスを登録する必要があります:

metrics:
  address: localhost:2112
  collect:
    request_counter:
      type: counter
      help: "リクエストカウンタ。"

アプリケーションからメトリクスを送信します:

$metrics = new RoadRunner\Metrics\Metrics(
    Goridge\RPC\RPC::create(RoadRunner\Environment::fromGlobals()->getRPCAddress())
);

$metrics->increment('request_counter');

サポートされているタイプ:Gauge、Counter、Summary、Histogram。

ラベル付きメトリクス

ラベル(タグ)を使用してメトリクスに値をグループ化できます:

metrics:
  address: localhost:2112
  collect:
    request_duration_by_type:
      type: histogram
      help: "リクエストの処理時間。"
      labels: ["type"]

メトリクスをプッシュするときに、ラベルの値を指定する必要があります:

$metrics = new RoadRunner\Metrics\Metrics(
    Goridge\RPC\RPC::create(RoadRunner\Environment::fromGlobals()->getRPCAddress())
);

$metrics->observe('request_duration_by_type', 0.5, ['api']);

メトリクスの宣言

PHPアプリケーション自体からメトリクスを宣言することもできます:

$metrics->declare(
    'test_metric',
    RoadRunner\Metrics\Collector::counter()->withHelp('テストカウンタ')
);

ヘルスエンドポイント

RoadRunnerサーバーには、ワーカーのヘルスステータスを返すヘルスチェックエンドポイントが含まれています。

ヘルスチェックの有効化

ヘルスチェックエンドポイントを有効にするには、設定に`status`セクションを追加します:

status:
  address: localhost:2114

ヘルスチェックにアクセスするには、次のURLを使用します:

`http://localhost:2114/health?plugin=http`

ヘルスチェックは、1つまたは複数のプラグインをチェックできます。現在、HTTPのみがサポートされています。

有効にすると、ヘルスチェックエンドポイントは次の内容で応答します:

  • 少なくとも1つのワーカーがリクエストを処理する準備ができている場合、`HTTP 200`。
  • リクエストを提供する準備ができているワーカーがない場合、`HTTP 500`。

ユースケース

ヘルスチェックエンドポイントは、次の目的で使用できます:

  • Kubernetesの準備完了およびライブネスプローブ
  • AWSターゲットグループのヘルスチェック
  • GCEロードバランサーのヘルスチェック

サーバーの構築

RoadRunnerはEndureを使用して依存関係を管理します。これにより、各プロジェクトに応じてアプリケーション機能を調整および拡張できます。

Golangのインストール

アプリケーションサーバーをビルドするには、Golang 1.16+をインストールする必要があります。

main.goの作成

main.goファイルをプロジェクトのルートディレクトリにコピーします。

package appserver

import (
    "log"

    endure "github.com/spiral/endure/pkg/container"
    // プラグイン
    "github.com/spiral/roadrunner-binary/v2/cli"
    httpPlugin "github.com/spiral/roadrunner/v2/plugins/http"
    "github.com/spiral/roadrunner/v2/plugins/informer"
    "github.com/spiral/roadrunner/v2/plugins/kv/boltdb"
    "github.com/spiral/roadrunner/v2/plugins/kv/memcached"
    "github.com/spiral/roadrunner/v2/plugins/kv/memory"
    "github.com/spiral/roadrunner/v2/plugins/logger"
    "github.com/spiral/roadrunner/v2/plugins/metrics"
    "github.com/spiral/roadrunner/v2/plugins/redis"
    "github.com/spiral/roadrunner/v2/plugins/reload"
    "github.com/spiral/roadrunner/v2/plugins/resetter"
    "github.com/spiral/roadrunner/v2/plugins/rpc"
    "github.com/spiral/roadrunner/v2/plugins/server"
    "github.com/temporalio/roadrunner-temporal/activity"
    temporalClient "github.com/temporalio/roadrunner-temporal/client"
    "github.com/temporalio/roadrunner-temporal/workflow"
)

func main() {
    var initErr error
    cli.Container, initErr = endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel), endure.RetryOnFail(false))
    if initErr != nil {
        log.Fatal(initErr)
    }

    initErr = cli.Container.RegisterAll(
        // ロガープラグイン
        &logger.ZapLogger{},
        // メトリクスプラグイン
        &metrics.Plugin{},
        // HTTPサーバープラグイン
        &httpPlugin.Plugin{},
        // リロードプラグイン
        &reload.Plugin{},
        // インフォーマープラグイン (./rr workers, ./rr workers -i)
        &informer.Plugin{},
        // リセットプラグイン (./rr reset)
        &resetter.Plugin{},
        // RPCプラグイン (workers, reset)
        &rpc.Plugin{},
        // サーバープラグイン (NewWorker, NewWorkerPool)
        &server.Plugin{},

        // Temporalプラグイン
        &temporalClient.Plugin{},
        &activity.Plugin{},
        &workflow.Plugin{},
    )
    if initErr != nil {
        log.Fatal(initErr)
    }

    cli.Execute()
}

これで、`go run main.go serve`を実行するだけでサーバーを起動できます。

HTTPフローをインターセプトするためのHTTPミドルウェアの作成方法を学習してください。

RPC統合

共有RPCバスを使用して、PHPワーカーからRoadRunnerサーバーに接続できます。これを行うには、`RPC`クラスのインスタンスを作成し、`.rr`ファイルで指定されたアドレスを使用するように設定する必要があります。

要件

PHPアプリケーションからRPCモードでRoadRunnerに接続するには、次のものが必要です:

  • ext-sockets
  • ext-json

設定

デフォルトのRPCポート(localhost:6001)を変更するには、次を使用します:

rpc:
  listen: tcp://127.0.0.1:6001
$rpc = Goridge\RPC\RPC::create(RoadRunner\Environment::fromGlobals()->getRPCAddress());

すぐにこのRPCを使用して、組み込みのRPCサービス(例:HTTP)を呼び出すことができます:

var_dump($rpc->call('informer.GetWorkerStatus', 'http'));

独自のサービスとRPCメソッドの作成方法は、このセクションで学習できます。

プラグインの作成

RoadRunnerはEndureコンテナを使用して依存関係を管理します。これは、自動メソッドインジェクションを持つPHPコンテナ実装に似ています。独自のプラグイン、イベントリスナー、ミドルウェアなどをを作成できます。

プラグインを定義するには、エラー戻り値を持つ公開`Init`メソッドを持つ構造体を作成します(`spiral/errors`を`error`パッケージとして使用できます):

package custom

const PluginName = "custom"

type Plugin struct{}

func (s *Plugin) Init() error {
    return nil
}

独自のバージョンの`main.go`ファイルを作成してビルドすることで、プラグインを登録できます。

依存関係

Initメソッドで依存関係を要求することで、他のRoadRunnerプラグインにアクセスできます:

package custom

import (
    "github.com/spiral/roadrunner/v2/plugins/http"
    "github.com/spiral/roadrunner/v2/plugins/rpc"
)

type CustomService struct {}

func (s *CustomService) Init(rpcClient *rpc.Plugin, httpServer *http.Plugin) error {
    return nil
}

依存関係をポインタとして要求することを確認してください。

設定

ほとんどの場合、サービスには一連の設定値が必要です。RoadRunnerは`config`プラグイン(インターフェースを介して)を使用して、設定構造体を自動的に埋め込み、検証できます:

設定の例:

custom:
  address: tcp://localhost:8888

挿入:

package custom

import (
    "github.com/spiral/roadrunner/v2/plugins/config"
    "github.com/spiral/roadrunner/v2/plugins/http"
    "github.com/spiral/roadrunner/v2/plugins/rpc"

    "github.com/spiral/errors"
)

const PluginName = "custom"

type PluginConfig struct{
    Address string `mapstructure:"address"`
}

type Plugin struct {
    cfg *PluginConfig
}

// 設定キーのデフォルト値を初期化することもできます
func (cfg *PluginConfig) InitDefaults() {
    if cfg.Address == "" {
        cfg.Address = "tcp://localhost:8088"
    }
}

func (s *Plugin) Init(rpcClient *rpc.Plugin, httpServer *http.Plugin, configurer config.Configurer) error {
    const op = errors.Op("custom_plugin_init") // エラー操作名
    if !configurer.Has(PluginName) {
        return errors.E(op, errors.Disabled)
    }

    // Unmarshal
    err := configurer.UnmarshalKey(PluginName, &s.cfg)
    if err != nil {
        // エラーは実行を停止します
        return errors.E(op, err)
    }

    // デフォルト値の初期化
    s.cfg.InitDefaults()

    return nil
}

`errors.Disabled`は、Endureにこのプラグインとそのすべての依存関係を無効にするように指示する特別なエラーです。少なくとも1つのプラグインがアクティブな状態でRR2はこのエラーの後も動作を続けます。

サービス

構造体に`Serve`と`Stop`メソッドを作成して、RoadRunnerがサービスを開始および停止できるようにします。

type Plugin struct {}

func (s *Plugin) Serve() chan error {
    const op = errors.Op("custom_plugin_serve")
    errCh := make(chan error, 1)

    err := s.runTask()
    if err != nil {
        errCh <- errors.E(op, err)
        return errCh
    }

    return nil
}

func (s *Plugin) Stop() error {
    return s.terminateService()
}

func (s *Plugin) runTask() error {
    return nil
}

`Serve`メソッドはスレッドセーフです。`Endure`コンテナによって管理される別のgoroutineで実行されます。注意点は、`Stop`コンテナが呼び出されたときにブロックを解除する必要があることです。そうしないと、タイムアウト後にサービスが強制終了されます(Endureで設定可能)。

実行時の依存関係の収集

RR2は、`Collects`インターフェースを介して実行時に依存関係を収集する方法を提供します。これは、ミドルウェアや追加機能を持つ拡張プラグインにとって非常に役立ちます。プラグイン自体を変更することなく機能を追加します。 HTTPミドルウェアを作成してみましょう:

ステップ(実際の`http`プラグインと`Middleware`インターフェースを例に):

  1. 必要なインターフェースを宣言します
    // Middlewareインターフェース
    type HttpMiddleware interface {
        Middleware(next http.Handler) http.HandlerFunc
    }
  2. 実装するメソッドには、パラメータ名(`endure.Named`インターフェース)と`Middleware`(ステップ1)を1つ持つ必要があります。
    // Collectsはhttpミドルウェアを収集します
    func (s *Plugin) RegisterMiddleware(name endure.Named, m HttpMiddleware) {
        s.middlewares[name.Name()] = m
    }
  3. `Collects`インターフェースを実装し、ステップ2のメソッドで実装されたものを返します。
    // Collectsはhttpミドルウェアを収集します
    func (s *Plugin) Collects() []interface{} {
        return []interface{}{
            s.RegisterMiddleware,
        }
    }

Endureは、登録された構造体が`RegisterMiddleware`メソッドのすべてのパラメータを実装しているか自動的にチェックします(パラメータが構造体の場合、構造体を見つけます)。この例では、構造体は`endure.Named`インターフェース(プラグインのユーザーフレンドリーな名前を返す)と`Middleware`インターフェースの両方を実装する必要があります。

RPCメソッド

Endureの`Collects`インターフェースを使用して、PHPワーカー向けに一連のRPCメソッドを公開することもできます。Endureは自動的に構造体を取得し、`PluginName`名前でRPCメソッドを公開します。

プラグインをRPCメソッドで拡張するには、プラグイン自体を変更する必要はありません。唯一のことは、RPCメソッドを作成するファイル(`rpc.go`と呼びましょう)を作成し、そこでプラグインのすべてのRPCメソッドを公開することです: `informer`プラグインを基にした例:

rpc.goファイルを作成したと仮定します。次に、構造体を作成します:

  1. 構造体を作成します:(ロガーはオプションです)
    package custom
    
    type rpcService struct {
        plugin *Plugin
        log logger.Logger
    }
  2. 公開するメソッドを作成します:
    func (s *rpcService) Greet(input string, output *string) error {
        *output = "Hello, " + input
        return nil
    }
  3. EndureにRPCサービスを公開するために`Collects`インターフェースを使用します:
    // CollectTargetはリセット可能なサービス。
    func (p *Plugin) CollectTarget(name endure.Named, informer Informer) error {
        p.informerRegistry[name.Name()] = informer
        return nil
    }
    
    // Collectsは収集するサービスを宣言します。
    func (p *Plugin) Collects() []interface{} {
        return []interface{}{
            p.CollectTarget,
        }
    }
    
    // サービスの名前。
    func (p *Plugin) Name() string {
        return PluginName
    }
    
    // RPCServiceは関連するrpcサービスを返します。
    func (p *Plugin) RPC() interface{} {
        return &rpcService{plugin: p, log: p.log}
    }

これらのメソッドを見てみましょう:

  1. `CollectTarget`:Endureに、`endure.Named`および`Informer`インターフェースを実装するすべてのプラグインを収集するように指示します。
  2. `Collects`:インターフェースの実装。
  3. `Name`:`endure.Named`インターフェースの実装で、ユーザーフレンドリーなプラグイン名を返します。
  4. `RPC`:RPCプラグインは、`RPC`インターフェースと`endure.Named`を実装するすべてのものを収集します。RPCインターフェースはパラメータを受け取りませんが、インターフェース(プラグイン)を返します。

PHPで`RPC`インスタンスを使用するには:

var_dump($rpc->call('custom.Greet', 'World'));

サービスプラグイン

サービスプラグインはRRの`v2.0.5`で導入されました。

主な機能

  1. PHPコード、バイナリ、bash/powershellスクリプトの実行。
  2. 指定された時間後に再起動。
  3. 特定のコマンドの実行時間の制御。
  4. `Informer`プラグインへの統計情報の提供(`%CPU`、`PID`、`RSS memory`など)。

設定

service:
  my_service:
    command: "php tests/plugins/service/test_files/loop.php"
    process_num: 5
    exec_timeout: 0
    remain_after_exit: true
    restart_delay: 2s

  backup_service:
    command: "tests/plugins/service/test_files/test_binary"
    process_num: 1
    remain_after_exit: true
    restart_delay: 1s
    exec_timeout: 0

説明:

  1. サービスプラグインは任意の数のネストされたコマンドをサポートします。
  2. `command` - 実行するコマンド。ここにコマンドの制限はありません。バイナリ、PHPファイル、スクリプトなどが考えられます。
  3. `process_num` - デフォルト:1、コマンドによってトリガーされるプロセスの数。
  4. `exec_timeout` - デフォルト:0(無制限)、プロセスが実行を許可される最大時間。
  5. `remain_after_exit` - デフォルト:false。プロセス終了後にプロセスを保持します。例えば、10秒ごとにプロセスを再起動する必要がある場合、`exec_timeout`は10sで、`remain_after_exit`はtrueに設定する必要があります。注意:外部からプロセスを終了させ、かつ`remain_after_exit`がtrueの場合、プロセスは再起動されます。
  6. `restart_delay` - デフォルト:30秒。プロセスの停止と再起動の間の遅延。

タグ: golang roadrunner PHP Microservices server

6月16日 19:38 投稿