RPC フレームワークについて深く考える Face to Face 4

7 netty メカニズムについてのある程度の理解

推奨読書: netty ネットワーク プログラミング フレームワークについての深い考察

7.1 Netty がサポートするポート番号:

Netty は、ほとんどのネットワーク ライブラリと同様に、任意の合法的なポート番号にバインドできます。有効なポートの範囲は 0 ~ 65535 ですが、ポート 0 ~ 1023 は既知のサービス用に予約されているため、通常は 1024 より上のポートを使用することをお勧めします。

7.2 Netty は NIO メカニズムを使用します

  • NIO と多重化チャネル:
    Netty は Java NIO (ノンブロッキング IO) 上に構築されており、そのコアはイベント駆動型およびコールバック メカニズムです。「チャネルの多重化」について話すときは、通常、Unix システムのセレクト、ポーリング、エポーリング メカニズムなどの IO 多重化テクノロジを指します。これらのメカニズムにより、単一のスレッドで複数の接続 (読み取り可能、書き込み可能など) の IO イベントを監視できるため、同時処理能力が向上します。

  • プログラマの手動セットアップ:
    Netty は高レベルの抽象化を提供するため、開発者は低レベルの多重化 API を直接操作する必要がありません。IO 多重化は、開発者が や他の Netty コンポーネントを作成および設定するときに自動的に行われChannelますEventLoopただし、もちろん、Netty は上級ユーザーが動作を調整して最適化できる十分な柔軟性も提供します。

7.3 Netty のデフォルトのトランスポート層プロトコル:

トランスポートの種類を明示的に指定せずに Netty サーバーまたはクライアントを作成すると、Netty はデフォルトでトランスポートとして TCP を使用します。ただし、使用するトランスポートの種類を明示的に指定することを常にお勧めします。たとえば、Netty では、NIO ベースの TCP トランスポート、または UDP トランスポートなどの他のチャネル タイプを選択NioServerSocketChannelできNioSocketChannelますNioDatagramChannel

7.4 ここでのチャネルは、特定のクライアントとの接続を確立することと同等ですが、EventLoop は、これらの接続を監視するために選択メソッドを呼び出し、スレッド モデルに従ってこれらの接続を対応するハンドルに引き渡すことと同等ですか?

はい、あなたの理解はほぼ正確です。Netty の概念は、従来のネットワーク プログラミング モデルと多くの類似点がありますが、開発者に高レベルの抽象化とより柔軟な構成オプションを提供します。以下は概要と、あなたが言及したいくつかの重要な点の補足です。

  1. チャンネル:

    Netty では、Channelこれはネットワーク通信の中核的な抽象概念であり、接続またはデータ伝送チャネルを表します。TCP の場合、1 つはChannelクライアントとサーバー間の接続に対応します。

  2. イベントループ:

    EventLoopネットワーク イベントの処理と I/O 操作の実行を担当します。実際、EventLoop通常、1 つは 1 つのスレッドに対応し、このスレッドは内部で 、selectpollなどの多重化技術を使用して、複数のスレッド上のイベントをepollリッスンします。Channelこのようにして、1 つのスレッド (またはEventLoop) で複数の接続を効率的に管理および処理できます。

  3. スレッドモデルとハンドラー:

    Netty にEventLoopGroupは と呼ばれる概念があり、これはEventLoopのコレクションであり、通常はワーカー スレッドのグループを表すために使用されます。EventLoopGroup新しい接続が受け入れられると、その接続はそのうちの 1 つに割り当てられEventLoop接続の存続期間中、すべての I/O 操作はEventLoopこの (つまり、固定スレッド) によって処理され、特定の接続に関連するイベントが保証されます。接続は常に同じスレッドによって順次処理されます。

    オブジェクト上のイベント (データが読み取り可能であるなど) をEventLoop検出すると、そのイベントを対応するハンドラーに渡します。これは、Netty でさまざまなネットワーク イベント (読み取り、書き込み、接続、切断など) を処理するために使用されるコンポーネントです。ChannelHandlerHandler

  4. パイプライン:

    Netty には、処理または傍受のための受信イベントと送信イベントのリンクされたリストであるChannelPipeline概念もありますこれにより、さまざまな処理ロジックを組み合わせて再利用するための強力なツールが開発者に提供されます。ChannelHandlerChannel

つまり、Netty は、ネットワーク プログラミングをより簡単かつ効率的にする、強力かつ柔軟なツールと抽象化のセットを提供します。

8 サービスの登録/検出/オフラインと負荷分散 (Alibaba 側)

8.1 rpc フレームワークはいつ登録センターと連携しますか? 導入できますか?

RPC フレームワークと登録センターは主に次の瞬間に対話します。

  • サービス登録: サービス プロバイダーが開始すると、提供するサービス情報が登録センターに登録されます。通常、このプロセスはサービス プロバイダーの開始の初期化フェーズ中に発生します。

  • サービス ディスカバリ: サービス コンシューマは、開始後、登録センターから必要なサービス情報を取得します。このプロセスはサービス ディスカバリと呼ばれます。サービスディスカバリは、サービスコンシューマの起動時に発生することもあれば、最新のサービス情報を取得するために動作中に定期的に実行されることもあります。

  • サービスがオフライン: サービス プロバイダーがシャットダウンするか、何らかの理由でサービスの提供に失敗すると、サービス プロバイダー自体が登録センターから削除されます。このプロセスは通常、サービス プロバイダーのシャットダウン時に発生します。

  • サービス変更:自身の運用状況が変化した場合、登録センターに報告可能

8.2 サービス登録時に登録される情報は何ですか?

正解:
サービスの登録は、RPC (リモート プロシージャ コール) フレームワークを使用する上で非常に重要な部分です。サービスの登録プロセス中に、通常は次の情報が登録されます。

  • サービス識別子 (インスタンス ID とは異なります): これは、個々のサービスを区別するために使用されるグローバルに一意の識別子です。通常、この識別子にはサービスの名前とバージョン番号が含まれるため、消費者はニーズに応じてサービスの対応するバージョンを選択できます。
  • サービス アドレス: 登録されたサービスは、サービスを見つけてアクセスできるネットワーク アドレスを提供する必要があります。これには通常、IP アドレスとポート番号が含まれます。
  • サービス インターフェイス メタデータ: これには、サービスの説明、サービスの状態 (健全性ステータスなど)、サービスによって提供されるメソッドまたはインターフェイスの詳細など
  • サービス プロバイダー情報:サービスの検出と負荷分散システムがより合理的な決定を下せるようにするために、CPU、メモリなどのプロバイダーのマシン情報が含まれる場合があります。

この情報は、消費者が照会して使用できるようにサービス登録センターに登録されます。登録する情報は、RPC フレームワークや使用シナリオによって異なる場合があることに注意してください。

8.3 RPC クライアントのルーティングを行う方法 (負荷分散の方法について話しています)? カスタム ルーティング操作 (カスタム ロード バランシング戦略) をサポートしていますか?

拡張を達成するための実際の方法:

RPC フレームワークは通常、ラウンド ロビン、ランダム、最小接続数などの組み込みの負荷分散戦略を提供します。ただし、場合によっては、これらの組み込みポリシーが特定のビジネス ニーズを満たさない場合があります。多くの RPC フレームワークもこの状況を考慮しているため、ユーザー定義の負荷分散戦略をサポートしています。
いくつかの一般的な RPC フレームワークを例として挙げます。

  • Dubbo では、ユーザーは LoadBalance インターフェイスを実装することで独自の負荷分散戦略を定義できます。サービス利用者側では、@Reference(loadbalance = "myLoadBalance") を通じてカスタムの負荷分散戦略を使用できます。

カスタムの負荷分散戦略を実装する場合は、サービスの健全性、ネットワーク遅延、サーバー負荷などの要素を考慮する必要があります。さらに、スレッドの安全性とパフォーマンスの問題にも注意を払う必要があります。
負荷分散戦略をカスタマイズする具体的な方法は、RPC フレームワークによって異なることに注意してください。具体的な実装については、対応する RPC フレームワークの公式ドキュメントまたは関連資料を参照する必要があります。

9 一般的な通話

9.1 ジェネリックコールとは何ですか?

回答: 一般的な呼び出しプロセスでは、使用されるメソッド署名は明確ですが、サーバー自体がそのような明確な API 署名を提供できない別の状況があります。このとき、渡すサービスとメソッドは文字列である可能性があります。過去に渡したパラメータは大きなマップであり、どのリクエストが対応するサーバーにヒットするかを指定するだけであり、サーバー層は対応する反映と検索を実行します。クライアントが応答データを取得するとき、受信するために既知の SDK オブジェクトを使用しないでください。ただし、受信には固定ジェネリックスまたはオブジェクト オブジェクトを使用します。

