分布式通信框架

01 项目简介

技术栈

  • 集群和分布式概念以及原理
  • RPC远程过程调用原理以及实现
  • Protobuf/数据序列化以及反序列化协议
  • ZooKeeper分布式一致性协调服务应用及编程
  • muduo网络库编程
  • conf配置文件读取
  • CMake构建项目集成编译环境
  • github管理项目

02 集群和分布式理论讲解

请添加图片描述
**集群的概念:**每一台服务器独立运行一个工程的所有模块。
**集群的优点:**用户的并发量提升了;
缺点:

  • 项目代码需要需要修改的话,需要重新编译,多次部署;
  • 不能根据模块的需求,配置到相应的硬件资源上。因为各个模块并没有分开去部署到不同的机器上。比如:后台管理要求的并发量很低,却部署到了多台服务器上,根本没有必要。

**分布式:**一个工程拆分了很多模块,每一个模块独立部署在一个服务器主机上,所有服务器协同工作共同提供服务,每一台服务器称作分布式的一个节点,根据节点的并发要求,对一个节点可以再做节点模块集群部署。

上图为单机聊天服务器,下图为集群聊天服务器
上图为单机聊天服务器,下图为集群聊天服务器。

单机版的聊天服务器的性能、或设计上的一些瓶颈有哪些?

  • 首先,受限于硬件资源,聊天服务器所能承受的并发量时受限的;
  • 假设用户管理中某个注销的函数出了bug,那么修改之后整个项目必须重新编译,重新部署。所以说,任意模块的修改都会导致整个项目代码重新编译、部署,费时费力;
  • 系统中,有些模块是属于CPU密集型的,有些模块是IO密集型的,造成各模块对于硬件资源的需求是不一样的;那么将所有模块放到一个服务器中对资源的利用是不合理的。

分布式系统解决了上述所有缺点。

分布式有哪些缺点呢?

  1. 各模块可能会有大量的重复代码;
  2. 各个模块之间的访问会变得复杂;
    请添加图片描述

05 RPC的通信原理以及项目的技术选型

请添加图片描述
请添加图片描述
请添加图片描述

06 项目环境搭建介绍

protobuf安装

GitHub 地址:
https://github.com/protocolbuffers/protobuf

官方文档地址:
https://developers.google.com/protocol-buffers/

Releases 下载地址:
https://github.com/protocolbuffers/protobuf/releases

首先去https://github.com/protocolbuffers/protobuf/releases中找到:
在这里插入图片描述
下载protobuf-cpp-3.21.9.tar.gz
https://github.com/protocolbuffers/protobuf/releases/download/v21.9/protobuf-cpp-3.21.9.tar.gz

# 安装所需配置工具
jixu@ubuntu:~/Downloads/protobuf-3.21.9$ sudo su
jixu@ubuntu:~/Downloads/protobuf-main$ apt-get install autoconf automake libtool curl make g++ unzip
root@ubuntu:/home/jixu/Desktop# sudo apt-get install build-essential
root@ubuntu:/home/jixu/Downloads/protobuf-3.21.9# ./autogen.sh
root@ubuntu:/home/jixu/Downloads/protobuf-3.21.9# ./configure
root@ubuntu:/home/jixu/Desktop# make
root@ubuntu:/home/jixu/Downloads/protobuf-3.21.9# make check
root@ubuntu:/home/jixu/Downloads/protobuf-3.21.9# make install
# 报错 添加环境变量
root@ubuntu:/home/jixu/Desktop# sudo ldconfig
root@ubuntu:/home/jixu/Desktop# protoc --version
libprotoc 3.21.9
# 安装成功

卸载protobuf

sudo rm /usr/local/bin/protoc  //执行文件
sudo rm -rf /usr/local/include/google //头文件
sudo rm -rf /usr/local/lib/libproto* //库文件

07 protobuf实践讲解一√

简单使用示例:

syntax = "proto3"; // 声明了protobuf的版本

package fixbug; // 声明了代码所在的包(对于C++来说就是namespace)

option cc_generic_services = true;

// 登录请求的消息类型
message LoginRequest{
    string name = 1; // 1代表第一个字段
    string pwd = 2; // 2代表第二个字段
}

// 登录响应的消息类型
message LoginResponse{
    int32 errcode = 1;
    string errmsg = 2;
    bool success = 3;
}

生成代码:

jixu@ubuntu:~/Mycode/ShiLei/mprpc$ cd test/
jixu@ubuntu:~/Mycode/ShiLei/mprpc/test$ cd protobuf/
jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ protoc test.proto --cpp_out=./

测试代码:

#include <iostream>
#include "test.pb.h"
using namespace fixbug;

int main(){
    // 封装了login请求对象的数据
    LoginRequest req;
    req.set_name("zhang san");
    req.set_pwd("123456");
    // 对象数据序列化 -> char*;
    std::string send_str;
    if(req.SerializeToString(&send_str)){
        std::cout << send_str.c_str() << std::endl;
    }

    // 反序列化 从send_str反序列化一个login请求
    LoginRequest reqB;
    if(reqB.ParseFromString(send_str)){
        std::cout << reqB.name() << std::endl;
        std::cout << reqB.pwd() << std::endl;
    }

    return 0;
}

编译:

jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ g++ -g -o test_proto main.cpp test.pb.cc -lprotobuf
jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ ./test_proto 

        zhang san123456
zhang san
123456

jixu@jixu-ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ gdb test_proto -silent
Reading symbols from test_proto...
(gdb) b 15
Breakpoint 1 at 0x4a10: file main.cpp, line 15.
(gdb) r
Starting program: /home/jixu/Mycode/ShiLei/mprpc/test/protobuf/test_proto 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at main.cpp:15
15              std::cout << send_str.c_str() << std::endl;
(gdb) p str
$1 = 0x5555555747b0 "\n\tzhang san\022\006\061\062\063\064\065\066"

