負載均衡有多神密?來研究下ShareWAF的開源負載均衡

ShareWAF有一款開源的負載均衡,名為ShareWAF-Blance(後文也簡稱其為Blance),本文通過解析這款負載均衡工具,來揭開負載均衡的神秘面紗,瞭解它的原理、瞭解它的工作方式,最後奉上乾貨:ShareWAF-Blance的完整源碼。

負載均衡有多神密?來研究下ShareWAF的開源負載均衡

ShareWAF-Blance的特點

1、反向代理模式

簡單的來說,可以說ShareWAF負載均衡其實是一個反向代理服務器,訪問數據先到達負載,負載再轉發給ShareWAF(我們在應用它時,當然可以不轉發給ShareWAF,而是轉發數據給我們的web什麼的),其工作模式很簡潔,如下圖:

負載均衡有多神密?來研究下ShareWAF的開源負載均衡

2、服務註冊表式的動態負載

服務註冊表有點高端,可以實現動態負載,即:我們可以動態的添加、刪除負載,實時調整負載數量和目標,該技術的示意圖如下:

負載均衡有多神密?來研究下ShareWAF的開源負載均衡

3、支持有狀態通信

即:負載均衡器總是會將所有的與會話關聯的請表路由到應用程序(web)的同一個實例。這種技術也稱為黏性負載均衡。該技術主要處理如下圖所示的問題:

負載均衡有多神密?來研究下ShareWAF的開源負載均衡

上圖是無狀態通信,如果不加以解決,負載有可能會將已經認證過的會話轉發給不同的應用目標,造成會話狀態丟失,影響有認證流程的業務功能。

Blance會通過會話池,將一個訪問者總是定向到同一個應用程序(WEB)實例。

隨機負載

負載均衡有多種負載方式,如輪詢、權重、隨機,Blance採用的是隨機的方式。

以上介紹了Blance的關鍵特徵,下面是源碼:

負載均衡有多神密?來研究下ShareWAF的開源負載均衡

上圖,是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

運行效果:

負載均衡有多神密?來研究下ShareWAF的開源負載均衡

現在負載均衡對我們來說一點也不神密了。

最後,讓我們至敬開源!


分享到:


相關文章: