## 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
![](https://img.kancloud.cn/54/84/54840375c2d793a337b4a1530f685a08_1172x571.png)
- 图2
![](https://img.kancloud.cn/2c/01/2c0136c133e4a007562920411a11f21e_1006x579.png)
如图所示,整体的调度节点的方式大致如下。这里每个节点就是一个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`
![](https://img.kancloud.cn/07/9d/079d375ee3226460c44aa5df3d373fa7_1024x768.png)
#### **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)
原创书籍: [https://www.kancloud.cn/@aceld](https://www.kancloud.cn/@aceld)
![](https://img.kancloud.cn/b0/d1/b0d11a21ba62e96aef1c11d5bfff2cf8_227x227.jpg)
>**原创声明:未经作者允许请勿转载, 如果转载请注明出处**
- 一、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启动工具脚本