组件间的远程通信、组件内的本地通信以及组件内的状态在并发情况下的维护,都是基于Akka Actor来实现的
1、Akka与Actor模型
Akka是构建高并发、分布式、可扩展应用的框架。Akka让开发者只需要关注业务逻辑,不需要写底层代码来支持可靠性、容错和高性能。Akka带来了诸多好处,比如 :
- 提供新的多线程模型,不需要使用低级的锁和原子性的操作来解决内存可见性问题
- 提供透明的远程通信,不再需要编写和维护复杂的网络代码
- 提供集群式、高可用的架构,方便构建真正的响应式模式的应用
- Akka是基于Actor模型实现的,Actor模型类似于Erlang的并行模型,能使实现并发、并行和分布式应用更加简单
- Actor是Actor模型中最重要的构成部分,作为最基本的计算单位,能接收消息并基于其执行计算。 每个Actor都有自己的邮箱,用来存储接收到的消息。每个Actor维持私有的状态,来实现Actor之间的隔离
- 每个Actor都是由单个线程负责从各自的邮箱拉取消息,并连续处理接收到的消息。对于接收到的消息,Actor可以更改其内部的状态,或者将其传给其他Actor,或者创建新的Actor
- ActorSystem是Actor的工厂和管理者。ActorSystem会为其Actor提供调度、配置和日志等公共服务。多个ActorSystem可以共存于同一台机器中。如果一个ActorSystem是以RemoteActorRefProvider的方式启动的,则它可以被远程ActorSystem访问到。ActorSystem能自动判断Actor消息是出自同一个ActorSystem的Actor还是来自远程ActorSystem的Actor。本地的Actor间通信,消息通过共享内存传递;而远程的Actor间通信,消息通过网络栈传递
2、了解Actor的创建、启动以及消息的发送
Server端
RemoteServerActor通过继承AbstractActor来实现Actor,并实现AbstractActor的 createReceive方法,来实现接收到消息的处理逻辑 : 接收到String的消息,将消息内容打印到控制台。
public class RemoteServerActor extends AbstractActor {
@Override
public Receive createReceive() {
return receiveBuilder()
.match(String.class, message-> {
System.out.println(message);
}) .build();
}
}
RemoteServerActorLauncher类首先通过加载remote.conf的配置(其中配置的provider为 RemoteActorRefProvider),来创建名为remote、支持远程通信的ActorSystem;再通过 actorSystem调用actorOf方法创建并启动名为remoteServerActor的Actor实例,并返回Actor地址 (ActorRef);最后通过ActorRef调用tell方法向RemoteServerActor发送消息
RemoteServerActorLauncher启动Actor和发送消息
public class RemoteServerActorLauncher {
public static void main(String[] args) {
Config config = ConfigFactory.load("remote.conf");
ActorSystem actorSystem = ActorSystem.create("remote", config);
ActorRef actor = actorSystem.actorOf(Props.create(RemoteServerActor.class), "remoteServerActor");
actor.tell("hello!", ActorRef.noSender());
}
}
remote.conf配置 :
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 50050
}
}
}
在这个示例中,整个Actor创建与消息发送的流程具体步骤如下 :
1) 创建远程的ActorSystem
2) 创建并启动RemoteServerActorLauncher实例,返回ActorRef,创建消息并通过ActorRef发送消息
3) ActorRef将消息委托给Dispatcher发送到Actor
4) Dispatcher把消息暂存在邮箱中,Dispatcher中封装了一个线程池,用于消息派发,实现异步消息发送的效果
5) 从邮箱中取出消息,委派给RemoteServerActorLauncher中通过createReceive方法创建的Receive实例来处理
Client端
在LocalClient中,通过Akka URL与RemoteServerActor进行远程通信。首先通过加载client.conf来 配置远程Actor的地址情况。client.conf的配置与remote.conf一样,在实际生产中,client.conf中 的hostName为远程Actor对应的机器或者虚拟机的真实IP。再根据加载的配置创建local的 ActorSystem,然后actorSystem调用actorSelection的方法得到远程Actor的地址。最后获取远程 Actor地址并发送给远程Actor
public class LocalClient {
public static void main(String[] args) {
Config config = ConfigFactory.load("client.conf");
ActorSystem actorSystem = ActorSystem.create("local", config);
ActorSelection toFind = actorSystem.actorSelection("akka.tcp://remote@127.0.0.1:50050/user/remoteServerActor");
toFind.tell("I am from local.", ActorRef.noSender());
}
}
client.conf配置内容:
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 50050
}
}
}
在上面的示例中,发送消息使用了tell模式。Actor的发送消息模式有ask、tell和forward,三者的特点如下 :
ask模式 : 发送消息异步,并返回一个Future来代表可能的消息回应
tell模式 : 一种fire-and-forget(发后即忘)的方式,发送消息异步并立即返回,无返回信息
forward模式 : 类似邮件的转发,将收到的消息由一个Actor转发到另一个Actor
3、组件内通信
- 对于这些组件内的多线程访问,没有锁和算子操作来保证状态,而主要通过runAsync方法、 callAsync方法、scheduleRunAsync方法,以及通过getMainThreadExecutor调度来执行Future的回调方法,来实现对组件 状态的安全操作
- 组件间通过RpcGateway子类的方法实现远程的方法调用
- 组件内部的安全状态操作是基于本地Actor实现的,而组件间的通信是通过远程Actor实现的
组件内的本地通信与组件间通信的设计与实现,首先来看下组件通信的整体情况。组件通信相关的类位于flink-runtime模块下的org.apache.flink.runtime.rpc包中,组件通信的主要部分如下 :
- RpcEndpoint : 远程过程调用端点(rpc)基础类,提供远程过程调用的分布式组件需要继承这个基础类。运行时组件Dispatcher、TaskExecutor、ResourceManager和JobMaster组件都继承了RpcEndpoint
- AkkaRpcActor : 接收RpcInvocation、RunAsync、CallAsync和ControlMessages的消息来实现运行时组件中状态的安全操作
- AkkaInvocationHandler : 作为RpcAkka调用的Handler,AkkaRpcActor接收到的RunAsync、CallAsync和RpcInvocation消息都由AkkaInvocationHandler发送
- AkkaRpcService : 实现RpcService接口,负责启动AkkaRpcActor和连接到RpcEndpoint。连接到一个RpcEndpoint,会返回RpcGateway,供远程过程调用
- 与不带Fenced开头的类相比,以Fenced开头的类只是多了对FencingToken的处理逻辑
AkkaRpcActor
- 首先来看处理消息的AkkaRpcActor。除REST以外,其他运行时组件(Dispatcher、
TaskExecutor、ResourceManager和JobMaster)都有一个AkkaRpcActor对象
- akkaRpcActor负责接收消息,并对消息进行处理,以操作RpcEndpoint(Dispatcher、 TaskExecutor、ResourceManager和JobMaster是RpcEnpoint类中的子类)的状态,实现对 RpcEndpoint实现类对象的生命周期控制和状态操作
- AkkaRpcActor处理的消息分为远程握手消息(RemoteHandshakeMessage)、控制消息和普通消息。远程握手消息主要用于在RpcEndpoint之间的远程通信建立连接之前,检查RpcEndpoint之间版本是否兼容
控制消息分START消息、STOP消息和TERMINATE消息。AkkaRpcActor接收到不同控制消息的场景与处理逻辑各不相同,具体如下 :
- 当AkkaRpcActor接收到START消息时,只有AkkaRpcActor的状态设置为开始状态,才可以处理流入的普通消息。在AkkaRpcActor对应的RpcEndpoint启动时,会发送START消息给AkkaRpcActor
- 当AkkaRpcActor接收到STOP消息时,AkkaRpcActor处于不再处理流入的普通消息且将接收到的普通消息丢弃的状态。此时只会发生JobMaster失去首领角色的情况。在这种 情况下,JobMaster会将作业设置为暂停状态(Suspended),同时向与其对应的 AkkaRpcActor发送STOP消息
- 当AkkaRpcActor接收到TERMINATE消息时,会调用对应RpcEndpoint的退出(onStop方法)逻辑。只有在Master或Worker进程正常退出或者进程中的组件发生致命错误 (Fatal Error)而退出时,才会接收到TERMINATE消息
普通消息有RunAsync、CallAsync和RpcInvocation消息三种类型。普通消息在组件内部与组件间的使用场景各不相同,具体如下 :
- RunAsync消息包含所需执行的Runnable和待执行的时间点,不需要返回执行结果。组件中的runAsync和scheduleRunAsync方法最终会将RunAsync消息发送给 AkkaRpcActor,从而线程安全地执行Runnable的run方法,修改RpcEndpoint实现类对象的状态
- CallAsync消息包含所需执行的Callable,需要返回执行结果。调用callAsync方法会触发 客户端以ask模式将CallAsync消息发送给AkkaRpcActor
RpcInvocation消息分LocalRpcInvocation消息和RemoteRpcInvocation消息二者的区别是:
- LocalRpcInvocation用于本地Actor之间的RPC,不需要消息的序列化和反序列 化,用于Master上运行时组件间的通信(如ResourceManager与JobMaster的通 信);
- RemoteRpcInvocation用于Actor远程通信中的RPC,需要序列化与反序列化,用 于Master组件与Worker组件的远程通信(如JobMaster与TaskExecutor的通信)
如感兴趣,点赞加关注,非常感谢!!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。