1、简说Akka
Flink 内部节点之间的通信是用 Akka,比如 JobManager 和 TaskManager 之间的通信。 而 operator 之间的数据传输是利用 Netty,所以是不是有必要说一下Akka ?
Akka和Actor
并发问题的核心就是存在数据共享,同时有多个线程对一份数据进行修改,就会出现错乱的情况
解决该问题一般有两种方式 :
1、基于JVM内存模型的设计,通常需要通过加锁等同步机制保证共享数据的一致性。但是加锁在高并发的场景下,往往性能不是很好
2、使用消息传递的方式
Actor的基础就是消息传递,一个Actor可以认为是一个基本的计算单元,它能接收消息并基于其执行运算,它也可以发送消息给其他Actor。Actors 之间相互隔离,它们之间并不共享内存
Actor 本身封装了状态和行为,在进行并发编程时,Actor只需要关注消息和它本身。而消息是一个不可变对象,所以 Actor 不需要去关注锁和内存原子性等一系列多线程常见的问题。
所以Actor是由状态(State)、行为(Behavior)和邮箱(MailBox,可以认为是一个消息队列)三部分组成
状态:Actor 中的状态指Actor对象的变量信息,状态由Actor自己管理,避免了并发环境下的锁和内存原子性等问题
行为:Actor 中的计算逻辑,通过Actor接收到的消息来改变Actor的状态
邮箱:邮箱是 Actor 和 Actor 之间的通信桥梁,邮箱内部通过 FIFO(先入先出)消息队列来存储发送方Actor消息,接受方Actor从邮箱队列中获取消息
任意一个Actor即可发送消息,也可以接受消息
Akka是一个构建在JVM上,基于Actor模型的的并发框架,支持Java和Scala两种API
2、Akka详解
使用 Akka 可以让你从为 Actor 系统创建基础设施和编写控制基本行为所需的初级代码中解脱出来。为了理解这一点,让我们看看在代码中创建的Actor与Akka在内部创建和管理的Actor之间的关系,Actor的生命周期和失败处理
Akka的Actor层级
- Akka的Actor总是属于父Actor。通常,你可以通过调用 getContext().actorOf() 来创建 Actor。与创建一个“独立的”Actor不同,这会将新Actor作为一个子节点注入到已经存在的树中,创建Actor的Actor成为新创建的子Actor的父级。你可能会问,你创造的第一个Actor的父节点是谁?
- 如下图所示,所有的 Actor 都有一个共同的父节点,即用户守护者。可以使用 system.actorOf() 在当前Actor下创建新的Actor实例。创建 Actor 将返回一个有效的 URL 引用。例如,如果我们用 system.actorOf(..., "someActor") 创建一个名为 someActor 的 Actor,它的引用将包括路径 /user/someActor
事实上,在你在代码中创建 Actor 之前,Akka 已经在系统中创建了三个 Actor 。这些内置的 Actor 的名字包含 guardian ,因为他们守护他们所在路径下的每一个子 Actor。守护者 Actor 包括 :
- / ,根守护者( root guardian )。这是系统中所有Actor的父Actor,也是系统本身终止时要停止的最后一个 Actor
- /user ,守护者( guardian )。这是用户创建的所有Actor的父 Actor。不要让用户名混淆,它与最终用户和用户处理无关。使用Akka库创建的每个Actor都将有一个事先准备的固定路径/user/
- /system ,系统守护者( system guardian )
pom.xml :
<properties>
<akka.version>2.6.9</akka.version>
</properties>
<dependencies>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor-typed_2.13</artifactId>
<version>${akka.version}</version>
</dependency>
</dependencies>
示例:
package com.journey.flink.akka.task2;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
public class HierarchyActorTest {
public static void main(String[] args) throws Exception {
ActorSystem system = ActorSystem.create("testSystem");
ActorRef firstActor = system.actorOf(MyPrintActor.props(), "firstActor");
System.out.println("firstActor : " + firstActor);
firstActor.tell("print_info", ActorRef.noSender());
System.out.println(">>> Press ENTER to exit <<<");
try {
System.in.read(); }
finally {
system.terminate();
}
}
// 创建Actor
static class MyPrintActor extends AbstractActor {
static Props props() {
return Props.create(MyPrintActor.class, MyPrintActor::new);
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals("print_info", p -> {
ActorRef secondActorRef = getContext().actorOf(Props.empty());
System.out.println("secondActorRef : " + secondActorRef);
}).build();
}
}
}
输出结果 :
firstActor : Actor[akka://testSystem/user/firstActor#-1802697549]
>>> Press ENTER to exit <<<
secondActorRef : Actor[akka://testSystem/user/firstActor/$a#-1282757800]
- 两条路径都以akka://testSystem/开头。因为所有 Actor的引用都是有效的URL, akka://是协议字段的值
- ActorSystem名为testSystem ,但它可以是任何其他名称。如果启用了多个系统之间的远程通信,则URL的这一部分包括主机名和端口,以便其他系统可以在网络上找到它,下面会有案例
- 因为第二个 Actor的引用包含路径 /firstActor/ ,这个标识它为第一个Actor的子Actor
- Actor引用的最后一部分,即#-1802697549和#-1282757800是唯一标识符
Actor的生命周期
- 既然了解了Actor层次结构的样子,你可能会想 : 为什么我们需要这个层次结构?它是用来干什么的?
- 层次结构的一个重要作用是安全地管理Actor的生命周期。接下来,我们来考虑一下,这些知识如何帮助我们编写更好的代码
- Actor在被创建时就会出现,然后在用户请求时被停止。每当一个Actor被停止时,它的所有子 Actor也会被递归地停止。这种行为大大简化了资源清理,并有助于避免诸如由打开的套接字和文件引起的资源泄漏
- 要停止Actor,建议的模式是调用Actor内部的 getContext().stop(getSelf()) 来停止自身,通常是对某些用户定义的停止消息的响应,或者当Actor完成其任务时
Akka Actor的API暴露了许多生命周期的钩子,你可以在 Actor 的实现中覆盖这些钩子。最常用的是 preStart() 和 postStop() 方法
- preStart() 在 Actor 启动之后但在处理其第一条消息之前调用
- postStop() 在 Actor 停止之前调用,在此时之后将不再处理任何消息
示例:
package com.journey.flink.akka.task3;
import akka.actor.AbstractActor;
import akka.actor.Props;
public class Actor1 extends AbstractActor {
static Props props() {
return Props.create(Actor1.class, Actor1:: new);
}
@Override
public void preStart() throws Exception {
System.out.println("first actor started");
getContext().actorOf(Actor2.props(), "second");
}
@Override
public void postStop() throws Exception {
System.out.println("first actor stopped");
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals("stop", s -> {
getContext().stop(getSelf());
}).build();
}
}
package com.journey.flink.akka.task3;
import akka.actor.AbstractActor;
import akka.actor.Props;
public class Actor2 extends AbstractActor {
static Props props() {
return Props.create(Actor2.class, Actor2::new);
}
@Override
public void preStart() throws Exception {
System.out.println("second actor started");
}
@Override
public void postStop() throws Exception {
System.out.println("second actor stopped");
}
@Override
public Receive createReceive() {
return receiveBuilder().build();
}
}
package com.journey.flink.akka.task3;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
public class LifeCycleMain {
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("testSystem");
ActorRef first = system.actorOf(Actor1.props(), "first");
first.tell("stop", ActorRef.noSender());
}
}
打印输出 :
first actor started
second actor started
second actor stopped
first actor stopped
- 当我们停止 first Actor 时,它会在停止自身之前,先停止了它的子 Actor second 。这个顺序是严格的,在调用父Actor的 postStop() 钩子之前,会先调用所有子Actor的 postStop() 钩子
- 失败处理
父 Actor 和子 Actor 在他们的生命周期中是相互联系的。当一个 Actor 失败(抛出一个异常或从接收中冒出一个未处理的异常)时,它将暂时挂起。如前所述,失败信息被传播到父 Actor,然后父 Actor 决定如何处理由子 Actor 引起的异常。这样,父 Actor 就可以作为子 Actor 的监督者( supervisors )。默认的监督策略是停止并重新启动子 Actor。如果不更改默认策略,所有失败都会导致重新启动
示例:
package com.journey.flink.akka.task4;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
public class SupervisingActor extends AbstractActor {
public static void main(String[] args) throws Exception {
ActorSystem system = ActorSystem.create("testSystem");
ActorRef supervisingActor = system.actorOf(SupervisingActor.props(), "supervising-actor");
supervisingActor.tell("failChild", ActorRef.noSender());
}
static Props props() {
return Props.create(SupervisingActor.class, SupervisingActor :: new);
}
ActorRef child = getContext().actorOf(SupervisedActor.props(), "supervised-actor");
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals("failChild", f -> {
child.tell("fail", getSelf());
}).build();
}
}
class SupervisedActor extends AbstractActor {
static Props props() {
return Props.create(SupervisedActor.class, SupervisedActor::new);
}
@Override
public void preStart() throws Exception {
System.out.println("supervised actor started");
}
@Override
public void postStop() throws Exception {
System.out.println("supervised actor stopped");
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals("fail", f -> {
System.out.println("supervised actor fails now");
throw new Exception("I failed");
}).build();
}
}
打印如下 :
supervised actor started
supervised actor fails now
supervised actor stopped
supervised actor started
[ERROR] [04/04/2023 10:37:51.084] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/supervising-actor/supervised-actor] I failed
java.lang.Exception: I failed
我们看到失败后,被监督的 Actor 停止并立即重新启动,我们使用了preStart()和postStop()钩子,这是重启后和重启前 默认调用的钩子,因此我们无法区分 Actor 内部是第一次启动还是重启。实际上,在重新启动时,调用 的是preRestart()和postRestart()方法,但如果不重写这两个方法,则默认委托给preStart()
Actor剖析
- 由于 Akka 实施父级监督,每个 Actor 都受到其父级的监督并且监督其子级
定义 Actor 类
- Actor 类是通过继承 AbstractActor 类并在 createReceive 方法中设置“初始行为”来实现 的
createReceive方法没有参数,并返回AbstractActor.Receive。它定义了 Actor 可以处理哪些消 息,以及如何处理消息的实现。可以使用名为ReceiveBuilder的生成器来构建此类行为。在 AbstractActor中,有一个名为receiveBuilder的方便的工厂方法
package com.journey.flink.akka.task5;
import akka.actor.AbstractActor;
public class MyActor extends AbstractActor {
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
String.class,
info -> {
System.out.println("Received String message " + info);
}
)
.matchAny(
e -> {
System.out.println("received unknown message");
}
)
.build();
}
}
Props
- Props 是一个配置类,用于指定创建 Actor 的选项,将其视为不可变的,因此可以自由共享,用于创建 Actor 的方法,包括关联的部署信息
- Props.create(Actor1.class, Actor1::new)
- Actor API
AbstractActor类定义了一个名为createReceive的方法,该方法用于设置 Actor 的“初始行为”
此外,它还提供 :
getSelf(),对 Actor 的ActorRef的引用
getSender(),前一次接收到的消息的发送方 Actor 的引用
supervisorStrategy(),用户可重写定义用于监视子 Actor 的策略
getContext() ,公开 Actor 和当前消息的上下文信息,例如: 创建子 Actor 的工厂方法(actorOf)
Actor核心概念
发送消息
- tell 的意思是“发送并忘记( fire-and-forget )”,例如异步发送消息并立即返回,target.tell(message, getSelf())
- ask 异步发送消息,并返回一个表示可能的答复,ask模式涉及 Actor 和Future
回复消息
如果你想有一个回复消息的句柄,可以使用 getSender() ,它会给你一个 ActorRef 。你可 以通过使用 getSender().tell(replyMsg, getSelf()) 发送 ActorRef 来进行回复。你还 可以存储 ActorRef 以供稍后回复或传递给其他 Actorpublic class ActionCommunication { static class Actor1 extends AbstractActor { private ActorRef actor2 = getContext().actorOf(Actor2.props(), "actor2"); static Props props() { return Props.create(Actor1.class, Actor1::new); } @Override public Receive createReceive() { return receiveBuilder() .matchEquals("hello", f -> { actor2.tell("hello", getSelf()); }) .matchEquals("ack", f -> { System.out.println("nice to meet you too."); }) .build(); } } static class Actor2 extends AbstractActor { static Props props() { return Props.create(Actor2.class, Actor2::new); } @Override public Receive createReceive() { return receiveBuilder().matchEquals("hello", f -> { System.out.println("nice to meet you."); getSender().tell("ack", getSelf()); }).build(); } } public static void main(String[] args) throws Exception { ActorSystem system = ActorSystem.create("testSystem"); ActorRef firstActor = system.actorOf(Actor1.props(), "first"); firstActor.tell("hello", ActorRef.noSender()); } }
- 接受超时
package com.journey.flink.akka.task5;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.actor.ReceiveTimeout;
import java.time.Duration;
public class TimeoutActor extends AbstractActor {
public TimeoutActor() {
// 设置初始化延迟
getContext().setReceiveTimeout(Duration.ofMillis(100));
}
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals("hello", s -> {
// 其实就是如果超过100ms没有接收消息,就会调用 ReceiveTimeout
getContext().setReceiveTimeout(Duration.ofMillis(100));
})
.match(ReceiveTimeout.class, r -> {
System.out.println("超时");
getContext().cancelReceiveTimeout();
getContext().stop(getSelf());
})
.build();
}
public static void main(String[] args) throws Exception {
ActorSystem system = ActorSystem.create("testSystem");
ActorRef timeoutActor2 = system.actorOf(Props.create(TimeoutActor.class, TimeoutActor::new));
int num = 0;
while (num <= 10) {
System.out.println("send...." + num);
timeoutActor2.tell("hello", ActorRef.noSender());
Thread.sleep(10);
num++;
}
}
}
Actor的调度器
- 调度器(Dispatchers)是 Akka 核心的一部分,这意味着它们也是 akka-actor 依赖的一部分
默认调度器
- 每个 ActorSystem 都将有一个默认的调度器,在没有为 Actor 配置其他内容的情况下使用该调度器。如果ActorSystem是通过ExecutionContext传入来创建的,则此ExecutionContext将用作此ActorSystem中所有程序的执行位置。如果没有给出ExecutionContext,它将回退到akka.actor.default-dispatcher.default-executor.fallback作为执行上下文。默认情况下,这是一个“fork-join-executor”,它在大多数情况下都有出色的性能
- 为 Actor 设置调度器
# 配置一个调度器
my-dispatcher {
# Dispatcher is the name of the event-based dispatcher
type = Dispatcher
# 使用哪种ExecutionService
executor = "fork-join-executor"
# 配置fork join池
fork-join-executor {
# 最小线程数
parallelism-min = 2
# Parallelism (threads) ... ceil(available processors * factor)
parallelism-factor = 2.0
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 10
}
# 吞吐量定义了最大的消息数量
# 一个actor最多只能占用某线程100个消息的时间
# 设置为1可以尽可能公平地
throughput = 100
}
代码中定义 :
ActorRef myActor = system.actorOf(Props.create(MyActor.class).withDispatcher("my-dispatcher"), "myactor3");
调度器类型
有三种不同类型的消息调度器- Dispatcher : 这是一个基于事件的调度程序,它将一组 Actor 绑定到线程池。如果未指定调度器,则使用默认调度器
- PinnedDispatcher : 这个调度器为每个使用它的演员分配一个独特的线程;即每个Actor将拥有自己的线程池,池中只有一个线程
- CallingThreadDispatcher : 该调度程序仅在当前线程上运行调用。这个调度程序不会创建任何新的线程,但可以同时为不同的线程使用同一个actor,主要用于测试
3、Flink中基于Akka的RPC
FLink RPC核心组件
Flink 中的 RPC 实现主要在 flink-runtime 模块下的 org.apache.flink.runtime.rpc 包中,涉及到的最重要的 API 主要是以下这四个
RpcGateway网关,都是 RpcGateWay 的子类
- 接口是用于远程调用的代理接口。 RpcGateway 提供了获取其所代理的 RpcEndpoint 的地址的方法
- 在实现一个提供 RPC 调用的组件时,通常需要先定一个接口,该接口继承 RpcGateway 并约定好提供的远程调用的方法,getAddress() & getHostname()
RpcService
- RPC服务的接口,用于连接到一个远程的RPC server,或者启动一个rpc server来转发远程调用到rpcEndpoint
- 是 RpcEndpoint 的运行时环境, RpcService 提供了启动 RpcEndpoint , 连接到远端 RpcEndpoint 并返回远端 RpcEndpoint 的代理对象等方法。此外, RpcService 还提供了某些异步任务或者周期性调度任务的方法
RpcServer
- 相当于 RpcEndpoint 自身的的代理对象(self gateway)。 RpcServer 是 RpcService 在启动了 RpcEndpoint 之后返回的对象,每一个 RpcEndpoint 对象内
部都有一个 RpcServer 的成员变量,通过 getSelfGateway 方法就可以获得自身的
代理,然后调用该Endpoint 提供的服务
- 相当于 RpcEndpoint 自身的的代理对象(self gateway)。 RpcServer 是 RpcService 在启动了 RpcEndpoint 之后返回的对象,每一个 RpcEndpoint 对象内
RpcEndpoint
- 是对 RPC 框架中提供具体服务的实体的抽象,所有提供远程调用方法的组件都需要继承 该抽象类
- 类似于Acotr,封装了传输的业务逻辑
源码分析
RpcGateway :
/**
* TODO 定义通信行为;用于远程调用RpcEndpoint的某些方法,可以理解为对方的客户端代理
*/
public interface RpcGateway {
String getAddress();
String getHostname();
}
RpcEndpoint : 启动rpcServer
public abstract class RpcEndpoint implements RpcGateway, AutoCloseableAsync {
protected final Logger log = LoggerFactory.getLogger(getClass());
/**
* 1、根据提供的RpcEndpoint来启动和停止RpcServer(Actor)
* 2、根据提供的地址连接到(对方)RpcServer,并返回一个RpcGateway
* 3、延迟、立刻调度Runnable、Callable
*/
private final RpcService rpcService;
/**
/
protected final RpcServer rpcServer;
.....
protected RpcEndpoint(final RpcService rpcService, final String endpointId) {
// 保存rpcService和endpointId
this.rpcService = checkNotNull(rpcService, "rpcService");
this.endpointId = checkNotNull(endpointId, "endpointId");
// 通过 RpcService 启动RpcServer
/**
* 构造的时候调用 rpcService.startServer()启动RpcServer,进入可以接受处理请求的状态,最后将RpcServer绑定到主线程上
* 真正执行起来
* 在RpcEndpoint中还定义了一些放入如 runAsync(Runnable)、callAsync(Callable,Time)方法来执行Rpc调用,值得注意的是在Flink
* 的设计中,对于同一个Endpoint,所有的调用都运行在主线程,因此不会有并发问题,当启动RpcEndpoint进行Rpc调用时,其会委托RpcServer进行处理
*/
this.rpcServer = rpcService.startServer(this);
// 主线程执行器,所有调用在主线程中串行执行
this.mainThreadExecutor = new MainThreadExecutor(rpcServer, this::validateRunsInMainThread);
}
....
@Override
public <C extends RpcEndpoint & RpcGateway> RpcServer startServer(C rpcEndpoint) {
checkNotNull(rpcEndpoint, "rpc endpoint");
/**
* 根据RpcEndpoint的类型来创建对应的Actor,目前支持两种Actor的创建
* 1、AkkaRpcActor
* 2、FencedAkkaRpcActor,对AkkaRpcActor进行扩展,能够过滤到与指定token无关的消息
*/
final SupervisorActor.ActorRegistration actorRegistration = registerAkkaRpcActor(rpcEndpoint);
// 这里拿到的是AkkaRpcActor的引用
final ActorRef actorRef = actorRegistration.getActorRef();
final CompletableFuture<Void> actorTerminationFuture = actorRegistration.getTerminationFuture();
LOG.info("Starting RPC endpoint for {} at {} .", rpcEndpoint.getClass().getName(), actorRef.path());
final String akkaAddress = AkkaUtils.getAkkaURL(actorSystem, actorRef);
final String hostname;
Option<String> host = actorRef.path().address().host();
if (host.isEmpty()) {
hostname = "localhost";
} else {
hostname = host.get();
}
// 提取集成RpcEndpoint的所有子类
Set<Class<?>> implementedRpcGateways = new HashSet<>(RpcUtils.extractImplementedRpcGateways(rpcEndpoint.getClass()));
implementedRpcGateways.add(RpcServer.class);
implementedRpcGateways.add(AkkaBasedEndpoint.class);
// 对上述指定的类集合进行代理
final InvocationHandler akkaInvocationHandler;
if (rpcEndpoint instanceof FencedRpcEndpoint) {
// a FencedRpcEndpoint needs a FencedAkkaInvocationHandler
akkaInvocationHandler = new FencedAkkaInvocationHandler<>(
akkaAddress,
hostname,
actorRef,
configuration.getTimeout(),
configuration.getMaximumFramesize(),
actorTerminationFuture,
((FencedRpcEndpoint<?>) rpcEndpoint)::getFencingToken,
captureAskCallstacks);
implementedRpcGateways.add(FencedMainThreadExecutable.class);
} else {
akkaInvocationHandler = new AkkaInvocationHandler(
akkaAddress,
hostname,
actorRef,
configuration.getTimeout(),
configuration.getMaximumFramesize(),
actorTerminationFuture,
captureAskCallstacks);
}
// Rather than using the System ClassLoader directly, we derive the ClassLoader
// from this class . That works better in cases where Flink runs embedded and all Flink
// code is loaded dynamically (for example from an OSGI bundle) through a custom ClassLoader
ClassLoader classLoader = getClass().getClassLoader();
// 针对RpcServer生成一个动态代理
@SuppressWarnings("unchecked")
RpcServer server = (RpcServer) Proxy.newProxyInstance(
classLoader,
implementedRpcGateways.toArray(new Class<?>[implementedRpcGateways.size()]),
akkaInvocationHandler);
return server;
}
AkkaRpcActor :
@Override
public Receive createReceive() {
return ReceiveBuilder.create()
// TODO 重点
// TODO 如果是 RemoteHandshakeMessage 信息,执行 handleHandshakeMessage,处理握手消息
.match(RemoteHandshakeMessage.class, this::handleHandshakeMessage)
// TODO 如果是 ControlMessages,执行 handleControlMessage,如启动、停止、中断
.match(ControlMessages.class, this::handleControlMessage)
// TODO 其他情况,handleMessage
.matchAny(this::handleMessage)
.build();
}
private void handleMessage(final Object message) {
if (state.isRunning()) {
mainThreadValidator.enterMainThread();
try {
// 处理消息
handleRpcMessage(message);
} finally {
mainThreadValidator.exitMainThread();
}
} else {
log.info("The rpc endpoint {} has not been started yet. Discarding message {} until processing is started.",
rpcEndpoint.getClass().getName(),
message.getClass().getName());
sendErrorIfSender(new AkkaRpcException(
String.format("Discard message, because the rpc endpoint %s has not been started yet.", rpcEndpoint.getAddress())));
}
}
connect :
@Override
public <C extends RpcGateway> CompletableFuture<C> connect(
final String address,
final Class<C> clazz) {
// 连接远程Rpc Server,返回的是代理对象
return connectInternal(
address,
clazz,
(ActorRef actorRef) -> {
Tuple2<String, String> addressHostname = extractAddressHostname(actorRef);
return new AkkaInvocationHandler(
addressHostname.f0,
addressHostname.f1,
actorRef,
configuration.getTimeout(),
configuration.getMaximumFramesize(),
null,
captureAskCallstacks);
});
}
private <C extends RpcGateway> CompletableFuture<C> connectInternal(
final String address,
final Class<C> clazz,
Function<ActorRef, InvocationHandler> invocationHandlerFactory) {
checkState(!stopped, "RpcService is stopped");
LOG.debug("Try to connect to remote RPC endpoint with address {}. Returning a {} gateway.",
address, clazz.getName());
// 根据Akka Actor地址获取ActorRef
final CompletableFuture<ActorRef> actorRefFuture = resolveActorAddress(address);
// 发送一个握手成功的消息给远程Actor
final CompletableFuture<HandshakeSuccessMessage> handshakeFuture = actorRefFuture.thenCompose(
(ActorRef actorRef) -> FutureUtils.toJava(
Patterns
.ask(actorRef, new RemoteHandshakeMessage(clazz, getVersion()), configuration.getTimeout().toMilliseconds())
.<HandshakeSuccessMessage>mapTo(ClassTag$.MODULE$.<HandshakeSuccessMessage>apply(HandshakeSuccessMessage.class))));
// 创建动态代理,并返回
return actorRefFuture.thenCombineAsync(
handshakeFuture,
(ActorRef actorRef, HandshakeSuccessMessage ignored) -> {
// AkkaInvocationHandler,针对客户端会调用 invokeRpc
InvocationHandler invocationHandler = invocationHandlerFactory.apply(actorRef);
// Rather than using the System ClassLoader directly, we derive the ClassLoader
// from this class . That works better in cases where Flink runs embedded and all Flink
// code is loaded dynamically (for example from an OSGI bundle) through a custom ClassLoader
ClassLoader classLoader = getClass().getClassLoader();
// 创建动态代理
@SuppressWarnings("unchecked")
C proxy = (C) Proxy.newProxyInstance(
classLoader,
new Class<?>[]{clazz},
invocationHandler);
return proxy;
},
actorSystem.dispatcher());
}
AkkaInvocationHandler : 同时服务端和客户端同时使用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> declaringClass = method.getDeclaringClass();
Object result;
// 判断方法的类是否为指定的类,符合如下的类,执行本地调用,否则实行远程调用
if (declaringClass.equals(AkkaBasedEndpoint.class) ||
declaringClass.equals(Object.class) ||
declaringClass.equals(RpcGateway.class) ||
declaringClass.equals(StartStoppable.class) ||
declaringClass.equals(MainThreadExecutable.class) ||
declaringClass.equals(RpcServer.class)) {
result = method.invoke(this, args);
} else if (declaringClass.equals(FencedRpcGateway.class)) {
throw new UnsupportedOperationException("AkkaInvocationHandler does not support the call FencedRpcGateway#" +
method.getName() + ". This indicates that you retrieved a FencedRpcGateway without specifying a " +
"fencing token. Please use RpcService#connect(RpcService, F, Time) with F being the fencing token to " +
"retrieve a properly FencedRpcGateway.");
} else {
// TODO 客户端会进行这里,进行远程的调用
result = invokeRpc(method, args);
}
return result;
}
4、Flink RPC实例
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor-typed_2.13</artifactId>
<version>2.6.9</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-remote_2.13</artifactId>
<version>2.6.9</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-rpc-akka</artifactId>
<version>1.17.0</version>
</dependency>
定义TaskGateway接口,继承RpcGateway接口 :
package org.apache.flink.runtime.rpc.akka;
import org.apache.flink.runtime.rpc.RpcGateway;
public interface TaskGateway extends RpcGateway {
String sayHello(String name);
}
创建一个TaskEndpoint,继承RpcEndpoint同时实现TaskGateway接口
package org.apache.flink.runtime.rpc.akka;
import org.apache.flink.runtime.rpc.RpcEndpoint;
import org.apache.flink.runtime.rpc.RpcService;
public class TaskEndpoint extends RpcEndpoint implements TaskGateway {
public TaskEndpoint(RpcService rpcService) {
super(rpcService);
}
@Override
public String sayHello(String name) {
return "hello " + name;
}
}
Server端 :
package org.apache.flink.runtime.rpc.akka;
import akka.actor.ActorSystem;
import org.apache.flink.runtime.rpc.akka.AkkaRpcService;
import org.apache.flink.runtime.rpc.akka.AkkaRpcServiceConfiguration;
public class FlinkRpcServer {
public static void main(String[] args) throws Exception {
// 1. 创建RPC服务
ActorSystem defaultActorSystem = AkkaUtils.createDefaultActorSystem();
AkkaRpcService akkaRpcService = new AkkaRpcService(defaultActorSystem,
AkkaRpcServiceConfiguration.defaultConfiguration());
// 2. 创建TaskEndpoint实例
TaskEndpoint endpoint = new TaskEndpoint(akkaRpcService);
System.out.println("address : "+endpoint.getAddress());
// 3. 启动Endpoint
endpoint.start();
}
}
client端 :
package org.apache.flink.runtime.rpc.akka;
import akka.actor.ActorSystem;
import org.apache.flink.runtime.rpc.akka.AkkaRpcService;
import org.apache.flink.runtime.rpc.akka.AkkaRpcServiceConfiguration;
import java.util.concurrent.CompletableFuture;
public class FlinkRpcClient {
public static void main(String[] args) throws Exception {
// 1. 创建RPC服务
ActorSystem defaultActorSystem = AkkaUtils.createDefaultActorSystem();
AkkaRpcService akkaRpcService = new AkkaRpcService(defaultActorSystem,
AkkaRpcServiceConfiguration.defaultConfiguration());
// 2. 连接远程RPC服务,注意:连接地址是服务端程序打印的地址
CompletableFuture<TaskGateway> gatewayFuture = akkaRpcService
.connect("akka.tcp://flink@192.168.31.162:53465/user/rpc/c38b60e7-c87c-4b7f-b948-3099dfb12999",
TaskGateway.class);
// 3. 远程调用
TaskGateway gateway = gatewayFuture.get();
System.out.println(gateway.sayHello("flink-akka"));
}
}
注意:必须要在 org.apache.flink.runtime.rpc.akka 这个包下,因为AkkaUtils是这个包的可见
如感兴趣,点赞加关注,谢谢
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。