9.1.1 gpt4: RPC フレームワークの一般化された呼び出しとは何ですか?

ジェネリック呼び出しを使用すると、クライアントはコンパイル時にサービスのインターフェイス定義を知らなくても、サーバー上のメソッドを呼び出すことができます。つまり、サービス インターフェイスやエンティティ クラスを使用せずにリモート サービスを呼び出すことができますこれは、API ゲートウェイや特定のサービス テスト ツールなど、特定の動的呼び出しシナリオで特に役立ちます。

通常、サービス コンシューマは一般化された呼び出しを使用して、サービス名、メソッド名、メソッド パラメータなどの詳細情報を渡します。RPC フレームワークは、これらの情報を実際のサービス呼び出しに変換する役割を果たします。

9.1.2この種の呼び出しをサポートしている RPC フレームワークはどれですか?

ほとんどの成熟した RPC フレームワークは汎用呼び出しをサポートしています。よくあるのは次のようなものです。

  • Dubbo : Alibaba のオープン ソース RPC フレームワーク Dubbo は汎用呼び出しをサポートしており、ユーザーはサービス定義を知らなくてもリモート メソッド呼び出しを行うことができます。
  • gRPC : Google の gRPC フレームワークも同様の機能をサポートしていますが、Dubbo などのフレームワークとは若干異なる方法で実装される場合があります。
  • Apache Thrift : これは、一般化された呼び出しをサポートする別の RPC フレームワークです。

9.1.3一般化コールでは、サービス プロバイダーはすべてのインターフェイス サービスを登録センターに登録する必要がありますか?

はい、サービス プロバイダーは引き続きインターフェイス サービスをレジストリに登録する必要があります。汎用呼び出しは、クライアントがインターフェイス定義を知らなくても呼び出しを行うことができるため、主にクライアントに影響します。サービスプロバイダーは、消費者がサービスを見つけて呼び出すことができるように、そのサービスとインターフェイスの情報を登録センターに登録する必要があります。レジストリの主な目的はサービスの検出であり、従来の通話であっても一般化された通話であっても、消費者はプロバイダーのアドレスとその他のメタデータを知る必要があります。

9.1.4 たとえば、私のクライアントの次の呼び出しメソッドは非一般化呼び出しです。

    public static void main2(String[] args) {
    
    
        RpcClient client = new NettyClient(CommonSerializer.PROTOBUF_SERIALIZER);
        RpcClientProxy rpcClientProxy = new RpcClientProxy(client);
        HelloService helloService = rpcClientProxy.getProxy(HelloService.class);
        HelloObject object = new HelloObject(12, "This is a message");
        String res = helloService.hello(object);
        System.out.println(res);
        ByeService byeService = rpcClientProxy.getProxy(ByeService.class);
        System.out.println(byeService.bye("Netty"));
    }

回答:
提供されたコードによると、その理由は次のとおりです。

  1. 明確に定義されたインターフェイス: 2 つの明確に定義されたインターフェイスと、対応するメソッド (例HelloService:) があります。非汎用呼び出しでは、通常、サービス コンシューマとサービス プロバイダの両方がこのインターフェイスの定義を知っています。ByeServicehello()bye()

  2. 明示的なタイプ: 明示的なタイプが使用されますHelloObjectこれは特定のタイプのパラメータであり、その構造はコンシューマとプロバイダの両方に知られています。

  3. プロキシの使用:RpcClientProxyインターフェイスのプロキシを作成し、リモート呼び出しをローカル呼び出しのように見せます。helloService.hello(object)ただし、引き続きand を明示的に呼び出しますbyeService.bye("Netty")。これは、呼び出すメソッドと渡す必要があるパラメーターがわかっていることを意味します。

一般化された呼び出しでは、通常、呼び出しはより動的で柔軟になります。たとえば、メソッドの名前、パラメータの型、およびパラメータの値を知っていればよい場合がありますが、メソッドのインターフェイスや明示的な定義を事前に知っておく必要はありません。一方、あなたの例のように、非ジェネリック呼び出しでは、これらはすべて明示的で事前定義されています。

9.1.5 単純な一般化呼び出しのデモを提供してもらえますか?

