Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
T

tinyrpc

> 前端框架
Open source

c++ async rpc framework. 14w+qps.

1.7K stars0 likes0 views
WebsiteGitHub

About

c++ async rpc framework. 14w+qps.

作者:**ikerli** **2022-05-13** **使用 TinyRPC, 轻松地构建高性能分布式 RPC 服务!** - [1. 概述](#1-概述) - [1.1. TinyRPC 特点](#11-tinyrpc-特点) - [1.2. TinyRPC 支持的协议报文](#12-tinyrpc-支持的协议报文) - [1.3. TinyRPC 的 RPC 调用](#13-tinyrpc-的-rpc-调用) - [1.3.1. 阻塞协程式异步调用](#131-阻塞协程式异步调用) - [1.3.2. 非阻塞协程式异步调用](#132-非阻塞协程式异步调用) - [2. 性能测试](#2-性能测试) - [2.1. HTTP echo 测试 QPS](#21-http-echo-测试-qps) - [3. 安装 TinyRPC](#3-安装-tinyrpc) - [3.1. 安装必要的依赖库](#31-安装必要的依赖库) - [3.1.1. protobuf](#311-protobuf) - [3.1.2. tinyxml](#312-tinyxml) - [3.2. 安装和卸载 (makefile)](#32-安装和卸载-makefile) - [3.2.1. 安装 TinyRPC](#321-安装-tinyrpc) - [3.2.2. 卸载 TinyRPC](#322-卸载-tinyrpc) - [3.3. 安装和卸载 (cmake)](#33-安装和卸载-cmake) - [3.3.1. 安装 TinyRPC](#331-安装-tinyrpc) - [3.3.2. 卸载 TinyRPC](#332-卸载-tinyrpc) - [4. 快速上手](#4-快速上手) - [4.1. 搭建基于 TinyPB 协议的 RPC 服务](#41-搭建基于-tinypb-协议的-rpc-服务) - [4.1.1. 实现 Protobuf 文件接口](#411-实现-protobuf-文件接口) - [4.1.2. 准备配置文件](#412-准备配置文件) - [4.1.3. 实现业务接口](#413-实现业务接口) - [4.1.4. 启动 RPC 服务](#414-启动-rpc-服务) - [4.2. 搭建基于 HTTP 协议的 RPC 服务](#42-搭建基于-http-协议的-rpc-服务) - [4.2.1. 准备配置文件](#421-准备配置文件) - [4.2.2. 实现 Servlet 接口](#422-实现-servlet-接口) - [4.2.3. 启动 RPC 服务](#423-启动-rpc-服务) - [4.3. RPC 服务调用](#43-rpc-服务调用) - [4.3.1. 阻塞协程式异步调用](#431-阻塞协程式异步调用) - [4.3.2. 非阻塞协程式异步调用](#432-非阻塞协程式异步调用) - [4.4. TinyRPC 脚手架(tinyrpc_generator)](#44-tinyrpc-脚手架tinyrpc_generator) - [4.4.1 准备 protobuf 文件](#441-准备-protobuf-文件) - [4.4.2 生成 TinyRPC 框架](#442-生成-tinyrpc-框架) - [4.4.3 业务逻辑开发](#443-业务逻辑开发) - [4.4.4 Protobuf 接口升级怎么办?](#444-protobuf-接口升级怎么办) - [4.4.5 tinyrpc_generator 选项详解](#445-tinyrpc_generator-选项详解) - [5. 概要设计](#5-概要设计) - [5.1. 异步日志模块](#51-异步日志模块) - [5.2. 协程模块](#52-协程模块) - [5.2.1. 协程封装](#521-协程封装) - [5.2.2. m:n 线程:协程模型](#522-mn-线程协程模型) - [5.3. Reactor 模块](#53-reactor-模块) - [5.4. Tcp 模块](#54-tcp-模块) - [5.4.1. TcpServer](#541-tcpserver) - [5.4.2. TcpConnection](#542-tcpconnection) - [5.5. TinyPB 协议](#55-tinypb-协议) - [5.5.1. TinyPB 协议报文格式分解](#551-tinypb-协议报文格式分解) - [5.6. Http 模块](#56-http-模块) - [5.7. RPC 调用封装](#57-rpc-调用封装) - [6. 错误码](#6-错误码) - [6.1. 错误码判断规范](#61-错误码判断规范) - [6.2. 错误码释义文档](#62-错误码释义文档) - [7. 问题反馈](#7-问题反馈) - [8. 参考资料](#8-参考资料) # 1. 概述 ## 1.1. TinyRPC 特点 **TinyRPC** 是一款基于 **C++11** 标准开发的小型**异步 RPC** 框架。TinyRPC 的核心代码应该也就几千行样子,尽量保持了简洁且较高的易读性。 麻雀虽小五脏俱全,从命名上就能看出来,TinyRPC 框架主要用义是为了让读者能**快速地**、**轻量化**地搭建出具有较高性能的异步RPC 服务。至少用 TinyRPC 搭建的 RPC 服务能应付目前大多数场景了。 **TinyRPC** 没有实现跨平台,只支持 Linux 系统,并且必须是 64 位的系统,因为协程切换只实现了 **64** 位系统的代码,而没有兼容 **32** 位系统。这是有意的,因为作者只会 Linux 下开发,没能力做到跨平台。 **TinyRPC** 的核心思想有两个: 1. 让搭建高性能 RPC 服务变得简单 2. 让异步调用 RPC 变得简单 必须说明的是, **TinyRPC** 代码没有达到工业强度,最好不要直接用到生产环境,也可能存在一些未知 BUG,甚至 coredump。读者请自行辨别,谨慎使用! ## 1.2. TinyRPC 支持的协议报文 **TinyRPC** 框架目前支持两类协议: 1. 纯 **HTTP** 协议: TinyRPC 实现了简单的很基本的 HTTP(1.1) 协议的编、解码,完全可以使用 HTTP 协议搭建一个 RPC 服务。 2. TinyPB 协议: 一种基于 **Protobuf** 的自定义协议,属于二进制协议。 ## 1.3. TinyRPC 的 RPC 调用 TinyRPC 是一款异步的 RPC 框架,这就意味着服务之前的调用是非常高效的。目前来说,TinyRPC 支持两种RPC 调用方式:**阻塞协程式异步调用** 和 **非阻塞协程式异步调用**。 ### 1.3.1. 阻塞协程式异步调用 阻塞协程式异步调用这个名字看上去很奇怪,阻塞像是很低效的做法。然而其实他是非常高效的。他的思想是**用同步的代码,实现异步的性能。** 也就是说,**TinyRPC** 在 RPC 调用时候不需要像其他异步操作一样需要写复杂的回调函数,只需要直接调用即可。这看上去是同步的过程,实际上由于内部的协程封装实现了完全的异步。而作为外层的使用者完全不必关系这些琐碎的细节。 阻塞协程式异步调用对应 TinyPbRpcChannel 类,一个简单的调用例子如下: ```c++ tinyrpc::TinyPbRpcChannel channel(std::make_shared("127.0.0.1", 39999)); QueryService_Stub stub(&channel); tinyrpc::TinyPbRpcController rpc_controller; rpc_controller.SetTimeout(10000); DebugLog << "RootHttpServlet begin to call RPC" << count; stub.query_name(&rpc_controller, &rpc_req, &rpc_res, NULL); DebugLog << "RootHttpServlet end to call RPC" << count; ``` 这看上去跟普通的阻塞式调用没什么区别,然而实际上在 stub.query_name 这一行是完全异步的,简单来说。线程不会阻塞在这一行,而会转而去处理其他协程,只有当数据返回就绪时,query_name 函数自动返回,继续下面的操作。 这个过程的执行流如图所示: 从图中可以看出,在调用 query_name 到 query_name 返回这段时间 T,CPU 的执行权已经完全移交给主协程了,也就说是这段时间主协程可以用来做任何事情:包括响应客户端请求、执行定时任务、陷入 epoll_wait 等待事件就绪等。对单个协程来说,它的执行流被阻塞了。但对于整个线程来说是完全没有被阻塞,它始终在执行着任务。 另外这个过程完全没有注册回调函数、另起线程之类的操作,可它确确实实达到异步了。这也是 **TinyRPC** 的核心思想之一。 这种调用方式是 TinyRPC 推荐的方式,它的优点如下: 1. 代码实现很简单,直接同步式调用,不需要写回调函数。 2. 对IO线程数没有限制,**即使只有 1 个 IO 线程**,仍然能达到这种效果。 3. 对于线程来说,他是**不会阻塞线程**的。 当然,它的缺点也存在: 1. 对于**当前协程来说,他是阻塞的**,必须等待协程再次被唤醒(**RESUME**)才能执行下面的代码。 ### 1.3.2. 非阻塞协程式异步调用 **非阻塞协程式异步调用**是 TinyRPC 支持的另一种 RPC 调用方式,它解决了**阻塞协程式异步调用** 的一些缺点,当然也同时引入了一些限制。这种方式有点类似于 C++11 的 future 特性, 但也不完全一样。 非阻塞协程式异步调用对应 TinyPbRpcAsyncChannel,一个简单调用例子如下: ``` … ``` 注意在这种调用方式中,query_age 会立马返回,协程 C1 可以继续执行下面的代码。但这并不代表着调用 RPC 完成,如果你需要获取调用结果,请使用: ```c++ async_channel->wait(); ``` 此时协程 C1 会阻塞直到异步 RPC 调用完成,注意只会阻塞当前协程 C1,而不是当前线程(其实调用 wait 后就相当于把当前协程 C1 Yiled 了,等待 RPC 完成后自动 Resume)。 当然,wait() 是可选的。如果你不关心调用结果,完全可以不调用 wait。即相当于一个**异步的任务队列**。 这种调用方式的原理很简单,会新生成一个协程 C2 去处理这次 RPC 调用,把这个协程 C2 加入调度池任务里面,而原来的协程 C1 可以继续往下执行。 新协程 C2 会在适当的时候被IO线程调度(可能是IO线程池里面任意一个 IO线程), 当 RPC 调用完成后,会唤醒原协程 C1 通知调用完成(前提是 C1 中调用了 wait 等待结果)。 这个调用链路如图: 总之,非阻塞协程式异步调用的优点如下: 1. RPC 调用不阻塞当前协程 C1,C1 可以继续往下执行代码(若遇到 wait 则会阻塞)。 而缺点如下: 1. 所有 RPC 调用相关的对象,**必须是堆上的对象,而不是栈对象**, 包括 req、res、controller、async_rpc_channel。强烈推荐使用 shared_ptr,否则可能会有意想不到的问题(基本是必须使用了)。 2. 在 RPC 调用前必须调用 TinyPbRpcAsyncChannel::saveCallee(), 提前预留资源的引用计数。实际上是第1点的补充,相当于强制要求使用 shared_ptr 了。 解释一下第一点:调用相关的对象是在线程 A 中声明的,但由于是异步 RPC 调用,整个调用过程是又另外一个线程 B 执行的。因此你必须确保当线程 B 在这些 RPC 调用的时候,这些对象还存在,即没有被销毁。 那为什么不能是栈对象?想像一下,假设你在某个函数中异步调用 RPC,如果这些对象都是栈对象,那么当函数结束时这些栈对象自动被销毁了,线程 B 此时显然会 coredump 掉。因此请在堆上申请对象。另外,推荐使用 shared_ptr 是因为 TinyPbRpcAsyncChannel 内部已经封装好细节了,当异步 RPC 完成之后会自动销毁对象,你不必担心内存泄露的问题! # 2. 性能测试 TinyRPC 底层使用的是 Reactor 架构,同时又结合了多线程,其性能是能得到保障的。进行几个简单的性能测试结果如下: ## 2.1. HTTP echo 测试 QPS 测试机配置信息:Centos**虚拟机**,内存**6G**,CPU为**4核** 测试工具:**wrk**: https://github.com/wg/wrk.git 部署信息:wrk 与 TinyRPC 服务部署在同一台虚拟机上, 关闭 TinyRPC 日志 测试命令: ``` // -c 为并发连接数,按照表格数据依次修改 wrk -c 1000 -t 8 -d 30 --latency 'http://127.0.0.1:19999/qps?id=1' ``` 测试结果: | **QPS** | **WRK 并发连接 1000** | **WRK 并发连接 2000** | **WRK 并发连接 5000** | **WRK 并发连接 10000** | | ---- | ---- | ---- | ---- | ---- | | IO线程数为 **1** | **27000 QPS** | **26000 QPS** | **20000 QPS** |**20000 QPS** | | IO线程数为 **4** | **140000 QPS** | **130000 QPS** | **123000 QPS**| **118000 QPS** | | IO线程数为 **8** | **135000 QPS** | **120000 QPS**| **100000 QPS**| **100000 QPS** | | IO线程数为 **16** | **125000 QPS** | **127000 QPS** |**123000 QPS** | **118000 QPS** | ``` … ``` 由以上测试结果,**TinyRPC 框架的 QPS 可达到 14W 左右**。 # 3. 安装 TinyRPC ## 3.1. 安装必要的依赖库 要正确编译 **TinyRPC**, 至少要先安装这几个库: ### 3.1.1. protobuf **protobuf** 是 **google** 开源的有名的序列化库。谷歌出品,必属精品!**TinyRPC** 的 **TinyPB** 协议是基于 protobuf 来 序列化/反序列化 的,因此这个库是必须的。 其地址为:https://github.com/protocolbuffers/protobuf 推荐安装版本 **3.19.4** 及以上。安装过程不再赘述, **注意将头文件和库文件 copy 到对应的系统路径下。** ### 3.1.2. tinyxml 由于 **TinyRPC** 读取配置使用了 **xml** 文件,因此需要安装 **tinyxml** 库来解析配置文件。 下载地址:https://sourceforge.net/projects/tinyxml/ 要生成 libtinyxml.a 静态库,需要简单修改 makefile 如下: ``` # 84 行修改为如下 OUTPUT := libtinyxml.a # 194, 105 行修改如下 ${OUTPUT}: ${OBJS} ${AR} $@ ${LDFLAGS} ${OBJS} ${LIBS} ${EXTRA_LIBS} ``` 安装过程如下: ``` cd tinyxml make -j4 # copy 库文件到系统库文件搜索路径下 cp libtinyxml.a /usr/lib/ # copy 头文件到系统头文件搜索路径下 mkdir /usr/include/tinyxml cp *.h /usr/include/tinyxml ``` ## 3.2. 安装和卸载 (makefile) ### 3.2.1. 安装 TinyRPC 在安装了前置的几个库之后,就可以开始编译和安装 **TinyRPC** 了。安装过程十分简单,只要不出什么意外就好了。 **祈祷**一下一次性成功,然后直接执行以下几个命令即可: ``` git clone https://github.com/Gooddbird/tinyrpc cd tinyrpc mkdir bin && mkdir lib && mkdir obj // 生成测试pb桩文件 cd testcases protoc --cpp_out=./ test_tinypb_server.proto cd .. // 先执行编译 make -j4 // 编译成功后直接安装就行了 make install ``` 注意, make install 完成后,默认会在 **/usr/lib** 路径下安装 **libtinyrpc.a** 静态库文件,以及在 **/usr/include/tinyrpc** 下安装所有的头文件。 如果编译出现问题,欢迎提 [issue](https://github.com/Gooddbird/tinyrpc/issues/), 我会尽快回应。 ### 3.2.2. 卸载 TinyRPC 卸载也很简单,如下即可: ``` make uninstall ``` **注:如果此前已经安装过 TinyRPC, 建议先执行卸载命令后再重新 make install 安装.** ## 3.3. 安装和卸载 (cmake) ### 3.3.1. 安装 TinyRPC ```shell $ git clone https://github.com/Gooddbird/tinyrpc # 需要先生成 pb 文件 $ cd tinyrpc/testcases $ protoc --cpp_out=./ test_tinypb_server.proto $ cd .. $ mkdir bin && mkdir lib && mkdir build # 安装 $ sudo ./build.sh ``` `build.sh` 也是通过 `cmake` 安装的,当然你也可以手动通过 `cmake` 去创建 ### 3.3.2. 卸载 TinyRPC ```shell $ sudo rm -rf /usr/include/tinyrpc/ $ sudo rm -rf /usr/lib/libtinyrpc.a # 如果没有更改 makefile 中和 CMakeLists 中的 头文件 和 静态库 的存储路径的话,也可以直接执行:make uninstall ``` # 4. 快速上手 ## 4.1. 搭建基于 TinyPB 协议的 RPC 服务 ### 4.1.1. 实现 Protobuf 文件接口 TinyPB 协议基于 Protobuf 来序列化的,在搭建基于 TinyPB 协议的 RPC 服务之前,需要先定义接口文档。具体的 Protobuf 文档需要根据业务的实际功能来编写,这里给出一个例子如下: ``` … ``` 使用 protoc 工具生成对应的 C++ 代码: ``` protoc --cpp_out=./ test_tinypb_server.proto ``` ### 4.1.2. 准备配置文件 **TinyRPC** 读取标准的 **xml** 配置文件完成一些服务初始化设置,这个配置文件模板如下,一般只需要按需调整参数即可: ``` … ``` ### 4.1.3. 实现业务接口 protobuf 文件提供的只是接口说明,而实际的业务逻辑需要自己实现。只需要继承 QueryService 并重写方法即可,例如: ``` … ``` ### 4.1.4. 启动 RPC 服务 TinyRPC 服务启动非常简单,只需寥寥几行代码即可: ```c++ int main(int argc, char* argv[]) { if (argc != 2) { printf("Start TinyRPC server error, input argc is not 2!"); printf("Start TinyRPC server like this: \n"); printf("./server a.xml\n"); return 0; } // 1. 读取配置文件 tinyrpc::InitConfig(argv[1]); // 2. 注册 service REGISTER_SERVICE(QueryServiceImpl); // 3. 启动 RPC 服务 tinyrpc::StartRpcServer(); return 0; } ``` 生成可执行文件 **test_tinypb_server** 后,启动命令如下: ``` nohup ./test_tinypb_server ../conf/test_tinypb_server.xml & ``` 如果没什么报错信息,那么恭喜你启动成功了。如果不放心,可以使用 ps 命令查看进程是否存在: ``` ps -elf | grep 'test_tinypb_server' ``` 或者使用 netstat 命令查看端口是否被监听: ``` netstat -tln | grep 39999 ``` 至此,基于 TinyPB 协议的 RPC 服务已经启动成功,后续我们将调用这个服务。 ## 4.2. 搭建基于 HTTP 协议的 RPC 服务 ### 4.2.1. 准备配置文件 同上,准备一个配置文件 **test_http_server.xml**: ``` … ``` ### 4.2.2. 实现 Servlet 接口 **TinyRPC** 提供类似 JAVA 的 **Servlet** 接口来实现 HTTP 服务。你只需要简单的继承 HttpServlet 类并实现 handle 方法即可,如一个 HTTP 的 echo 如下: ``` … ``` ### 4.2.3. 启动 RPC 服务 将 Servlet 注册到路径下,启动 RPC 服务即可。注意这个注册路径相对于项目的根路径而言: ```c++ // test_http_server.cc int main(int argc, char* argv[]) { if (argc != 2) { printf("Start TinyRPC server error, input argc is not 2!"); printf("Start TinyRPC server like this: \n"); printf("./server a.xml\n"); return 0; } tinyrpc::InitConfig(argv[1]); // 访问 http://127.0.0.1:19999/qps, 即对应 QPSHttpServlet 这个接口 REGISTER_HTTP_SERVLET("/qps", QPSHttpServlet); tinyrpc::StartRpcServer(); return 0; } ``` 启动命令同样如下: ``` nohup ./test_http_server ../conf/test_http_server.xml & ``` 使用 curl 工具可以测试 HTTP 服务是否启动成功: ``` [ikerli@localhost bin]$ curl -X GET 'http://127.0.0.1:19999/qps?id=1'

Welcome to TinyRPC, just enjoy it!

QPSHttpServlet Echo Success!! Your id is,1

``` ## 4.3. RPC 服务调用 这一节将使用 test_http_server 服务调用 test_rpc_server,前面说过,TinyRPC 支持两种 RPC 调用方式:**阻塞协程式异步调用** 和 **非阻塞协程式异步调用** ### 4.3.1. 阻塞协程式异步调用 这种调用方式适用于我们依赖 RPC 调用结果的场景,必须等待 RPC 调用返回后才能进行下一步业务处理。BlockHttpServlet 即属于这种调用方式: ``` … ``` 注册此 Servlet, 然后重启 **test_http_server** ``` REGISTER_HTTP_SERVLET("/block", BlockCallHttpServlet); ``` 使用 curl 测试 ``` [ikerli@localhost bin]$ curl -X GET 'http://127.0.0.1:19999/block?id=1'

Welcome to TinyRPC, just enjoy it!

Success!! Your age is,100100111 and Your id is 1

``` ### 4.3.2. 非阻塞协程式异步调用 这种调用方式适用于我们不依赖 RPC 调用结果的场景,即我们可以继续业务处理,而不关心何时 RPC 调用成功。NonBlockHttpServlet 即属于这种调用方式: ``` … ``` 注册此 Servlet, 然后重启 **test_http_server** ``` REGISTER_HTTP_SERVLET("/nonblock", NonBlockCallHttpServlet); ``` 使用 curl 测试 ``` [ikerli@localhost bin]$ curl -X GET 'http://127.0.0.1:19999/nonblock?id=1'

Welcome to TinyRPC, just enjoy it!

Success!! Your age is,0 and Your id is 0

``` ## 4.4. TinyRPC 脚手架(tinyrpc_generator) TinyRPC 提供了代码生成工具,简单到只需要一个 protobuf 文件,就能生成全部框架代码,作为使用者只需要写业务逻辑即可,不必关心框架的原理,也不用再去写繁琐的重复代码,以及考虑如何链接 tinyrpc 库的问题。接下来用一个实例来说明如何使用 `tinyrpc_generator`. ### 4.4.1 准备 protobuf 文件 例如我们需要搭建一个订单服务: `order_server`. 它的提供一些简单的订单操作:查询订单、生成订单、删除订单等。 首先定义 `order_server.proto` 如下: ``` … ``` ### 4.4.2 生成 TinyRPC 框架 这一步很简单,简单到只需要一行命令: ``` tinyrpc/generator/tinyrpc_generato

GitHub Issues· 0 open

View all on GitHub

No open issues yet, or sync has not completed.

Highlights

  • •1.1. TinyRPC 特点
  • •1.2. TinyRPC 支持的协议报文
  • •1.3. TinyRPC 的 RPC 调用
  • •1.3.1. 阻塞协程式异步调用
  • •1.3.2. 非阻塞协程式异步调用
  • •2.1. HTTP echo 测试 QPS
  • •3. 安装 TinyRPC
  • •3.1. 安装必要的依赖库
  • •3.1.1. protobuf
  • •3.1.2. tinyxml

> Tags

C++coroutinesprotobufreactorrpc

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category前端框架
PricingOpen source

> Related tools

R
React
用于构建用户界面的 JavaScript 库
V
Vue.js
渐进式 JavaScript 框架
N
Next.js
基于 React 的全栈 Web 框架