08 protobuf实践讲解二√

proto文件中,string替换成bytes,会提高一些效率。代码上使用没有任何改变。
感觉在一种消息中嵌套另一种类型时,我想填充其中的数据时都要使用返回的指针去填充它。

然后主要介绍一下protobuf中列表的使用:

syntax = "proto3"; // 声明了protobuf的版本

package fixbug; // 声明了代码所在的包(对于C++来说就是namespace)

option cc_generic_services = true; // 启用rpc



// 可以发现每个响应类型里面都有errcode、errmsg两个消息类型,很麻烦。因此在这里把它们封装起来:
message ResultCode{
    int32 errcode = 1;
    bytes errmsg = 2;
}

// 登录请求的消息类型
message LoginRequest{
    bytes name = 1; // 1代表第一个字段
    bytes pwd = 2; // 2代表第二个字段
}

// 登录响应的消息类型
message LoginResponse{
    ResultCode result = 1;
    bool success = 2;
}

// 要存储的数据无非就三种: 数据 列表 映射表

message GetFriendListsRequest{
    uint32 userid = 1;
}


message User{
    bytes name = 1;
    uint32 age = 2;
    enum Sex{
        MAN = 0;
        WOMAN = 1;
    }
    Sex sex = 3;
}
 
message GetFriendListsResponse{
    ResultCode result = 1;
    repeated User friend_list = 2; // 用repeated定义一个列表类型
}
# 生成
jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ protoc test.proto --cpp_out=./

测试代码:

#include "test.pb.h"
#include <iostream>
#include <string>
using namespace fixbug;

int main()
{

    GetFriendListsResponse rsp;
    ResultCode *rc = rsp.mutable_result();
    rc->set_errcode(0);

    User *user1 = rsp.add_friend_list();
    user1->set_name("zhang san");
    user1->set_age(20);
    user1->set_sex(User::MAN);

    User *user2 = rsp.add_friend_list();
    user2->set_name("li si");
    user2->set_age(22);
    user2->set_sex(User::MAN);

    std::cout << rsp.friend_list_size() << std::endl;
    for(int i  = 0; i <rsp.friend_list_size(); ++i){
        std::cout << rsp.friend_list(i).name() << std::endl;
    }
    return 0;
}

编译:

jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ g++ -o test_proto main.cpp test.pb.cc -lprotobuf
jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ ./test_proto 
2
zhang san
li si

09 protobuf实践讲解三

在protobuf里面怎么定义描述rpc方法的类型 - service
注意定义rpc方法时必须要定义option选项。

syntax = "proto3"; // 声明了protobuf的版本

package fixbug; // 声明了代码所在的包(对于C++来说是namespace)

// 定义下面的选项,表示生成service服务类和rpc方法描述,默认不生成
option cc_generic_services = true;

message ResultCode
{
    int32 errcode = 1;
    bytes errmsg = 2;
}

// 数据   列表   映射表
// 定义登录请求消息类型  name   pwd
message LoginRequest
{
    bytes name = 1;
    bytes pwd = 2;

}

// 定义登录响应消息类型
message LoginResponse
{
    ResultCode result = 1;
    bool success = 2;
}

message GetFriendListsRequest
{
    uint32 userid = 1;
}

message User
{
    bytes name = 1;
    uint32 age = 2;
    enum Sex
    {
        MAN = 0;
        WOMAN = 1;
    }
    Sex sex = 3;
}

message GetFriendListsResponse
{
    ResultCode result = 1;
    repeated User friend_list = 2;  // 定义了一个列表类型
}


// 在protobuf里面怎么定义rpc方法的类型 - service
service UserServiceRpc{
    rpc Login(LoginRequest) returns(LoginResponse);
    rpc GetFriendLists(GetFriendListsRequest) returns(GetFriendListsResponse);
}
# 生成
jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ protoc test.proto --cpp_out=./

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

10 protobuf实践讲解四

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

11 本地服务怎么发布成rpc服务一√

syntax = "proto3";

package fixbug;

option cc_generic_services = true;

message ResultCode{
    int32 errcode = 1;
    bytes errmsg = 2;
}

message LoginRequest{
    bytes name = 1;
    bytes pwd = 2;
}

message LoginResponse{
    ResultCode result = 1;
    bool success = 2;
}

service UserServiceRpc{
    rpc Login(LoginRequest) returns(LoginResponse);
}
# 生成
jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ protoc test.proto --cpp_out=./
#include <iostream>
#include <string>
#include "user.pb.h"
using namespace std;
using namespace fixbug;

#ifdef LOCAL

// 假设UserService 当前是一个本地服务,提供了两个进程内的本地方法 Login和GetFriendLists
class UserService{
public:
    bool Login(std::string name, std:string pwd){
        cout << "doing local service:Login" << endl;
        cout << "name:" << name << " pwd:" << pwd << endl;
    }
};

#else

class UserService: public UserServiceRpc{
public:
    bool Login(std::string name, string pwd){
        cout << "doing local service:Login" << endl;
        cout << "name:" << name << " pwd:" << pwd << endl;
    }

    // 重写基类UserServiceRpc的虚函数 下面这些方法都是框架直接调用的
    virtual void Login(::PROTOBUF_NAMESPACE_ID::RpcController* controller,
                       const ::fixbug::LoginRequest* request,
                       ::fixbug::LoginResponse* response,
                       ::google::protobuf::Closure* done){

    }
};



#endif


int main(){
    
    return 0;
}

12 本地服务怎么发布成rpc服务二√

#include <iostream>
#include <string>
#include "user.pb.h"
using namespace std;
using namespace fixbug;

