各位观众,各位朋友,大家好!今天咱们来聊聊分布式系统里的“通信员”——RPC框架,重点说说两位重量级选手:gRPC和Thrift。这俩哥们儿,一个出身名门(Google出品),一个历史悠久(Facebook贡献),都是解决分布式系统服务间通信问题的利器。
想象一下,你开了个饭馆,后厨(服务A)负责做菜,前台(服务B)负责点单。客人点了菜,前台得告诉后厨做什么,做好后还得通知前台上菜。如果前台和后厨离得近,吆喝一声就行。但如果他们不在一栋楼里,甚至不在一个城市,那吆喝就不好使了,得用“对讲机”或者“电话”。RPC框架,就是分布式系统里的“对讲机”或者“电话”,让服务之间可以像调用本地函数一样调用远程服务。
什么是RPC?
RPC,全称Remote Procedure Call,远程过程调用。简单来说,就是让一个程序(客户端)调用另一个程序(服务端)的函数,就像调用本地函数一样。你不用关心底层网络通信的细节,RPC框架会帮你搞定。
为什么我们需要RPC框架?
- 解耦: 服务之间通过接口通信,降低耦合度,方便独立开发和部署。
- 可扩展性: 可以轻松地增加或减少服务节点,提高系统的吞吐量和可用性。
- 异构性: 支持不同的编程语言和平台,可以构建异构的分布式系统。
- 简化开发: 隐藏底层通信细节,让开发者专注于业务逻辑。
gRPC:Google的现代战舰
gRPC,是Google开源的一个高性能、通用的RPC框架,基于 Protocol Buffers (protobuf) 作为接口定义语言(IDL)和序列化协议。它采用 HTTP/2 作为底层传输协议,支持多种编程语言。
gRPC的优点:
- 高性能: HTTP/2 协议带来多路复用、头部压缩等优化,protobuf 序列化效率高。
- 跨语言: 支持多种编程语言,包括 C++, Java, Python, Go, Ruby, C#, Node.js, Android, Objective-C, PHP。
- 代码生成: 通过 protobuf 定义服务接口,自动生成客户端和服务端代码,减少手动编写代码的工作量。
- 流式传输: 支持单向流、双向流等多种流式传输模式,适用于实时性要求高的场景。
- 身份验证: 内置身份验证机制,保障服务安全。
gRPC的缺点:
- 学习曲线: 需要学习 protobuf 语法和 gRPC 的使用方式。
- protobuf 依赖: 必须使用 protobuf 作为 IDL 和序列化协议,虽然protobuf 性能好,但可能不适用于所有场景。
- HTTP/2 兼容性: 部分旧版本的 HTTP 客户端可能不支持 HTTP/2。
gRPC实战:一个简单的计算器服务
咱们来用 gRPC 实现一个简单的计算器服务,包括加法和乘法两个操作。
- 定义 protobuf 文件 (calculator.proto):
syntax = "proto3";
package calculator;
service Calculator {
rpc Add (AddRequest) returns (AddResponse) {}
rpc Multiply (MultiplyRequest) returns (MultiplyResponse) {}
}
message AddRequest {
int32 num1 = 1;
int32 num2 = 2;
}
message AddResponse {
int32 result = 1;
}
message MultiplyRequest {
int32 num1 = 1;
int32 num2 = 2;
}
message MultiplyResponse {
int32 result = 1;
}
这个文件定义了一个 Calculator
服务,包含 Add
和 Multiply
两个 RPC 方法。每个方法都有对应的请求和响应消息。
- 生成 C++ 代码:
你需要安装 protobuf 编译器 (protoc
) 和 gRPC C++ 插件。然后使用以下命令生成 C++ 代码:
protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` calculator.proto
这会生成 calculator.pb.h
, calculator.pb.cc
, calculator.grpc.pb.h
和 calculator.grpc.pb.cc
四个文件。
- 实现服务端 (server.cc):
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "calculator.grpc.pb.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using calculator::Calculator;
using calculator::AddRequest;
using calculator::AddResponse;
using calculator::MultiplyRequest;
using calculator::MultiplyResponse;
class CalculatorServiceImpl final : public Calculator::Service {
Status Add(ServerContext* context, const AddRequest* request,
AddResponse* reply) override {
int32_t num1 = request->num1();
int32_t num2 = request->num2();
reply->set_result(num1 + num2);
return Status::OK;
}
Status Multiply(ServerContext* context, const MultiplyRequest* request,
MultiplyResponse* reply) override {
int32_t num1 = request->num1();
int32_t num2 = request->num2();
reply->set_result(num1 * num2);
return Status::OK;
}
};
void RunServer() {
std::string server_address("0.0.0.0:50051");
CalculatorServiceImpl service;
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
server->Wait();
}
int main() {
RunServer();
return 0;
}
这个代码定义了一个 CalculatorServiceImpl
类,实现了 Calculator::Service
接口,提供了 Add
和 Multiply
两个方法的具体实现。 RunServer
函数启动 gRPC 服务器,监听 50051 端口。
- 实现客户端 (client.cc):
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#include "calculator.grpc.pb.h"
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using calculator::Calculator;
using calculator::AddRequest;
using calculator::AddResponse;
using calculator::MultiplyRequest;
using calculator::MultiplyResponse;
class CalculatorClient {
public:
CalculatorClient(std::shared_ptr<Channel> channel)
: stub_(Calculator::NewStub(channel)) {}
int32_t Add(int32_t num1, int32_t num2) {
AddRequest request;
request.set_num1(num1);
request.set_num2(num2);
AddResponse reply;
ClientContext context;
Status status = stub_->Add(&context, request, &reply);
if (status.ok()) {
return reply.result();
} else {
std::cout << status.error_code() << ": " << status.error_message()
<< std::endl;
return -1;
}
}
int32_t Multiply(int32_t num1, int32_t num2) {
MultiplyRequest request;
request.set_num1(num1);
request.set_num2(num2);
MultiplyResponse reply;
ClientContext context;
Status status = stub_->Multiply(&context, request, &reply);
if (status.ok()) {
return reply.result();
} else {
std::cout << status.error_code() << ": " << status.error_message()
<< std::endl;
return -1;
}
}
private:
std::unique_ptr<Calculator::Stub> stub_;
};
int main(int argc, char** argv) {
CalculatorClient calculator(grpc::CreateChannel(
"localhost:50051", grpc::InsecureChannelCredentials()));
int32_t num1 = 10;
int32_t num2 = 20;
int32_t add_result = calculator.Add(num1, num2);
std::cout << num1 << " + " << num2 << " = " << add_result << std::endl;
int32_t multiply_result = calculator.Multiply(num1, num2);
std::cout << num1 << " * " << num2 << " = " << multiply_result << std::endl;
return 0;
}
这个代码定义了一个 CalculatorClient
类,使用 gRPC 连接到服务器,并调用 Add
和 Multiply
方法。
- 编译和运行:
编译服务端和客户端代码,并先运行服务端,再运行客户端。客户端会输出加法和乘法的结果。
Thrift:身经百战的老兵
Thrift,是 Apache 基金会下的一个跨语言的 RPC 框架,最初由 Facebook 开发。它也使用 IDL 定义服务接口,支持多种序列化协议和传输协议。
Thrift的优点:
- 跨语言: 支持多种编程语言,包括 C++, Java, Python, PHP, Ruby, Erlang, Go, Haskell, Perl, Objective-C, Delphi, C#, Node.js。
- 多种序列化协议: 支持多种序列化协议,包括 Binary, Compact, JSON 等,可以根据场景选择合适的协议。
- 多种传输协议: 支持多种传输协议,包括 TCP, HTTP, Memory 等,可以根据场景选择合适的协议。
- 成熟稳定: 经过多年的发展,Thrift 已经非常成熟稳定,在很多大型系统中得到应用。
Thrift的缺点:
- 性能: 相比 gRPC,Thrift 的性能可能稍逊一筹,尤其是在 CPU 密集型场景下。
- 代码生成: 虽然 Thrift 也有代码生成功能,但生成的代码可能不如 gRPC 简洁易用。
- 社区活跃度: 相比 gRPC,Thrift 的社区活跃度较低。
Thrift实战:同样是计算器服务
咱们再用 Thrift 实现同样的计算器服务。
- 定义 Thrift 文件 (calculator.thrift):
namespace cpp calculator
struct AddRequest {
1: i32 num1;
2: i32 num2;
}
struct AddResponse {
1: i32 result;
}
struct MultiplyRequest {
1: i32 num1;
2: i32 num2;
}
struct MultiplyResponse {
1: i32 result;
}
service Calculator {
AddResponse Add(1: AddRequest request)
MultiplyResponse Multiply(1: MultiplyRequest request)
}
这个文件定义了和 gRPC 版本类似的 Calculator
服务和相关的数据结构。
- 生成 C++ 代码:
你需要安装 Thrift 编译器 (thrift
). 然后使用以下命令生成 C++ 代码:
thrift -r --gen cpp calculator.thrift
这会生成 gen-cpp
目录,包含 calculator_types.h
, calculator_types.cpp
, Calculator.h
和 Calculator.cpp
等文件。
- 实现服务端 (server.cc):
#include <iostream>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include "gen-cpp/Calculator.h"
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
using boost::shared_ptr;
using namespace ::calculator;
class CalculatorHandler : virtual public CalculatorIf {
public:
CalculatorHandler() {
// Your initialization goes here
}
void Add(AddResponse& _return, const AddRequest& request) {
// Your implementation goes here
_return.result = request.num1 + request.num2;
printf("Add(%d, %d)n", request.num1, request.num2);
}
void Multiply(MultiplyResponse& _return, const MultiplyRequest& request) {
// Your implementation goes here
_return.result = request.num1 * request.num2;
printf("Multiply(%d, %d)n", request.num1, request.num2);
}
};
int main(int argc, char **argv) {
int port = 9090;
shared_ptr<CalculatorHandler> handler(new CalculatorHandler());
shared_ptr<TProcessor> processor(new CalculatorProcessor(handler));
shared_ptr<TServerSocket> serverTransport(new TServerSocket(port));
shared_ptr<TBufferedTransportFactory> transportFactory(new TBufferedTransportFactory());
shared_ptr<TBinaryProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
return 0;
}
这个代码定义了一个 CalculatorHandler
类,实现了 CalculatorIf
接口,提供了 Add
和 Multiply
两个方法的具体实现。 main
函数启动 Thrift 服务器,监听 9090 端口。
- 实现客户端 (client.cc):
#include <iostream>
#include <memory>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include "gen-cpp/Calculator.h"
using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
using boost::shared_ptr;
using namespace ::calculator;
int main(int argc, char **argv) {
shared_ptr<TTransport> socket(new TSocket("localhost", 9090));
shared_ptr<TTransport> transport(new TBufferedTransport(socket));
shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
CalculatorClient client(protocol);
try {
transport->open();
AddRequest add_request;
add_request.num1 = 10;
add_request.num2 = 20;
AddResponse add_response;
client.Add(add_response, add_request);
std::cout << add_request.num1 << " + " << add_request.num2 << " = " << add_response.result << std::endl;
MultiplyRequest multiply_request;
multiply_request.num1 = 10;
multiply_request.num2 = 20;
MultiplyResponse multiply_response;
client.Multiply(multiply_response, multiply_request);
std::cout << multiply_request.num1 << " * " << multiply_request.num2 << " = " << multiply_response.result << std::endl;
transport->close();
} catch (TException &tx) {
std::cout << "ERROR: " << tx.what() << std::endl;
}
return 0;
}
这个代码创建了一个 Thrift 客户端,连接到服务器,并调用 Add
和 Multiply
方法。
- 编译和运行:
编译服务端和客户端代码,并先运行服务端,再运行客户端。客户端会输出加法和乘法的结果。
gRPC vs. Thrift:谁更胜一筹?
特性 | gRPC | Thrift |
---|---|---|
IDL | Protocol Buffers (protobuf) | Thrift IDL |
序列化协议 | protobuf | Binary, Compact, JSON 等 |
传输协议 | HTTP/2 | TCP, HTTP, Memory 等 |
性能 | 较高,尤其是在 CPU 密集型场景下 | 相对较低,但在 IO 密集型场景下表现良好 |
跨语言支持 | 广泛 | 广泛 |
代码生成 | 简洁易用 | 相对复杂 |
流式传输 | 支持 | 不直接支持,但可以通过其他方式实现 |
社区活跃度 | 较高 | 较低 |
适用场景 | 高性能、实时性要求高的场景,微服务架构 | 异构系统集成,传统 RPC 应用,对性能要求不高的场景 |
如何选择?
选择 gRPC 还是 Thrift,取决于你的具体需求:
- 如果你追求高性能、需要流式传输、构建微服务架构,并且不介意学习 protobuf,那么 gRPC 是一个不错的选择。
- 如果你的系统需要集成多种编程语言、需要支持多种序列化协议和传输协议、对性能要求不高,并且已经熟悉 Thrift,那么 Thrift 也是一个可行的方案。
总结
gRPC 和 Thrift 都是优秀的 RPC 框架,各有优缺点。选择哪个,需要根据你的实际情况进行权衡。希望今天的讲解能帮助你更好地理解 gRPC 和 Thrift,并在实际项目中做出正确的选择。记住,没有银弹,只有最适合你的工具!
下次有机会,咱们再聊聊其他RPC框架,比如Dubbo,或者更深入地探讨gRPC和Thrift的内部实现。 谢谢大家!