Dubboソースコード分析1:RPCフレームワークを手動で作成する方法は?

ここに画像の説明を挿入

前書き

モノリシックプロジェクトを開発するときは、誰もが同様のコードを書いている必要があります。つまり、サービスプロバイダーとサービス発信者は1つのサービスにあります

public interface HelloService {
    
    
    public String sayHello(String content);
}
public class HelloServiceImpl implements HelloService {
    
    

    @Override
    public String sayHello(String content) {
    
    
        return "hello, " + content;
    }
}
public class Test {
    
    

    public static void main(String[] args) {
    
    
        HelloService helloService = new HelloServiceImpl();
        String msg = helloService.sayHello("world");
        // hello world
        System.out.println(msg);
    }
}

ただし、単一のサービスには多くの欠点があるため、多くの企業は現在、無関係な機能を異なるサービスに分割しています。

ローカルサービスのようなリモートサービスを呼び出す方法は?ここで、RPCフレームワーク(リモートプロシージャコール)について言及する必要があります。彼は私たちがネットワーク通信、シリアル化、その他の操作の実現を保護するのを手伝ってくれ、リモートサービスの呼び出しをローカルサービスの呼び出しと同じくらい便利にしました。

よく知られているRPCフレームワークには、Spring Cloud、AlibabaのDubbo、FacebookのThrift、Googlegrpcなどがあります。

RPC呼び出しプロセス

ここに画像の説明を挿入
RPC呼び出しのプロセスは次のとおりです

  1. 呼び出し元が要求を送信した後にプロキシクラスによって呼び出されるメソッド、およびパラメータは、ネットワーク送信が可能なメッセージ本文にアセンブルされます
  2. 発信者はメッセージ本文をプロバイダーに送信します
  3. プロバイダーはメッセージをデコードし、呼び出しのパラメーターを取得します
  4. プロバイダーは、対応するメソッドをリフレクションで実行し、結果を返します

以下では、rpcフレームワークがどのように実装されているかを分析しますか?拡張できるもの。
誰もがより鮮明に理解できるようにするために、私はgithubプロジェクトを作成しました。単純なものから、rpcフレームワークを実現するのが難しいものまで、ウェルカムスターです。

https://github.com/erlieStar/simple-rpc

プロキシクラスを生成する

前に述べたように、呼び出し元がメソッドを実行した後、実際にはプロキシクラスのメソッドを実行します。プロキシクラスは、シリアル化、エンコード、およびデコード操作を支援します。では、プロキシクラスを生成する方法は?

主流の慣行を見てみましょう。

FacebookのThriftとGoogleのgrpcはどちらもスキーマファイルを定義し、プログラムを実行してクライアントプロキシクラスとインターフェイスの生成を支援します。呼び出し元は生成されたプロキシクラスを直接使用して要求し、プロバイダーは生成されたインターフェイスを継承できます。

この方法の最大の利点は、複数の言語通信できることです。つまり、スキーマファイルでJavaプログラムまたはPythonプログラムを生成できます。呼び出し元はJavaプログラムであり、プロバイダーはPythonプログラムであり、正常に通信できます。また、バイナリプロトコルであり、通信効率は比較的高いです。

Javaでプロキシクラスを生成する方法はいくつかあります

  1. JDK動的プロキシ(InvocationHandlerインターフェースの実装)
  2. バイトコード操作ライブラリ(cglib、Javassistなど)

Dubboでプロキシクラスを生成するには、jdk動的プロキシとJavassistの2つの方法があります。デフォルトはjavassistです。理由は?もちろん、javassistの方が効率的です

プロトコル

なぜ合意が必要なのですか?Spring CloudはHttpプロトコルを介して通信しますが、Dubboはどのプロトコルを介して通信しますか?

なぜ合意が必要なのですか?

データはネットワーク上でバイナリ形式で送信されるため、RPC要求データはプロバイダー全体に送信されるのではなく、複数のデータパケットに分割されて送信される場合があります。プロバイダーはどのようにデータを識別しますか?

たとえば、テキストABCDEFの場合、プロバイダーが受信するデータはABCDEFまたはABCDEFの場合があります。プロバイダーはこのデータをどのように処理する必要がありますか?

シンプルで、ルールを設定するだけです。このルールには多くの種類があります。ここに3つの例があります

  1. 固定長プロトコルプロトコルの内容の長さは固定されています。50バイトが読み取られると、デコード操作が開始されます。NettyのFixedLengthFrameDecoderを参照できます。
  2. 特別なターミネータは、メッセージの最後に区切り文字を定義します。\ nを読み取る場合は、データが読み取られたことを意味します。読み取られない場合は、引き続き読み取られます。NettyのDelimiterBasedFrameDecoderを参照できます。
  3. 可変長プロトコル(プロトコルヘッダー+プロトコル本文)、固定長はメッセージ本文の長さを示すために使用され、残りのコンテンツはメッセージ本文です。必要に応じて、プロトコルヘッダーはいくつかの一般的に使用される属性も配置します.Httpプロトコルのヘッダーは、content-type、content-lengthなどのプロトコルヘッダーです。NettyのDelimiterBasedFrameDecoderを参照できます

Dubboはカスタムプロトコルを介して通信します。プロトコルヘッダー形式は次のとおり
ここに画像の説明を挿入
です。各ビットの意味は次のとおりです。
ここに画像の説明を挿入

