远程负载均衡系统

系统总体架构

对于一个部门的后台,为增强灵活性,一个服务可以被抽象为命令字:modid+cmdid的组合,称为一个模块,而这个服务往往有多个服务节点,其所有服务节点的地址集合被称为这个模块下的路由,节点地址简称为节点

  • modid:标识业务的大类,如:“直播列表相关”
  • cmdid:标识具体服务内容,如:“批量获取直播列表”

业务代码利用modid,cmdid,就可以调用对应的远程服务,一个Lars系统包含一个DNSService,一个Report Service,以及部署于每个服务器的LoadBalance Agent,业务代码通过API与ELB系统进行交互
API :根据自身需要的modid,cmdid,向ELB系统获取节点、汇报节点调用结果;提供C++、Java、Python接口
LoadBalance Agent:运行于每个服务器上,负责为此服务器上的业务提供节点获取、节点状态汇报、路由管理、负载调度等核心功能
DNSService : 运行于一台服务器上(也可以用LVS部署多实例防单点),负责modid,cmdid到节点路由的转换
Report Service : 运行于DNSService同机服务器上,负责收集各modid,cmdid下各节点调用状况,可用于观察、报警
modid,cmdid数据由Mysql管理,具体SQL脚本在common/sql路径下 至于modid,cmdid的注册、删除可以利用Web端操作MySQL。
如图,每个服务器(虚线)部署了一台LoadBalance Agent,以及多个业务服务


1、开发者在Web端注册、删除、修改modid,cmdid的路由信息,信息被写入到MySQL数据库;


2、服务器上每个业务biz都把持着自己需要通信的远程服务标识modid+cmdid,每个biz都向本机LoadBalance Agent获取远程节点,进而可以和远程目标服务通信,此外业务模块会汇报本次的节点调用结果给LoadBalance Agent;


3、LoadBalance Agent负责路由管理、负载均衡等核心任务,并周期性向DNSService获取最新的路由信息,周期性把各modid,cmdid的各节点一段时间内的调用结果传给Report Service


4、DNSService监控MySQL,周期性将最新路由信息加载出来;


5、Report Service将各modid,cmdid的各节点一段时间内的调用结果写回到MySQL,方便Web端查看、报警


在这里插入图片描述

Reactor服务框架开发

(3)内存管理与buffer封装

1、先创建一个io_buf类,主要用来封装基本的buffer结构。然后用一个buf_pool来管理全部的buffer集合。

2、接下来用一个buf_pool来管理全部未被使用的io_buf集合,而且buf_pool的管理的内存是程序开始预开辟的,不会做清理工作,内存池buf_pool设计为单例模式,限制是5GB

3、内存池用一个hash_map进行管理,其中key就是每组内存的容量,每个key下面挂载一个io_buf链表,而且buf_pool预先会给map下的每个key的内存组开辟好一定数量的内存块。

4、读写buffer机制
专门实现一个专用的用来读(输入)数据的input_buffer和专门用来写(输出)数据的out_put buffer,方法就是定义一个基础父类reactor_buf
在这里插入图片描述

(4)事件触发event_loop:多路IO机制

1、 io_event基于IO事件封装
封装事件(基于epoll原生事件来封装):定义一个IO事件类来包括一个时间需要拥有的基本成员信息,一个io_event对象应该包含 一个epoll的事件标识EPOLLIN/EPOLLOUT,和对应事件的处理函数read_callback,write_callback。他们都应该是io_callback类型。

2、 event_loop事件循环处理机制
// map: fd->io_event
typedef __gnu_cxx::hash_map<int, io_event> io_event_map;
//定义指向上面map类型的迭代器
typedef __gnu_cxx::hash_map<int, io_event>::iterator io_event_map_it;
//全部正在监听的fd集合
typedef __gnu_cxx::hash_set listen_fd_set;
在这里插入图片描述

(5)TCP连接和消息封装

我们要将我们所发的数据做一个规定,采用TLV的格式,来进行封装。 解决tcp粘包问题:消息的封装msg_head
在这里插入图片描述
在这里插入图片描述

