一、简介
RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许程序在不同的计算机上执行过程或服务。RPC 使得开发者能够像调用本地函数一样调用远程服务,简化了网络编程的复杂性。使得开发者能够专注于业务逻辑,而不必过多关注底层的网络通信细节。
二、工作原理
- 客户端调用:客户端程序调用一个本地的代理(Stub),这个代理的功能是模拟远程过程的调用。
- 参数打包:代理将调用的参数打包(序列化),将其转换为可以通过网络传输的格式。这一过程通常称为"编码"或"序列化"。
- 发送请求:代理通过网络将请求发送到远程服务器。这个请求包含了要调用的函数名和参数。
- 服务器接收请求:远程服务器上的代理(Stub)接收到请求后,进行解码(反序列化),将参数转换回原始格式。
- 执行过程:服务器的代理调用实际的服务端过程,并将结果返回给代理。
- 结果返回:服务器的代理将结果打包并发送回客户端。
- 客户端接收结果:客户端的代理接收到结果后进行解码,并将结果返回给原始调用的客户端程序。
三、优缺点
优点:
- 透明性:开发者可以像调用本地函数一样调用远程服务,简化了分布式系统的开发。
- 语言无关性:RPC 可以在不同的编程语言之间进行通信,只要双方遵循相同的协议。
- 高效性:通过使用序列化和网络传输,RPC 可以高效地进行远程调用。
缺点:
- 网络延迟:由于涉及网络通信,RPC 调用的延迟通常高于本地调用。
- 错误处理:网络问题可能导致调用失败,开发者需要处理这些异常情况。
- 安全性:远程调用可能面临安全风险,需要采取适当的安全措施。
四、常见RPC框架
一、gRPC
gRPC(Google Remote Procedure Call)是一个高性能、开源和通用的远程过程调用(RPC)框架,由Google开发。它基于HTTP/2协议,支持多种编程语言,旨在简化微服务之间的通信。
- 语言支持:多种编程语言,包括(C++, Java, Python, Go, C#, Node.js)等。
- 主要特性:
- 高性能:使用HTTP/2,支持多路复用、流控和头压缩,能够提高网络效率和降低延迟。
- IDL(接口定义语言):使用Protocol Buffers(protobuf)作为接口定义语言,允许开发者定义服务和消息格式,具有良好的跨语言兼容性。
- 流式传输:支持四种类型的服务方法:单向 RPC、服务器流式 RPC、客户端流式 RPC 和双向流式 RPC,适合不同的应用场景。
- 负载均衡和故障恢复:内置了负载均衡和故障恢复机制,能够提高系统的可用性和可靠性。
- 安全性:支持TLS加密,确保数据在传输过程中的安全性。
示例代码:
下面使用gRPC实现一个Java客户端调用Python服务端的示例,包括服务定义、服务实现和客户端调用。主要有以下几个步骤:
- 定义gRPC服务:使用Protocol Buffers(.proto文件)定义服务和消息。
- 生成代码:使用protoc编译器生成Java和Python代码。
- 实现Python服务端:编写Python代码来实现gRPC服务。
- 实现Java客户端:编写Java代码来调用Python服务。
(1)定义 gRPC 服务
首先,需要定义一个 gRPC 服务。创建一个 .proto 文件,例如:example.proto:
bash
syntax = "proto3";
package example;
// 定义请求消息
message HelloRequest {
string name = 1;
}
// 定义响应消息
message HelloResponse {
string message = 1;
}
// 定义服务
service Greeter {
rpc SayHello(HelloRequest) returns (HelloResponse);
}
(2)生成代码
使用protoc编译器生成Java和Python代码。确保已经安装了protoc和相应的gRPC插件。
-
生成Python代码
bashpython -m grpc_tools.protoc -I. --python_out=./python_out --grpc_python_out=./python_out helloworld.proto -I.:指定 proto 文件的搜索路径。 --python_out=./python_out:指定生成的 Python 代码输出目录。 --grpc_python_out=./python_out:指定生成的 gRPC Python 代码输出目录。
-
生成Java代码
bashprotoc --java_out=./java_out --grpc_out=./java_out --plugin=protoc-gen-grpc-java=path/to/protoc-gen-grpc-java helloworld.proto --java_out=./java_out:指定生成的 Java 代码输出目录。 --grpc_out=./java_out:指定生成的 gRPC 代码输出目录。 --plugin=protoc-gen-grpc-java=path/to/protoc-gen-grpc-java:指定 gRPC Java 插件的路径。
生成的代码将会在指定的输出目录中。对于 Java,通常会生成以下文件:
- GreeterGrpc.java
- HelloRequest.java
- HelloResponse.java
对于 Python,通常会生成以下文件:
- example_pb2.py
- example_pb2_grpc.py
(3)实现Python服务端
在 Python 中,使用生成的代码来实现 gRPC 服务,创建一个名为server.py的文件,内容如下:
bash
import grpc
from concurrent import futures
import time
# 导入生成的python代码
import example_pb2
import example_pb2_grpc
// 定义一个ExampleService类,继承自 example_pb2_grpc.ExampleServiceServicer,这意味着它实现了 gRPC 服务的基本功能
class ExampleService(example_pb2_grpc.ExampleServiceServicer):
/**
* SayHello 是一个 RPC 方法,接收两个参数:
* request: 包含客户端发送的数据,通常是一个包含请求字段的对象。
* context: 提供与请求相关的上下文信息,例如元数据、取消请求等。
* 方法返回一个 HelloResponse 对象,该对象是通过 example_pb2 模块定义的。
*/
def SayHello(self, request, context):
return example_pb2.HelloResponse(message=f"Hello, {request.name}!")
def serve():
// 创建一个 gRPC 服务器,使用线程池执行器,最大工作线程数为 10
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
// 将 ExampleService 的实现添加到服务器
example_pb2_grpc.add_ExampleServiceServicer_to_server(ExampleService(), server)
// 服务器监听所有可用的网络接口,端口为 50051
server.add_insecure_port('[::]:50051')
// 启动服务器
server.start()
print("Server is running on port 50051...")
try:
while True:
# 运行一天
time.sleep(86400)
except KeyboardInterrupt:
# 停止服务器
server.stop(0)
if __name__ == '__main__':
serve()
(4)实现Java客户端
在Java中,使用生成的代码来实现客户端,创建一个名为Client.java的文件,内容如下:
bash
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
# 导入生成的Java代码
import example.ExampleServiceGrpc;
import example.HelloRequest;
import example.HelloResponse;
public class Client {
public static void main(String[] args) {
// 创建一个 gRPC 管道,连接到本地的 50051 端口
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext() // 使用明文传输,不加密
.build(); // 构建管道
// 创建一个阻塞式的存根,用于调用 ExampleService 服务
ExampleServiceGrpc.ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(channel);
// 构建请求
HelloRequest request = HelloRequest.newBuilder().setName("World").build();
// 通过存根发送请求并接收响应
HelloResponse response = stub.sayHello(request);
// 打印从服务器接收到的响应消息
System.out.println("Response from server: " + response.getMessage());
// 关闭管道
channel.shutdown();
}
}
二、Thrift
Thrift 是一个开源的跨语言服务开发框架,最初由 Facebook 开发,旨在简化不同编程语言之间的服务调用。
- 语言支持:多种编程语言,包括(Java、C++、Python、PHP、Ruby、Go)等。
- 主要特性:
- 高效的序列化:Thrift 提供了高效的二进制序列化机制,能够快速地将数据结构转换为字节流,适合高性能的网络通信。
- 灵活的接口定义:Thrift 使用一种简单的接口定义语言(IDL)来定义服务和数据结构,开发者可以通过 Thrift 编译器生成相应语言的代码。
- 多种传输和协议:Thrift 支持多种传输方式(如 TCP、HTTP)和协议(如二进制协议、JSON 协议),可以根据需求选择合适的组合。
- 服务治理:Thrift 提供了服务注册和发现的功能,方便管理和调用分布式服务。
示例代码:
下面使用Thrift实现一个Java客户端调用Python gRPC服务端的示例。主要涉及以下几个步骤,包括定义服务、生成代码、实现服务端和客户端。
(1)定义服务
首先,创建一个IDL(Interface Definition Language)文件,主要用于定义服务接口和数据结构,以便在不同编程语言之间进行高效的远程过程调用(RPC)。例如下面创建的example.thrift文件中主要包含以下几部分:
- 命名空间:使用 namespace 关键字定义不同编程语言的命名空间。
- 结构体:使用 struct 定义数据结构,字段使用 类型 名称 的格式,并且每个字段都有一个唯一的 ID(数字)。
- 服务:使用 service 定义服务接口,服务中的每个方法可以定义输入参数和返回值。
bash
namespace py example // Python 命名空间
namespace java example // Java 命名空间
// 定义一个简单的结构
struct User {
1: i32 id, // 用户 ID
2: string name, // 用户名
3: string email // 用户邮箱
}
// 定义一个服务
service ExampleService {
// 创建用户
void createUser(1: User user),
// 获取用户信息
User getUser(1: i32 id),
// 更新用户信息
void updateUser(1: User user),
// 删除用户
void deleteUser(1: i32 id)
}
(2)生成代码
使用Thrift编译器生成Java和Python代码,生成Python和Java的代码,分别在 gen-py 和 gen-java 目录下。在已经安装了Thrift编译器的情况下,可以使用以下命令:
bash
thrift --gen py example.thrift
thrift --gen java example.thrift
(3)实现Python服务端
在Python中实现服务端。创建一个文件 server.py:
bash
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TBinaryProtocol
from example import ExampleService
from example.ttypes import User
class ExampleServiceHandler:
def __init__(self):
self.users = {}
def createUser(self, user):
self.users[user.id] = user
def getUser(self, id):
return self.users.get(id)
def updateUser(self, user):
if user.id in self.users:
self.users[user.id] = user
def deleteUser(self, id):
if id in self.users:
del self.users[id]
if __name__ == '__main__':
handler = ExampleServiceHandler()
processor = ExampleService.Processor(handler)
transport = TSocket.TServerSocket(host='127.0.0.1', port=9090)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TBinaryProtocol.TBinaryProtocolFactory()
server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
print("Starting the server...")
server.serve()
(4)实现Java客户端
在Java中实现客户端。创建一个文件 Client.java:
bash
import org.apache.thrift.TException;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.protocol.TBinaryProtocol;
import example.ExampleService;
import example.User;
public class ExampleClient {
public static void main(String[] args) {
TTransport transport = new TSocket("127.0.0.1", 9090);
try {
transport.open();
TBinaryProtocol protocol = new TBinaryProtocol(transport);
ExampleService.Client client = new ExampleService.Client(protocol);
// 创建用户
User user = new User();
user.setId(1);
user.setName("Alice");
user.setEmail("alice@example.com");
client.createUser(user);
// 获取用户信息
User retrievedUser = client.getUser(1);
System.out.println("Retrieved User: " + retrievedUser.getName());
// 更新用户信息
user.setEmail("alice_new@example.com");
client.updateUser(user);
// 删除用户
client.deleteUser(1);
} catch (TException x) {
x.printStackTrace();
} finally {
transport.close();
}
}
}
三、Dubbo
Dubbo 是一个开源的高性能 Java RPC 框架,最初由阿里巴巴开发。它主要用于构建分布式服务,支持服务的注册、发现、调用和负载均衡等功能。Dubbo 以其高效、灵活和可扩展性而受到广泛欢迎,尤其是在微服务架构中。
- 语言支持:Java
- 主要特性:
- 高性能:Dubbo 采用了高效的网络通信协议,能够处理大量的并发请求。
- 服务治理:提供服务注册与发现、负载均衡、容错、限流等功能,帮助开发者管理微服务。
- 多协议支持:支持多种协议(如 Dubbo、HTTP、REST、gRPC 等),可以根据需求选择合适的协议。
- 扩展性:支持 SPI(Service Provider Interface)机制,允许用户自定义扩展。
- 监控与管理:提供监控和管理工具,帮助开发者实时监控服务的状态和性能。
- 组件
- Provider:提供服务的应用。
- Consumer:调用服务的应用。
- Registry:服务注册中心,负责服务的注册与发现。
- Monitor:监控中心,收集服务调用的统计信息。
示例代码:
通过将服务提供者和消费者同时注册到nacos注册中心后,直接在消费者服务中掉用提供者服务的接口。主要包含以下几步:
(1)环境准备
- 一个能正常启动的提供者服务。
- 一个能正常启动的消费者服务。
- 一个能正常启动的Nacos 服务器。
(2)配置提供者服务
-
首先在提供者服务的 pom.xml 中添加以下依赖:
bash<dependencies> <!-- Dubbo 依赖 --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>3.0.0</version> <!-- 请根据需要选择版本 --> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.0.0</version> </dependency> <!-- Nacos 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.0</version> <!-- 请根据需要选择版本 --> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.2.0</version> </dependency> </dependencies>
-
然后在提供者服务的 src/main/resources/application.properties 中添加 Nacos 的配置:
bashspring.application.name=dubbo-demo spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.config.server-addr=127.0.0.1:8848
-
在提供者服务中创建一个服务接口,例如 HelloService:
bashpackage com.example.service; public interface HelloService { String sayHello(String name); }
-
实现服务接口,创建接口的实现类,使用@DubboService注解来定义这个服务是dubbo服务:
bashpackage com.example.service.impl; import com.example.service.HelloService; import org.apache.dubbo.config.annotation.DubboService; @DubboService public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "Hello, " + name; } }
(3)配置消费者服务
-
首先在消费者服务的 pom.xml 中添加以下依赖:
bash<dependencies> <!-- Dubbo 依赖 --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>3.0.0</version> <!-- 请根据需要选择版本 --> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.0.0</version> </dependency> <!-- Nacos 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.0</version> <!-- 请根据需要选择版本 --> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.2.0</version> </dependency> </dependencies>
-
然后在消费者服务的 src/main/resources/application.properties 中添加 Nacos 的配置:
bashspring.application.name=dubbo-consumer spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
-
使用 Dubbo 进行 RPC 调用,通过创建一个消费者类,并使用@DubboReference注解自动注入远程服务的代理对象来调用提供者服务的service:
bashimport org.apache.dubbo.config.annotation.DubboReference; import com.example.service.HelloService; import org.springframework.stereotype.Component; @Component public class HelloConsumer { @DubboReference // Dubbo 的注解,标识这是一个服务消费者 private HelloService helloService; public void sayHello(String name) { String result = helloService.sayHello(name); System.out.println(result); } }
四、Spring Cloud
Spring Cloud 是一组工具和框架,旨在帮助开发者构建分布式系统,特别是微服务架构。在 Spring Cloud 中实现 RPC 调用通常可以通过使用 Spring Cloud OpenFeign 或 Spring Cloud Ribbon + Spring Cloud Eureka 来完成。
- 语言支持:Java
- 主要特性:
- 服务注册与发现:Spring Cloud 提供了 Eureka 作为服务注册与发现的解决方案,并结合 Ribbon 或 Spring Cloud LoadBalancer,客户端可以在多个服务实例之间进行负载均衡。
- 服务调用:通过Spring Cloud Feign 是一个声明式的 Web 服务客户端,可以简化 HTTP 请求的编写。传统的 RESTful 服务调用方式,可以使用RestTemplate 来进行 HTTP 请求。
- 容错处理:Hystrix提供了熔断器模式的实现,能够在服务调用失败时快速返回默认值,防止服务雪崩。而Resilience4j作为 Hystrix 的替代品,提供了更轻量级的熔断、限流和重试机制。
- API 网关:Spring Cloud Gateway提供了一个简单的 API 网关解决方案,可以路由请求到不同的微服务,并支持过滤器、负载均衡等功能。
- 配置管理:Spring Cloud Config提供了集中式的配置管理,支持动态刷新配置,方便微服务的配置管理。
- 链路追踪:Sleuth 和 Zipkin提供了分布式追踪的能力,可以追踪请求在微服务之间的流转,帮助开发者分析性能瓶颈和故障。
- 安全性:Spring Security: 可以与 Spring Cloud 结合,提供安全认证和授权机制,保护微服务的访问。
- 消息驱动:Spring Cloud Stream提供了基于消息中间件的微服务通信方式,支持多种消息中间件(如 RabbitMQ、Kafka),实现异步消息传递。
- 监控与管理:Spring Boot Actuator: 提供了监控和管理微服务的功能,可以查看服务的健康状态、指标等。
示例代码:
下面使用 Spring Cloud OpenFeign 实现rpc跨服务调用:
(1)添加依赖
在 pom.xml 中添加 OpenFeign 的依赖。
bash
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)启用 Feign 客户端
在主应用类上添加 @EnableFeignClients 注解。
bash
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
(3)定义 Feign 客户端
创建一个接口并使用 @FeignClient 注解。
bash
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "service-name") // service-name 是要调用的服务名称
public interface MyFeignClient {
@GetMapping("/api/resource/{id}") // 指定调用服务的接口地址
Resource getResourceById(@PathVariable("id") Long id);
}
(4)使用 Feign 客户端
在服务中注入并使用 Feign 客户端。通过调用客户端接口中的方法,就会调用到该方法@GetMapping注解中对应的远程服务的接口。
bash
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Autowired
private MyFeignClient myFeignClient;
public Resource fetchResource(Long id) {
return myFeignClient.getResourceById(id);
}
}