在很多分布式系统里面会遇到一个均衡节点选取的问题:一般是1个负载管理服务器,多个应用服务单元。当有连接或者业务来是,先会去询问负载管理器获取一个负载轻的服务单元,一般的选取就是选取负载最轻的那个。通常情况下是不会有问题的,如果你的应用服务器单元跑的是类似视频服务这种应用,就会出现这样一种情况,某个视频服务A崩溃或者异常了,这个视频服务的所有用户在瞬间会转移到负载最轻的B上,这个时候可能B也异常了,B 和A的用户又全部转到C,这样会产生多咪若效应,所有的服务在瞬间全部不可用。碰到这样的问题我们该怎么避免?问题的根本是负载评估是周期性的,不是每接入一个用户就报一次负载给负载管理器。最理想的是,根据负载管理上所有单元的负载做权重随机。
算法描述如下:
假设有N个服务单元,(服务单元的负载是1 ~ 100,100是满负荷运转)。他们的负载分别是L1 L2 L3 .... Ln。
第i台服务的选中的权值 Ti = 100 - Li;
那么整个掉落区间就为 Mn= T1 + T2 + T3 + ... + Tn;
T0 = 0;
随进产生一个0 ~ Mn之间的数V,如果 V < T0 + T1 +...Ti 并且V > T0 + T1 + ... +T(i - 1),那么就 选取第i台服务作为处理新业务的服务单元。
代码实现:
节点负载结构:
typedef struct NodeLoadInfo
{
uint32_t node_id; //节点ID
uint16_t node_load; //节点负载值,0到100,100表示负载最大
NodeLoadInfo()
{
node_id = 0;
node_load = 100;
};
}NodeLoadInfo;
选取区间结构:
typedef struct NodeRange
{
int32_t min_value;//T0 + T1 + ... + T(i-1)
int32_t max_value;//T0 + T1 + ... + Ti
uint32_t node_id;//节点的服务ID
NodeRange()
{
min_value = 0;
max_value = 0;
node_id = 0;
};
}NodeRange;
负载选取器的实现接口:
class CNodeLoadManager
{
public:
CNodeLoadManager();
~CNodeLoadManager();
void add_node(const NodeLoadInfo& info);//添加一个新的节点负载
void update_node(const NodeLoadInfo& info);//更新一个节点的负载
void del_node(uint32_t node_id);//删除一个节点
uint32_t select_node();//选取一个适合的节点
private:
NodeLoadInfoMap node_info_map_; //节点负载表
NodeRangeArray node_ranges_; //一定周期的概率区间表
bool create_range_; //是否需要重建概率选取表
int32_t region_; //概率全区间
};
在上面的接口当中,select_node是核心函数,一下是select_node的伪代码实现:
uint32_t select_node()
{
uint32_t ret = 0;
node_ranges_.clear();
region_ = 0;
int32_t interval = 0;
NodeRange range;
//计算各个节点的掉落区间
for(NodeLoadInfoMap::iterator it = node_info_map_.begin(); it != node_info_map_.end(); ++it)
{
if(it->second.node_load <= MAX_LOAD_VALUE) //计算负载区间,如果负载太大,不参与计算
{
interval = MAX_LOAD_VALUE - it->second.node_load + 1;
range.node_id = it->second.node_id;
range.min_value = region_;
region_ += interval;
range.max_value = region_ - 1;
node_ranges_.push_back(range);
}
}
if(region_ > 0) //随机概率选取
ret = locate_server(region_);
return ret;
}
在上面的代码中,实现了区间的计算。
locate_server函数查找对应的服务节点,伪代码:
uint32_t locate_server(int32_t region)
{
uint32_t ret = 0;
//产生一个随机数
int32_t seed = rand() % region;
int32_t ranges_size = node_ranges_.size();
int32_t max_pos = ranges_size;
int32_t min_pos = 0;
int32_t pos = 0;
//2分法查询掉落到的节点
do {
pos = (min_pos + max_pos) / 2;
if(pos > ranges_size - 1){
ret = node_ranges_[ranges_size - 1].node_id;
break;
}
if(pos < 0){
ret = node_ranges_[0].node_id;
break;
}
if(node_ranges_[pos].max_value >= seed && node_ranges_[pos].min_value <= seed){
ret = node_ranges_[pos].node_id;
break;
}
else if(node_ranges_[pos].max_value < seed){
min_pos = pos + 1;
}
else if(node_ranges_[pos].min_value > seed){
max_pos = pos - 1;
}
} while (true);
return ret;
}
完全的代码在revolver的base_nodes_load.hbase_nodes_load.cpp之中。
我们模拟了10个节点,随机指定10 ~ 100的负载值,然后选取1000次业务处理的节点,结果如下图:
从结果看来是比较均衡的选取,比较理想。