C++负载均衡远程调用学习之订阅功能与发布功能

目录

1.lars-DnsV0.1回顾

2.Lars-DnsV0.2-订阅功能的订阅模块分析

3.Lars-DnsV0.2-订阅模块的类的单例创建及方法属性初始化

4.Lars-DnsV0.2-发布功能的实现

5.Lars-DnsV0.2-发布功能的总结

6.Lars-DnsV0.2-订阅流程复习

7.Lars-DnsV0.2-订阅模块的集成

8.Lars-DnsV0.2订阅模块的测试

9.Lars-DnsV0.2订阅模块测试2

10.Lars-DnsV0.2的发布问题bug修复

11.Lars-DnsV0.2订阅发布流程梳理


1.lars-DnsV0.1回顾

  1. Route订阅模式

6.1 订阅模块的设计与实现

​ 订阅模式整体的设计.

> lars_dns/include/subscribe.h

```c

#pragma once

#include <vector>

#include <pthread.h>

#include <ext/hash_set>

#include <ext/hash_map>

#include "lars_reactor.h"

#include "lars.pb.h"

#include "dns_route.h"

using namespace __gnu_cxx;

//定义订阅列表数据关系类型,key->modid/cmdid, value->fds(订阅的客户端文件描述符)

typedef hash_map<uint64_t, hash_set<int>> subscribe_map;

//定义发布列表的数据关系类型, key->fd(订阅客户端的文件描述符), value->modids

typedef hash_map<int, hash_set<uint64_t>> publish_map;

2.Lars-DnsV0.2-订阅功能的订阅模块分析

class SubscribeList {

public:

//设计单例

static void init() {

_instance = new SubscribeList();

}

static SubscribeList *instance() {

//保证init方法在这个进程执行中,只执行一次

pthread_once(&_once, init);

return _instance;

}

//订阅

void subscribe(uint64_t mod, int fd);

//取消订阅

void unsubscribe(uint64_t mod, int fd);

//发布

void publish(std::vector<uint64_t> &change_mods);

//根据在线用户fd得到需要发布的列表

void make_publish_map(listen_fd_set &online_fds,

publish_map &need_publish);

private:

//设计单例

SubscribeList();

SubscribeList(const SubscribeList &);

const SubscribeList& operator=(const SubscribeList);

static SubscribeList *_instance;

static pthread_once_t _once;

subscribe_map _book_list; //订阅清单

pthread_mutex_t _book_list_lock;

publish_map _push_list; //发布清单

pthread_mutex_t _push_list_lock;

};

```

​ 首先`SubscribeList`采用单例设计。这里面定义了两种数据类型

3.Lars-DnsV0.2-订阅模块的类的单例创建及方法属性初始化

``c

//定义订阅列表数据关系类型,key->modid/cmdid, value->fds(订阅的客户端文件描述符)

typedef hash_map<uint64_t, hash_set<int>> subscribe_map;

//定义发布列表的数据关系类型, key->fd(订阅客户端的文件描述符), value->modids

typedef hash_map<int, hash_set<uint64_t>> publish_map;

