<tbody id="86a2i"></tbody>


<dd id="86a2i"></dd>
<progress id="86a2i"><track id="86a2i"></track></progress>

<dd id="86a2i"></dd>
<em id="86a2i"><ruby id="86a2i"><u id="86a2i"></u></ruby></em>

    <dd id="86a2i"></dd>
    songyz
    將單體服務拆分為微服務后,為了服務高可用,一般會做集群多實例。但在分布式下,怎么進行高效、便捷的進行服務訪問問題,出現了各類服務注冊和服務發現框架。這里使用的是Zookeeper。ZooKeeper 官網 https://zookeeper.apache.org。
    我們的業務系統使用的開發語言是JAVA,但是部分頁面請求是先到nodejs 做的webportal服務,進行權限校驗,校驗通過后調用Java提供的API。當前階段Java端已經微服務化,使用Zookeeper作為注冊中心,目前只需要讓nodejs端,也接入到Zookeeper,作為服務消費者,就能搭建機器環境。

    找輪子

    通過查找,發現npm有現成的庫?node-zookeeper-client?,避免重復造輪子,就用它了。

    接入思路

    由于我們只是作為服務消費者,不需要使用服務注冊的api,大部分可以直接在文檔中找到API。

    編碼過程?

    npm 安裝

    ?1 npm i node-zookeeper-client?

    連接ZK

     1 const Zookeeper = require('node-zookeeper-client');
     2 const CONNECTION_STRING = "127.0.0.1:2181"; // ZK的服務地址
     3 const OPTIONS = {
     4   sessionTimeout: 5000 
     5 }
     6 const zk = Zookeeper.createClient(CONNECTION_STRING, OPTIONS);
     7   zk.on('connected', function(){
     8   console.log("zk=====", zk);
     9 });
    10 //獲取根節點下的子節點數據
    11 zk.getChildren('/', function(error, children, stat){
    12   if(error){
    13     console.log(error.stack);
    14     return;
    15   }
    16   console.log(children);
    17 })
    18 zk.connect();

    其他API(僅供參考)

     1 // 判斷節點是否已存在
     2 zk.exists('/phpnode',function(error,stat){
     3    if(stat){
     4         console.log("節點存在");
     5    }else{
     6        console.log("節點不存在");
     7    }
     8 })
     9  
    10  // 創建/注冊節點
    11 zk.create('/phpnode',new Buffer('hello'),function(error,path){
    12    console.log(path);
    13 })
    14 
    15 // 獲取節點數據
    16 zk.getData('/phpnode',function(error,data,stat){
    17    console.log(data.toString());
    18 });
    19 
    20 //節點刪除
    21 zk.remove('/phpnode',function(error){
    22    if(!error){
    23        console.log('node 節點刪除成功');
    24     }
    25 })

    于是有了第一版本代碼

     1 const zookeeper = require('node-zookeeper-client');
     2 
     3 
     4 // ZK基礎配置信息,正式項目需要從環境文件導入
     5 export const ZK = {
     6     clientAddress: 'localhost:2181/zk/test', // ZK地址
     7     servicePath: '/test-service', // 服務路徑
     8 };
     9 
    10 let zkClient = null;
    11 
    12 // 獲取服務ip+port
    13 export const getZKServiceBaseUrl = (servicePath) => {
    14     return new Promise((resolve, reject) => {
    15         try {
    16             // 防止重復連接
    17             if (zkClient) {
    18                 disconnectZKService();
    19             }
    20 
    21             // 新建連接
    22             zkClient = zookeeper.createClient(ZK.clientAddress);
    23             // 連接后執行一次
    24             zkClient.once('connected', async function () {
    25                 // 獲取服務節點信息
    26                 const res = await listChildren(zkClient, servicePath);
    27                 res.message ? reject(res) : resolve(res);
    28             });
    29 
    30             zkClient.connect();
    31         } catch (error) {
    32             reject(error);
    33         }
    34     });
    35 };
    36 
    37 // 斷開鏈接
    38 export const disconnectZKService = () => {
    39     if (zkClient) {
    40         zkClient.close();
    41     }
    42 };
    43 
    44 // 獲取節點信息,ip+port
    45 function listChildren(client, path) {
    46     return new Promise((resolve, reject) => {
    47         client.getChildren(path,
    48             function () {},
    49             function (error, children) {
    50                 if (error) {
    51                     reject({
    52                         ...error,
    53                         message: `獲取ZK節點error,Path: ${path}`
    54                     });
    55                 }
    56                 try {
    57                     let addressPath = path + '/';
    58                     if (children.length > 1) {
    59                         //若存在多個地址,則隨機獲取一個地址
    60                         addressPath += children[Math.floor(Math.random() * children.length)];
    61                     } else {
    62                         //若只有唯一地址,則獲取該地址
    63                         addressPath += children[0];
    64                     }
    65                     //獲取服務地址
    66                     client.getData(addressPath, function (err, data) {
    67                         if (err) {
    68                             reject({
    69                                 ...error,
    70                                 message: `獲取ZK服務地址error,Stack: ${err.stack}`
    71                             });
    72                         }
    73                         if (!data) {
    74                             reject({
    75                                 ...error,
    76                                 message: `ZK data is not exist`
    77                             });
    78                         }
    79                         const serviceInfo = JSON.parse(data);
    80 
    81                         const url = serviceInfo.address + ':' + serviceInfo.port;
    82                         resolve(url);
    83                     });
    84                 } catch (error) {
    85                     reject({
    86                         ...error,
    87                         message: `list ZK children error`
    88                     });
    89                 }
    90             }
    91         );
    92     });
    93 }

    通過測試代碼,可以實現調用Java服務??赡芤话愕某绦騿T實現功能了就好了,可是作為一個有點追求的,感覺代碼哪里有問題。具體是哪里呢,盯著屏幕瞅了兩分鐘,發現每次獲取服務都取 ZK 注冊中心獲取,這個過程涉及到的網絡請求而且還不是一次HTTP,如果只是這么簡單的改造,程序單純在性能響應上很有可能還不如老版本。我們可以在獲取服務的真實遠程地址前,添加一個本地緩存。通過ZK訂閱機制,更新本地緩存數據。

    思路雖然明確了,可以api掃了掃,沒有我們想要的監聽器,如下所示

    ?

    這怎么辦,按理說的應該會有一個,節點數據改變推送的監聽器,例如新增,刪除,修改等等。找了半天也沒找到合適的。

    沒辦法,接著看源碼吧,看了一會,忽然,看到一個似乎可用的,類

    ?

    ?這不就是我需要的類嗎,但是居然在一方法中注入監聽器,先試試吧。

    ?

    ?試了一下,嘿,真的可以了,當服務端節點數據發生變動后,會自動觸發監聽器?watcher 的回調邏輯。這就好辦了,改造開始。

    改進后的代碼

    const zookeeper = require('node-zookeeper-client');
    var ZK = require('../config/env.js').zk;
    
    const client = Object.freeze({
        zkClient: zookeeper.createClient(ZK.connectionString),
        serviceSet: [], //
        serviceCache: Object.freeze({
            map: new Map(),
    
            /**
             * 更新緩存
             * @param {String} path 服務路徑
             * @param {Array<String>} arr 真實訪問集合
             */
            updateCache: function (path, arr) {
                this.map.set(path, arr);
            },
    
            /**
             * 從緩存中獲取訪問地址
             * 
             * @param {String} path 服務路徑
             * @returns String 真實訪問地址
             */
            getRealPath: function (path) {
                let arr = this.map.get(path);
    
                if (arr.length > 1) //若存在多個地址,則隨機獲取一個地址
                    return arr[Math.floor(Math.random() * arr.length)];
                else //若只有唯一地址,則獲取該地址
                    return arr[0];
            }
        }),
    
        connect: function () {
            console.info("連接 zookeeper");
    
            this.zkClient.once('connected', function () {
                console.info("連接成功");
            });
    
            this.zkClient.connect();
        },
    
        getRealPath: function (serviceName) {
            return new Promise(async (resolve, reject) => {
                if (this.serviceSet.includes(serviceName)) {
                    resolve(this.serviceCache.getRealPath(serviceName));
                } else {
                    // 加載服務節點信息
                    this.loadChildren(serviceName).then(url => resolve(url)).catch(error => reject(error));
                }
            });
        },
    
        loadChildren: function (path) {
            console.info("進入 loadChildren ");
            return new Promise((resolve, reject) => {
                this.zkClient.getChildren(path, (event) => {
                    console.info(" loadChildren watcher ", path, event);
    
                    this.getChildren(event.path);
                }, (error, ids) => {
                    console.info(" loadChildren callback ", path, error, ids);
                    if (error) {
                        reject({
                            ...error,
                            message: `獲取ZK節點error,Path: ${path}`
                        });
                    } else {
                        resolve(this.getData(path, ids));
                    }
                });
            });
        },
    
        getChildren: function (path) {
            console.info("進入 getChildren ");
            return new Promise((resolve, reject) => {
                this.zkClient.getChildren(path, (error, ids) => {
                    console.info(" getChildren callback ", path, error, ids);
                    if (error) {
                        reject({
                            ...error,
                            message: `獲取ZK節點error,Path: ${path}`
                        });
                    }
    
                    resolve(this.getData(path, ids));
                });
            });
        },
    
        getData: function (path, ids) {
            console.info("進入 getData ");
    
            let pros = ids.map(id => new Promise((resolve, reject) => {
                //獲取服務地址
                this.zkClient.getData(path + "/" + id, (error, data) => {
                    console.info(" getData callback ", path, id);
                    if (error) {
                        reject({
                            ...error,
                            message: `獲取ZK服務地址error,Stack: ${err.stack}`
                        });
                    }
                    if (!data) {
                        reject({
                            ...error,
                            message: `ZK data is not exist`
                        });
                    }
    
                    const node = JSON.parse(data).payload;
                    const protocol = node.ssl ? "https://" : "http://";
    
                    resolve(`${protocol}${node.host}:${node.port}`);
                });
            }));
    
            return Promise.all(pros).then(arr => this.serviceCache.updateCache(path, arr)).then(() => this.serviceCache.getRealPath(path));
        },
    
        disconnect: function () { //斷開連接
            console.info("進入 disconnect ")
            if (this.zkClient) {
                console.info("執行 close")
                this.zkClient.close();
            }
        },
    });
    
    client.connect();
    
    module.exports = {
        getServiceUrl: (path) => client.getRealPath(path),
        disconnect: () => client.disconnect(),
    }

    這樣終于,好一點了。

    未完待續(多節點的選擇問題)

    多節點選擇策略:隨機,輪轉,粘性 等等,一般不同的項目使用的策略也不太一樣,實例中使用的是簡單隨機策略,后續再進行節點選擇的策略問題優化啦。

    關機,收工?。?!

    ?

    相關文章:

    免费一级a片在线播放视频|亚洲娇小性XXXX色|曰本无码毛片道毛片视频清|亚洲一级a片视频免费观看
    <tbody id="86a2i"></tbody>

    
    
    <dd id="86a2i"></dd>
    <progress id="86a2i"><track id="86a2i"></track></progress>

    <dd id="86a2i"></dd>
    <em id="86a2i"><ruby id="86a2i"><u id="86a2i"></u></ruby></em>

      <dd id="86a2i"></dd>