NodeJsサービス登録とサービス発見実現

9264 ワード

前言
作者がNodeJsを勉強し始めたばかりなので、レベルは本当に限られています.本論文はノートを勉強しているようで、NodeJsを勉強し始めたばかりの友達と読むのに適しています.
サービス治理
もしあなたのチームがマイクロサービスの構築を模索しているなら、このメカニズムは各サービスに動的に住所を作成することができます.同時にこれらのサービスアドレスのダイナミックな変化を感知することができます.サービス登録とサービス発見はこの中の一つの仕組みです.大体の流れは:
その中:
  • 登録センター:zookeeper
  • サービス提供者:NodeJsアプリケーションサービス
  • サービス消費者:NodeJs API Gateway
  • Zoo Keeperサービス登録センター
    Zoo Keeperのアイデンティティは管理者であり、分散型のデータ整合性の解決策であり、分散型のタスクはデータの公開と購読、負荷の均衡、ネーミングサービス、分散式の協調と通知、クラスタ管理、指導選挙、分散式のロック、分散型の行列などを実現することができる.この文章はすべての面について解説しません.作者もまだ触れていませんから.私達の目標はZoo Keeperを利用してサービスの登録センターを実現することです.興味があれば、自分で研究してみてもいいです.後で研究したらまたシェアします.
    1、ツリーモデルを利用してサービスアドレスを構築し、データ構造を記憶する
    zk内部には木のようなメモリモデルがあります.ファイルシステムと似ています.いくつかのディレクトリがあります.各ディレクトリにはいくつかのフォルダ、ファイルがあります.
    zkには4つのノードがあります.
  • 持久ノード:セッションが終了すると、ノードは削除されない
  • 持続的な順序ノード:セッションが終了すると、ノードは削除されず、ノード名は増加数の拡張子
  • を持参する.
  • 一時ノード:セッションが終了すると、ノードは
  • を削除される.
  • 一時的な順序ノード:セッションが終了すると、ノードは削除され、ノード名は増加数の拡張子
  • を持参する.
    長いノードだけがサブノードを持つことができる.
    現在はクラスタ配置アプリケーションが一般的であるので、クラスタ配置におけるサービスアドレスの状況を見てみよう.例えば、アプリケーションAを持っている場合、アプリケーションAは2台のマシンに配備されています.マシンIPはそれぞれ127.1.0.1と127..0.2、アプリケーションサービスポート6666、アプリケーションAはこの2つのサービスアドレスがあります.
    全てのサービスアドレスのルートノードとしてノードを指定しますので、このノードは永続的ノードであるべきです.n個のアプリケーションを持っています.各アプリケーションにはn台のマシンがありますので、アプリケーションノードもサブノードを持っています.各マシンはアプリケーションサービスを起動する時にzkにアドレスを登録し、サービスのオフライン時にzkのアドレスを削除しますので、臨時ノードの特徴を使ってこの挙動にぴったりです.同時に順番ノードを使って自動的にノード名を管理してくれます.
    私達はすべてnode操作を使うので、zkのnodeクライアントnode-zookeeper-clientを使います.
    2、アプリ起動前にzkを接続する
    もともと私はeggajsプラグインで書いたのですが、ここでフレームのものを取り除いて、他のものを取り出して、フレームにフックしません.
    const { createClient, ACL, CreateMode } = require('node-zookeeper-client');
    
    const zkClient = createClient('127.0.0.1:2181');
    const promisify = require('util').promisify;
    
    zkClient.connect();
    
    zkClient.once('connected', () => {
       registerService();
    });
    
    //  zkClient  promise
    const proto = Object.getPrototypeOf(zkClient);
    Object.keys(proto).forEach(fnName => {
      const fn = proto[fnName];
      if (proto.hasOwnProperty(fnName) && typeof fn === 'function') {
        zkClient[`${fnName}Async`] = promisify(fn).bind(zkClient);
      }
    });
    
    // host port           
    // serviceName    
    const { serviceName, host, port } = config;
    
    async function registerService() {
        try {
          //      ,    
          const rootNode = await zkClient.existsAsync('/services');
          if (rootNode == null) {
            await zkClient.createAsync('/services', null, ACL.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
          }
          //       ,    
          const servicePath = `/services/${serviceName}`;
          const serviceNode = await zkClient.existsAsync(servicePath);
          if (serviceNode == null) {
            await zkClient.createAsync(servicePath, null, ACL.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
          }
          //       ,      ,  name          ,  
          const addressPath = `${servicePath}/address-`;
          const serviceAddress = `${host}:${port}`;
          const addressNode = await zkClient.createAsync(addressPath, Buffer.from(serviceAddress), ACL.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        } catch (error) {
          throw new Error(error);
        }
    }
    
    上のコードは実は簡単です.zkに接続してから、ルートノードが作成されているかどうかを判定します.もしないなら、アプリケーションノードが作成されているかどうかを判断して、ないならば作成します.最後にマシンノードを作成します.ここでは、一時的な順序ノードを使って、私達が唯一のnameを維持する面倒を省きます.hostとportは格納内容として、これはappの配置が必要な場合に配置システムが提供され(自動配備システムを使用すれば)、アドレスがBufferに変更されて保存される.
    実はサービス登録が完了しました.
    NodeJs API Gatewayゲートウェイサービス
    上はすでにサービスが開始された時にzkに登録されました.今はインターフェースを起動してサービスにアクセスする時、サービスの住所を知る必要があります.これはサービス発見過程です.
    API Gatewayは文字通りAPIの入り口であり、ルーティング要求に用いられる.実は、ルーティング要求だけでなく、API Gatewayはプロトコルを変換して、データ、認証、制限速度などのロジックを統合することができます.
    例えば、フロントエンドにユーザ取得の要求があり、こう書くべきである.
    fetch('/api/user/get', {
        method: 'POST',
        body: { id: 1 },
        headers: {
            // header     service
            'servive-name': 'user'
        }
    })
    
    API Gatewayの本質もサービスです.Eggajsを使って作成したサービスは中間部品にパッケージされていることを発見しました.だからここでは中間部品の内容だけを展示して、他の自分はeggの文書を見ます.
    const proxy = require('koa-proxies');
    
    module.exports = (options, app) => {
      return async (ctx, next) => {
        const serviceName = ctx.request.headers['servive-name'];
        if (!serviceName) {
          ctx.throw(404, 'no service found.');
        }
        const servicePath = `/services/${serviceName}`;
        const addressNodes = await app.zookeeper.getChildrenAsync(servicePath);
        const size = addressNodes.length;
        if (size === 0) {
          ctx.throw(404, 'no service found.');
        }
        let addressPath = `${servicePath}/`;
        if (size === 1) {
          addressPath += addressNodes[0];
        } else {
          //           
          addressPath += addressNodes[parseInt(Math.random() * size)];
        }
        const serviceAddress = await app.zookeeper.getDataAsync(addressPath);
        if (!serviceAddress) {
          ctx.throw(404, 'no service found.');
        }
        await proxy('/', {
          target: `http://${serviceAddress}/`,
        })(ctx, next);
      };
    };
    
    上のミドルウェアでは、headersのservice-nameに基づいてアプリケーションのサービスアドレスをすべて取得し、あるポリシーに従ってサービスを選択し、プロキシを使って対応するサービスに転送します.
    締め括りをつける
    以上は簡単に実現されました.使えますが、API Gatewayではすでにもらったサービスアドレスをキャッシュしてzk変化を購読します.例えばサービスを選ぶ時に負荷のバランスを取ることができます.