#ifdef LOCAL // 假设UserService 当前是一个本地服务,提供了两个进程内的本地方法 Login和GetFriendLists

class UserService{
public:
    bool Login(std::string name, std:string pwd){
        cout << "doing local service:Login" << endl;
        cout << "name:" << name << " pwd:" << pwd << endl;
    }
};

#else

class UserService: public UserServiceRpc{
public:
    // 本地业务
    bool Login(std::string name, string pwd){
        cout << "doing local service:Login" << endl;
        cout << "name:" << name << " pwd:" << pwd << endl;
        
    }

    // 重写基类UserServiceRpc的虚函数 下面这些方法都是框架直接调用的
    // caller ===> Login(LoginRequest) ==> muduo ==> callee
    // callee ===> Login(LoginRequest) => 交到下面重写的这个Login方法
    virtual void Login(::google::protobuf::RpcController* controller,
                       const ::fixbug::LoginRequest* request, // 函数参数
                       ::fixbug::LoginResponse* response, // 函数返回值
                       ::google::protobuf::Closure* done){ 
        // 框架给业务上报了请求参数LoginRequest,应用获取相应数据做本地业务
        string name = request->name();
        string pwd = request->pwd();
        // 做本地业务
        bool login_result = Login(name, pwd);
        // 把响应写入
        ResultCode* code = response->mutable_result();
        code->set_errcode(0);
        code->set_errmsg("");
        response->set_success(login_result);
        
        // 执行回调操作, 执行响应对象数据的序列化和网络发送(都是由框架来完成的)
        done->run(); 
    }
};

#endif

int main(){
    
    return 0;
}

13 Mprpc框架基础类设计

14 Mprpc框架项目动态库编译

首先先定义一个.proto文件作文协议。

15 Mprpc框架的配置文件加载一×

这个用法需要查一下

int c = 0;
    std::string config_file;
    while(c = getopt(argc, argv, "i:") != -1){ // !!! 这里写的是啥  后面再看
        switch(c){
            case 'i':
                config_file = optarg;
                break;
            case '?':
                std::cout << "invalid args!" << std::endl;
                showArgsHelp();
                exit(EXIT_FAILURE);
            case ':':
                std::cout << "need <configfile>!" << std::endl;
                showArgsHelp();
                exit(EXIT_FAILURE);
            default:
                break;
        }
    }

16 Mprpc框架的配置文件加载二

    std::cout << "rpcserverip:" << m_config.Load("rpcserverip") << std::endl;
    std::cout << "rpcserverport:" << m_config.Load("rpcserverport") << std::endl;
    std::cout << "zookeeperip:" << m_config.Load("zookeeperip") << std::endl;
    std::cout << "zookeeperport:" << m_config.Load("zookeeperport") << std::endl;

output:

jixu@ubuntu:~/Mycode/ShiLei/mprpc/bin$ ./provider -i ./test.conf 
rpcserverip:127.0.0.1

rpcserverport:8000

zookeeperip:127.0.0.1

zookeeperport:5000
jixu@ubuntu:~/Mycode/ShiLei/mprpc/bin$ 

调试了一下为什么多出一个换行符,因为配置文件中,每行末尾会有一个换行符。

17 本地服务怎么发布成rpc服务一√

18 RpcProvider发布服务方法一×

理论太多,代码写完了再看。

19 RpcProvider发布服务方法二×

主要是实现RpcProvider发布服务的代码。

// 这里是框架给外部使用的,可以发布rpc方法的函数接口
void RpcProvider::NotifyService(google::protobuf::Service *service){

    ServiceInfo service_info;
    // 获取了服务对象的描述信息
    const google::protobuf::ServiceDescriptor* pserviceDesc = service->GetDescriptor();
    // 获取服务的名字
    std::string service_name = pserviceDesc->name();
    // 获取服务对象service的方法的数量
    int methodCnt = pserviceDesc->method_count();
    for(int i = 0; i < methodCnt; ++i){
        const google::protobuf::MethodDescriptor* pmethodDesc = pserviceDesc->method(i);
        std::string method_name = pmethodDesc->name();
        service_info.m_methodMap.insert({method_name, pmethodDesc});
    }
    service_info.m_service = service;
    m_serviceMap.insert({service_name, service_info});
    std::cout << "service_name: " << service_name <<std::endl;
}

20 RpcProvider分发rpc服务一√

主要是用protobuf实现一下rpc发布者与消费者之间通讯的协议:

syntax = "proto3";

package mprpc;

message RpcHeader{
    bytes service_name = 1;
    bytes method_name = 2;
    int32 args_size = 3;
}

header_size是为了记录消息的长度。然后把消息交给protobuf去反序列化,解析出服务名和方法名,还有参数的大小。把序列化的参数字符串再交给响应的proto类去反序列化。

void RpcProvider::OnMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buffer , muduo::Timestamp){
    std::string recv_buf = buffer->retrieveAllAsString();
    
    // 从字符流中读取前四个字节的内容
    uint32_t header_size = 0;
    recv_buf.copy((char*)&header_size, 4, 0);
    // 根据head_size读取数据头的原始字符流
    std::string rpc_header_str = recv_buf.substr(4, header_size);
    mprpc::RpcHeader rpcHeader;
    std::string service_name;
    std::string method_name;
    uint32_t args_size;
    if(rpcHeader.ParseFromString(rpc_header_str)){
        // 数据反序列化成功
        service_name = rpcHeader.service_name();
        method_name = rpcHeader.method_name();
        args_size = rpcHeader.args_size();
    }else{
        // 数据反序列化失败
        std::cout << "rpc_header_str: " << rpc_header_str << " parse error" << std::endl;
        return;
    }
    // 获取rpc方法参数的字符流数据
    std::string args_str = recv_buf.substr(4 + header_size, args_size);

    // 打印调试信息
    std::cout << "===========================" << std::endl;
    std::cout << "header_size: " << header_size << std::endl;
    std::cout << "rpc_header_str: " << rpc_header_str << std::endl;
    std::cout << "service_name: " << service_name << std::endl;
    std::cout << "args_size: " << args_size << std::endl;


}

