负载均衡有多神密?来研究下ShareWAF的开源负载均衡!

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; i++){ 
            //匹配 
            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; i++){ 
        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; i++){ 
        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; i++){ 
        if(signatures[i].time * 1 + 1000 * 60 * 10  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); 
            } 
        }); 
    } 
}

代码量不大,而且注释很清晰,细细口味很快便可取得其精华。

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代码:

    

ShareWAF Blance

.blance_div{

border:1px solid #cccccc;

background: #f4f5f8;

padding: 10px;

margin: 10px;

}

ShareWAF-Blance

添加负载:

<form action=" http://localhost:8090/register_blance ” method=”post”>

密码:

目标:

删除负载:

<form action=" http://localhost:8090/unregister_blance ” method=”post”>

密码:

目标:

负载列表

<form action=" http://localhost:8090/get_blance_list ” method=”post”>

密码:

以上便是该负载均衡的全部实现。

完整的代码也可以从ShareWAF官网下载获取。

使用:

由上述代码可知,ShareWAF-Blance是Node.JS开发的。需先安装Node再运行,

启动:

 Node blance 

运行效果: