自学内容网 自学内容网

RPC 详解

一、简介

RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许程序在不同的计算机上执行过程或服务。RPC 使得开发者能够像调用本地函数一样调用远程服务,简化了网络编程的复杂性。使得开发者能够专注于业务逻辑,而不必过多关注底层的网络通信细节。

二、工作原理

  1. 客户端调用:客户端程序调用一个本地的代理(Stub),这个代理的功能是模拟远程过程的调用。
  2. 参数打包:代理将调用的参数打包(序列化),将其转换为可以通过网络传输的格式。这一过程通常称为“编码”或“序列化”。
  3. 发送请求:代理通过网络将请求发送到远程服务器。这个请求包含了要调用的函数名和参数。
  4. 服务器接收请求:远程服务器上的代理(Stub)接收到请求后,进行解码(反序列化),将参数转换回原始格式。
  5. 执行过程:服务器的代理调用实际的服务端过程,并将结果返回给代理。
  6. 结果返回:服务器的代理将结果打包并发送回客户端。
  7. 客户端接收结果:客户端的代理接收到结果后进行解码,并将结果返回给原始调用的客户端程序。

三、优缺点

优点:

  • 透明性:开发者可以像调用本地函数一样调用远程服务,简化了分布式系统的开发。
  • 语言无关性: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服务端的示例,包括服务定义、服务实现和客户端调用。主要有以下几个步骤:

  1. 定义gRPC服务:使用Protocol Buffers(.proto文件)定义服务和消息。
  2. 生成代码:使用protoc编译器生成Java和Python代码。
  3. 实现Python服务端:编写Python代码来实现gRPC服务。
  4. 实现Java客户端:编写Java代码来调用Python服务。
(1)定义 gRPC 服务

首先,需要定义一个 gRPC 服务。创建一个 .proto 文件,例如:example.proto:

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代码
    python -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代码
    protoc --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的文件,内容如下:

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的文件,内容如下:

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 定义服务接口,服务中的每个方法可以定义输入参数和返回值。
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编译器的情况下,可以使用以下命令:

thrift --gen py example.thrift
thrift --gen java example.thrift
(3)实现Python服务端

在Python中实现服务端。创建一个文件 server.py:

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:

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)配置提供者服务
  1. 首先在提供者服务的 pom.xml 中添加以下依赖:

    <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>
    
  2. 然后在提供者服务的 src/main/resources/application.properties 中添加 Nacos 的配置:

    spring.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
    
  3. 在提供者服务中创建一个服务接口,例如 HelloService:

    package com.example.service;
    
    public interface HelloService {
    String sayHello(String name);
    }
    
  4. 实现服务接口,创建接口的实现类,使用@DubboService注解来定义这个服务是dubbo服务:

    package 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)配置消费者服务
  1. 首先在消费者服务的 pom.xml 中添加以下依赖:

    <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>
    
  2. 然后在消费者服务的 src/main/resources/application.properties 中添加 Nacos 的配置:

    spring.application.name=dubbo-consumer
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    
  3. 使用 Dubbo 进行 RPC 调用,通过创建一个消费者类,并使用@DubboReference注解自动注入远程服务的代理对象来调用提供者服务的service:

    import 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 的依赖。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)启用 Feign 客户端

在主应用类上添加 @EnableFeignClients 注解。

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 注解。

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注解中对应的远程服务的接口。

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);
    }
}

原文地址:https://blog.csdn.net/qq_33807380/article/details/144139364

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!