もちろん。一般化された呼び出しは、より柔軟で動的な呼び出し方法であり、主に次のシナリオで使用されます。

  1. 呼び出す具体的なサービスやメソッドがわからない場合。
  2. サービスコンシューマとサービスプロバイダが共有インターフェース定義を持たない場合。
  3. 実行時に呼び出すサービスを決定できる、より柔軟なシステムを構築する必要がある場合。

一般化された呼び出しの重要な点は、固定の既知のインターフェイスまたはクラスに基づいて呼び出しを開始するのではなく、実行時に呼び出されるサービス情報を動的に構築することです。

以下は汎用呼び出しの簡単な例です。

public class GenericInvokeDemo {
    
    

    public static void main(String[] args) throws Exception {
    
    
        RpcClient client = new NettyClient(CommonSerializer.PROTOBUF_SERIALIZER);
        RpcClientProxy rpcClientProxy = new RpcClientProxy(client);

        // 使用泛化调用,不需要预先定义接口
        String serviceName = "HelloService";
        String methodName = "hello";
        
        // 这里仅仅是为了示意,实际的参数名和参数类型可能需要通过其他方式获得
        String[] parameterTypes = new String[] {
    
    "HelloObject"};
        Object[] arguments = new Object[] {
    
    new HelloObject(12, "This is a message")};

        Object result = invoke(rpcClientProxy, serviceName, methodName, parameterTypes, arguments);
        System.out.println(result);
    }

    public static Object invoke(RpcClientProxy proxy, String serviceName, String methodName, String[] parameterTypes, Object[] arguments) throws Exception {
    
    
        // 构建RpcRequest,通常需要更加复杂的逻辑来处理参数类型和参数值
        RpcRequest request = new RpcRequest();
        request.setInterfaceName(serviceName);
        request.setMethodName(methodName);
        request.setParameters(arguments);
        // ... 其他属性设置

        // 使用RpcClientProxy发送请求
        RpcResponse response = proxy.sendRequest(request);
        
        // 检查响应并返回结果
        if (response.getStatus() == RpcResponse.SUCCESS) {
    
    
            return response.getData();
        } else {
    
    
            throw new Exception("RPC调用失败: " + response.getMessage());
        }
    }
}

HelloServiceこの例では、インターフェイスまたはそのhelloメソッドを直接使用する代わりに、呼び出すサービスを動的に構築しserviceNameて決定します。methodNameこれにより、特定のサービスを事前に知らなくても RPC 呼び出しを行うことができます。

これは単なる単純化された例であり、実際の汎化呼び出しには、さまざまなパラメータ型の処理やさまざまな戻り値の型の処理など、より複雑なロジックが含まれる場合があることに注意してください。

9.2 なぜ一般化された呼び出しが必要なのでしょうか?

回答: 汎用呼び出しの主な利点は、非常に柔軟な呼び出し方法が提供されるため、呼び出し元がサーバーの特定のインターフェイス定義に依存する必要がないことです。一般化された呼び出しには、次のような主なアプリケーション シナリオと利点があります。

  1. 動的呼び出し: インターフェイスの数が膨大な場合、またはインターフェイスが頻繁に変更される場合、汎用呼び出しを使用すると、クライアント SDK やコードの頻繁な更新を回避できます。

  2. 異なるシステムのブリッジング: 複数のプラットフォームまたは言語間で動作する必要がある一部のアプリケーションでは、システムごとに API をカスタマイズすることなく、汎化呼び出しを中間層として使用して、異なるシステム間をブリッジすることができます。

  3. テストとツール開発: 一般化された呼び出しによりテスト ツールを簡単に構築できるため、開発者やテスターはサービスの具体的な実装を事前に知らなくてもサービスを動的に呼び出すことができます。

  4. API ゲートウェイと管理: API ゲートウェイまたはサービス管理プラットフォームの場合、汎用呼び出しにより、統合されたロギング、監視、セキュリティ ポリシーを提供しながら、リクエストを特定のサービスに転送できます。

  5. コードの冗長性の削減: 一般化された呼び出しにより、サービスまたはインターフェイスごとにクライアント コードの生成と保守が不要になり、保守コストが削減されます。

9.3 汎用呼び出しの欠点は何ですか?