(5)tcp客户端触发模型

这里注意的是,tcp_client并不是tcp_server的一部分,而是单纯为写客户端提供的接口。所以这里也需要实现一套对读写事件处理的业务。 这里使用的读写缓冲是原始的io_buf,并不是服务器封装好的reactor_buf原因是后者是转为server做了一层封装,io_buf的基本方法比较全
在这里插入图片描述

(6)tcp客户端触发模型

这里注意的是,tcp_client并不是tcp_server的一部分,而是单纯为写客户端提供的接口。所以这里也需要实现一套对读写事件处理的业务。 这里使用的读写缓冲是原始的io_buf,并不是服务器封装好的reactor_buf原因是后者是转为server做了一层封装,io_buf的基本方法比较全
在这里插入图片描述
6.3非阻塞客户端socket创建连接的问题
这里转载一篇文章,是有关非阻塞套接字,connect返回-1,并且errno是EINPROGRESS的情况。因为我们的client是采用event_loop形式,socket需要被设置为非阻塞。所以需要针对这个情况做处理。下面是说明。
客户端测试程序时,由于出现很多客户端,经过connect成功后,代码卡在recv系统调用中,后来发现可能是由于socket默认是阻塞模式,所以会令很多客户端链接处于链接却不能传输数据状态。
后来修改socket为非阻塞模式,但在connect的时候,发现返回值为-1,刚开始以为是connect出现错误,但在服务器上看到了链接是ESTABLISED状态。证明链接是成功的(如何查看连接建立成功netstat -an |grep ESTABLISHED |wc -l 查看建立稳定连接数量)
但为什么会出现返回值是-1呢? 经过查询资料,以及看stevens的APUE,也发现有这么一说。
当connect在非阻塞模式下,会出现返回-1值,错误码是EINPROGRESS,但如何判断connect是联通的呢?stevens书中说明要在connect后,继续判断该socket是否可写?

7) tcp_server端集成tcp_conn链接属性

在这里插入图片描述

8) 消息业务路由分发机制

现在我们发送的消息都是message结构的,有个message头里面其中有两个关键的字段,msgid和msglen,其中加入msgid的意义就是我们可以甄别是哪个消息,从而对这类消息做出不同的业务处理。但是现在我们无论是服务端还是客户端都是写死的两个业务,就是"回显业务",显然这并不满足我们作为服务器框架的需求。我们需要开发者可以注册自己的回调业务。所以我们需要提供一个注册业务的入口,然后在后端根据不同的msgid来激活不同的回调业务函数。
开发者需要注册一个msg_callback类型的函数,通过msg_router类的register_msg_router()方法来注册,同时通过call()方法来调用。
全部回调业务函数和msgid的对应关系保存在一个hash_map类型的_routermap中,_args保存对应的参数。

//针对消息的路由分发,key为msgID, value为注册的回调业务函数

__gnu_cxx::hash_map<int, msg_callback *> _router;
//回调业务函数对应的参数,key为msgID, value为对应的参数
__gnu_cxx::hash_map<int, void *> _args;

在这里插入图片描述

9) 链接创建/销毁Hook机制

下面我们来给链接注册两个hook节点的函数,即服务端有新的客户端链接创建之后用户可以注册一个回调,有客户端断开链接的回调。还有客户端在成功与服务端创建链接之后创建的回调,和客户端与服务端断开链接之前的回调

在这里插入图片描述

消息队列与线程池

在这里插入图片描述
在这里插入图片描述

这里面有几个类型,thread_pool就是我们要创建的线程池,这里面会有很多thread其中每个thread都会启动一个epoll也就是我们封装好的event_loop来监控各自创建好的tcp_conn的读写事件。每个thread都会有一个thread_queue消息任务队列与之绑定,每个thread_queue里面会接收task_msg任务类型

