## 5) 負載均衡模塊基礎設計
### 5.1 基礎
? 每個模塊`modid/cmdid`下有若干節點,節點的集合稱為此模塊的路由; 對于每個節點,有兩種狀態:
- `idle`:此節點可用,可作為API**(相當于Agent的客戶端)**請求的節點使用;
- `overload`:此節點過載,暫時不可作為API請求的節點使用
? 在請求節點時,有幾個關鍵屬性:
- 虛擬成功次數`vsucc`,API匯報節點調用結果是成功時,該值+1
- 虛擬失敗次數`verr`,API匯報節點調用結果是失敗時,該值+1
- 連續成功次數`contin_succ`,連續請求成功的次數
- 連續失敗次數`contin_err`,連續請求失敗的次數
這4個字段,在節點狀態改變時(idle<—>overload),會被重置。
### 5.2 調度方式
- 圖1

- 圖2

如圖所示,整體的調度節點的方式大致如下。這里每個節點就是一個Host主機信息,也就是我們需要被管理的主機信息,一個主機信息應該包括基本的ip和port還有一些其他屬性。
如圖1,API相當于我們的Agent模塊的客戶端,也是業務端調用的請求主機接口。 API發送GetHost請求,發送給Agent的server端,傳遞信息包括modID/cmdID. AgentServer 使用UDPserver處理的API網絡請求,并且交給了某個"負載均衡算法".
如圖2,一個負載均衡算法,我們稱之為是一個"load balance", 一個"load balance"對應針對一組modID/cmdID下掛在的全部host信息進行負載。每個"load balance"都會有兩個節點隊列。一個隊列是"idle_list",存放目前可用的Host主機信息(ip+port), 一個隊列是"overload_list",存放目前已經過載的Host主機信息(ip+port).
當API對某模塊發起節點獲取時:
- Load Balance從空閑隊列拿出隊列頭部節點,作為選取的節點返回,同時將此節點重追到隊列尾部;
- **probe機制** :如果此模塊過載隊列非空,則每經過`probe_num`次節點獲取后(默認=10),給過載隊列中的節點一個機會,從過載隊列拿出隊列頭部節點,作為選取的節點返回,讓API試探性的用一下,同時將此節點重追到隊列尾部;
- 如果空閑隊列為空,說明整個模塊過載了,返回過載錯誤;且也會經過`probe_num`次節點獲取后(默認=10),給過載隊列中的節點一個機會,從過載隊列拿出隊列頭部節點,作為選取的節點返回,讓API試探性的用一下,同時將此節點重追到隊列尾部;
> 調度就是:從空閑隊列輪流選擇節點;同時利用probe機制,給過載隊列中節點一些被選擇的機會
### 5.2 API層與Agent Load Balance的通信協議
> /Lars/base/proto/lars.proto
```protobuf
syntax = "proto3";
package lars;
/* Lars系統的消息ID */
enum MessageId {
ID_UNKNOW = 0; //proto3 enum第一個屬性必須是0,用來占位
ID_GetRouteRequest = 1; //向DNS請求Route對應的關系的消息ID
ID_GetRouteResponse = 2; //DNS回復的Route信息的消息ID
ID_ReportStatusRequest = 3; //上報host調用狀態信息請求消息ID
ID_GetHostRequest = 4; //API 發送請求host信息給 Lb Agent模塊 消息ID
ID_GetHostResponse = 5; //agent 回執給 API host信息的 消息ID
}
enum LarsRetCode {
RET_SUCC = 0;
RET_OVERLOAD = 1; //超載
RET_SYSTEM_ERROR = 2; //系統錯誤
RET_NOEXIST = 3; //資源不存在
}
//...
//...
// API 請求agent 獲取host信息 (UDP)
message GetHostRequest {
uint32 seq = 1;
int32 modid = 2;
int32 cmdid = 3;
}
// Agent回執API的 host信息 (UDP)
message GetHostResponse {
uint32 seq = 1;
int32 modid = 2;
int32 cmdid = 3;
int32 retcode = 4;
HostInfo host = 5;
}
```
這里主要增加兩個ID:`ID_GetHostRequest`和`ID_GetHostResponse`,即,API的getHost請求的發送ID和回收ID。其中兩個ID對應的message包為`GetHostRequest`和`GetHostResponse`。
### 5.3 host_info與Load Balance初始化
我們首先應該定義幾個數據結構,分別是
`host_info`:表示一個host主機的信息
`load_balance`:針對一組`modid/cmdid`的負載均衡模塊
`route_lb`:一共3個,和agent的udp server(提供api服務)的數量一致,一個server對應一個route_lb,每個route_lb負責管理多個`load_balance`

