本文使用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可以定时监听文件的变化,代码更新无需重启)
启动网关
当然也需要启动在上文《基于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 src="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"
运行结果
AB测试(Apache Bench)模拟1000个用户每次并发100请求:
注 :在测试前,将serviceName设为固定值:var serviceName='HelloService'
AB测试
阅读本文,请结合上篇文章《基于ZooKeeper的服务注册实现》进行理解。
作者:billJiang
链接:https://www.jianshu.com/p/a6b01048d94f

随时随地看视频