一種服務器的負載均衡選取算法

在很多分佈式系統裏面會遇到一個均衡節點選取的問題:一般是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次業務處理的節點,結果如下圖:



從結果看來是比較均衡的選取,比較理想。



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