当前位置: 首页 > news >正文

施磊老师rpc(一)

文章目录

  • mprpc项目
    • **项目概述**:
    • 深入学习到什么
    • **前置学习建议**:
    • 核心内容
    • 其他技术与工具
    • **项目特点与要求**:
    • **环境准备**:
  • 技术栈
  • 集群和分布式理论
    • 单机聊天服务器案例分析
    • 集群聊天服务器分析
    • 分布式系统介绍
    • 多个模块的局限
    • 引入分布式 RPC 通信框架的意义
    • 三大形态对比--**重点**
  • RPC 通信原理讲解整理
    • 为什么要使用 RPC?
    • RPC 通信的完整过程(调用链)
    • 关键模块说明(图中重要角色)
    • 返回结果的组成与处理
    • 框架的作用与实现目标
    • 举例巩固(Login 方法调用)
    • protobuf优点
    • 总结
  • 环境配置
    • 类似于集群项目
    • protobuf
  • protobuf使用(一)
    • 安装vscode插件
    • 简单使用
    • 代码
    • protoc编译
    • 序列化和反序列化使用
    • 编译注意
  • protobuf使用(二)
    • string和**bytes**
      • `bytes` 类型的作用:
      • 与 `string` 的区别:
      • 注意事项
    • protobuf的枚举
    • repeated(重复)
    • repeated常用方法(C++)
    • message嵌套message
    • mutable_字段--**重点**
    • add_成员和()
    • main使用
    • 重要的是-学会看**.h**里面的函数

mprpc项目

分布式 网络通信 框架

基于muduo+protobuf

业界优秀的RPC框架:baidu的brpc,google的grpc

项目概述

本课程将使用 C/C++ 编写分布式网络通信框架项目,重点讲解从单机服务器到集群服务器,再到项目模块化分解与分布式部署的过程。

深入学习到什么

希望 可以 对 单机----集群-----分布式 有更好的理解

前置学习建议

学习本项目前,建议先完成 C/C++ 项目集群的网络聊天通信项目,以便更好地理解服务器集群概念及其优势,为学习分布式知识打下基础。

技术选型

  • 网络库:采用 muduo 高性能网络库(底层基于 I/O 线程池模型) 。
  • 序列化 / 反序列化:使用 protobuf 处理数据序列化和反序列化,以及远程调用方法的识别、参数处理。
  • 命名:基于 muduo 库和 protobuf 首字母,将项目命名为 mprpc。

核心内容

  • 讲解集群与分布式的概念及原理。
  • 剖析 rpc 远程过程调用的原理与实现。
  • 阐述服务注册中心(如 ZooKeeper)在分布式环境中的作用。

其他技术与工具

  • 涉及 C++11 和 C++14 的新语法(如线程级别的本地变量、绑定器与函数对象等)。
  • 使用 VS Code 进行跨平台开发,在 Linux 环境下远程开发项目。
  • 介绍 muduo 库网络编程、conf 配置文件读取、cmake 构建集成编译环境及 GitHub 项目托管。

项目特点与要求

项目代码量虽比集群聊天项目少,但对技术栈的理解深度和广度要求更高,更注重对集群和分布式的理解 。

环境准备

开发前需掌握 Linux 环境下 muduo 网络库(依赖 boost 库)的安装,相关安装步骤可参考博主博客,且 muduo 库网络编程示例、cmake 构建编译环境在集群聊天服务器项目中已详细讲解。

技术栈

  1. 集群和分布式概念以及原理
  2. RPC远程过程调用原理以及实现
  3. Protobuf数据序列化和反序列化协议
  4. ZooKeeper分布式一致性协调服务应用以及编程
  5. muduo网络库编程
  6. conf配置文件读取
  7. 异步日志
  8. CMake构建项目集成编译环境
  9. github管理项目

代码没有集群多, 但是 知识更深入

集群和分布式理论

单机聊天服务器案例分析

服务器模块与业务:以单机聊天服务器为例,其包含用户管理、好友管理、群组管理、消息管理和后台管理五个模块。每个模块对应多项特定业务,如用户管理包括登录、注册、注销;好友管理涉及添加、删除好友等,这些业务由一个或多个相关函数实现。

