分散型(一)サービス登録と発見を完了する


背景
最近分布式の仕事をしていますが、人手が足りないので、私一人で恨むしかありません.この間の残業表を見ていると惨めだ.
しかし、中には面白いこともたくさんあって、これから共有して、今日はサービスの登録と発見に重点を置いて議論します.
分散による問題
私のビジネスは簡単ですが、現在使用可能なサービスインスタンスを知る必要があります(リモートコールではなく、情報を取得するだけです).
この機能を実装する最も簡単な方法は、アプリケーション内ですべてのサービスノードを構成することであり、使用するたびに何らかのアルゴリズムで構成リストから1つを選択するだけでよい.
しかし、これは非常に深刻な問題です.
アプリケーションはアプリケーションの負荷状況に応じてサービスノードの数を柔軟に調整する必要があるため、私の構成は死んではいけません.
そうでないと、新しく追加されたノードがアクセスしていないか、ダウンしたノードが要求されているかのいずれかになります.これはだめです.
このような分散問題を解決するには、Redisを利用できるかどうかなど、共通の領域が必要になります.
各ノードは起動後にRedisに情報を登録し,クローズ時にもデータを削除する.
実はノードを格納するip + portであり,サービスノード情報を知る必要がある場合はRedisで取得するだけでよい.
次の図に示します.
しかし、使用するたびにRedisを頻繁にクエリーする必要があります.この問題を回避するために、クエリーのたびに最新のデータをローカルにキャッシュすることができます.これにより、ローカルから優先的に取得することで、効率が向上します.
しかし、同じように新しい問題が発生し、サービスプロバイダのノードが追加または削除された場合、消費者側は状況を知らない.
この問題を解決するには,タイミングタスクを用いて定期的にサービスリストを更新することが最初に考えられる.
以上の案は完璧ではなく、優雅ではないに違いない.主に以下の点があります.
  • タイミングタスクに基づいて、多くの無効な更新が発生します.
  • タイミングタスクには周期性があり、リアルタイムではできないため、リクエスト異常が発生する可能性があります.
  • サービスが強引にkillされた場合、Redisをタイムリーにクリアすることはできません.これにより、利用可能に見えるサービスは永遠に利用できません.

  • だから私たちはもっと頼りになる解決策が必要です.このようなシーンは実はDubboとよく似ています.
    使ったことがある学生はきっとこの図に慣れていないに違いない.
    Dubbo公式サイトより引用
    その中の1つの非常に核心的な内容(赤枠出)はサービスの登録と発見である.
    通常、消費者は、リモートコールを開始するには、サービスプロバイダのネットワークアドレス(ip+port)を知る必要があります.この内容は、私の上記のニーズと非常に似ています.
    DubboはZookeeperを利用して問題を解決している.
    Zookeeperは何ができるの?
    どのように実現するかを具体的に議論する前にZookeeperのいくつかの重要な特性を見てみましょう.
    Zookeeperはファイルシステムに似たツリー構造を実現した.
    これらのノードはznode(名前は重要ではありません)と呼ばれ、各ノードは一定のデータを格納することができます.
    最も重要なのはznodeには4つのタイプがあります.
  • 永続ノード(手動で削除しない限り、ノードは永遠に存在する)
  • 永続秩序ノード(作成順に各ノードの末尾に前の番号(root-1など)
  • を付ける.
  • 瞬時ノード(作成クライアントとZookeeperが接続を保持するノードが存在し、切断時に削除され、対応する通知がある)
  • .
  • 瞬時秩序ノード(瞬時ノードに加えて順序付け)
  • .
    Redisを使用する上で最も大きな問題は何ですか?
    サービスプロバイダの情報をリアルタイムで更新することはできません.
    ではZookeeperを利用してどのように実現したのでしょうか?
    主に3つ目の特性を見ます:瞬時ノード
    Zookeeperは典型的な観察者モードである.
  • 瞬時ノードの特徴により、消費者は瞬時ノードの親ノードを購読することができる.
  • ノードが追加され、削除されると、すべての瞬時ノードも自動的に更新されます.
  • が更新されると、購読者に最新のノード情報を通知します.

  • これにより、サービスノードの情報をリアルタイムで取得できます.また、リストを最初に取得したときにローカルにキャッシュするだけです.Zookeeperと頻繁に対話する必要もなく,通知の更新を待つだけでよい.
    また、どのような理由でノードがダウンしてもZookeeperから削除されます.
    効果のデモ
    このように実現方式はこのようになる.
    このため、プレゼンテーションのためにアプリケーションを新規作成しました.
    https://github.com/crossoverJie/netty-action/tree/master/netty-action-zk
    簡単なSpringBootアプリですが、いくつかのことをしただけです.
  • アプリケーションが起動すると、Zookeeperにサービスを登録するために新しいスレッドが開きます.
  • は、ローカル・サービス・リストを更新するためにノードを同時にリスニングする.
  • は、あるサービスノードを返すためのインタフェースを提供する.

  • 私はローカルで2つのアプリケーションを起動しました.それぞれ127.0.0.1:8083,127.0.0.1:8084です.効果図を見てみましょう.
    2つのアプリケーションの起動が完了しました.
    現在のZookeeperの可視化されたツリー:
    すべてのサービスノード情報を知りたい場合:
    利用可能なサービスノードを取得するには、次の手順に従います.
    ここでは簡単なポーリングを取っただけです.
    downがノードを1つ削除すると、アプリケーションはローカルキャッシュの更新を通知します.同時にZookeeperのノードは自動的に削除されます.
    最新ノードを再取得する場合:
    ノードが復元されると、自然に最新の情報も取得されます.ローカルキャッシュもタイムリーに更新されます.
    コーディング実装
    実現は比較的簡単で、主にZKclientのapiが使用されています.
    コアをいくつか貼りましょう.
    登録
    Zookeeperの登録を開始します.
    主な論理はすべてこのスレッドにあります.
  • 親ノードが最初に作成されます.上図のZookeeperノードに示すように;まず/route本のノードを作成する必要があり、作成時にすでに存在するかどうかを判断します.
  • は、その後、Zookeeperに登録する必要があるかどうかを判断する必要があります.一部のノードは、サービス発見にのみ使用されているため、ビジネス機能(私のプロジェクトのニーズ)を負担する必要はありません.
  • は、現在のアプリケーションが存在するipおよびポートを登録するとともに、ルートノード/routeをリスニングする必要があり、他のサービスがオフラインになったときに通知を得ることができる.

  • ローカルキャッシュによる
    サービスの変更を監視
        public void subscribeEvent(String path) {
            zkClient.subscribeChildChanges(path, new IZkChildListener() {
                @Override
                public void handleChildChange(String parentPath, List currentChilds) throws Exception {
                    logger.info("  /       parentPath=【{}】,currentChilds=【{}】", parentPath,currentChilds.toString());
    
                    //      /       
                    serverCache.updateCache(currentChilds) ;
                }
            });
    
    
        }

    ここでは、Guavaが提供するCacheを採用したローカルキャッシュが更新されていることがわかります.興味のあるのは、以前のソースコード分析を表示することができます.
        /**
         *       /       
         *
         * @param currentChilds
         */
        public void updateCache(List currentChilds) {
            cache.invalidateAll();
            for (String currentChild : currentChilds) {
                String key = currentChild.split("-")[1];
                addCache(key);
            }
        }

    クライアント負荷
    同時に、クライアントに負荷アルゴリズムが提供されます.
    ポーリングの実装です
        /**
         *      
         *
         * @return
         */
        public String selectServer() {
            List all = getAll();
            if (all.size() == 0) {
                throw new RuntimeException("      ");
            }
            Long position = index.incrementAndGet() % all.size();
            if (position < 0) {
                position = 0L;
            }
    
            return all.get(position.intValue());
        }

    もちろん、ここでは重み、ランダム、LRUなどのアルゴリズムをより多く拡張することができます.
    Zookeeperのその他の利点と問題
    Zookeeperはもちろん素晴らしい分布協調ツールであり,その特性を利用して他の役割を果たすこともできる.
  • データ変更送信通知という特性は、統合構成センターを実現することができ、サービスごとに構成を個別に維持する必要はありません.
  • は、瞬時秩序ノードを用いて分散ロックを実現することもできる.

  • 登録を実現し、このニーズを発見するには、Zookeeperが最も好ましいわけではありません.
    ZookeeperはCAP理論においてCP(コンシステンシ,パーティションフォールトトレランス)を選択しているため,Zookeeperクラスタの半数のノードが利用できない場合,いかなるデータも取得できない.
    コンシステンシにはもちろん問題ありませんが、登録・発見のシーンではEurekaが推奨され、SpringCloudで検証されています.具体的には本稿では議論しない.
    しかし、私の使用シーンを考えると、Zookeeperはすでに適任です.
    まとめ
    本明細書のすべての完全なコードはGitHubに管理されています.
    https://github.com/crossoverJie/netty-action.
    単純な登録、発見機能が実現されたように見えますが、分散アプリケーションはそれだけではありません.
    ネットワークの隔離後にもたらした一連の問題は、他の方法で一つ一つ改善する必要があるからです.その後も分布式関連の更新が続きますので、興味のある方は引き続き注目してみてください.
    あなたのいいねと転送は最大のサポートです.