```

​ `subscribe_map`是目前dns系统的总体订阅列表,记录了订阅的modid/cmdid都有哪些fds已经订阅了,启动一个fd就代表一个客户端。

​ `publish_map`是即将发布的表,启动这里面是subscribe_map的一个反表,可以是订阅的客户端fd,而value是该客户端需要接收的订阅modid/cmdid数据。

**属性**:

`_book_list`:目前dns已经全部的订阅信息清单。

`_push_list`:目前dns即将发布的客户端及订阅信息清单。

**方法**

`void subscribe(uint64_t mod, int fd)`: 加入modid/cmdid 和订阅的客户端fd到_book_list中。

`void unsubscribe(uint64_t mod, int fd)`:取消一条订阅数据。

`void publish(std::vector<uint64_t> &change_mods)`: 发布订阅数据,其中change_mods是需要发布的那些modid/cmdid组合。

`void make_publish_map(listen_fd_set &online_fds, publish_map &need_publish)`: 根据目前在线的订阅用户,得到需要通信的发布订阅列表。

4.Lars-DnsV0.2-发布功能的实现

//根据在线用户fd得到需要发布的列表

void SubscribeList::make_publish_map(

listen_fd_set &online_fds,

publish_map &need_publish)

{

publish_map::iterator it;

pthread_mutex_lock(&_push_list_lock);

//遍历_push_list 找到 online_fds匹配的数据,放到need_publish中

for (it = _push_list.begin(); it != _push_list.end(); it++) {

//it->first 是 fd

//it->second 是 modid/cmdid

if (online_fds.find(it->first) != online_fds.end()) {

//匹配到

//当前的键值对移动到need_publish中

need_publish[it->first] = _push_list[it->first];

//当该组数据从_push_list中删除掉

_push_list.erase(it);

}

}

pthrea

5.Lars-DnsV0.2-发布功能的总结

//发布

void SubscribeList::publish(std::vector<uint64_t> &change_mods)

{

//1 将change_mods已经修改的mod->fd

// 放到 发布清单_push_list中

pthread_mutex_lock(&_book_list_lock);

pthread_mutex_lock(&_push_list_lock);

std::vector<uint64_t>::iterator it;

for (it = change_mods.begin(); it != change_mods.end(); it++) {

uint64_t mod = *it;

if (_book_list.find(mod) != _book_list.end()) {

//将mod下面的fd set集合拷迁移到 _push_list中

hash_set<int>::iterator fds_it;

for (fds_it = _book_list[mod].begin(); fds_it != _book_list[mod].end(); fds_it++) {

int fd = *fds_it;

_push_list[fd].insert(mod);

}

}

}

pthread_mutex_unlock(&_push_list_lock);

pthread_mutex_unlock(&_book_list_lock);

//2 通知各个线程去执行推送任务

server->thread_poll()->send_task(push_change_task, this);

}

```

​ 这里需要注意的是`publish()`里的server变量是全局变量,全局唯一的server句柄。

6.Lars-DnsV0.2-订阅流程复习

6.2 开启订阅

​ 那么订阅功能实现了,该如何是调用触发订阅功能能,我们可以在一个客户端建立连接成功之后来调用.

> lars_dns/src/dns_service.cpp

```c

#include <ext/hash_set>

#include "lars_reactor.h"

#include "subscribe.h"

#include "dns_route.h"

#include "lars.pb.h"

tcp_server *server;

using __gnu_cxx::hash_set;

typedef hash_set<uint64_t> client_sub_mod_list;

// ...

//订阅route 的modid/cmdid

void create_subscribe(net_connection * conn, void *args)

{

conn->param = new client_sub_mod_list;

}

//退订route 的modid/cmdid

void clear_subscribe(net_connection * conn, void *args)

{

client_sub_mod_list::iterator it;

client_sub_mod_list *sub_list = (client_sub_mod_list*)conn->param;

for (it = sub_list->begin(); it != sub_list->end(); it++) {

uint64_t mod = *it;

SubscribeList::instance()->unsubscribe(mod, conn->get_fd());

}

delete sub_list;

conn->param = NULL;

}

int main(int argc, char **argv)

{

event_loop loop;

//加载配置文件

config_file::setPath("conf/lars_dns.conf");

std::string ip = config_file::instance()->GetString("reactor", "ip", "0.0.0.0");

short port = config_file::instance()->GetNumber("reactor", "port", 7778);

//创建tcp服务器

server = new tcp_server(&loop, ip.c_str(), port);

//==========注册链接创建/销毁Hook函数============

server->set_conn_start(create_subscribe);

server->set_conn_close(clear_subscribe);

//============================================

//注册路由业务

server->add_msg_router(lars::ID_GetRouteRequest, get_route);

//开始事件监听

printf("lars dns service ....\n");

loop.event_process();

return 0;

}

```

​ 这里注册了两个链接Hook。`create_subscribe()`和`clear_subscribe()`。

`client_sub_mod_list`为当前客户端链接所订阅的route信息列表。主要存放当前客户订阅的modid/cmdid的集合。因为不同的客户端订阅的信息不同,所以要将该列表与每个conn进行绑定。

7.Lars-DnsV0.2-订阅模块的集成

7) Backend Thread实时监控

​ Backend Thread的后台总业务流程如下:

![11-Lars-dns_BackendThread](./pictures/11-Lars-dns_BackendThread.png)

7.1 数据库表相关查询方法实现

​ 我们先实现一些基本的数据表达查询方法:

> lars_dns/src/dns_route.cpp

```c

/*

* return 0, 表示 加载成功,version没有改变

* 1, 表示 加载成功,version有改变

* -1 表示 加载失败

* */

int Route::load_version()

{

//这里面只会有一条数据

snprintf(_sql, 1000, "SELECT version from RouteVersion WHERE id = 1;");

int ret = mysql_real_query(&_db_conn, _sql, strlen(_sql));

if (ret)

{

fprintf(stderr, "load version error: %s\n", mysql_error(&_db_conn));

return -1;

}

MYSQL_RES *result = mysql_store_result(&_db_conn);

if (!result)

{

fprintf(stderr, "mysql store result: %s\n", mysql_error(&_db_conn));

return -1;

}

long line_num = mysql_num_rows(result);

if (line_num == 0)

{

fprintf(stderr, "No version in table RouteVersion: %s\n", mysql_error(&_db_conn));

return -1;

}

MYSQL_ROW row = mysql_fetch_row(result);

//得到version

long new_version = atol(row[0]);

if (new_version == this->_version)

{

//加载成功但是没有修改

return 0;

}

this->_version = new_version;

printf("now route version is %ld\n", this->_version);

mysql_free_result(result);

return 1;

}

8.Lars-DnsV0.2订阅模块的测试

//加载RouteChange得到修改的modid/cmdid

//将结果放在vector中

void Route::load_changes(std::vector<uint64_t> &change_list)

{

//读取当前版本之前的全部修改

snprintf(_sql, 1000, "SELECT modid,cmdid FROM RouteChange WHERE version <= %ld;", _version);

int ret = mysql_real_query(&_db_conn, _sql, strlen(_sql));

if (ret)

{

fprintf(stderr, "mysql_real_query: %s\n", mysql_error(&_db_conn));

return ;

}

MYSQL_RES *result = mysql_store_result(&_db_conn);

if (!result)

{

fprintf(stderr, "mysql_store_result %s\n", mysql_error(&_db_conn));

return ;

}

long lineNum = mysql_num_rows(result);

if (lineNum == 0)

{

fprintf(stderr, "No version in table ChangeLog: %s\n", mysql_error(&_db_conn));

return ;

}

MYSQL_ROW row;

for (long i = 0;i < lineNum; ++i)

{

row = mysql_fetch_row(result);

int modid = atoi(row[0]);

int cmdid = atoi(row[1]);

uint64_t key = (((uint64_t)modid) << 32) + cmdid;

change_list.push_back(key);

}

mysql_free_result(result);

}

9.Lars-DnsV0.2订阅模块测试2

//将RouteChange

//删除RouteChange的全部修改记录数据,remove_all为全部删除

//否则默认删除当前版本之前的全部修改

void Route::remove_changes(bool remove_all)

{

if (remove_all == false)

{

snprintf(_sql, 1000, "DELETE FROM RouteChange WHERE version <= %ld;", _version);

}

else

{

snprintf(_sql, 1000, "DELETE FROM RouteChange;");

}

int ret = mysql_real_query(&_db_conn, _sql, strlen(_sql));

if (ret != 0)

{

fprintf(stderr, "delete RouteChange: %s\n", mysql_error(&_db_conn));

return ;

}

return;

}

```

这里面提供了基本的对一些表的加载和删除操作:

`load_version()`:加载当前route信息版本号。

`load_route_data()`:加载`RouteData`信息表,到_temp_pointer中。

`swap()`:将__temp_pointer的表数据同步到_data_temp表中.

`load_changes()`:加载RouteChange得到修改的modid/cmdid,将结果放在vector中

`remove_changes()`:清空之前的修改记录。

10.Lars-DnsV0.2的发布问题bug修复

7.2 Backend Thread业务流程实现

> lars_dns/src/dns_route.cpp