性能与设计瓶颈:

  • 硬件资源限制单机服务器受硬件资源制约,例如 32 位 Linux 系统的聊天服务器,进程资源耗尽时,最多仅能支持约两万用户同时在线,难以承载更多客户端连接与服务。
  • 运维与代码编译成本高:由于模块都在同一项目运行单元,任意模块的修改(哪怕只是一行代码),都需重新编译整个项目代码(耗时约 2 小时),并重新部署(耗时约 3 小时),成本巨大。
  • 硬件资源分配不合理:不同模块对硬件资源需求不同,存在 CPU 密集型和 IO 密集型模块。但单机服务器只能采用平衡方案部署,无法针对各模块需求进行硬件资源的精准匹配。

集群聊天服务器分析

性能提升:通过水平扩展硬件资源,增加服务器数量(如三台或更多),每台独立运行聊天服务器程序,解决了单机服务器受硬件资源限制导致的用户并发量低的问题。

存在问题

  • 编译成本高:各服务器上的模块仍在同一项目中部署,运行于一个服务进程,因此任意模块修改仍需整体重新编译代码,且需多次部署到不同服务器,运维成本更高
  • 硬件资源分配不合理:集群只是简单扩展机器,无法针对不同模块(CPU 密集型或 IO 密集型)的硬件资源需求进行精准部署,存在资源浪费。例如后台管理模块并发需求低,却随整体系统在多台服务器部署 。

其他特点应用:集群部署方式简单,在高并发突发场景(如双 11)能快速通过增加服务器和负载均衡器提升服务能力

在集群中机器数量与性能并非成正比,原因如下:

  • 通信成本:机器增多使节点间通信开销增大,占用带宽与处理时间,易引发网络拥塞。
  • 分配难题:数据和任务难以在更多机器上均匀分配,易造成资源浪费。
  • 复杂故障:系统复杂度随机器数上升,故障和配置问题更易影响性能。
  • 并行局限:部分任务不适合大规模并行或并行度有限,加机器也无法提升性能。

以下任务不适合大规模集群并行处理:

  • 顺序依赖型:步骤间严格先后关联,无法拆分并行,如按序的数据清洗与分析。
  • 高通信成本型:执行中需频繁大量数据交互,易受网络带宽制约,如高频金融交易数据处理。
  • 任务粒度过小:任务简单微小,集群调度、协调开销超执行时间,如大量小文件简单格式转换。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分布式系统介绍

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

定义与特点:将一个工程拆分为多个模块,每个模块独立部署为可运行的服务进程,多台服务器(分布式节点)协同工作构成完整系统。与集群区别在于,集群中每台服务器运行完整系统,而分布式是多台服务器共同组成一个系统 。

解决的问题

  • 并发与资源优化:可根据分布式节点的并发需求灵活扩容,如对用户管理模块所在节点增加服务器以支持更高并发,同时合理利用其他节点空闲资源,提升资源利用率。
  • 编译与部署优化模块独立部署,单个模块修改仅需重新编译和更新该模块,无需影响其他模块,大大降低编译和部署成本。
  • 硬件匹配优化:模块拆分后,可依据各模块特性(CPU 密集型或 IO 密集型)精准匹配硬件资源,实现资源的合理配置。

潜在问题与应对:分布式系统中部分节点故障可能影响整体服务,但实际生产中可通过配置主备服务器等容灾方案保障高可用性 。

多个模块的局限

模块划分困难

  • 模块之间的边界不清晰,容易出现功能重叠或代码重复。
  • 模块耦合度高,修改难、维护难。
  • 若划分不当,容易造成大量重复代码、逻辑冗余、维护成本高。

模块之间的通信复杂–重点

  • 分布式部署后,模块间通信需跨进程、跨机器
  • 函数调用从本地调用变为远程调用,需涉及:
    • 函数名、参数传输
    • 网络通信、序列化/反序列化
    • 异常处理、响应返回等机制

引入分布式 RPC 通信框架的意义

核心作用

让“跨主机远程调用函数”像“调用本地函数”一样简单透明

解决的核心问题

  1. 统一通信流程,屏蔽底层复杂性
    • 请求封装 + 网络传输 + 响应处理全部自动完成。
  2. 提高模块间调用效率与开发体验
    • 用户感知不到远程调用的差异,只需像本地函数一样使用接口。
  3. 支持参数序列化与传输
    • 使用 Protobuf 进行高效的数据结构序列化。
  4. 自动服务发现与定位
    • 通过 ZooKeeper 注册中心查找服务位置,实现动态服务绑定。

三大形态对比–重点