一个模板类,主要是消息任务队列里的元素类型未必一定是task_msg类型。
thread_queue需要绑定一个event_loop。来触发消息到达,捕获消息并且触发处理消息业务的动作。
这里面有个_evfd是为了触发消息队列消息到达,处理该消息作用的,将_evfd加入到对应线程的event_loop中,然后再通过set_callback设置一个通用的该queue全部消息所触发的处理业务call_back,在这个call_back里开发者可以自定义实现一些处理业务流程。

  • 通过send将任务发送给消息队列。
  • 通过event_loop触发注册的io_callback得到消息队列里的任务。
  • 在io_callback中调用recv取得task任务,根据任务的不同类型,处理自定义不同业务流程。

在这里插入图片描述

异步消息任务机制

我们之前在include/task_msg.h中, 其中task的消息类型我们只是实现了NEW_CONN,目的是thread_pool选择一个线程,让一个线程里的thread_queue去创建一个连接对象。但是并没有对NEW_TASK的任务类型进行定义。这种类型是允许服务端去执行某项具体的业务。并不是根据客户端来消息去被动回复的业务,而是服务端主动发送的业务给到客户端。
//定义异步任务回调函数类型
typedef void (*task_func)(event_loop *loop, void *args);
task_func是我们定义的一个任务的回调函数类型,第一个参数当然就是让哪个loop机制去执行这个task任务。很明显,一个loop是对应一个thread线程的。也就是让哪个thread去执行这个task任务。args是task_func的函数形参。
// ===========================================
//需要被执行的task集合
typedef std::pair<task_func, void*> task_func_pair;
std::vector<task_func_pair> _ready_tasks;
// ===========================================

那么execute_ready_tasks()函数需要在一个恰当的时候被执行,我们这里就放在每次event_loop一次epoll_wait()处理完一组fd事件之后,触发一次额外的task任务。(muduo异步处理也是如此)
在这里插入图片描述

Dns_Service

主要功能:

  • 提供agent获取一个最新的modID/cmdID和真实配置的host的IP和port的对应关系
  • 具备订阅和推送最新的modID/cmdID给Agent
  • 定期去监控mysql中routeData表中的modID/cmdID和host主机信息的数据,保证版本的更新和最新数据的迭代

1 架构

在这里插入图片描述

2 网络模块

​ DnsService服务模型采用了one loop per thread TCP服务器,主要是基于Lars-Reactor:

  • 主线程Accepter负责接收连接(agent端连接)
  • Thread loop们负责处理连接的请求、回复;(agent端发送查询请求,期望获取结果)

3 双map模型

​DnsServer使用两个map存储路由数据(key = modid<<32 + cmdid , value = set of ip<<32 + port

  • 一个RouterDataMap_A:主数据,查询请求在此map执行
  • 另一个RouterDataMap_B:后台线程周期性重加载路由到此map,作为最新数据替换掉上一个map

这两个map分别由指针data_pointertemp_pointer指向.

4 Backend Thread守护线程

dns service还有个业务线程:

1、负责周期性(default:1s)检查RouteVersion表版本号,如有变化,说明RouteData有变更,则重加载RouteData表内容;然后将RouteChange表中被变更的modid取出,根据订阅列表查出modid被哪些连接订阅后,向所有工作线程发送任务:要求订阅这些modid的连接推送modid路由到agent

2、此外,还负责周期性(default:8s)重加载RouteData表内容

PS:重加载RouteData表内容的细节

重加载RouteData表内容到temp_pointer指向的RouterDataMap_B,而后上写锁,交换指针data_pointertemp_pointer的地址,于是完成了路由数据更新

主业务

  • 服务启动时,RouteData表被加载到data_pointer指向的RouterDataMap_A中,
    temp_pointer指向的RouterDataMap_B为空
  • 服务启动后,agent发来Query for 请求某modid/cmdid,到其所在Thread
    Loop上,上读锁查询data_pointer指向的RouterDataMap_A,返回查询结果;
  • 如果此modid/cmdid不存在,则把agent ip+port+moid/cmdid发送到Backend thread
    loop1的队列,让其记录到ClientMap

后台线程Backend thread每隔10s清空temp_pointer指向的RouterDataMap_B,再加载RouteData表内容到temp_pointer指向的RouterDataMap_B,加载成功后交换指针data_pointer与temp_pointer指针内容,于是完成了路由数据的更新.

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值