22 RpcProvider分发rpc服务二

22 RpcProvider的rpc响应回调实现

23 RpcChannel的调用过程

谁提供的rpc服务,谁就要来定义proto文件。

24 实现RPC方法的调用过程一 √

实现的是请求远程调用的数据序列化以及网络发送。

25 实现RPC方法的调用过程二

为什么连接失败就调用exit,发送失败就return呢?

后面关闭套接字,尝试改成用智能指针使用自定义删除器。

26 点对点通信功能测试

服务端:

jixu@ubuntu:~/Mycode/ShiLei/mprpc/bin$ ./provider -i test.conf
rpcserverip:127.0.0.1
rpcserverport:8000
zookeeperip:127.0.0.1
zookeeperport:5000
service_name: UserServiceRpc
method_name: Login
RpcProvider start service at ip:127.0.0.1 port:8000
20230321 14:10:23.240633Z 17999 INFO  TcpServer::newConnection [RpcProvider] - new connection [RpcProvider-127.0.0.1:8000#1] from 127.0.0.1:59888 - TcpServer.cc:80
===========================
header_size: 25
rpc_header_str: 
UserServiceRpcLogin
service_name: UserServiceRpc
args_size: 19
===========================
doing local service:Login
name:zhang san pwd:123456
20230321 14:10:23.240916Z 17999 INFO  TcpServer::removeConnectionInLoop [RpcProvider] - connection RpcProvider-127.0.0.1:8000#1 - TcpServer.cc:109

客户端:

jixu@ubuntu:~/Mycode/ShiLei/mprpc/bin$ ./consumer -i test.conf
rpcserverip:127.0.0.1
rpcserverport:8000
zookeeperip:127.0.0.1
zookeeperport:5000
===========================
header_size: 25
rpc_header_str: 
UserServiceRpcLogin
service_name: UserServiceRpc
method_name: Login
args_str: 
        zhang san123456
===========================
rpc login response success :0

调试rpc login response success :0返回值不正确的问题:

jixu@ubuntu:~/Mycode/ShiLei/mprpc/bin$ gdb ./consumer -q
Reading symbols from ./consumer...
(gdb) b mprpcchannel.cpp:84
Breakpoint 1 at 0x1ba6d: file ../src/mprpcchannel.cpp, line 87.
(gdb) run -i test.conf
Starting program: /home/jixu/Mycode/ShiLei/mprpc/bin/consumer -i test.conf
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
rpcserverip:127.0.0.1
rpcserverport:8000
zookeeperip:127.0.0.1
zookeeperport:5000
===========================
header_size: 25
rpc_header_str: 
UserServiceRpcLogin
service_name: UserServiceRpc
method_name: Login
args_str: 
        zhang san123456
===========================

Breakpoint 1, MprpcChannel::CallMethod (this=0x55555558e6c0, method=0x55555559cca0, controller=0x0, 
    request=0x7fffffffdee0, response=0x7fffffffdec0, done=0x0) at ../src/mprpcchannel.cpp:87
87          char recv_buf[1024] = {0};
(gdb) n
88          int recv_size = 0;
(gdb) n
89          if(-1 == (recv_size = recv(clientfd, recv_buf, 1024, 0))){
(gdb) n
94          std::string response_str(recv_buf, 0, recv_size);
(gdb) n
95          if(response->ParseFromString(response_str)){
(gdb) n
100         close(clientfd);
(gdb) p recv_buf
$1 = "\n\000\020\001", '\000' <repeats 1019 times>
(gdb) n
101     }
(gdb) p response_str 
$2 = "\n" # !!!!!!!!!!!!!!!!!!!!
(gdb) 

在这里插入图片描述

27 Mprpc框架的应用示例

假设rpc服务方想要新发布一个方法Register。

  • 一、 那么首先要在proto 文件中添加:
    	message RegisterRequest{
        uint32 id = 1;
        bytes name = 1;
        bytes pwd = 2;
    }
    message RegisterResponse{
        ResultCode result = 1;
        bool success = 2;
    }
    
    
    service UserServiceRpc{
        rpc Login(LoginRequest) returns(LoginResponse);
        rpc Register(RegisterRequest) returns(RegisterResponse); // 新增
    }
    
  • 二 、生成proto代码:
    jixu@ubuntu:~/Mycode/ShiLei/mprpc/test/protobuf$ protoc test.proto --cpp_out=./
    
  • 编写业务代码:

28 RpcController控制模块实现 √

对于rpc服务的调用方来说,先定义一个代理对象Stub,传入一个channel,channel是框架实现的。
在远程调用的过程中,会有很多异常产生并直接return的情况,所以后续读函数的返回值response是没意义的。这时就要用到rpc方法的第一个参数Controller。
所以我们可以自定义一个MprpcController。没什么复杂的,就是记录rpc调用过程中可能发生的各种错误,然后在rpc调用返回后判断一下是否产生了错误,如果有错误就不继续解析response了。

29 日志系统设计实现一√

因为写日志是个磁盘I/O操作,所以我们不能占用业务的时间来写日志。
故创建一个消息队列,每个线程都可以往队尾写日志,同时新建一个线程,专门用于从队头取出日志写到日志文件中。

30 日志系统设计实现二√

31 异步日志缓冲队列实现

记得复习一下多线程。

32 Zookeeper分布式协调服务

33 zk服务配置中心和znode节点

zookeeper简介:什么是zookeeper

先按照这个教程装jdk:ubuntu安装jdk
再按照这个教程安装zookeeper:ubuntu安装zookeeper

# 启动服务
jixu@ubuntu:~/Downloads/zookeeper-3.4.10/bin$ ./zkServer.sh start
jixu@ubuntu:~/Documents/apache-zookeeper-3.7.1-bin/bin$ sudo netstat -tanp
[sudo] password for jixu: 
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp6       0      0 :::2181                 :::*                    LISTEN      4946/java   
# 启动一个zkClient试试

zk也有一个服务器和一个客户端,与mysql是一样的,也就是要启动一个sql server,然后我们自己作为客户端
在这里插入图片描述

34 zk的watcher机制和原生api安装

ZK的原生开发API(C/C++接口)

 # 进入src/c下
 sudo ./configure
 sudo make
 sudo make install
 # 过程中会报错,需要把MakeFile中的-Weror(把警告当错误)删掉,再执行第二步

ZookKeeper原生提供了C和Java的客户端编程接口,但是使用起来相对复杂,几个弱点:
2 设置监听watcher只能是一次性的,每次触发后需要重复设置
3 znode节点只存储简单的byte字节数组,如果存储对象,需要自己转换对象生成字节数组。

35 封装zookeeper的客户端类

测试:

jixu@jixu-ubuntu:~/Documents/zookeeper-3.4.10/bin$ ./zkCli.sh
Connecting to localhost:2181
2023-04-14 04:12:01,818 [myid:] - INFO  [main:Environment@100] - Client environment:zookeeper.version=3.4.10-39d3a4f269333c922ed3db283be479f9deacaa0f, built on 03/23/2017 10:13 GMT
2023-04-14 04:12:01,821 [myid:] - INFO  [main:Environment@100] - Client environment:host.name=jixu-ubuntu
2023-04-14 04:12:01,821 [myid:] - INFO  [main:Environment@100] - Client environment:java.version=1.8.0_361
2023-04-14 04:12:01,822 [myid:] - INFO  [main:Environment@100] - Client environment:java.vendor=Oracle Corporation
2023-04-14 04:12:01,822 [myid:] - INFO  [main:Environment@100] - Client environment:java.home=/home/jixu/Documents/jdk-8u361-linux-x64/jdk1.8.0_361/jre
2023-04-14 04:12:01,822 [myid:] - INFO  [main:Environment@100] - Client environment:java.class.path=/home/jixu/Documents/zookeeper-3.4.10/bin/../build/classes:/home/jixu/Documents/zookeeper-3.4.10/bin/../build/lib/*.jar:/home/jixu/Documents/zookeeper-3.4.10/bin/../lib/slf4j-log4j12-1.6.1.jar:/home/jixu/Documents/zookeeper-3.4.10/bin/../lib/slf4j-api-1.6.1.jar:/home/jixu/Documents/zookeeper-3.4.10/bin/../lib/netty-3.10.5.Final.jar:/home/jixu/Documents/zookeeper-3.4.10/bin/../lib/log4j-1.2.16.jar:/home/jixu/Documents/zookeeper-3.4.10/bin/../lib/jline-0.9.94.jar:/home/jixu/Documents/zookeeper-3.4.10/bin/../zookeeper-3.4.10.jar:/home/jixu/Documents/zookeeper-3.4.10/bin/../src/java/lib/*.jar:/home/jixu/Documents/zookeeper-3.4.10/bin/../conf:.:/home/jixu/Documents/jdk-8u361-linux-x64/jdk1.8.0_361/lib:/home/jixu/Documents/jdk-8u361-linux-x64/jdk1.8.0_361/jre/lib:
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:java.io.tmpdir=/tmp
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:java.compiler=<NA>
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:os.name=Linux
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:os.arch=amd64
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:os.version=5.19.0-38-generic
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:user.name=jixu
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:user.home=/home/jixu
2023-04-14 04:12:01,823 [myid:] - INFO  [main:Environment@100] - Client environment:user.dir=/home/jixu/Documents/zookeeper-3.4.10/bin
2023-04-14 04:12:01,824 [myid:] - INFO  [main:ZooKeeper@438] - Initiating client connection, connectString=localhost:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@306a30c7
Welcome to ZooKeeper!
2023-04-14 04:12:01,834 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1032] - Opening socket connection to server localhost/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
JLine support is enabled
2023-04-14 04:12:01,840 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@876] - Socket connection established to localhost/127.0.0.1:2181, initiating session
2023-04-14 04:12:01,845 [myid:] - INFO  [main-SendThread(localhost:2181):ClientCnxn$SendThread@1299] - Session establishment complete on server localhost/127.0.0.1:2181, sessionid = 0x1876ead296f0003, negotiated timeout = 30000

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] ls /
[UserServiceRpc, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /UserServiceRpc
[Login, Register]
[zk: localhost:2181(CONNECTED) 2] get /UserServiceRpc

cZxid = 0x2
ctime = Fri Apr 14 04:03:38 EDT 2023
mZxid = 0x2
mtime = Fri Apr 14 04:03:38 EDT 2023
pZxid = 0x4
cversion = 2
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 2
[zk: localhost:2181(CONNECTED) 3] get /UserServiceRpc/Login
127.0.0.1:8000
cZxid = 0x4
ctime = Fri Apr 14 04:03:38 EDT 2023
mZxid = 0x4
mtime = Fri Apr 14 04:03:38 EDT 2023
pZxid = 0x4
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x1876ead296f0000
dataLength = 14
numChildren = 0
[zk: localhost:2181(CONNECTED) 4] 

测试心跳消息:

jixu@jixu-ubuntu:~/Mycode/ShiLei/mprpc$ sudo tcpdump -i lo port 2181
[sudo] password for jixu: 
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
04:15:39.282951 IP localhost.54054 > localhost.2181: Flags [P.], seq 2103290286:2103290298, ack 1666618150, win 512, options [nop,nop,TS val 3426300562 ecr 3426290551], length 12
04:15:39.283466 IP localhost.2181 > localhost.54054: Flags [P.], seq 1:21, ack 12, win 512, options [nop,nop,TS val 3426300563 ecr 3426300562], length 20
04:15:39.283495 IP localhost.54054 > localhost.2181: Flags [.], ack 21, win 512, options [nop,nop,TS val 3426300563 ecr 3426300563], length 0
04:15:48.872189 IP localhost.40294 > localhost.2181: Flags [P.], seq 2478688742:2478688746, ack 2537665670, win 512, options [nop,nop,TS val 3426310151 ecr 3426300142], length 4
04:15:48.872263 IP localhost.40294 > localhost.2181: Flags [P.], seq 4:12, ack 1, win 512, options [nop,nop,TS val 3426310152 ecr 3426300142], length 8
04:15:48.872403 IP localhost.2181 > localhost.40294: Flags [.], ack 12, win 512, options [nop,nop,TS val 3426310152 ecr 3426310151], length 0
04:15:48.872806 IP localhost.2181 > localhost.40294: Flags [P.], seq 1:21, ack 12, win 512, options [nop,nop,TS val 3426310152 ecr 3426310151], length 20
04:15:48.872826 IP localhost.40294 > localhost.2181: Flags [.], ack 21, win 512, options [nop,nop,TS val 3426310152 ecr 3426310152], length 0
04:15:49.291792 IP localhost.54054 > localhost.2181: Flags [P.], seq 12:24, ack 21, win 512, options [nop,nop,TS val 3426310571 ecr 3426300563], length 12
04:15:49.292329 IP localhost.2181 > localhost.54054: Flags [P.], seq 21:41, ack 24, win 512, options [nop,nop,TS val 3426310572 ecr 3426310571], length 20
04:15:49.292354 IP localhost.54054 > localhost.2181: Flags [.], ack 41, win 512, options [nop,nop,TS val 3426310572 ecr 3426310572], length 0
04:15:58.883220 IP localhost.40294 > localhost.2181: Flags [P.], seq 12:16, ack 21, win 512, options [nop,nop,TS val 3426320162 ecr 3426310152], length 4
04:15:58.883270 IP localhost.40294 > localhost.2181: Flags [P.], seq 16:24, ack 21, win 512, options [nop,nop,TS val 3426320163 ecr 3426310152], length 8
04:15:58.883452 IP localhost.2181 > localhost.40294: Flags [.], ack 24, win 512, options [nop,nop,TS val 3426320163 ecr 3426320162], length 0
04:15:58.884005 IP localhost.2181 > localhost.40294: Flags [P.], seq 21:41, ack 24, win 512, options [nop,nop,TS val 3426320163 ecr 3426320162], length 20
04:15:58.884057 IP localhost.40294 > localhost.2181: Flags [.], ack 41, win 512, options [nop,nop,TS val 3426320163 ecr 3426320163], length 0
04:15:59.294002 IP localhost.54054 > localhost.2181: Flags [P.], seq 24:36, ack 41, win 512, options [nop,nop,TS val 3426320573 ecr 3426310572], length 12
04:15:59.294719 IP localhost.2181 > localhost.54054: Flags [P.], seq 41:61, ack 36, win 512, options [nop,nop,TS val 3426320574 ecr 3426320573], length 20
04:15:59.294770 IP localhost.54054 > localhost.2181: Flags [.], ack 61, win 512, options [nop,nop,TS val 3426320574 ecr 3426320574], length 0
04:16:08.894239 IP localhost.40294 > localhost.2181: Flags [P.], seq 24:28, ack 41, win 512, options [nop,nop,TS val 3426330173 ecr 3426320163], length 4
04:16:08.894292 IP localhost.40294 > localhost.2181: Flags [P.], seq 28:36, ack 41, win 512, options [nop,nop,TS val 3426330174 ecr 3426320163], length 8
04:16:08.894488 IP localhost.2181 > localhost.40294: Flags [.], ack 36, win 512, options [nop,nop,TS val 3426330174 ecr 3426330173], length 0
04:16:08.895027 IP localhost.2181 > localhost.40294: Flags [P.], seq 41:61, ack 36, win 512, options [nop,nop,TS val 3426330174 ecr 3426330173], length 20
04:16:08.895053 IP localhost.40294 > localhost.2181: Flags [.], ack 61, win 512, options [nop,nop,TS val 3426330174 ecr 3426330174], length 0
04:16:09.305122 IP localhost.54054 > localhost.2181: Flags [P.], seq 36:48, ack 61, win 512, options [nop,nop,TS val 3426330584 ecr 3426320574], length 12
04:16:09.305727 IP localhost.2181 > localhost.54054: Flags [P.], seq 61:81, ack 48, win 512, options [nop,nop,TS val 3426330585 ecr 3426330584], length 20
04:16:09.305764 IP localhost.54054 > localhost.2181: Flags [.], ack 81, win 512, options [nop,nop,TS val 3426330585 ecr 3426330585], length 0
04:16:18.905794 IP localhost.40294 > localhost.2181: Flags [P.], seq 36:40, ack 61, win 512, options [nop,nop,TS val 3426340185 ecr 3426330174], length 4
04:16:18.905827 IP localhost.40294 > localhost.2181: Flags [P.], seq 40:48, ack 61, win 512, options [nop,nop,TS val 3426340185 ecr 3426330174], length 8
04:16:18.905988 IP localhost.2181 > localhost.40294: Flags [.], ack 48, win 512, options [nop,nop,TS val 3426340185 ecr 3426340185], length 0
04:16:18.906366 IP localhost.2181 > localhost.40294: Flags [P.], seq 61:81, ack 48, win 512, options [nop,nop,TS val 3426340186 ecr 3426340185], length 20
04:16:18.906385 IP localhost.40294 > localhost.2181: Flags [.], ack 81, win 512, options [nop,nop,TS val 3426340186 ecr 3426340186], length 0
04:16:19.316382 IP localhost.54054 > localhost.2181: Flags [P.], seq 48:60, ack 81, win 512, options [nop,nop,TS val 3426340596 ecr 3426330585], length 12
04:16:19.316834 IP localhost.2181 > localhost.54054: Flags [P.], seq 81:101, ack 60, win 512, options [nop,nop,TS val 3426340596 ecr 3426340596], length 20
04:16:19.316853 IP localhost.54054 > localhost.2181: Flags [.], ack 101, win 512, options [nop,nop,TS val 3426340596 ecr 3426340596], length 0
04:16:28.916699 IP localhost.40294 > localhost.2181: Flags [P.], seq 48:52, ack 81, win 512, options [nop,nop,TS val 3426350196 ecr 3426340186], length 4
04:16:28.916728 IP localhost.40294 > localhost.2181: Flags [P.], seq 52:60, ack 81, win 512, options [nop,nop,TS val 3426350196 ecr 3426340186], length 8
04:16:28.917008 IP localhost.2181 > localhost.40294: Flags [.], ack 60, win 512, options [nop,nop,TS val 3426350196 ecr 3426350196], length 0
04:16:28.917430 IP localhost.2181 > localhost.40294: Flags [P.], seq 81:101, ack 60, win 512, options [nop,nop,TS val 3426350197 ecr 3426350196], length 20
04:16:28.917444 IP localhost.40294 > localhost.2181: Flags [.], ack 101, win 512, options [nop,nop,TS val 3426350197 ecr 3426350197], length 0
04:16:29.318247 IP localhost.54054 > localhost.2181: Flags [P.], seq 60:72, ack 101, win 512, options [nop,nop,TS val 3426350598 ecr 3426340596], length 12
04:16:29.319942 IP localhost.2181 > localhost.54054: Flags [P.], seq 101:121, ack 72, win 512, options [nop,nop,TS val 3426350599 ecr 3426350598], length 20
04:16:29.320051 IP localhost.54054 > localhost.2181: Flags [.], ack 121, win 512, options [nop,nop,TS val 3426350599 ecr 3426350599], length 0

36 zk在项目上的应用实践√

37项目总结以及编译脚本√

框架总结

框架给rpc服务方提供:

MprpcApplication:mprpc应用类,主要是执行一些初始化操作,读取配置文件等等;
RpcProvider:框架提供的专门发布rpc服务的网络对象类,在该类中有以下几个成员函数:
NotifyService

  • **成员函数,用于发布rpc方法的函数接口,它的形参类型是google::protobuf::Service *service,rpc服务方编写的rpc服务类都继承于这个service基类。举例:UserService继承于UserServiceRpc,目的是重写其中的rpc方法;UserServiceRpc又继承于service。
  • 函数主要的作用是将服务以及服务对应的rpc方法存到map中,以供收到rpc请求时快速检索rpc调用方想调用的方法是什么。
// 这里是框架给外部使用的,可以发布rpc方法的函数接口
void RpcProvider::NotifyService(google::protobuf::Service *service){

    ServiceInfo service_info;
    // 获取了服务对象的描述符的指针 GetDescriptor是个纯虚函数,在proto生成的UserServiceRpc类中会重写这个方法,在该描述符中包括服务的名字,rpc方法的数量,同时还可以返回每个rpc方法的描述符,每个rpc方法的描述符中又包括方法的名字,从下面的代码可以看出
    const google::protobuf::ServiceDescriptor* pserviceDesc = service->GetDescriptor();
    // 获取服务的名字
    std::string service_name = pserviceDesc->name();
    std::cout << "service_name: " << service_name <<std::endl;
    // 获取服务对象service的方法的数量 
    int methodCnt = pserviceDesc->method_count();
    for(int i = 0; i < methodCnt; ++i){
        const google::protobuf::MethodDescriptor* pmethodDesc = pserviceDesc->method(i);
        std::string method_name = pmethodDesc->name();
        // 最关键的是在这一行,我们要把方法名和方法对应的描述符存储到一个map中!!!!!!
        service_info.m_methodMap.insert({method_name, pmethodDesc});
        std::cout << "method_name: " << method_name <<std::endl;
    }
    service_info.m_service = service; // 一个rpc服务节点可能有多个服务,每一个服务对应一个map
    m_serviceMap.insert({service_name, service_info});
    
}

Run():主要是启动网络服务,后面的OnMessage回调的过程比较重要。

// 启动rpc服务节点,开始提供rpc远程远程网络调用服务
void RpcProvider::Run(){
    // 组合了TcpServer
    std::unique_ptr<muduo::net::TcpServer> m_tcpserverPtr;
    std::string ip = MprpcApplication::GetInstance().GetConfig().Load("rpcserverip");
    uint16_t port = atoi(MprpcApplication::GetInstance().GetConfig().Load("rpcserverport").c_str());
    muduo::net::InetAddress address(ip, port);
    // 创建TcpServer对象
    muduo::net::TcpServer server(&m_eventLoop, address, "RpcProvider");
    // 绑定连接回调和消息读写回调方法 分离了网络代码和业务代码
    server.setConnectionCallback(std::bind(&RpcProvider::OnConnection, this, std::placeholders::_1));
    server.setMessageCallback(std::bind(&RpcProvider::OnMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    // 绑定muduo库的线程数量
    server.setThreadNum(4);


    // rpc服务启动,打印信息
    std::cout << "RpcProvider start service at ip:" << ip << " port:" << port << std::endl;
    // 启动网络服务
    server.start();
    m_eventLoop.loop();
}

OnMessage():在这个回调函数中:

  • 当收到从网上收到字节流时,首先要从数据的前四个字节提取出rpcHeader的长度,rpcHeader包括服务名,方法名,参数字符串的长度。
  • 通过proto反序列化出rpcHeader,就得到了包括服务名,方法名,参数字符串的长度。
  • 解析出参数字符串;
  • 这个时候就可以通过rpcHeader中的服务名,方法名在m_serviceMap中找到对应的rpc方法的描述符。
  • 再然后就可以通过rpc方法的描述符得到一个request对象和response对象。
	// 生成rpc方法调用的请求request和响应response参数
    google::protobuf::Message* request = service->GetRequestPrototype(method).New();
    if(!request->ParsePartialFromString(args_str)){
        std::cout << "request parse error, content: " << args_str << std::endl;
    }
    google::protobuf::Message* response = service->GetResponsePrototype(method).New();
  • 这时就可以把参数字符串反序列化到request对象中了。
  • 现在知道了服务名,方法名,方法参数,返回值类型,那么框架就可以通过这些来调用rpc方法了:
    // 给下面的method方法的调用,绑定一个Closure的回调函数
    google::protobuf::Closure* done = google::protobuf::NewCallback<RpcProvider, const muduo::net::TcpConnectionPtr&, google::protobuf::Message*>(this, &RpcProvider::SendRpcResponse, conn, response);

    // 在框架上根据远端rpc请求,调用当前rpc节点上发布的方法
    service->CallMethod(method, nullptr, request, response, done);
    

void RpcProvider::SendRpcResponse(const muduo::net::TcpConnectionPtr& conn, google::protobuf::Message* response){
    std::string response_str;
    if(response->SerializeToString(&response_str)){ // response进行序列化
        // 序列化成功后,通过网络把rpc方法执行的结果发送回给rpc的调用方
        conn->send(response_str);
    }
    else{
        std::cout << "serialize response_str error!" << std::endl;
    }
    conn->shutdown(); // 模拟http的短连接服务,由rpcProvider主动断开连接
}
  • CallMethod第二个参数controller的作用:记录rpc调用过程中发生的各种错误。

  • done !!!这个要好好看看:首先就是,这个callMethod是Service定义的纯虚函数,UserServiceRpc中重写了纯虚函数,然后用户定义的UserService继承了UserServiceRpc也就继承了这个类。同时用户在UserService也重写了rpc方法Login(),所以使用在UserService对象中调用CallMethod进而调用rpc方法Login会发生动态绑定,调用用户重写的Login(),并且在其中调用done->Run(),执行rpc方法的返回值数据的序列化和网络发送,也就是说,这个done,起一个回调函数的作用。done是由框架来定义的并且传递给用户的Rpc方法。那就有必要分析google::protobuf::NewCallback这个函数了:
    1 NewCallback是个函数模板,三个模板参数分别代表要调用的回调函数所属的类类型,回调函数的两个参数;
    2 NewCallback的形参分别是回调函数所属的类的指针,回调函数的函数指针,回调函数的两个参数。
    3 用这些参数来构造一个Closure类型的MethodClosure2对象,其中有个Run()方法,帮我们调用自己的回调函数。第三个参数代表这个临时构造的对象用完就要自己及时销毁。
    4 再看我们自己往NewCallback中传入了什么:this, &RpcProvider::SendRpcResponse, conn, response(RpcProvider的指针,回调函数的地址,Tcp连接对象,rpc方法的未序列化的返回值)。

template <typename Class, typename Arg1, typename Arg2>
inline Closure* NewCallback(Class* object, void (Class::*method)(Arg1, Arg2),
                            Arg1 arg1, Arg2 arg2) {
  return new internal::MethodClosure2<Class, Arg1, Arg2>(
    object, method, true, arg1, arg2);
}
template <typename Class, typename Arg1, typename Arg2>
class MethodClosure2 : public Closure {
 public:
  typedef void (Class::*MethodType)(Arg1 arg1, Arg2 arg2);

  MethodClosure2(Class* object, MethodType method, bool self_deleting,
                 Arg1 arg1, Arg2 arg2)
    : object_(object), method_(method), self_deleting_(self_deleting),
      arg1_(arg1), arg2_(arg2) {}
  ~MethodClosure2() {}

  void Run() override {
    bool needs_delete = self_deleting_;  // read in case callback deletes
    (object_->*method_)(arg1_, arg2_);
    if (needs_delete) delete this;
  }

所以说框架到底为rpc服务方提供了什么呢?它提供了rpcProvider类。
在NotifyService中接受rpc服务方传入的服务对象,帮忙发布了服务,意思是将服务和服务中的方法存到Map中。
当收到rpc调用方发来的请求数据时,rpcProvider类帮忙解析了请求头,进而调用服务方的本地方法,再把返回值发回给调用方。

给rpc调用方提供

rpc服务方要自己继承ServiceRpc实现子类;
对于rpc调用方来说,先看一下整体的rpc流程吧:

1 fixbug::UserServiceRpc_Stub stub(new MprpcChannel());
2 stub.Login(nullptr, &request, &response, nullptr);
3 在Login中:这个channel_就是框架定义的channel
channel_->CallMethod(descriptor()->method(0),
                       controller, request, response, done);
到此,函数的返回值也就存到response中了。

因此,我们要实现的也就是MprpcChannel类,它的作用就是序列化参数-发送请求-接受返回值。但是这里只有一个channel。那有多个channel怎么办?

分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述 分布式架构体系描述
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值