系统形态特点优势局限性
单机服务器所有模块在一个进程开发简单扩展性差、耦合高
集群服务器多台相同服务器水平扩展、简单粗暴不是线性扩展、资源浪费
分布式模块化系统各模块独立部署高可维护性、易扩展、低耦合设计复杂、通信困难(需RPC)

RPC 通信原理讲解整理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么要使用 RPC?

  1. 为了解耦与扩展:
    • 大型系统按需模块化(不同模块对硬件/并发等要求不同),分布式部署成为必然。
    • 模块分布在不同进程、甚至不同机器上,相互之间仍需调用方法 —— 就必须跨进程/跨机器通信
  2. 屏蔽底层通信细节:
    • 本质是“远程函数调用”,但不同于本地函数调用的直接跳转和传参。
    • 不希望每个开发者都去手动处理 socket、序列化、反序列化、错误码等细节。
    • 引入“框架”来自动完成这些通信细节,开发者只专注于业务逻辑即可。

RPC 通信的完整过程(调用链)

举例:用户模块调用好友模块的 getUserFriendList(userId) 方法(模块部署在不同服务器)

Caller(调用方)          →         Stub(客户端代理)          →         网络层↓                                          ↓                                     ↓发起调用(方法名+参数) → 序列化(打包)         → 网络发送↑                                          ↑                                     ↑接收返回(结果/错误) ← 反序列化(解包) ← 网络接收
步骤说明
1. 调用方发起函数调用比如:getUserFriendList(userId),但这个方法实际存在于另一台机器。
2. Stub 代理类拦截调用替你处理所有 RPC 通信细节。
3. 参数序列化(打包)方法名、参数 → 序列化成字节流(如 JSON、Protobuf)
4. 网络传输使用网络库(如 muduo)将字节流发送到目标服务器
5. 服务端接收网络层接收到请求后交给服务端的 Stub 处理
6. 参数反序列化字节流 → 方法名 + 参数
7. 执行远程函数找到目标函数(如 getUserFriendList),执行逻辑处理
8. 返回结果处理执行结果、错误码、错误信息 → 序列化返回
9. 调用方接收响应解包结果 → 返回给应用层,像本地函数一样使用返回值

关键模块说明(图中重要角色)

角色功能简述
Caller发起方(如用户模块),调用远端方法
Stub(客户端桩)代理模块,封装参数、处理序列化、发送请求等
网络层通信基础设施(如 muduo 库),负责字节流的收发
Stub(服务端桩)接收数据并反序列化,请求转发到本地业务模块
Callee被调方(如好友模块),真正执行业务方法
结果返回路径与调用路径对称,同样涉及打包、网络传输和反序列化

返回结果的组成与处理

  • 返回值通常包括:
    1. 错误码(errorCode)
    2. 错误信息(errorMessage)
    3. 业务数据(result)
  • 如果错误码为非零,说明远程执行出错,不应使用返回值,仅使用错误信息

框架的作用与实现目标

  1. 由框架来完成:
    • 参数/返回值的序列化与反序列化
    • 方法名的标识与分发
    • 网络通信(请求发送/接收)
    • 错误处理与返回机制
  2. 开发者只需写业务逻辑函数,像调用本地函数一样调用远程服务

举例巩固(Login 方法调用)

  • 示例函数:login(string name, string password)
  • 发起调用:login("zhangsan", "123456")
  • 步骤:
    • Stub 序列化请求(函数名 + 参数)
    • 网络发送请求
    • 远端反序列化 + 调用 login()
    • 执行返回 true/false + 错误码 + 信息
    • 远端再次序列化发送
    • 调用方反序列化,判断错误码再处理返回值

protobuf优点

高效的序列化性能

  • 体积小:二进制格式,比 JSON、XML 更精简,节省网络带宽。
  • 速度快:序列化和反序列化速度远快于 JSON/XML,适合高频数据传输场景。
  • 示例:1000 条用户消息用 JSON 可能几百 KB,而 Protobuf 仅几十 KB。

跨语言支持

  • 支持多种语言自动生成代码(C++、Java、Python、Go 等)。
  • 不同平台、语言之间通信无需手写解析逻辑,提高开发效率。
  • 示例:后端使用 C++,前端用 JavaScript,通过 proto 文件即可对接。

总结

  • RPC 的目标:让远程调用就像本地函数调用一样简单
  • 框架解决的是“通信”本质问题,而不是业务逻辑问题
  • 图中的每一步都需要代码支持,RPC 框架的核心就是实现这些自动化处理

zookeeper服务配置中心(专门做服务发现)

环境配置

类似于集群项目

