Bootstrap

Java:什么是RPC框架?

RPC

远程调用的通信(RPC)

RPC(Remote Procedure Call Protocol)是指远程过程调用协议。

通俗描述

客户端在不知道调用细节的情况下,可以像调用本地应用程序中的对象一样,调用存在于远程计算机上的某个对象。

正式描述

一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

比喻

RPC好比建在小河上的桥一样,连接河的两岸。如果没有桥,我们需要通过划船、绕道等其他方式才能到达对面,但有了桥之后,我们就可以直接到达对面,无需额外的操作。

RPC 的作用体现在以下两个方面:

  • 屏蔽远程调用和本地调用的区别,使其像调用项目内的方法;
  • 隐藏底层网络通信的复杂性,让开发者专注于业务逻辑。

RPC关键要点

RPC 关键要点

  1. 定义服务接口:通过接口定义语言(IDL)定义服务接口。
  2. 创建服务端:实现接口定义的功能,并将服务实现类注册到 RPC 框架的服务端。
  3. 创建客户端:客户端调用远程服务,通常使用RPC框架生成的客户端代理对象。
  4. 序列化和反序列化:在客户端和服务端之间传输数据时需要序列化和反序列化。
  5. 通信协议和传输层:建立客户端和服务端之间的通信连接。
  6. 服务发现和负载均衡:用于找到可用服务实例,并分配请求。

RPC的用处

随着系统访问量增长和业务扩展,单机系统难以满足需求。为了提高效率,我们可以将业务逻辑分解并部署到多台机器上。RPC框架是应对这种需求的有效解决方案。

RPC 应用场景


RPC的优缺点

优点

  1. 抽象层支持,让远程调用看起来像本地调用。
  2. 跨平台和跨语言支持,适合异构系统的集成。
  3. 高性能,某些框架使用二进制数据传输格式(如 Protocol Buffers)。
  4. 天生的可扩展性,支持多种通信模式。

缺点

  1. 网络延迟和可靠性较差。
  2. 调试和故障排查困难。
  3. 序列化和反序列化开销较大。
  4. 学习成本较高。

RPC与本地调用的比较

  1. 通信方式

    • 本地调用:在同一进程内部调用,传输和处理在本地进行。
    • RPC:跨计算机调用,通过网络请求远程服务。
  2. 性能

    • 本地调用:无网络通信,性能高。
    • RPC:涉及网络传输、序列化等,性能较低。
  3. 可靠性

    • 本地调用:受网络因素影响较小,可靠性高。
    • RPC:可能受网络延迟、丢包影响。
  4. 透明性

    • 本地调用:调用者不关心实现细节。
    • RPC:尽量模拟本地调用,但需考虑网络因素等。
  5. 扩展性

    • 本地调用:扩展性依赖单机资源。
    • RPC:通过多机部署提升扩展性。

RPC工作流程

RPC 能帮助应用完成远程调用,调用方通过动态代理以本地调用方式调用服务的接口。整个流程包括序列化、协议编码、网络传输、反序列化和服务端响应等。

RPC 工作流程
服务消费方(client):调用方通过动态代理,以本地调用方式调用服务的接口
序列化:网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是肯定没法直接在网络中传输的,需要提前把它转成可传输的二进制,并且要求转换算法是可逆的,这个过程我们一般叫做“序列化”。
协议编码:
调用方持续地把请求参数序列化成二进制后,经过 TCP 传输给了服务提供方。服务提供方从 TCP 通道里面收到二进制数据,那如何知道一个请求的数据到哪里结束,是一个什么类型的请求呢?
我们把数据格式的约定内容叫做“协议”。大多数的协议会分成两部分,分别是数据头和消息体。数据头一般用于身份识别,包括协议标识、数据大小、请求类型、序列化类型等信息;消息体主要是请求的业务参数信息和扩展属性等。
网络传输:我们已经知道 RPC 是一个远程调用,那肯定就需要通过网络来传输数据,并且 RPC 常用于业务系统之间的数据交互,需要保证其可靠性,所以 RPC 一般默认采用 TCP 来传输。我们常用的 HTTP 协议也是建立在 TCP 之上的。
反序列化:根据协议格式,服务提供方就可以正确地从二进制数据中分割出不同的请求来,同时根据请求类型和序列化类型,把二进制的消息体逆向还原成请求对象。这个过程叫作“反序列化”。
服务方响应(业务逻辑):服务提供方再根据反序列化出来的请求对象找到对应的实现类,完成真正的方法调用,然后把执行结果序列化后,回写到对应的 TCP 通道里面。调用方获取到应答的数据包后,再反序列化成应答对象,这样调用方就完成了一次 RPC 调用。
RPC框架
RPC是为了实现应用之间的通信而产生的技术。无论是在大型的分布式系统中,还是在中小型系统中,随着业务复杂度的增长,许多组织都会从“单体”应用转向“微服务化”架构。这导致原有的单体应用被拆分成多个独立功能的子应用,分别部署在不同的服务器上。为了保持这些应用间的通信流畅,他们主要依赖于RPC,这使得RPC在整个分布式应用中像“经络”一样,承担着重要的作用。
RPC框架简化了在微服务架构中的通信过程,让远程方法调用几乎与本地调用一样直观。借助RPC框架,我们不仅能够更轻松地迁移应用架构从“单体”到“微服务”,还能避免因高耦合而导致的开发效率低下,从而打造出清晰、健壮并且易于维护的系统架构。
常见的RPC框架介绍
Spring Cloud:Spring Cloud是一系列为分布式系统提供解决方案的子项目集合。它提供了一整套微服务开发工具,包括配置管理、服务发现、断路器等。基于Spring Boot的设计使得其开发和部署变得简洁。
Dubbo:阿里巴巴的Dubbo是一个高效的分布式服务框架,强调SOA治理。其核心功能包括高性能的NIO通讯、服务动态寻址、路由、以及软负载均衡等。自2011年开源以来,Dubbo已经被众多组织采用。
gRPC:最初由Google开发并维护,gRPC是一个开源的远程过程调用(RPC)系统。它使用Protocol Buffers作为其接口定义语言,并且支持多种编程语言。gRPC设计着重于性能、效率和安全性,使其在低延迟和高并发的应用场景中表现出色。
Thrift:由Apache维护的Thrift是一个跨语言的服务开发框架,尤其在大数据领域得到了广泛应用。它采用专有的定义语言(Thrift IDL)进行数据和服务描述,并为多种编程语言如C++, Java, Python等生成高效的代码。其模块化架构支持多种协议和数据格式,被多个大数据项目,如HBase和Hive,作为通讯标准。
Akka:由Lightbend推出,Akka是一个基于Actor模型的并发和分布式计算框架。这个开源工具集专为构建高并发、分布式、容错和响应式的应用程序而设计。除了支持大规模的并发处理,Akka还特色地提供了分布式部署方案、强大的错误恢复机制以及细致的系统监控。
注:Akka和一些传统的RPC框架都能够处理分布式通信,但它们的设计目标和应用场景有所不同。Akka更偏重于并发性、响应性和容错能力的实现,而RPC框架则更加强调远程方法调用的流畅性和简化。
gRPC框架
gRPC 简介
gRPC是Google于2015年推出的开源RPC框架,专门为高性能、面向移动应用而设计。它基于HTTP/2协议标准,采用ProtoBuf序列化协议,且能够支持多种开发语言。由于gRPC的开源特性,开发者可以对其进行二次开发,使得客户端和服务器间的通信更专注于业务数据,而不需过多关注底层通信细节。 如下图,DATA部分即业务层面内容,下面所有的信息都由gRPC进行封装。
gRPC 特点
语言中立,支持多种语言;
基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
默认使用 PB(Protocol Buffer)做为序列化,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能。
gRPC 交互过程
终端在开启gRPC功能后充当gRPC客户端的角色,采集服务器充当gRPC服务器角色;
终端会根据订阅的事件构建对应数据的格式(GPB/JSON),通过Protocol Buffers进行编写proto文件,终端与服务器建立gRPC通道,通过gRPC协议向服务器发送请求消息;
服务器收到请求消息后,服务器会通过Protocol Buffers解译proto文件,还原出最先定义好格式的数据结构,进行业务处理;
数据处理完后,服务器需要使用Protocol Buffers重编译应答数据,通过gRPC协议向交换机发送应答消息;
终端收到应答消息后,结束本次的gRPC交互。
gRPC 客户端和服务端的交互模式
grpc 定义语言
自动生成的代码
服务端
客户端
Thrift 框架
Thrift 简介
Thrift是一个可扩展的、支持多种编程语言的RPC框架。它拥有强大的代码生成引擎,可以无缝并高效地在不同的编程语言之间构建和集成服务。Thrift起初在2007年由Facebook开发,后被捐赠给Apache基金会,成为其顶级项目。它具有以下特点:
接口维护简单:Thrift允许通过维护其特定的IDL(接口描述语言)文件(确保加上详细的注释)来作为客户端的接口文档,并自动生成对应的接口代码,确保代码与文档始终同步。此外,Thrift协议具有出色的接口扩展性。
学习成本低:因为其来自Google Protobuf开发团队,所以其IDL文件风格类似Google Protobuf,且更加易读易懂;特别是RPC服务接口的风格就像写一个面向对象的Class一样简单。
多语言/跨语言支持: Thrift支持C++、 Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk等多种语言,即可生成上述语言的服务器端和客户端程序。
稳定/广泛使用:Thrift在很多开源项目中已经被验证是稳定和高效的,在大数据领域有广泛的使用,例如Hive,HBase,Cassandra等;国外在Facebook中有广泛使用,国内包括百度、美团小米、和饿了么等公司。
Thrift 框架结构
Thrift技术栈分层从下向上分别为:
传输层(Transport Layer)
协议层(Protocol Layer)
处理层(Processor Layer)
服务层(Server Layer)。
层级
作用
Transport Layer
负责直接从网络中读取和写入数据,它定义了具体的网络传输协议,比如TCP/IP。
Protocol Layer
协议层定义了数据传输格式,负责网络传输数据的序列化和反序列化; 比如说JSON、XML、二进制数据等。
Processor Layer
处理层是由具体的IDL(接口描述语言)生成的,封装了具体的底层网络传输和序列化方式,并委托给用户实现的Handler进行处理。
Server Layer
整合上述组件,提供具体的网络IO模型(单线程/多线程/事件驱动),形成最终的服务。
Thrift交互过程
Thrift的协议(序列化协议)
Thrift可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本(text)和二进制(binary)传输协议。
TBinaryProtocol:二进制编码格式进行数据传输 (默认)
TCompactProtocol:高效率的、密集的二进制编码格式进行数据传输
TJSONProtocol: 使用JSON文本的数据编码协议进行数据传输
TSimpleJSONProtocol:只提供JSON只写的协议,适用于通过脚本语言解析
Thrift 的实践
Thrift IDL 语言定义
自动生成的代码
服务端
客户端
Akka 并发框架
Akka 介绍
Akka 是一个基于 Actor 模型的并发框架,由 Scala 语言实现。它为构建基于 JVM 的高并发、分布式、容错性强、事件驱动的应用程序提供了支持。在大数据处理框架如 Spark 和 Flink 中,Akka 被采用来实现进程与节点之间的通信。
在所展示的模型图中,每个 Actor 表示一个可调度的轻量级执行单元。如图展示,当 Actor A 和 Actor C 向 Actor B 发送消息时,这些消息会被串行地放入 Actor B 的 Mailbox 中。随后,Akka 框架的底层调度机制会激活 Actor B,使其接收并处理这些消息。这种基于 Actor 模型的并发框架确保了消息在各个 Actor 间安全、有序地传递,同时也确保了每个 Actor 以串行的方式处理其收到的所有消息。
采用 Akka 框架基于 Actor 模型,开发者无需过多地关注底层的并发同步机制,而只需专注于定义每个 Actor 的业务逻辑:它需要处理哪些消息以及应当向哪些 Actor 发送何种消息。由于 Actor 模型内的消息传递机制保证了消息在各 Actor 间串行处理,这天然地规避了并发情境下的数据一致性问题。这意味着在并发单元之间需要频繁交互信息的应用场景中,Akka 框架在性能上具有显著优势。更为重要的是,Actor 模型的轻量性能够支撑极大规模的并发,并能够均匀分布到每个 CPU 核心,最大化地利用硬件资源,从而进一步提高应用性能。
Actor 模型特点
更高的抽象:Actor 模型提供了对并发的高级抽象
编程模型:使用了异步、非阻塞、高性能的事件驱动编程模型。
轻量级事件处理:1 GB 内存可容纳百万级别 Actor
Akka Actor
Akka 提供了一个基于 Actor 模型的并发框架,运行在 JVM 上。其目标是为开发者打造一个具备高伸缩性和弹性的响应式并发应用平台。
ActorSystem
ActorSystem 可以看做是 Actor 的系统工厂或管理者。主要有以下功能:
管理调度服务
配置相关参数
日志功能
Actor 层次结构
Akka 有在系统中初始化三个 Actor:
/:被称为根监护人。它是所有 Actor 的顶级父节点,在整个系统终止时,它是最后被停止的。
/user:这是用户自定义 Actor 的父节点。不要误以为“user”与最终用户直接相关,实际上它只是路径的一部分。当你使用 Akka 创建 Actor 时,其路径将始终以 /user/ 开头。
/system系统监护人
创建 Actor 时,通常使用 system.actorOf() 方法,这会在 /user 路径下生成一个 Actor。尽管这只是用户定义的层级中的顶级 Actor,但我们通常称其为顶级 Actor。
在 Akka 中,每个 Actor 都有一个父 Actor。要在现有 Actor 树中添加一个新的 Actor,可以通过 context.actorOf() 方法实现。这样,新 Actor 的创建者即成为其父 Actor。
ActorRef
在使用 system.actorOf() 创建 Actor 时,其实返回的是一个 ActorRef 对象。
ActorRef 可以看做是 Actor 的引用,是一个 Actor 的不可变,可序列化的句柄(handle),它可能不在本地或同一个 ActorSystem 中,它是实现网络空间位置透明性的关键设计。
ActorRef 最重要功能是支持向它所代表的 Actor 发送消息:
Akka 实践
服务端
客户端
Actor的实现类
常见大数据组件使用的RPC框架


常见的RPC框架介绍

  1. Spring Cloud:微服务开发工具集,包含配置管理、服务发现等。
  2. Dubbo:阿里巴巴的高效分布式服务框架,支持服务动态寻址。
  3. gRPC:Google开发的高性能RPC系统,使用 Protocol Buffers。
  4. Thrift:Apache的跨语言服务开发框架,应用于大数据领域。
  5. Akka:基于Actor模型的并发和分布式计算框架。

gRPC框架

gRPC是Google于2015年推出的开源RPC框架,专门为高性能、面向移动应用而设计。它基于HTTP/2协议标准,采用ProtoBuf序列化协议,且能够支持多种开发语言。由于gRPC的开源特性,开发者可以对其进行二次开发,使得客户端和服务器间的通信更专注于业务数据,而不需过多关注底层通信细节。 如下图,DATA部分即业务层面内容,下面所有的信息都由gRPC进行封装。

gRPC 特点
  • 语言中立,支持多种语言。
  • 基于 IDL 文件定义服务,通过 proto3 工具生成数据结构和客户端 Stub。
  • 使用 HTTP/2 设计,支持双向流、消息头压缩等特性。

gRPC交互过程

gRPC 和 Thrift 框架介绍


gRPC

定义语言

gRPC 使用 Protocol Buffers (protobuf) 作为接口定义语言 (IDL),它能够定义服务及其传输的数据结构。开发者可以通过 proto 文件定义消息结构和服务接口。

