Zookeeper に基づくサービス登録とサービス検出

序文

SOA アーキテクチャを使用するかマイクロサービス アーキテクチャを使用するかにかかわらず、サービス登録コンポーネントとサービス検出コンポーネントを使用する必要があります。初めて Dubbo に接したときは、サービスの登録/検出と Zookeeper の役割について常に混乱していましたが、今では分散システムに対する理解が十分に深くなく、Dubbo と Zookeeper の動作原理が十分に明確ではないようです。 。

この記事では、Zookeeper に基づいてサービス登録およびサービス検出機能を実装します。私と同じ混乱を抱えている場合は、この記事を使用して、他のコンポーネントが Zookeeper を登録センターとして使用する方法を理解していただければ幸いです。

声明

この記事で提供されているコードは参照のみを目的としており、基本的な知識が不足している開発者がサービス登録とサービス検出の概念をよりよく理解できるようにすることを目的としています。これらのコードは実際のアプリケーションでの使用を目的としたものではないことに注意してください

予備知識

サービスの登録と検出

SOA またはマイクロサービス アーキテクチャでは、多数のサービスが存在し、相互呼び出しが発生する可能性があるため、これらのサービスをより効果的に管理するには、通常、それらを一元管理するための統一された場所、つまり登録センターを導入する必要があります。登録センターは最も基本的なもので、その機能はサービスの登録/検出です。

  • サービス登録: 他のサービスまたはクライアントがサービスを検出して使用できるように、サービス インスタンスのメタデータ (IP アドレス、ポート番号、正常性ステータスなど) を登録センターに登録します。
  • サービス検出: サービスが他のサービスを呼び出す必要がある場合、静的構成を使用することはできませんが、この時点では、レジストリに移動して利用可能なサービス インスタンスを取得し、それらを呼び出すことができます。

動物園の飼育員

Zookeeper は従来の分散調整サービスであり、Hadoop クラスターの管理の調整や Kafka のリーダー選挙の調整など、コーディネーターとして使用されることが多いです。

一部のコンポーネントがそれをレジストリとして使用するのはなぜですか? いくつかの理由があると思います。

  1. Zookeeper は分散システムにおける一貫性と信頼性が強化されており、各サービスの登録情報の一貫性を保証できます。
  2. Zookeeper はメモリを使用してデータを保存し、高い読み取りおよび書き込みパフォーマンスを備えています。レジストリはクライアントの要求に迅速に応答する必要があるため、これはレジストリにとって重要です。
  3. Zookeeper の Watcher メカニズムを使用すると、クライアントは指定されたノードの変更を監視できます。ノード (レジストリ) が変更されると、Zookeeper は他のサービスに通知してリアルタイム更新を実現できます。

動作原理

次の図を例として、Dubbo が Zookeeper を使用してサービスの登録/検出を実現する方法を示します。

ここに画像の説明を挿入

  1. サービス プロバイダーは、/dubbo/com.foo.BarService/providers独自の URL アドレスをディレクトリに書き込みます。
  2. サービス コンシューマは、/dubbo/com.foo.BarService/providersディレクトリの下のプロバイダ URL アドレスをサブスクライブします。そして、/dubbo/com.foo.BarService/consumersディレクトリに独自の URL アドレスを書き込みます。

ここでのディレクトリは Zookeeper のデータ構造です。原理は非常に単純です。本質的に、サービス プロバイダーと消費者は、契約に従って Zookeeper 上のデータを読み書きし、同時にその Watcher メカニズム、一時ノード、および信頼性を使用して、効率的にデータを読み書きします。次の関数を実装します。

  • プロバイダーサービスに停電などの異常なダウンタイムが発生した場合、登録センターがプロバイダー情報を自動的に削除できます。
  • 登録センターが再起動すると、登録データとサブスクリプション要求が自動的に復元されます。

実装プロセス

登録センター

次に、Zookeeper の Java API を介したサービスの登録/検出のみを含む登録センターを実装します。コードは次のとおりです。

public class RegistrationCenter {
    
    

    // 连接信息
    private String connectString = "192.168.10.11:2181,192.168.10.11:2182,192.168.10.11:2183";

    // 超时时间
    private int sessionTimeOut = 30000;

    private final String ROOT_PATH = "/servers";

    private ZooKeeper client;

    public RegistrationCenter() {
    
    
        this(null);
    }