protobuf

github 进行下载安装 https://github.com/protocolbuffers/protobuf

一定要 下载 包里面 有 autogen.sh 的版本

没有的 就是 高版本, bazel 和 cmake 都不好用, bug特别多!!

折腾了 半天, 高版本 一个是 安装步骤变了, 一个是 bug一堆!!

不要安装 21 版本之上, 一堆bug, 就装 21版本及以下

sudo apt-get update
sudo apt-get install autoconf automake libtool curl make g++ unzip
git clone 下来
./autogen.sh
./configure
make && sudo make install
sudo ldconfig

protobuf使用(一)

内容并不多, 后续 从实践中 学习

安装vscode插件

vscode-proto3

简单使用

Protobuf 中,package 后面跟的就是 包名,表示该 .proto 文件中定义的所有消息、服务、枚举等都属于这个“命名空间”,称为 包名

包名是你自己定义的一个标识符,用来给这组 protobuf 定义加上“命名空间”。

代码

test/protobuf/test.proto------proto 配置文件

syntax = "proto3"; //声明protobuf的版本package hzhpro; // 声明代码所在的包(例如c++就是namespace)// 定义登录请求消息类型 name pwd
message LoginRequest
{string name = 1;string pwd = 2;
}// 定义登录响应消息类型
message LoginResponse
{int32 errcode = 1;string errmsg = 2;bool success = 3;
}

protoc编译

–cpp_out=OUT_DIR Generate C++ header and source.

protoc test.proto --cpp_out=./

生成

test.pb.cc  test.pb.h

messgae 相当于 class类, 里面的 相当于 成员变量

序列化和反序列化使用

test/protobuf/main.cc

#include "test.pb.h"
#include <iostream>
#include <string>
using namespace hzhpro;  // 实际开发 要少用命名空间int main()
{// 封装了login请求对象的数据LoginRequest req;req.set_name("zhang san");req.set_pwd("123123");// 对象数据序列化=>char*std::string send_buf;   if(req.SerializeToString(&send_buf)){std::cout<< send_buf.c_str()<<std::endl;}// 从send_buf反序列化LoginRequest reqB;if(reqB.ParseFromString(send_buf)){std::cout<<reqB.name()<<std::endl;std::cout<<reqB.pwd()<<std::endl;}return 0;
}

编译注意

