一种服务器的负载均衡选取算法

在很多分布式系统里面会遇到一个均衡节点选取的问题:一般是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.h base_nodes_load.cpp之中。


测试结果:

我们模拟了10个节点,随机指定10 ~ 100的负载值,然后选取1000次业务处理的节点,结果如下图:



从结果看来是比较均衡的选取,比较理想。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章