    public RegistrationCenter(Consumer<List<String>> consumer) {
    
    
        try {
    
    
            getConnection(null == consumer ? null : watchedEvent -> {
    
    
                //监听服务器地址的上下线
                if (watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged) {
    
    
                    try {
    
    
                        consumer.accept(subServers());
                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                    }
                }
            });
            Stat stat = client.exists(ROOT_PATH, false);
            if (stat == null) {
    
    
                //创建根节点
                client.create(ROOT_PATH, ROOT_PATH.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * @param serverName 将服务器注册到zk集群时,所需的服务名称
     * @param metadata   服务元数据
     * @throws Exception
     */
    public void doRegister(String serverName, Metadata metadata) throws Exception {
    
    


        /**
         * ZooDefs.Ids.OPEN_ACL_UNSAFE: 此权限表示允许所有人访问该节点(服务器)
         * CreateMode.EPHEMERAL_SEQUENTIAL: 由于服务器是动态上下线的,上线后存在,下线后不存在,所以是临时节点
         * 而服务器一般都是有序号的,所以是临时、有序的节点.
         */
        String node = client.create(ROOT_PATH + "/" + serverName, metadata.toString().getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

        System.out.println(serverName + " 已经上线");
    }


    /**
     * 发现/订阅服务
     */
    public List<String> subServers() throws InterruptedException, KeeperException {
    
    
        List<String> zkChildren = client.getChildren(ROOT_PATH, true);
        List<String> servers = new ArrayList<>();
        zkChildren.forEach(node -> {
    
    
            //拼接服务完整信息
            try {
    
    
                byte[] data = client.getData(ROOT_PATH + "/" + node, false, null);
                servers.add(new String(data));
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        });
        return servers;
    }

    private void getConnection(Watcher watcher) throws IOException {
    
    
        this.client = new ZooKeeper(connectString, sessionTimeOut, watcher);
    }

    /**
     * 服务元数据
     */
    public static class Metadata {
    
    
        public Metadata() {
    
    
        }

        public Metadata(String ip, int port) {
    
    
            this.ip = ip;
            this.port = port;
        }

        private String ip;

        private int port;

        public String getIp() {
    
    
            return ip;
        }

        public void setIp(String ip) {
    
    
            this.ip = ip;
        }

        public int getPort() {
    
    
            return port;
        }

        public void setPort(int port) {
    
    
            this.port = port;
        }

        @Override
        public String toString() {
    
    
            return "{" + "ip='" + ip + '\'' + ", port=" + port + '}';
        }
    }
}

このクラスにはdoRegister()subServers()サービス登録とサブスクリプションという 2 つのコア メソッドがあります。

  • doRegister()主にZookeeper上で一時的なノードデータを作成するのですが、一時的なノードの利点は、停電などの異常終了時にノードが自動的に削除されることです。
  • subServers()Zookeeper にすべてのサービスプロバイダーの情報を読み込み、ノードの状態を監視し、ノードの作成、削除、更新などのイベントが発生した場合には、サービスプロバイダーの情報を再取得し、データを更新することができます。リアルタイム。

これまでのところ、単純な登録センターが完成していますが、成熟した登録センターを実装するには、負荷分散、高可用性とフォールトトレランス、サービスガバナンス、ルーティング制御などの機能も考慮する必要があります。ここで展開されます。

サービス登録

登録センターがある場合、サービスプロバイダーは電話してdoRegister() 登録できます。コードは次のとおりです。

public class ProviderServer {
    
    
    public static void main(String[] args) throws Exception {
    
    
        RegistrationCenter registrationCenter = new RegistrationCenter();
        registrationCenter.doRegister("provider", new RegistrationCenter.Metadata("127.0.0.1", 8080));
        Thread.sleep(Long.MAX_VALUE);
    }
}

サービスディスカバリ

同様に、サービス消費者はサービス プロバイダーを見つけるために電話をかけることができsubServers()、サービス プロバイダーが変更されると消費者に通知されます。コードは以下のように表示されます。

public class ConsumerServer {
    
    
    public static void main(String[] args) throws Exception {
    
    
        RegistrationCenter registrationCenter = new RegistrationCenter(newServers -> {
    
    
            System.out.println("服务更新了..."+newServers);
        });
        List<String> servers = registrationCenter.subServers();
        System.out.println(servers);
        Thread.sleep(Long.MAX_VALUE);
    }
}

要約する

サービス登録機能とサービス検出機能は、分散システムにおけるサービス管理と通信の問題を解決するために設計されており、負荷分散、健全性監視、サービス ガバナンス、ルーティング制御などの機能の継続的な開発と改善を経て、登録センターになります。サービス レジストリとサービス ディスカバリは、既存のコードを手動で構成したり変更したりすることなく、新しいサービス インスタンスをシステムに動的に追加できるため、システムの弾力性とスケーラビリティの実現に役立ちます。

おすすめ

転載: blog.csdn.net/qq_28314431/article/details/132581610