ShareWAF有一款開源的負載均衡,名為ShareWAF-Blance(後文也簡稱其為Blance),本文通過解析這款負載均衡工具,來揭開負載均衡的神秘面紗,瞭解它的原理、瞭解它的工作方式,最後奉上乾貨:ShareWAF-Blance的完整源碼。
ShareWAF-Blance的特點
1、反向代理模式
簡單的來說,可以說ShareWAF負載均衡其實是一個反向代理服務器,訪問數據先到達負載,負載再轉發給ShareWAF(我們在應用它時,當然可以不轉發給ShareWAF,而是轉發數據給我們的web什麼的),其工作模式很簡潔,如下圖:
2、服務註冊表式的動態負載
服務註冊表有點高端,可以實現動態負載,即:我們可以動態的添加、刪除負載,實時調整負載數量和目標,該技術的示意圖如下:
3、支持有狀態通信
即:負載均衡器總是會將所有的與會話關聯的請表路由到應用程序(web)的同一個實例。這種技術也稱為黏性負載均衡。該技術主要處理如下圖所示的問題:
上圖是無狀態通信,如果不加以解決,負載有可能會將已經認證過的會話轉發給不同的應用目標,造成會話狀態丟失,影響有認證流程的業務功能。
Blance會通過會話池,將一個訪問者總是定向到同一個應用程序(WEB)實例。
隨機負載
負載均衡有多種負載方式,如輪詢、權重、隨機,Blance採用的是隨機的方式。
以上介紹了Blance的關鍵特徵,下面是源碼:
上圖,是ShareWAF-Blance的項目文件,
blance.JS是核心文件;
Config.JS是配置文件;
Blance.Html是動態添加、刪除、負載目標的操作頁面;
Log.TXT是日誌文件,先透露個彩蛋,源碼中日誌的記錄顧頗有技巧,使用的是API HOOK技術。
Blance源碼:
//*****************************************/
// Blance
// ShareWAF.com 負載均衡模塊
// Auther: WangLiwen
//*****************************************/
/**
* 使用方法:
* 打開Config.JS,進行配置
* port為負載端口,接受Web訪問
* admin_port為負載管理端口,用於管理負載,添加、刪除、查看負載
* password為管理密碼,進行管理操作時,要校驗此密碼
* blance_pool為負載池,即多個負載目標,可以為ip或域名
* (需最少添加一個負載目標,方可正常工作,但要達到負載效果,則至少需添加兩個)
* (可以在這裡直接配置好,也可以啟動後通過管理端口號訪問進行動態添加、刪除)
* Ready,可以開始使用!
*
* 說明:同一訪問者,會訪問到同一負載目標,即:可負載有狀態通信
*/
//三方模塊
var express = require("express")();
var http_proxy = require("http-proxy");
var body_parser = require("body-parser");
var admin_express = require("express")();
var fs = require("fs");
//調試信息
var debug = require("./config.js").debug;
//日誌
var log = require("./config.js").log;
//端口
var port = require("./config.js").port;
//管理密碼
var password = require("./config.js").admin_password;
//管理端口
var admin_port = require("./config.js").admin_port;
//調試開關
var debug = true;
//代理
var proxy = http_proxy.createProxyServer({});
//存放目標
var pool = require("./config.js").blance_pool;
//特徵池,實現同一人訪問同一目標
var signatures = [];
//監聽
express.listen(port);
admin_express.listen(admin_port);
console.info("ShareWAF-Blance v1.0.2");
console.info("Blance server at port:",port);
console.info("Blance admin server at port:",admin_port);
console.info("Copyright (c) 2020 ShareWAF.com");
//管理後臺
admin_express.get("/",function(req,res){
fs.readFile("./blance.html",function(err,std_out,std_err){
res.writeHead(200,{'Content-type':"text/html"});
if(!err){
res.end(std_out);
}else{
res.end("Error while read blance.html");
}
})
});
proxy.on("error",function(err,req,res){
try{
res.end("error");
}catch(e){
console.log(e.message);
}
});
//body-parser
express.use(body_parser.urlencoded({extended: true}));
//註冊
express.post("/register_blance",function(req,res,next){
//密碼,用於校驗
if(req.body.password == password){
//添加到負載均衡池
pool.push(req.body.target);
console.log("add blance:" + req.body.target);
res.end("blance added!");
}else{
console.log("register blance error: password error!");
res.end("error!");
}
return;
});
//獲取列表
express.post("/get_blance_list",function(req,res,next){
//密碼,用於校驗
if(req.body.password == password){
console.log("get_blance_list" + pool.toString());
res.end(pool.toString());
}else{
console.log("register blance error: password error!");
res.end("error!");
}
return;
});
//反註冊
express.post("/unregister_blance",function(req,res,next){
//密碼,用於校驗
if(req.body.password == password){
var remove_flag = 0;
//遍歷
for(i=0; i<pool.length>
//匹配
if(pool[i] == req.body.target){
//刪除
delete pool[i];
pool.splice(i,1);
console.log("remove blance:" + req.body.target);
res.end("blance removed!");
remove_flag = 1;
}
}
if(remove_flag == 0){
res.end("unregister blance error:blance not exist!");
console.log("error,blance not exist")
}
}else{
console.log("unregister blance error: password error!");
res.end("error!")
}
return;
});
//隨機訪問負載
express.use(function(req,res,next){
if(pool.length == 0){
console.log("error: blance pool is null.")
res.end("Error:No blance! Config first,Please!");
return;
}
//隨機數
var rnd = random_number(0,pool.length - 1);
//訪問者特徵:IP+AGENT
var req_signature = get_req_ip(req) + req.headers["user-agent"];
//從特徵庫中獲取負載目標
for(i=0; i<signatures.length>
if(signatures[i].signature == req_signature){
rnd = signatures[i].index;
console.log("get blance from signature pool:" + i + ".");
signatures[i].time = (new Date).getTime();
}
}
//訪問
proxy.web(req, res, {target: pool[rnd], selfHandleResponse : false, changeOrigin:true} );
console.log("blance visit: " + rnd + " " + pool[rnd] + ",url:" + req.url);
//遍歷,檢查特存是否已存入特徵池
for(i=0; i<signatures.length>
if(signatures[i].signature == req_signature){
return;
}
}
//保存到特徵池
signatures.push({signature:req_signature, index:rnd, time:(new Date).getTime()});
})
//10秒檢查一次,將特徵池中超時的特徵移除
setInterval(function(){
//遍歷特徵池
for(i=0; i<signatures.length>
if(signatures[i].time * 1 + 1000 * 60 * 10 <= (new Date).getTime()){
console.log("remove signature:" + signatures[i]);
delete signatures[i];
signatures.splice(i,1);
}
}
},1000 * 10)
//獲取訪問者ip
var get_req_ip = function(req) {
try{
var ip = req.headers["x-forwarded-for"] || req.ip || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress || "";
if(ip.split(",").length > 0){
ip = ip.split(",")[0];
}
return ip.replace("::ffff:", "");
}catch(e){
console.log("error while get client ip." + e.message);
return "127.0.0.1";
}
};
//範圍內隨機數
function random_number(min,max){
var range = max - min;
var rand = Math.random();
var num = min + Math.round(rand * range);
return num;
}
//API hook,處理console.log
var old_console_log = console.log;
console.log = function(msg){
if(debug == 1){
old_console_log("\\\\u001b[32m" + msg +"\\\\u001b[0m");
}
if(log == 1){
fs.appendFile("log.txt", new Date() + " " + msg + "\\r\\n",function(e){
if(e){
console.error("Error while write to log.txt:",e.message);
}
});
}
}
/<signatures.length>/<signatures.length>/<signatures.length> /<pool.length>代碼量不大,而且註釋很清晰,細細口味很快便可取得其精華。
Config.JS源碼:
exports.port = 8090;
exports.admin_port = 9000;
exports.admin_password = "pass";
exports.blance_pool = ["http://www.sharewaf.com","http://www.jshaman.com"];
exports.debug = 1;
exports.log = 1;
這是個單純的配置文件,內容一目瞭然。
Blance.HTML代碼:
<title>ShareWAF Blance/<title>
<style>
.blance_div{
border:1px solid #cccccc;
background: #f4f5f8;
padding: 10px;
margin: 10px;
}
ShareWAF-Blance
添加負載:
刪除負載:
負載列表
以上便是該負載均衡的全部實現。
完整的代碼也可以從ShareWAF官網下載獲取。
使用:
由上述代碼可知,ShareWAF-Blance是Node.JS開發的。需先安裝Node再運行,
啟動:
Node blance
運行效果:
現在負載均衡對我們來說一點也不神密了。
最後,讓我們至敬開源!
閱讀更多 WangLiwen 的文章