自动生成的代码

通过编写 .proto 文件并运行生成工具,gRPC 可以自动生成对应编程语言的客户端和服务端代码,这些代码包括消息格式和服务接口。

服务端

服务端代码实现了 gRPC 服务接口,通过 gRPC 提供的框架调用定义好的逻辑。

客户端

客户端代码通过调用自动生成的客户端 Stub,与服务端进行 RPC 通信,发送请求并接收响应。


Thrift 框架

Thrift 简介

Thrift 是一个支持多种编程语言的高性能 RPC 框架,拥有强大的代码生成引擎,可高效地在不同编程语言间构建和集成服务。它起初由 Facebook 开发,现已捐赠给 Apache 基金会。Thrift 具有以下特点:

  • 接口维护简单:通过 IDL 文件维护服务接口,并自动生成客户端和服务端接口代码,保证代码与文档同步,接口扩展性强。
  • 学习成本低:IDL 文件风格类似 Google Protobuf,更易读易懂。
  • 多语言支持:支持多种语言,如 C++、Java、Python 等,生成对应语言的客户端和服务端代码。
  • 稳定/广泛使用:被广泛应用于大数据领域,验证了其稳定性和高效性,常用于开源项目和大型公司中。

Thrift 框架结构

Thrift 技术栈主要包括以下层次:

层级作用
Transport Layer负责数据传输协议(如 TCP/IP)的定义,支持从网络中读取和写入数据。
Protocol Layer定义数据的传输格式,负责序列化和反序列化,支持 JSON、XML 等格式。
Processor Layer封装网络传输和序列化方式,并处理由用户实现的 Handler。
Server Layer整合所有组件,提供网络 I/O 模型(单线程、多线程、事件驱动等)以支持服务。

Thrift 交互过程

Thrift 的协议(序列化协议)

Thrift 提供不同的传输协议:

  • TBinaryProtocol:二进制编码格式传输数据(默认)。
  • TCompactProtocol:高效密集的二进制编码格式。
  • TJSONProtocol:使用 JSON 文本编码数据。
  • TSimpleJSONProtocol:仅支持 JSON 的写协议,适用于脚本语言解析。

Thrift 的实践

Thrift IDL 语言定义

Thrift 通过 IDL 文件定义服务接口,生成不同编程语言的客户端和服务端代码。

自动生成的代码

运行 Thrift 编译器,可以自动生成客户端和服务端接口代码。

服务端

服务端代码通过实现 Thrift 生成的接口来提供 RPC 服务。

客户端

客户端代码通过 Thrift 生成的 Stub 与服务端通信,实现远程过程调用。


Akka 并发框架

Akka 介绍

Akka 是基于 Actor 模型的并发框架,用 Scala 实现,为 JVM 提供高并发、分布式、容错的应用支持。Akka 常用于大数据处理框架,如 Spark 和 Flink。

在 Actor 模型中,每个 Actor 是一个轻量级执行单元,消息在各 Actor 间传递,避免并发环境下的数据不一致问题,具有出色的性能和扩展性。

Actor 模型特点

  • 更高的抽象:提供对并发的高级抽象。
  • 编程模型:使用异步、非阻塞、高性能事件驱动模型。
  • 轻量级事件处理:1 GB 内存可容纳百万级别的 Actor。

Akka 实践

服务端
public class ServerApp {
    public static void main(String[] args) {
        Map<String, Object> map = new HashMap<>();
        map.put("akka.actor.provider", "remote");
        map.put("akka.remote.netty.tcp.hostname", "127.0.0.1");
        map.put("akka.remote.netty.tcp.port", 2551);

        Config config = ConfigFactory.parseMap(map).withFallback(ConfigFactory.load());
        ActorSystem system = ActorSystem.create("ServerSystem", config);
        ActorRef serverActorRef = system.actorOf(Props.create(ServerActor.class), "ServerActor");
        System.out.println("ServerActor started at: " + serverActorRef.path().toString());
    }
}
客户端
public class ClientApp {
    public static void main(String[] args) {
        Config config = ConfigFactory.parseString(
            "akka.actor.provider = remote\n" +
            "akka.remote.netty.tcp.hostname = 127.0.0.1\n" +
            "akka.remote.netty.tcp.port = 2550\n"
        ).withFallback(ConfigFactory.load());

        ActorSystem system = ActorSystem.create("ClientSystem", config);
        ActorRef clientActorRef = system.actorOf(Props.create(ClientActor.class), "clientActor");

        String serverActorPath = "akka.tcp://[email protected]:2551/user/ServerActor";
        ActorRef serverRef = system.actorSelection(serverActorPath)
                .resolveOne(Duration.ofSeconds(3)).toCompletableFuture().join();
        serverRef.tell("hello", clientActorRef);
    }
}
Actor 实现类
public class ServerActor extends AbstractActor {
    int count = 0;
    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .matchEquals("hello", message -> {
                System.out.print("Received hello from client, count: " + count++);
                getSender().tell("Hello from Actor Server!", getSelf());
            })
            .match(String.class, message -> {
                System.out.println("Server received: " + message);
                getSender().tell("Server response, count: " + count, getSelf());
            })
            .build();
    }
}

Thrift 框架

Thrift是一个可扩展、支持多语言的RPC框架,拥有强大的代码生成引擎。

;