Dubboが既存のHttpプロトコルの代わりにプロトコルをカスタマイズする必要があるのはなぜですか?

主な理由は、カスタムプロトコルがパフォーマンスを向上させることができるということです

  1. Httpプロトコルのリクエストパケットは比較的大きく、役に立たないコンテンツがたくさん含まれています。カスタムプロトコルは多くのコンテンツを合理化できます
  2. Httpプロトコルはステートレスであり、接続は毎回再確立する必要があり、応答が完了すると接続が閉じられます

契約をカスタマイズする方法は?

シリアル化

プロトコルヘッダーの内容はビットで表され、プロトコル本体はアプリケーション内のオブジェクトにカプセル化されます。たとえば、Dubboは要求を要求にカプセル化し、応答を応答にカプセル化します。
ここに画像の説明を挿入

ネットワークで送信されるデータはバイナリデータである必要があると前述しましたが、呼び出し元の入力パラメーターとプロバイダーの戻り値はすべてオブジェクトであるため、シリアル化と逆シリアル化のプロセスが必要です。

シリアル化する方法はいくつかあります

  1. JDKネイティブシリアル化
  2. JSON
  3. Protobuf
  4. Kryo
  5. Hessian2
  6. MessagePack

シリアル化方法を選択する際には、主に次の要素を考慮します。

  1. 効果
  2. スペースオーバーヘッド
  3. 汎用性と互換性
  4. 安全性

コミュニケーション

次の4つの一般的なIOモデルがあります

  1. 同期ブロッキングIO(ブロッキングIO)
  2. 同期ノンブロッキングIO(ノンブロッキングIO)
  3. IO多重化(IO多重化)
  4. 非同期IO(非同期IO)

これらの4つのIOモデルについて個別に詳しく説明することはしません。次の記事を参照してください。

JavaNIOの基本原則である10分で理解する

RPCは一般に同時実行性の高いシナリオで使用されるため、IO多重化のモデルを選択します。NettyのIO多重化は、Reactor開発モデルに基づいて実装されます。この開発モデルがどのようになっているのかを次の記事で分析します。同時実行性の高いサポート

レジストリ

登録センターの役割は電話帳に似ています。サービス名と特定のサービスアドレスとのマッピング関係が保存されます。サービスと通信する場合は、サービス名に基づいてサービスアドレスを見つけるだけで済みます。

さらに重要なことに、この電話帳は動的です。サービスのアドレスが変更されると、電話帳のアドレスが変更され、サービスが利用できない場合、電話帳のアドレスは消えます。

このダイナミックな電話帳は登録センターです。

Zookeeper、Redis、Nocasなど、レジストリを実装する方法はたくさんあります。

Zookeeperを使用して登録センターを実装する方法を紹介します

Zookeeperには、永続ノードと一時ノードの2種類のノードがあります。

動物園の飼育係にサービスを登録するときは一時ノードを使用するので、サービスが切断されたときにノードを削除できます

ノードタイプ 説明
永続ノード ノードを永続ノードとして作成すると、データは常にzookeeperサーバーに保存されます。ノードを作成したクライアントとサーバー間のセッションが閉じられても、ノードは削除されません。
永続シーケンスノード 永続ノードに基づいて、ノードの順序が追加されます
一時ノード ノードを一時ノードとして作成します。データは常にzookeeperサーバーに保存されるとは限りません。一時ノードを作成したクライアントセッションが閉じられると、ノードは対応するzookeeperサーバーで削除されます。
一時シーケンスノード 一時ノードに基づいて、ノードの順序が追加されます

登録センターが電話を切ったときの連絡方法は?

動物園の飼育係が電話を切ると、自動的に別の飼育係に切り替わります。dubboはマッピング関係のコピーをローカルに保存するため、全員が電話を切るかどうかは関係ありません。このマッピング関係は、マップまたはファイルに保存できます。

レジストリに新しいサービスが登録されると、ローカルキャッシュは更新されますか?

モニタリングに登録すると、もちろん更新されます。監視対象ノードまたは子ノードが変更されると、対応するコンテンツが監視対象クライアントにプッシュされ、ローカルキャッシュを更新できます。

Zookeeperのイベントは次のとおりです。この監視は分散オブザーバーモードとして理解できます。
ここに画像の説明を挿入

負荷分散戦略

同じサービスに1つのノードだけをデプロイすることは不可能です。呼び出すたびに呼び出しを開始するノードを選択する必要があります。これには、負荷分散戦略が含まれます。

一般的な負荷分散戦略は次のとおりです。

  1. ランダム
  2. ポーリング
  3. コンシステントハッシュ

概要

もちろん、成熟したRPCフレームワークでは、ルーティング戦略、異常な再試行、監視、非同期呼び出しなど、メインプロセスに関係のない多くのことを考慮する必要があるため、これ以上紹介しません。

リファレンスブログ

[1] https://blog.csdn.net/zzti_erlie/article/details/82292083
[2] https://www.cnblogs.com/LBSer/p/4853234.html
协议
[3] https:// dubbo 。 apache.org/zh-cn/blog/dubbo-protocol.html

おすすめ

転載: blog.csdn.net/zzti_erlie/article/details/110188704