必须加 pthread-----因为 Protobuf 内部使用了线程相关的功能(如 std::thread, pthread_create

g++ main.cc test.pb.cc -lprotobuf -pthread

protobuf使用(二)

string和bytes

在 Protobuf 中,bytes 是一种字段类型,表示原始二进制数据,用途非常广泛。

bytes 类型的作用:

它可以用来存储:

  • 二进制数据(图片、文件内容、压缩数据)
  • 自定义序列化的结构体
  • 加密密钥、哈希值
  • 或者就是一个 UTF-8 编码的字符串(但不推荐当字符串来用)

string 的区别:

类型内容编码是否可包含 \0推荐用途
stringUTF-8❌ 不可包含正常文本(人读的)
bytes原始数据✅ 可以包含任意二进制数据

注意事项

  • 使用 bytes 时不能用 set_content("str") 来设置包含 \0 的数据,否则会截断。
  • 应使用 set_content(const void* data, size_t size)

protobuf的枚举

.proto 中每个枚举成员 必须指定数值(不自动递增)。

必须用分号 ; 结束每一行,这和 C++ 是不同的。

枚举成员名建议用 全大写字母,符合 protobuf 的命名习惯。

repeated(重复)

repeated 类型 字段名 = 编号;
message FriendList {repeated string friends = 1;
}

下面这个是 基本类型

FriendList list;
list.add_friends("Tom");
list.add_friends("Jerry");for (int i = 0; i < list.friends_size(); ++i) {std::cout << list.friends(i) << std::endl;
}

repeated常用方法(C++)

  • add_字段()     → 添加一个元素
  • 字段_size()     → 获取数量
  • 字段(index)    → 获取第 index 个元素(从 0 开始)
  • mutable_字段()  → 获取可修改的容器(高级操作)

message嵌套message

test/protobuf/test.proto

syntax = "proto3"; //声明protobuf的版本package hzhpro; // 声明代码所在的包(例如c++就是namespace)message ResultCode
{int32 errcode = 1;bytes errmsg = 2;
}// 定义登录请求消息类型 name pwd
message LoginRequest
{bytes name = 1;bytes pwd = 2;
}// 定义登录响应消息类型
message LoginResponse
{ResultCode result = 1;// int32 errcode = 1;// bytes errmsg = 2;bool success = 3;
}message GetFriendListsRequest
{uint32 userid = 1;  // 获取谁的请求
}message User
{bytes name =1;uint32 age = 2;enum  Sex  // 枚举写法注意{MAN=0;WOMAN=1;}Sex sex=3;
}message GetFriendResponse
{// int32 errcode = 1;  // 代码重复// bytes errmsg = 2;ResultCode result = 1;repeated User friend_list=2; // 定义了一个列表类型,  这个_list没啥特殊意义
}

重新编译, 注意 vscode 缓存, 容易没反应, 重新拉一下 头文件

mutable_字段–重点

通过查找 pb.h 的 result 函数 ===== 这个result 就是 那个ResultCode 类对象

result 返回 const 引用, 不能修改值

mutable_result 返回指针, 可以修改值

const ::hzhpro::ResultCode& result() const;
::hzhpro::ResultCode* mutable_result();
void set_allocated_result(::hzhpro::ResultCode* result);
函数名返回类型用途
result()const ResultCode&只读访问
mutable_result()ResultCode*可写访问
set_allocated_result(ResultCode*)void设置已存在对象的所有权(高级用法)

add_成员和()

::hzhpro::User* add_friend_list();
const ::hzhpro::User& friend_list(int index) const;   //查看第几个,  index 根据上面 

main使用

LoginResponse rsp;
ResultCode *rc = rsp.mutable_result();
rc->set_errcode(1);
rc->set_errmsg("登录处理失败");

自定义类型的 add_

int main()
{// LoginResponse rsp;// ResultCode *rc = rsp.mutable_result();// rc->set_errcode(1);// rc->set_errmsg("登录处理失败");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(26);user1->set_sex(User::MAN);User *user2 = rsp.add_friend_list();user2->set_name("zhang san-2");user2->set_age(26);user2->set_sex(User::MAN);User *user3 = rsp.add_friend_list();user3->set_name("zhang san-3");user3->set_age(26);user3->set_sex(User::WOMAN);std::cout<<rsp.friend_list_size()<<std::endl;User user = rsp.friend_list(2);std::string userstr;if(user.SerializeToString(&userstr)){// std::cout<<userstr.c_str()<<std::endl;// 这个有问题, 序列化后是二进制数据流, 本身是 字符串能打印出来, 要是有别的 类型, 就不好说了}User userB;if(userB.ParseFromString(userstr)){std::cout<<userB.name()<<std::endl;std::cout<<userB.age()<<std::endl;std::cout<<userB.sex()<<std::endl;}return 0;
}
3
zhang san-3
26
1

重要的是-学会看**.h**里面的函数

相关文章:

  • Kubernetes 核心组件架构详解
  • PostgreSQL中的SSL
  • Nginx核心功能02
  • 【android bluetooth 协议分析 01】【HCI 层介绍 2】【Malformed Packet 介绍】
  • IsaacLab最新2025教程(7)-创建Interactive Scene
  • jmeter读取CSV文件中文乱码的解决方案
  • JavaScript 中循环控制语句跳出与示例说明
  • 测试 用例篇
  • 一种基于光源评估并加权平均的自动白平衡方法(一)
  • 用Python构建自动驾驶传感器融合算法:从理论到实践
  • foxmail时不时发送不了邮件问题定位解决过程
  • EasyRTC嵌入式音视频实时通话SDK技术,打造低延迟、高安全的远程技术支持
  • Java使用xmind8提供plugin sdk导出图片
  • 零基础学指针2
  • 第三部分:特征提取与目标检测
  • 计算机视觉综合实训室解决方案
  • Java:List集合size>0但元素为:ALL elements are null
  • 前端开发 Markdown 编辑器与富文本编辑器详解
  • 湖北理元理律师事务所:从法律视角看债务优化的合规实践
  • 腾讯云web服务器配置步骤是什么?web服务器有什么用途?
  • 中国金茂向滨江集团提供11.21亿元诚意金借款,拟合作开发3月获取的地块
  • 2025年第一批“闯中人”已经准备好了
  • 郭向阳任广东省公安厅分管日常工作副厅长(正厅级)
  • 美的集团一季度净利增长38%,库卡中国机器人接单增超35%
  • 量子传感新技术“攻克”退相干难题
  • 海量数据处于“原矿”状态,数据价值释放如何破局?