本文使用nodejs作為微服務(wù)API網(wǎng)關(guān)猴凹,從而將消費(fèi)端的請(qǐng)求痒筒,隨機(jī)路由到一個(gè)可用的服務(wù)節(jié)點(diǎn)上宰闰。核心代碼如下:
本文參考了《架構(gòu)探險(xiǎn)》輕量級(jí)服務(wù)架構(gòu)
本文示例代碼: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();
//創(chuàng)建代理服務(wù)器對(duì)象并監(jiān)聽錯(cuò)誤事件
var proxy = httpProxy.createProxyServer();
proxy.on('error', function (err, req, res) {
res.end();//輸出空白響應(yīng)數(shù)據(jù)
});
//啟動(dòng)web服務(wù)器
app.use(express.static('public'));
app.all('*', function (req, res) {
//處理圖標(biāo)請(qǐng)求
if (req.path == '/favicon.ico') {
res.end();
return;
}
//獲取服務(wù)名稱
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;
}
//獲取服務(wù)路徑
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] //目標(biāo)地址
});
}
else {
//獲取服務(wù)路徑下的地址節(jié)點(diǎn)
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 {
//若存在多個(gè)地址簿透,則隨機(jī)獲取一個(gè)地址
addressPath += addressNodes[parseInt(Math.random() * size)];
}
console.log('addressPath:%s', addressPath);
//獲取服務(wù)地址
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]);
//執(zhí)行反向代理
proxy.web(req, res, {
target: 'http://' + serviceAddress //目標(biāo)地址
});
});
});
}
});
app.listen(PORT, function () {
console.log('server is running at %d', PORT);
});
}
使用supervisor app-gateway.js
啟動(dòng)API網(wǎng)關(guān)后:(supervisor可以定時(shí)監(jiān)聽文件的變化移袍,代碼更新無需重啟)
啟動(dòng)網(wǎng)關(guān)
當(dāng)然也需要啟動(dòng)在上文《基于ZooKeeper的服務(wù)注冊(cè)實(shí)現(xiàn)》提到的兩個(gè)客戶端,讓服務(wù)在ZooKeeper上注冊(cè)老充。
測(cè)試頁(yè)面
<!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" >點(diǎn)我一下調(diào)用服務(wù)</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>
點(diǎn)擊測(cè)試按鈕葡盗,在界面服務(wù)的顯示運(yùn)行結(jié)果"Hello"
運(yùn)行結(jié)果
AB測(cè)試(Apache Bench)模擬1000個(gè)用戶每次并發(fā)100請(qǐng)求:
注 :在測(cè)試前,將serviceName設(shè)為固定值:var serviceName='HelloService'
AB測(cè)試
閱讀本文啡浊,請(qǐng)結(jié)合上篇文章《基于ZooKeeper的服務(wù)注冊(cè)實(shí)現(xiàn)》進(jìn)行理解觅够。