回答: 一般化により多くの柔軟性が得られますが、いくつかの欠点もあります。

  1. パフォーマンスのオーバーヘッド: 動的な解析とリフレクションが含まれるため、一般化された呼び出しには通常、特定のメソッドの呼び出しよりもパフォーマンスのオーバーヘッドが高くなります。

  2. 複雑さの増加: 一般化呼び出しにより追加の解析およびマッピング ロジックが導入され、システムの複雑さが増加する可能性があります。

  3. Type-unsafe : メソッドのシグネチャとパラメーターが動的に指定されるため、コンパイル時に検出するのが難しい実行時エラーが発生する可能性があります。

  4. 開発経験: 開発者にとって、特定のインターフェイス定義がないため、理解や使用がさらに困難になる可能性があり、コード補完やヒントなどの一部の IDE 機能を活用することも困難になります。

一般に、汎用通話は強力で柔軟なツールですが、その利点と欠点を特定のアプリケーション シナリオに従って比較検討する必要があります。

10プロトコル バッファプロトコル

推奨読書: protobuf は複雑なデータ構造をどのように送信しますか?

10.1 .proto ファイルとは何ですか? そのファイルには何が含まれていますか? 通常、ファイルはどこに保存されますか?

  1. .proto ファイル:
    .protoこのファイルは、言語に依存しないプロトコル バッファー (protobuf) の記述ファイルです。protobuf を介してシリアル化または逆シリアル化するメッセージの形式を定義します。protocデータ アクセス クラスは、Java、C++、Python などの言語固有のコンパイラを使用して.protoファイルから生成できます。

  2. .proto ファイルの内容:
    .protoファイルには次のものが含まれます。

    • メッセージ定義: メッセージ タイプとそのフィールドを定義します。
    message Person {
      string name = 1;
      int32 age = 2;
      string email = 3;
    }
    
    • フィールド データ型: int32、string、bool、bytes などの基本データ型とカスタム型 (他のメッセージ型または列挙型)。

    • サービス定義(オプション): gRPC との RPC 通信に protobuf を使用する場合、.protoファイル内でサービスとそのメソッドを定義できます。

    service MyService {
      rpc MyMethod(RequestType) returns (ResponseType);
    }
    
    • 列挙型定義:
    enum MyEnum {
      UNKNOWN = 0;
      TYPE_A = 1;
      TYPE_B = 2;
    }
    
    • .proto他のファイルをインポート:ステートメントを使用して、あるファイル内の定義を別のファイルで再利用importできます。.proto.proto
    import "other_file.proto";
    
    • パッケージと名前空間: 名前の競合を避けるために、.protoファイルのパッケージを定義できます。
    package mypackage;
    
    • その他のオプション: カスタム Java パッケージの定義、最適化モードなど。
    option java_package = "com.mycompany.mycode";
    
  3. .proto ファイルの保存場所:
    .protoファイルの保存場所は、プロジェクトの構造と開発チームの選択によって異なります。ファイルが複数のプロジェクトまたは言語間で共有される場合.proto、それらは中央の場所に保存されることがあります。大規模な組織の場合、チーム間での共有とバージョン管理を確実にするために、すべてのファイルに専用の Git リポジトリが存在することがあります.proto

    単一プロジェクト内では、.protoファイルは通常、バージョン管理システム内のソース コードとともに、たとえばsrc/main/proto/(Java Maven プロジェクトの場合) などの特定のディレクトリに保存されます。

全体として、.protoファイルは protobuf システムの中核部分であり、シリアル化および逆シリアル化されるメッセージの構造を定義します。

10.2 .proto ファイルはメッセージの形式を定義するため、クライアントとサーバーの両方がこのファイルの反映に基づいて対応するクラスを生成します。

はい、あなたの理解は正しいです。

ファイルを定義した後.proto、プロトコル バッファー コンパイラーを使用して、protoc対応するコードを生成できます。生成されたコードには、.protoファイル内で定義したメッセージ タイプのクラス定義が含まれます。

たとえば、.proto次の内容のファイルを定義するとします。

syntax = "proto3";

message Person {
  string name = 1;
  int32 age = 2;
  string email = 3;
}

コンパイラーを使用するとprotoc、Java、C++、Python などの複数のプログラミング言語のコードを生成できます。たとえば、Java のコードを生成する場合は、次のコマンドを使用できます。

protoc --java_out=. your_file_name.proto

このコマンドを実行すると、コンパイラはPersonクラスの定義を含むJava ファイルを生成します。このクラスには、フィールドのゲッター、セッター、およびシリアル化や逆シリアル化などのその他のメソッドが含まれます。