#### **host_info**
> lars_loadbalance_agent/include/host_info.h
```c
#pragma once
/*
* 被代理的主機基本信息
*
* */
struct host_info {
host_info(uint32_t ip, int port, uint32_t init_vsucc):
ip(ip),
port(port),
vsucc(init_vsucc),
verr(0),
rsucc(0),
rerr(0),
contin_succ(0),
contin_err(0),
overload(false)
{
//host_info初始化構造函數
}
uint32_t ip; //host被代理主機IP
int port; //host被代理主機端口
uint32_t vsucc; //虛擬成功次數(API反饋),用于過載(overload),空閑(idle)判定
uint32_t verr; //虛擬失敗個數(API反饋),用于過載(overload),空閑(idle)判定
uint32_t rsucc; //真實成功個數, 給Reporter上報用戶觀察
uint32_t rerr; //真實失敗個數,給Reporter上報用戶觀察
uint32_t contin_succ; //連續成功次數
uint32_t contin_err; //連續失敗次數
bool overload; //是否過載
};
```
`host_info`包含,最關鍵的主機信息ip+port.除了這個還有一些用戶負載均衡算法判斷的屬性。
#### **load_balance**
> lars_loadbalance_agent/include/load_balance.h
```c
#pragma once
#include <ext/hash_map>
#include <list>
#include "host_info.h"
#include "lars.pb.h"
//ip + port為主鍵的 host信息集合
typedef __gnu_cxx::hash_map<uint64_t, host_info*> host_map; // key:uint64(ip+port), value:host_info
typedef __gnu_cxx::hash_map<uint64_t, host_info*>::iterator host_map_it;
//host_info list集合
typedef std::list<host_info*> host_list;
typedef std::list<host_info*>::iterator host_list_it;
/*
* 負載均衡算法核心模塊
* 針對一組(modid/cmdid)下的全部host節點的負載規則
*/
class load_balance {
public:
load_balance(int modid, int cmdid):
_modid(modid),
_cmdid(cmdid)
{
//load_balance 初始化構造
}
//判斷是否已經沒有host在當前LB節點中
bool empty() const;
//從當前的雙隊列中獲取host信息
int choice_one_host(lars::GetHostResponse &rsp);
//如果list中沒有host信息,需要從遠程的DNS Service發送GetRouteHost請求申請
int pull();
//根據dns service遠程返回的結果,更新_host_map
void update(lars::GetRouteResponse &rsp);
//當前load_balance模塊的狀態
enum STATUS
{
PULLING, //正在從遠程dns service通過網絡拉取
NEW //正在創建新的load_balance模塊
};
STATUS status; //當前的狀態
private:
int _modid;
int _cmdid;
int _access_cnt; //請求次數,每次請求+1,判斷是否超過probe_num閾值
host_map _host_map; //當前load_balance模塊所管理的全部ip + port為主鍵的 host信息集合
host_list _idle_list; //空閑隊列
host_list _overload_list; //過載隊列
};
```
一個`load_balance`擁有兩個host鏈表集合,還有一個以ip/port為主鍵的`_host_map`集合。
**屬性**:
`_idle_list`:全部的空閑節點Host集合列表。
`_overload_list`:全部的過載節點Host集合列表。
`_host_map`:當前`load_balance`中全部所管理的ip+port是host總量,是一個`hash_map<uint64_t, host_info*>`類型。
`_modid,_cmdid`:當前`load_balance`所綁定的`modid/cmdid`。
`_access_cnt`:記錄當前`load_balance`被api的請求次數,主要是用戶判斷是否觸發probe機制(從overload_list中嘗試取節點)
`status`: 當前`load_balance`所處的狀態。包括`PULLING`,`NEW`. 是當`load_balance`在向遠程`dns service`請求host 的時候,如果正在下載中,則為`PULLING`狀態,如果是增加被創建則為`NEW`狀態。
**方法**
`choice_one_host()`:根據負載均衡算法從兩個list中取得一個可用的host信息放在`GetRouteResponse &rsp`中
`pull()`:如果list中沒有host信息,需要從遠程的DNS Service發送GetRouteHost請求申請。
`update()`:根據dns service遠程返回的結果,更新`_host_map`
以上方法我們暫時先聲明,暫不實現,接下來,我們來定義`route_lb`數據結構
#### **route_lb**
> /lars_loadbalance_agent/include/route_lb.h
```c
#pragma once
#include "load_balance.h"
//key: modid+cmdid value: load_balance
typedef __gnu_cxx::hash_map<uint64_t, load_balance*> route_map;
typedef __gnu_cxx::hash_map<uint64_t, load_balance*>::iterator route_map_it;
/*
* 針對多組modid/cmdid ,route_lb是管理多個load_balanace模塊的
* 目前設計有3個,和udp-server的數量一致,每個route_lb分別根據
* modid/cmdid的值做hash,分管不同的modid/cmdid
*
* */
class route_lb {
public:
//構造初始化
route_lb(int id);
//agent獲取一個host主機,將返回的主機結果存放在rsp中
int get_host(int modid, int cmdid, lars::GetHostResponse &rsp);
//根據Dns Service返回的結果更新自己的route_lb_map
int update_host(int modid, int cmdid, lars::GetRouteResponse &rsp);
private:
route_map _route_lb_map; //當前route_lb下的管理的loadbalance
pthread_mutex_t _mutex;
int _id; //當前route_lb的ID編號
};
```
**屬性**
`_route_lb_map`: 當前route_lb下的所管理的全部`load_balance`(一個load_balance負責一組modid/cmdid的集群負載)。其中key是modid/cmdid,value則是load_balance對象.
`_mutex`:保護`_route_lb_map`的鎖。
`_id`:當前route_lb的id編號,編號從1-3,與udpserver的數量是一致的。一個agent udp server對應一個id。
**方法**
`get_host()`:直接處理API業務層發送過來的`ID_GetHostRequest`請求。agent獲取一個host主機,將返回的主機結果存放在rsp中.
`update_host()`:這個是`load_balance`觸發`pull()`操作,遠程Dns Service會返回結果,`route_lb`根據Dns Service返回的結果更新自己的`_route_lb_map`.
---
### 關于作者:
作者:`Aceld(劉丹冰)`
mail: [danbing.at@gmail.com](mailto:danbing.at@gmail.com)
github: [https://github.com/aceld](https://github.com/aceld)
原創書籍: [http://www.hmoore.net/@aceld](http://www.hmoore.net/@aceld)

>**原創聲明:未經作者允許請勿轉載, 如果轉載請注明出處**
- 一、Lars系統概述
- 第1章-概述
- 第2章-項目目錄構建
- 二、Reactor模型服務器框架
- 第1章-項目結構與V0.1雛形
- 第2章-內存管理與Buffer封裝
- 第3章-事件觸發EventLoop
- 第4章-鏈接與消息封裝
- 第5章-Client客戶端模型
- 第6章-連接管理及限制
- 第7章-消息業務路由分發機制
- 第8章-鏈接創建/銷毀Hook機制
- 第9章-消息任務隊列與線程池
- 第10章-配置文件讀寫功能
- 第11章-udp服務與客戶端
- 第12章-數據傳輸協議protocol buffer
- 第13章-QPS性能測試
- 第14章-異步消息任務機制
- 第15章-鏈接屬性設置功能
- 三、Lars系統之DNSService
- 第1章-Lars-dns簡介
- 第2章-數據庫創建
- 第3章-項目目錄結構及環境構建
- 第4章-Route結構的定義
- 第5章-獲取Route信息
- 第6章-Route訂閱模式
- 第7章-Backend Thread實時監控
- 四、Lars系統之Report Service
- 第1章-項目概述-數據表及proto3協議定義
- 第2章-獲取report上報數據
- 第3章-存儲線程池及消息隊列
- 五、Lars系統之LoadBalance Agent
- 第1章-項目概述及構建
- 第2章-主模塊業務結構搭建
- 第3章-Report與Dns Client設計與實現
- 第4章-負載均衡模塊基礎設計
- 第5章-負載均衡獲取Host主機信息API
- 第6章-負載均衡上報Host主機信息API
- 第7章-過期窗口清理與過載超時(V0.5)
- 第8章-定期拉取最新路由信息(V0.6)
- 第9章-負載均衡獲取Route信息API(0.7)
- 第10章-API初始化接口(V0.8)
- 第11章-Lars Agent性能測試工具
- 第12章- Lars啟動工具腳本