```c

//周期性后端检查db的route信息的更新变化业务

//backend thread完成

void *check_route_changes(void *args)

{

int wait_time = 10;//10s自动修改一次,也可以从配置文件读取

long last_load_time = time(NULL);

//清空全部的RouteChange

Route::instance()->remove_changes(true);

//1 判断是否有修改

while (true) {

sleep(1);

long current_time = time(NULL);

//1.1 加载RouteVersion得到当前版本号

int ret = Route::instance()->load_version();

if (ret == 1) {

//version改版 有modid/cmdid修改

//2 如果有修改

//2.1 将最新的RouteData加载到_temp_pointer中

if (Route::instance()->load_route_data() == 0) {

//2.2 更新_temp_pointer数据到_data_pointer map中

Route::instance()->swap();

last_load_time = current_time;//更新最后加载时间

}

//2.3 获取被修改的modid/cmdid对应的订阅客户端,进行推送

std::vector<uint64_t> changes;

Route::instance()->load_changes(changes);

//推送

SubscribeList::instance()->publish(changes);

//2.4 删除当前版本之前的修改记录

Route::instance()->remove_changes();

}

else {

//3 如果没有修改

if (current_time - last_load_time >= wait_time) {

//3.1 超时,加载最新的temp_pointer

if (Route::instance()->load_route_data() == 0) {

//3.2 _temp_pointer数据更新到_data_pointer map中

Route::instance()->swap();

last_load_time = current_time;

}

}

}

}

return NULL;

}

```

11.Lars-DnsV0.2订阅发布流程梳理

完成dns模块的订阅功能测试V0.3

​ 我们提供一个修改一个modid/cmdid的sql语句来触发订阅条件,并且让dns service服务器主动给订阅的客户端发送该订阅消息。

> lars_dns/test/test_insert_dns_route.sql

```sql

USE lars_dns;

SET @time = UNIX_TIMESTAMP(NOW());

INSERT INTO RouteData(modid, cmdid, serverip, serverport) VALUES(1, 1, 3232235953, 9999);

UPDATE RouteVersion SET version = @time WHERE id = 1;

INSERT INTO RouteChange(modid, cmdid, version) VALUES(1, 1, @time);

```

客户端代码:

> lars_dns/test/lars_dns_test1.cpp

```c

#include <string.h>

#include <unistd.h>

#include <string>

#include "lars_reactor.h"

#include "lars.pb.h"

//命令行参数

struct Option

{

Option():ip(NULL),port(0) {}

char *ip;

short port;

};

Option option;

void Usage() {

printf("Usage: ./lars_dns_test -h ip -p port\n");

}

//解析命令行

void parse_option(int argc, char **argv)

{

for (int i = 0; i < argc; i++) {

if (strcmp(argv[i], "-h") == 0) {

option.ip = argv[i + 1];

}

else if (strcmp(argv[i], "-p") == 0) {

option.port = atoi(argv[i + 1]);

}

}

if ( !option.ip || !option.port ) {

Usage();

exit(1);

}

}

相关推荐
Wabi_sabi_x1 小时前
C++设计模式:面向对象的八大设计原则之三
开发语言·c++·设计模式
qq_447429411 小时前
数据结构与算法:图论——最短路径
c语言·数据结构·c++·图论
ZeroOne电平浪客1 小时前
AUTOSAR_BSW_从入门到精通学习笔记系列_EcuM
笔记·mcu·学习·汽车·autosar·普华小满
dankokoko1 小时前
OPENGLPG第九版学习 -视口变换、裁减、剪切与反馈
学习
yi个名字1 小时前
C++ STL vector容器详解:从原理到实践
开发语言·c++
xin007hoyo2 小时前
算法笔记.求约数
c++·笔记·算法
共享家95272 小时前
C++ 多态:原理、实现与应用
c++
虾球xz2 小时前
c++环境和vscode常用的一些有用插件
c++·ide·vscode
柏木乃一2 小时前
平衡二叉搜索树模拟实现1-------AVL树(插入,删除,查找)
c++·学习·程序人生·算法·二叉搜索树·avl树·平衡二叉搜索树
我命由我123452 小时前
C++ - 数据容器之 forward_list(创建与初始化、元素访问、容量判断、元素遍历、添加元素、删除元素)
c语言·开发语言·c++·后端·visualstudio·visual studio·后端开发