したがって、サーバーとクライアントの両方がこの.protoファイルを使用して対応するコードを生成し、それらの間のメッセージ形式の一貫性を確保できます。これがプロトコル バッファーの威力です。メッセージ形式を一度定義するだけで、複数のプログラミング言語のコードを生成できます。

10.3 プロトコル バッファーを使用する場合、クライアントとサーバー上でシリアル化されたオブジェクトのシリアル化可能インターフェイスを実装する必要がありますか? (10.1と2によれば、必要ないことがわかっています)

プロトコル バッファー (protobuf) をシリアル化フレームワークとして使用する場合、オブジェクトに Java の Serializable インターフェイスを実装させる必要はありません。実際、protobuf にはデータを定義してシリアル化する独自の方法があります。

protobuf では、データ構造を記述する .proto ファイルを定義します。次に、protobuf コンパイラー (protoc) が、対応する言語のデータ クラスとシリアル化/逆シリアル化コードを生成します。これらの生成されたコードは、Java の Serializable インターフェイスに依存しません。

要約すると、protobuf の使用を選択した場合は、Serializable インターフェイスを実装する必要はありません (実装すべきではありません)。protobuf によって提供されるシリアル化および逆シリアル化メソッドは、Java のネイティブ Serializable よりも効率的かつ柔軟です。

10.4 プロトコルバッファの互換性を理解する方法

プロトコル バッファー (略して Protobuf) の設計の焦点の 1 つは、下位互換性と上位互換性です。これにより、他のサービスやクライアントとの通信を中断することなく、サービスを独立して繰り返してデプロイできるようになります。この互換性は、個々のサービスが個別にアップグレードおよびデプロイされる可能性があるマイクロサービス アーキテクチャおよび分散システムにとって特に重要です。

Protobuf の互換性に関する重要なポイントをいくつか示します。

  1. 下位互換性:

    • 新しいフィールドを追加すると、古いバージョンのコードが新しいバージョンのデータを読み取るときに、新しいフィールドは無視されます。
    • フィールドを削除した場合、そのフィールドの番号は使用できなくなります。新しいバージョンのコードは、古いバージョンのデータを読み取るときに、フィールドをデフォルト値 (指定されている場合) として扱います。
  2. 上位互換性:

    • 古いバージョンのコードは、新しいバージョンのコードによって生成されたデータを解析できます (新しいコードによって導入された新しいフィールドを除く)。
    • 新しいフィールドは、古いバージョンのコードでは無視されます。
  3. 互換性のない変更:

    • 既存のフィールドの番号を変更すると破壊的になります。
    • フィールドのタイプの変更 (例: からint32string) も破壊的です。
  4. 予約済みフィールド:

    • フィールドを削除する場合は、.protoそのフィールド番号または名前が将来再び使用されないように、ファイル内でそのフィールドを予約済みとして宣言する必要があります。

互換性を最大限に高めるための一般的なアドバイスは次のとおりです。

  • すでに存在するフィールドの数とタイプは変更しないでください。
  • 追加されたフィールドはオプションであるか、明示的なデフォルト値を持つ必要があります。
  • フィールドを削除するときは、将来の競合を防ぐために予約されたキーワードを使用してください。

この互換性メカニズムにより、サーバーとクライアントのバージョンが正確に一致しない場合でも、少なくともほとんどの場合、それらの間の通信が機能することが保証されます。

10.5 例を挙げてもらえますか?

もちろん。具体的な例を通して、プロトコル バッファーの上位互換性と下位互換性を理解しましょう。

ユーザー情報を管理するサービスがあるとします。.protoファイルの初期バージョンは次のようになります。

バージョン 1 のファイル.proto:

message User {
    int32 id = 1;
    string name = 2;
}
  1. サーバーとクライアントの両方がこのバージョンであれば、相互にスムーズに通信できます。

ここで、このメッセージを拡張し、新しいフィールドを追加することにしました。

バージョン 2 のドキュメント.proto:

message User {
    int32 id = 1;
    string name = 2;
    string email = 3; // 新增的字段
}
  1. 下位互換性: 新しいバージョンのサービスが古いバージョンのクライアントにemailフィールドを含むメッセージを送信すると、古いクライアントはこの新しいフィールドを無視し、他のフィールドを通常どおり処理します。Userこれは下位互換性です。

後で、フィールドが不要になったと判断したため、フィールドを削除します。

バージョン 3 の.protoドキュメント:

message User {
    int32 id = 1;
    string name = 2;
    reserved 3; // 字段编号 3 现在已经被保留
}
  1. 上位互換性: 古いバージョンのサービス (およびemailフィールドのバージョン)Userが新しいバージョンのクライアントにメッセージを送信すると、新しいクライアントにはフィールドが予約済みとしてマークされていることがわかります (ただし、実際の名前はemail表示されません)。 , したがって、これは無視され、他のフィールドは通常どおり処理されます。これは上位互換性です。

これらの例は、Protobuf メッセージ構造が変更された場合に通信の互換性を維持する方法を示しています。この互換性により、分散システムの反復と展開が大幅に容易になります。

10.6 上位互換性と下位互換性とは何ですか

この質問に対する答えは、「前」と「後」の概念をどのように定義し、この互換性をどの方向から考えるかに関係します。

  1. 下位互換性:

    • 私たちが何かに「下位互換性がある」と言うとき、通常は、システムの新しいバージョンが古いバージョンからのデータやリクエストを処理できることを意味します。つまり、新しいバージョンと以前のバージョンの互換性を考慮して、「後ろ向き」に検討しています。
    • たとえば、ソフトウェアの新しいバージョンは、古いバージョンで作成されたファイルを読み取ることができます。または、この場合、サービスの新しいバージョンは、古いバージョンのクライアントからのリクエストを処理できます。
    • 実際のシナリオ: サーバーは新しいバージョン (新しいフィールドの追加など) を使用しますが、クライアントは依然として古いバージョンを使用します。クライアントが新しいフィールドを含まないデータをサーバーに送信すると、サーバーはデータを通常どおり処理できます。
  2. 上位互換性:

    • 「上位互換性がある」というときは、システムの古いバージョンが新しいバージョンのデータまたはリクエストを処理できることを意味します。私たちは「先」を見据えて、古いバージョンと将来のバージョンの間で互換性を維持する方法を考えています。
    • たとえば、古いバージョンのソフトウェアは、新しいバージョンで作成されたファイルを読み取ることができます。または、この場合、古いバージョンのクライアントが新しいバージョンのサーバーの応答を処理できます。
    • 実際のシナリオ: クライアントは新しいバージョン (新しいフィールドの追加など) を使用しますが、サーバーは依然として古いバージョンを使用します。サーバーが新しいフィールドを含まないデータをクライアントに送信すると、クライアントはデータを通常どおり処理し、新しいフィールドを無視できます。

この命名法は混乱を招く可能性がありますが、主にシステムの観点からのものであり、「新しい」バージョンについて考えるときは下位互換性が考えられ、「以前」のバージョンについて考えるときは前方互換性が考えられます。

10.7 の新旧バージョンはサーバーとクライアントの比較に基づいており、サーバーを中心としています。

はい、あなたの理解は正しいです。多くの分散システムやクライアント/サーバー モデルでは、特に互換性を考慮する場合、サーバーが中心とみなされます。これにはいくつかの理由があります。

  1. 集中制御: 通常、サーバーはより詳細な制御を行います。たとえば、サーバー ソフトウェアをいつ更新するかを決定することはできますが、すべてのクライアントをいつ更新するかを決定することはできません。

  2. 多様なクライアント: クライアントにはさまざまなバージョン、プラットフォーム、構成があります。この多様性を考慮すると、サーバーの安定性と互換性を維持することが特に重要です。

  3. 安定性と継続性: さまざまなバージョンのクライアントにサービスを提供するには、通常、サーバーは 24 時間年中無休でオンラインである必要があります。したがって、サーバーの互換性を考慮することが特に重要です。

  4. リリース プロセス: 場合によっては、サーバー側の更新は比較的集中化され制御される場合がありますが、クライアント側 (特にモバイル環境) の更新は、ユーザーの選択やデバイスの互換性などの要因の影響を受ける可能性があります。

したがって、互換性について話すときは、実際にはサーバー側の観点から見るのが一般的です。たとえば、下位互換性により、新しいバージョンのサーバーが古いバージョンのクライアントと対話できることが保証され、上位互換性により、古いバージョンのサーバーが新しいバージョンのクライアントと対話できることが保証されます。このサーバー中心の観点により、バージョン管理とプロトコルの更新が比較的シンプルになり、制御しやすくなります。

おすすめ

転載: blog.csdn.net/yxg520s/article/details/132286806