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