为了账号安全,请及时绑定邮箱和手机立即绑定

基于nodejs+zookeeper服务发现

标签:
Kubernetes

本文使用nodejs作为微服务API网关,从而将消费端的请求,随机路由到一个可用的服务节点上。核心代码如下:

本文参考了《架构探险》轻量级服务架构
本文示例代码:node-zookeeper-demo

var express = require('express');var zookeeper = require('node-zookeeper-client');var httpProxy = require('http-proxy');var cluster = require('cluster');var os = require('os');var cache = {};var CPUS = os.cpus().length;var PORT = 8084;var CONNECTION_STRING = '127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183';var REGISTRY_ROOT = '/registry';var app = express();if (cluster.isMaster) {    for (var i = 0; i < CPUS; i++) {
        cluster.fork();
    }
}else {    //连接zookeeper
    var zk = zookeeper.createClient(CONNECTION_STRING);
    zk.connect();    //创建代理服务器对象并监听错误事件
    var proxy = httpProxy.createProxyServer();
    proxy.on('error', function (err, req, res) {
        res.end();//输出空白响应数据
    });    //启动web服务器
    app.use(express.static('public'));
    app.all('*', function (req, res) {        //处理图标请求
        if (req.path == '/favicon.ico') {
            res.end();            return;
        }        //获取服务名称
        var serviceName = req.get('Service-Name');        
        console.log('ServiceName:%s', serviceName);        if (!serviceName) {            console.log('Service-Name request header is not exist');
            res.end();            return;
        }        //获取服务路径
        var servicePath = REGISTRY_ROOT + "/" + serviceName;        console.log('ServicePath:%s', servicePath);        console.log('cache[serviceName]:'+JSON.stringify(cache));        if (cache[serviceName]) {        //if(false){
            //TODO
            /*zk.exists(servicePath, function (event) {
                if (event.NODE_DELETED) {
                    cache = {};
                }
            }, function (error, stat) {
                if (stat) {

                }
            })*/
            console.log("-----------cache---------------"+cache[serviceName]);
            proxy.web(req, res, {                target: 'http://' + cache[serviceName] //目标地址
            });

        }        else {            //获取服务路径下的地址节点
            zk.getChildren(servicePath, function (error, addressNodes) {                if (error) {                    console.log(error.stack);
                    res.end();                    return;
                }                var size = addressNodes.length;                if (size == 0) {                    console.log('address node is not exist');
                    res.end();                    return;
                }                //生成地址容器
                var addressPath = servicePath + "/";                if (size == 1) {                    //若只有唯一地址,则获取该地址
                    addressPath += addressNodes[0];
                } else {                    //若存在多个地址,则随机获取一个地址
                    addressPath += addressNodes[parseInt(Math.random() * size)];
                }                console.log('addressPath:%s', addressPath);                //获取服务地址
                zk.getData(addressPath, function (err, serviceAddress) {                    if (error) {                        console.log(error.stack);
                        res.end();                        return;
                    }                    console.log('serviceAddress:%s', serviceAddress);                    if (!serviceAddress) {                        console.log('serviceAddress is not exist');
                        res.end();                        return;
                    }

                    cache[serviceName] = serviceAddress;                    console.log("cache"+  serviceName+": "+cache[serviceName]);                    //执行反向代理
                    proxy.web(req, res, {                        target: 'http://' + serviceAddress //目标地址
                    });
                });

            });
        }

    });

    app.listen(PORT, function () {        console.log('server is running at %d', PORT);
    });
}

使用supervisor app-gateway.js启动API网关后:(supervisor可以定时监听文件的变化,代码更新无需重启)

webp

启动网关


当然也需要启动在上文《基于ZooKeeper的服务注册实现》提到的两个客户端,让服务在ZooKeeper上注册。

测试页面

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>Demo</title></head><body><div id="console"></div><div id="container">
    <h1>
        hello world    </h1></div><button id="btn" >点我一下调用服务</button></body><script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="js/jquery-2.2.3.min.js"></script><script>
    $(function () {
        $("#btn").click(function () {            //alert("hello world");
            $.ajax({                method: 'GET',                url: '/hello',                headers: {                    'Service-Name': 'HelloService'
                },                success: function (data) {
                    $("#console").text(data);
                }
            })
        });

    })</script></html>

点击测试按钮,在界面服务的显示运行结果"Hello"

webp

运行结果

AB测试(Apache Bench)模拟1000个用户每次并发100请求:
:在测试前,将serviceName设为固定值:var serviceName='HelloService'

webp

AB测试

阅读本文,请结合上篇文章《基于ZooKeeper的服务注册实现》进行理解。



作者:billJiang
链接:https://www.jianshu.com/p/a6b01048d94f


点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消