Flink1.12采用的是Akka,1.19换成了pekko,其实都差不多
具体不了解akka和actor的,请看Akka和Actor
一.初识RPC
1.RPC是啥
RPC是更广范的概念,是统称
而Akka和Netty是RPC中具体某一种的实现
组件之间的通信:Akka
上下游算子之间的通信:Netty
2.Flink中的RPC
(1) Flink中的Akka
Flink 通过 Akka 进行的分布式通信的实现,在 0.9 版中采用。使用 Akka,所有远程过程调用现在都实现为异步消息。这主要影响组件 JobManager,TaskManager 和 JobClient。将来,甚至有可能将更多的组件转换为参与者,从而允许它们发送和处理异步消息。
RPC 框架是 Flink 任务运行的基础,Flink 整个 RPC 框架基于 Akka 实现,并对 Akka 中的 ActorSystem、Actor 进行了封装和使用,Flink 整个通信框架的组件主要由 RpcEndpoint、RpcService、RpcServer、AkkaInvocationHandler、AkkaRpcActor 等构成。
-
RpcEndpoint 定义了一个 Actor 的路径;
-
RpcService 提供了启动 RpcServer 、执行代码体等方法;
-
RpcServer/AkkaInvocationHandler 提供了与 Actor 通信的接口;AkkaRpcActor 为 Flink 封装的 Actor。
下面分析 Flink 底层 RPC 通信框架的实现和相关流程。
(2) Flink中的Actor
Actor 是一个包含状态和行为的容器。actor 线程顺序处理收到的消息。这样就让用户摆脱锁和线程管理的管理,因为一次只有一个线程对一个 actor 有效。但是,必须确保只有这个 actor 线程可以处理其内部状态。Actor 的行为由 receive 函数定义,该函数包含收到的消息的处理逻辑。
Flink 系统由 3 个分布式组件构成:JobClient,JobManager 和 TaskManager。JobClient 从用户处得到 Flink Job,并提交给 JobManager。JobManager 策划这个 job 的执行。首先,它分配所需的资源,主要就是 TaskManagers 上要执行的 slot。
在资源分配之后,JobManager 部署单独的任务到响应的 TaskManager 上。一旦收到一个任务,TaskManager 产生一个线程用来执行这个任务。状态的改变,比如开始计算或者完成计算,将被发送回 JobManager。基于这些状态的更新,JobManager 将引导这个 job 的执行直到完成。一旦一个 job 被执行完,其结果将会被发送回 JobClient。Job 的执行图如下所示:
(3) 异步和同步消息通信
在任何地方,Flink 尝试使用异步消息和通过 Futures(用来获取异步的响应)来处理响应。Futures 和很少的几个阻塞调用有一个超时时间,以防操作失败。这是为了防止死锁,当消息丢失或者分布式组件 crash。但是,如果在一个大集群或者慢网络的情况下,超时可能会使得情况更糟。因此,操作的超时时间可以通过“akka.timeout.timeout”来配置。
在两个 actor 可以通信之前,需要获取一个 ActorRef。这个操作的查找同样需要一个超时。为了使得系统尽可能快速的失败,如果一个 actor 还没开始,超时时间需要被设置的比较小。为了以防经历查询超时,可以通过“akka.lookup.timeout”配置增加查询时间。
Akka 的另一个特点是限制发送的最大消息大小。原因是它保留了同样数据大小的序列化 buffer 和不想浪费空间。如果你曾经遇到过传输失败,因为消息超过了最大大小,你可以增加akka.framesize配置来增加大小。
二.Flink RPC怎么实现的?
1.流程及基本概念
| 组件 | 作用 |
|---|---|
| RpcGateway接口 | 获取对端的远程连接代理的,两个端的RpcEndpoint不能直接通信,要通过远端代理才可以,通过RpcService.connect()向对端的RpcEndpoint发送连接,并返回对端的远端代理,每个RPC的组件必须实现RpcGateway方法 |
| RpcEndpoint类 | 组件的终端,属于消息通信实体,TM有一个,JM也有一个,他俩的RpcEndpoint之间开始通信 |
| RpcService接口 | 操作RpcEndpoint的直接对象,比如我要连接远端RpcEndpoint、连接内部Rpc等操作 |
| RpcServer接口 | 由RpcService调用,相当于RpcEndpoint内部自己的代理,发送消息的,具体实现类如AkkaInvocationHandler |
| RpcActor接口 | 根据消息的类型,进行处理消息,返回响应,具体实现类如:AkkaRpcActor |
客户端 代理对象 Actor 服务端
myService ──> InvocationHandler ──> RpcInvocation ─────> 反射执行 ──> endpoint
(发送消息) (处理消息)
│
↓
Success/FailureRpcResponse ──> 客户端接收结果
2.RpcGateway
RpcGateway是最基础的远端代理接口,Flink所有组件必须全部实现该接口
若想与远端 Actor 通信,则必须提供地址(ip 和 port),如在 Flink-on-Yarn 模式下, JobMaster 会先启动 ActorSystem,此时 TaskExecutor 的 Container 还未分配,后面与 TaskExecutor 通信时,必须让其提供对应地址。
从类继承图可以看到基本上所有组件都实现了 RpcGateway 接口,其代码如下:
public interface RpcGateway {
String getAddress();
String getHostname();
}
3.RpcEndpoint
RpcEndpoint 是通信终端,提供 RPC 服务组件的生命周期管理(start、stop)。每个 RpcEndpoint对应了一个路径(endpointId和actorSystem共同确定),每个路径对应一个Actor,其实现了 RpcGateway 接口,其构造函数如下:
// RpcService 和 RpcServer 是 RpcEndPoint 的成员变量。
protected RpcEndpoint(final RpcService rpcService, final String endpointId) {
// 保存 rpcService 和 endpointId
this.rpcService = checkNotNull(rpcService, "rpcService");
this.endpointId = checkNotNull(endpointId, "endpointId");
// 调用 RpcService的startServer方法,启动RpcServer
this.rpcServer = rpcService.startServer(this);
// 主线程执行器,所有调用在主线程中串行执行
this.mainThreadExecutor = new MainThreadExecutor(rpcServer,this::validateRunsInMainThread);
}
构造的时候调用rpcService.startServer()启动RpcServer,进入可以接收处理请求的状态,最后将 RpcServer 绑定到主线程上真正执行起来。
在 RpcEndpoint 中还定义了一些方法如 runAsync(Runnable)、callAsync(Callable, Time)方法来执行 Rpc 调用,值得注意的是在 Flink 的设计中,对于同一个 Endpoint,所有的调用都运行在主线程,因此不会有并发问题,当启动 RpcEndpoint/进行 Rpc 调用时,其会委托RcpServer 进行处理。
4.RpcService
Rpc 服务的接口,其主要作用如下:
⚫ 根据提供的 RpcEndpoint 来启动和停止 RpcServer(Actor);
⚫ 根据提供的地址连接到(对方的)RpcServer,并返回一个 RpcGateway;
⚫ 延迟/立刻调度 Runnable、Callable;
在 Flink1.12中实现类为 AkkaRpcService,是 Akka 的一个ActorSystem 的封装,基本可以理解成 ActorSystem 的一个适配器。在 ClusterEntrypoint(JobMaster)和 TaskManagerRunner (TaskExecutor)启动的过程中初始化并启动。
AkkaRpcService中封装了ActorSystem,并保存了ActorRef到RpcEndpoint的映射关系。 RpcService 也提供了获取地址和端口的方法。
在构造 RpcEndpoint 时会启动指定RpcEndpoint上的RpcServer,其会根据 RpcEndpoint 类型(FencedRpcEndpoint 或其他)来创建不同的 AkkaRpcActor(FencedAkkaRpcActor 或 AkkaRpcActor),并将RpcEndpoint和AkkaRpcActor对应干系的ActorRef保存起来,AkkaRpcActor是底层 Akka 调用的实际接收者,
而FlinkRPC中,使用一个RpcInvocation封装类去表示RpcActor,以 Akka 消息的形式发送。最终使用动态代理将所有的消息转发到 InvocationHandler
(1) startServer--核心
@Override
public <C extends RpcEndpoint & RpcGateway> RpcServer startServer(C rpcEndpoint) {
checkNotNull(rpcEndpoint, "rpc endpoint");
// 1.根据RpcEndpoint去注册对应的actor对象,如AkkaRpcActor,返回actor引用
final SupervisorActor.ActorRegistration actorRegistration = registerAkkaRpcActor(rpcEndpoint);
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();
}
// 这里其实是放接口,下面动态代理会代理这些接口下面的方法
Set<Class<?>> implementedRpcGateways = new HashSet<>(RpcUtils.extractImplementedRpcGateways(rpcEndpoint.getClass()));
implementedRpcGateways.add(RpcServer.class);
implementedRpcGateways.add(AkkaBasedEndpoint.class);
final InvocationHandler akkaInvocationHandler;
// 2.根据不同的Endpoint类型创建不同的InvocationHandler处理器,用于捕获处理方法和调用
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);
}
ClassLoader classLoader = getClass().getClassLoader();
@SuppressWarnings("unchecked")
// 3.生成动态代理,创建实现多个接口的代理对象
RpcServer server = (RpcServer) Proxy.newProxyInstance(
classLoader,
implementedRpcGateways.toArray(new Class<?>[implementedRpcGateways.size()]),
akkaInvocationHandler);
// 4.将代理对象强转为RpcServer,并返回
return server;
}
(2) registerAkkaRpcActore--注册actor
// 注册actor
private <C extends RpcEndpoint & RpcGateway> SupervisorActor.ActorRegistration registerAkkaRpcActor(C rpcEndpoint) {
final Class<? extends AbstractActor> akkaRpcActorType;
// 1.根据RpcEndpoint类型选择对应的actor类型
if (rpcEndpoint instanceof FencedRpcEndpoint) {
akkaRpcActorType = FencedAkkaRpcActor.class;
} else {
akkaRpcActorType = AkkaRpcActor.class;
}
synchronized (lock) {
checkState(!stopped, "RpcService is stopped");
// 2.通过SupervisorActor创建并启动actor
final SupervisorActor.StartAkkaRpcActorResponse startAkkaRpcActorResponse = SupervisorActor.startAkkaRpcActor(
supervisor.getActor(), // 父级actor,因为actor不能直接new,必须由父级actor创建
actorTerminationFuture -> Props.create( // 创建Actor的工厂函数
akkaRpcActorType,
rpcEndpoint,
actorTerminationFuture,
getVersion(),
configuration.getMaximumFramesize()),
rpcEndpoint.getEndpointId()); // RPC 端点唯一标识符
// 如果创建为null,则抛出异常
final SupervisorActor.ActorRegistration actorRegistration = startAkkaRpcActorResponse.orElseThrow(cause -> new AkkaRpcRuntimeException(
String.format("Could not create the %s for %s.",
AkkaRpcActor.class.getSimpleName(),
rpcEndpoint.getEndpointId()),
cause));
// 3.将actor引用与RpcEndpoint关联起来
actors.put(actorRegistration.getActorRef(), rpcEndpoint);
// 4.返回注册的结果
return actorRegistration;
}
}
(3) connect--连接,本地和远端都干啥了
这里涉及一个本地和远端的关系,可以简单理解为:
- 本地负责 “调用转换” 和 “结果转换”
- 远程负责 “实际执行”
@Override
public <C extends RpcGateway> CompletableFuture<C> connect(
final String address,
final Class<C> clazz) {
return connectInternal(
address,
clazz,
(ActorRef actorRef) -> {
// 从 ActorRef 中提取地址和主机名信息
Tuple2<String, String> addressHostname = extractAddressHostname(actorRef);
// 创建本地 Akka 调用处理器,用于处理方法调用和消息发送,持有一个远端actor的引用
return new AkkaInvocationHandler(
addressHostname.f0, // 远程地址
addressHostname.f1, // 主机名
actorRef, // Actor 引用
configuration.getTimeout(), // 超时设置
configuration.getMaximumFramesize(), // 最大消息大小
null, // 监控器
captureAskCallstacks); // 是否捕获调用栈信息
});
}
// connect调用的connectInternal方法
private <C extends RpcGateway> CompletableFuture<C> connectInternal(
final String address,
final Class<C> clazz,
Function<ActorRef, InvocationHandler> invocationHandlerFactory) {
// 确保对端的RpcService处于运行状态
checkState(!stopped, "RpcService is stopped");
LOG.debug("Try to connect to remote RPC endpoint with address {}. Returning a {} gateway.",
address, clazz.getName());
// 1. 解析对端地址获取远程Actor的引用(异步操作)
final CompletableFuture<ActorRef> actorRefFuture = resolveActorAddress(address);
// 2. 与远程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))));
// 3. 结合两个异步操作的结果,创建代理对象
return actorRefFuture.thenCombineAsync(
handshakeFuture,
(ActorRef actorRef, HandshakeSuccessMessage ignored) -> {
// 在本地创建InvocationHandler处理方法调用
InvocationHandler invocationHandler = invocationHandlerFactory.apply(actorRef);
// 使用当前类的类加载器,确保在复杂环境下正确加载类
ClassLoader classLoader = getClass().getClassLoader();
// 在本地创建动态代理对象,并返回
@SuppressWarnings("unchecked")
C proxy = (C) Proxy.newProxyInstance(
classLoader,
new Class<?>[]{clazz},
invocationHandler);
return proxy;
},
actorSystem.dispatcher());
}
5.RpcServer
负责发送RPC请求给actor,其有两个实现类:
⚫ AkkaInvocationHandler
⚫ FencedAkkaInvocationHandler 这里,我们主要看AkkaInvocationHandler
(1) RpcServer接口
public interface RpcServer extends StartStoppable, MainThreadExecutable, RpcGateway {
CompletableFuture<Void> getTerminationFuture();
/*
* 1.该接口继承了RpcGateway,有getAddress和getHostname方法
* 2.该接口继承了MainThreadExecutable,有runAsync、callAsync、scheduleRunAsync方法
* 3.该接口继承了StartStoppable,有start和stop方法
*/
}
(2) AkkaInvocationHandler解析
先看一下成员变量和构造函数
class AkkaInvocationHandler implements InvocationHandler, AkkaBasedEndpoint, RpcServer {
private static final Logger LOG = LoggerFactory.getLogger(AkkaInvocationHandler.class);
private final String address;
private final String hostname;
private final ActorRef rpcEndpoint;
protected final boolean isLocal;
private final Time timeout;
private final long maximumFramesize;
@Nullable
private final CompletableFuture<Void> terminationFuture;
private final boolean captureAskCallStack;
AkkaInvocationHandler(
String address,
String hostname,
ActorRef rpcEndpoint,
Time timeout,
long maximumFramesize,
@Nullable CompletableFuture<Void> terminationFuture,
boolean captureAskCallStack) {
this.address = Preconditions.checkNotNull(address);
this.hostname = Preconditions.checkNotNull(hostname);
this.rpcEndpoint = Preconditions.checkNotNull(rpcEndpoint);
this.isLocal = this.rpcEndpoint.path().address().hasLocalScope();
this.timeout = Preconditions.checkNotNull(timeout);
this.maximumFramesize = maximumFramesize;
this.terminationFuture = terminationFuture;
this.captureAskCallStack = captureAskCallStack;
}
RpcServer 的启动是通知底层的 AkkaRpcActor 切换为 START 状态,开始处理远程调用请求:
其实就是调用invoke去发送消息给actor对象
<1> invoke--核心
下面代码涉及反射和动态代理,如果不了解的,可先看:JAVA反射-2 反射和动态代理结合
// 发送消息的方法---核心
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这里的method,是已经绑定好方法的Method对象了
// 反射获取method的主类
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)) {
/*TODO 如果是网关类的调用,利用反射机制,使用被调用方法的反射接口的方法*/
// 这是java的动态代理机制
result = method.invoke(this, args); // 调用反射者绑定的方法,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 如果不是网关类的,走这里,处理RPC请求*/
result = invokeRpc(method, args);
}
return result;
}
// 发送RPC请求的消息
private Object invokeRpc(Method method, Object[] args) throws Exception {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Time futureTimeout = extractRpcTimeout(parameterAnnotations, args, timeout);
/*TODO 把消息封装成 RpcInvocation,如果是本地,那就创建Local类型;如果是远程,就创建Remote类型*/
final RpcInvocation rpcInvocation = createRpcInvocationMessage(methodName, parameterTypes, args);
Class<?> returnType = method.getReturnType();
final Object result;
if (Objects.equals(returnType, Void.TYPE)) {
/*TODO 如果方法的返回值是void,那么就使用 tell方式*/
tell(rpcInvocation);
result = null;
} else { // 有返回值
final Throwable callStackCapture = captureAskCallStack ? new Throwable() : null;
// execute an asynchronous call
/*TODO 有返回值,使用 ask的方式*/
final CompletableFuture<?> resultFuture = ask(rpcInvocation, futureTimeout);
final CompletableFuture<Object> completableFuture = new CompletableFuture<>();
// 创建回调函数
resultFuture.whenComplete((resultValue, failure) -> {
if (failure != null) {
completableFuture.completeExceptionally(resolveTimeoutException(failure, callStackCapture, method));
} else {
completableFuture.complete(deserializeValueIfNeeded(resultValue, method));
}
});
if (Objects.equals(returnType, CompletableFuture.class)) {
/*TODO 如果返回类型是 CompletableFuture,直接把 CompletableFuture对象返回(不用阻塞)*/
result = completableFuture;
} else {
try {
/*TODO 如果返回类型不是CompletableFuture,那么就主动去获取结果(阻塞着等待结果返回)*/
result = completableFuture.get(futureTimeout.getSize(), futureTimeout.getUnit());
} catch (ExecutionException ee) {
throw new RpcException("Failure while obtaining synchronous RPC result.", ExceptionUtils.stripExecutionException(ee));
}
}
}
return result;
}
// 根据远端or本地,去创建对应的消息对象
protected RpcInvocation createRpcInvocationMessage(
final String methodName,
final Class<?>[] parameterTypes,
final Object[] args) throws IOException {
final RpcInvocation rpcInvocation;
// 这个isLocal是通过this.rpcEndpoint.path().address().hasLocalScope();获取的,判断当前通信的RpcEndpoint是本地还是远端
if (isLocal) {
rpcInvocation = new LocalRpcInvocation(
methodName,
parameterTypes,
args);
} else {
try {
RemoteRpcInvocation remoteRpcInvocation = new RemoteRpcInvocation(
methodName,
parameterTypes,
args);
if (remoteRpcInvocation.getSize() > maximumFramesize) {
throw new IOException(
String.format(
"The rpc invocation size %d exceeds the maximum akka framesize.",
remoteRpcInvocation.getSize()));
} else {
rpcInvocation = remoteRpcInvocation;
}
} catch (IOException e) {
LOG.warn("Could not create remote rpc invocation message. Failing rpc invocation because...", e);
throw e;
}
}
return rpcInvocation;
}
<2> 其他接口的实现方法等
private static Time extractRpcTimeout(Annotation[][] parameterAnnotations, Object[] args, Time defaultTimeout) {
if (args != null) {
Preconditions.checkArgument(parameterAnnotations.length == args.length);
for (int i = 0; i < parameterAnnotations.length; i++) {
if (isRpcTimeout(parameterAnnotations[i])) {
if (args[i] instanceof Time) {
return (Time) args[i];
} else {
throw new RuntimeException("The rpc timeout parameter must be of type " +
Time.class.getName() + ". The type " + args[i].getClass().getName() +
" is not supported.");
}
}
}
}
return defaultTimeout;
}
// 检查RPC处理是否超时
private static boolean isRpcTimeout(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(RpcTimeout.class)) {
return true;
}
}
return false;
}
// 采用tell方式去发送消息
protected void tell(Object message) {
rpcEndpoint.tell(message, ActorRef.noSender());
}
// 采用ask方式去发送消息
protected CompletableFuture<?> ask(Object message, Time timeout) {
return FutureUtils.toJava(
Patterns.ask(rpcEndpoint, message, timeout.toMilliseconds()));
}
// 实现RpcGateway接口的方法
@Override
public String getAddress() {
return address;
}
@Override
public String getHostname() {
return hostname;
}
// 实现MainThreadExecutable接口的方法
@Override
public void runAsync(Runnable runnable) {
scheduleRunAsync(runnable, 0L);
}
@Override
public void scheduleRunAsync(Runnable runnable, long delayMillis) {
checkNotNull(runnable, "runnable");
checkArgument(delayMillis >= 0, "delay must be zero or greater");
if (isLocal) {
long atTimeNanos = delayMillis == 0 ? 0 : System.nanoTime() + (delayMillis * 1_000_000);
tell(new RunAsync(runnable, atTimeNanos));
} else {
throw new RuntimeException("Trying to send a Runnable to a remote actor at " +
rpcEndpoint.path() + ". This is not supported.");
}
}
@Override
public <V> CompletableFuture<V> callAsync(Callable<V> callable, Time callTimeout) {
if (isLocal) {
@SuppressWarnings("unchecked")
CompletableFuture<V> resultFuture = (CompletableFuture<V>) ask(new CallAsync(callable), callTimeout);
return resultFuture;
} else {
throw new RuntimeException("Trying to send a Callable to a remote actor at " +
rpcEndpoint.path() + ". This is not supported.");
}
}
// 实现StartStoppable接口的方法
@Override
public void start() {
rpcEndpoint.tell(ControlMessages.START, ActorRef.noSender());
}
@Override
public void stop() {
rpcEndpoint.tell(ControlMessages.STOP, ActorRef.noSender());
}
// 省略不重要的方法
}
6.RpcActor
RpcActor是具体消息的处理接口,实现类如AkkaRpcActor AkkaRpcActor 是 Akka 的具体实现,主要负责处理如下类型消息:
- 本地的 Rpc 调用: LocalRpcInvocation会指派给 RpcEndpoint 进行处理,如果有响应结果,则将响应结果返还给 Sender。
- RunAsync & CallAsync:这类消息带有可执行的代码,直接在 Actor 的线程中执行。
- 控制消息 ControlMessages:用来控制 Actor 行为,START 启动,STOP 停止,停止后收到的消息会丢弃掉。
<1> 不同消息不同处理
// 将不同的消息交给不同的处理方法
@Override
public Receive createReceive() {
return ReceiveBuilder.create()
.match(RemoteHandshakeMessage.class, this::handleHandshakeMessage) // q1-握手消息
.match(ControlMessages.class, this::handleControlMessage) // q2-控制消息
.matchAny(this::handleMessage) // q3-其他消息,包括RPC消息
.build();
}
// q1-处理远端握手消息
private void handleHandshakeMessage(RemoteHandshakeMessage handshakeMessage) {
if (!isCompatibleVersion(handshakeMessage.getVersion())) {
sendErrorIfSender(new AkkaHandshakeException(
String.format(
"Version mismatch between source (%s) and target (%s) rpc component. Please verify that all components have the same version.",
handshakeMessage.getVersion(),
getVersion())));
} else if (!isGatewaySupported(handshakeMessage.getRpcGateway())) {
sendErrorIfSender(new AkkaHandshakeException(
String.format(
"The rpc endpoint does not support the gateway %s.",
handshakeMessage.getRpcGateway().getSimpleName())));
} else {
// tell的方式回应
getSender().tell(new Status.Success(HandshakeSuccessMessage.INSTANCE), getSelf());
}
}
// q2-处理控制消息
private void handleControlMessage(ControlMessages controlMessage) {
try {
switch (controlMessage) {
case START:
state = state.start(this); // 启动服务
break;
case STOP:
state = state.stop(); // 关闭服务
break;
case TERMINATE:
state = state.terminate(this); // 终止服务
break;
default:
handleUnknownControlMessage(controlMessage);
}
} catch (Exception e) {
this.rpcEndpointTerminationResult = RpcEndpointTerminationResult.failure(e);
throw e;
}
}
// q3-处理其他消息
private void handleMessage(final Object message) {
if (state.isRunning()) {
mainThreadValidator.enterMainThread();
try {
// 处理RPC消息
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())));
}
}
// q3调用的处理RPC消息,被handleMessage调用
protected void handleRpcMessage(Object message) {
if (message instanceof RunAsync) {
handleRunAsync((RunAsync) message); // 异步执行,无返回值
} else if (message instanceof CallAsync) {
handleCallAsync((CallAsync) message); // 异步调用,有返回值
} else if (message instanceof RpcInvocation) {
handleRpcInvocation((RpcInvocation) message); // 同步RPC调用
} else {
log.warn(
"Received message of unknown type {} with value {}. Dropping this message!",
message.getClass().getName(),
message);
sendErrorIfSender(new AkkaUnknownMessageException("Received unknown message " + message +
" of type " + message.getClass().getSimpleName() + '.'));
}
}
<2> handleRpcMessage调用的三个方法
//异步调用,有返回值
private void handleCallAsync(CallAsync callAsync) {
try {
// 直接在actor线程中执行Callable
Object result = callAsync.getCallable().call();
getSender().tell(new Status.Success(result), getSelf());
} catch (Throwable e) {
getSender().tell(new Status.Failure(e), getSelf());
}
}
// 异步执行,无返回值
private void handleRunAsync(RunAsync runAsync) {
final long timeToRun = runAsync.getTimeNanos();
final long delayNanos;
// 如果timeToRun为0或者timeToRun小于当前时间,说明已过期,则立即执行
if (timeToRun == 0 || (delayNanos = timeToRun - System.nanoTime()) <= 0) {
// 立即执行
try {
runAsync.getRunnable().run();
} catch (Throwable t) {
log.error("Caught exception while executing runnable in main thread.", t);
ExceptionUtils.rethrowIfFatalErrorOrOOM(t);
}
}
else {
// 延迟执行,通过Akka Scheduler 发送延迟消息
FiniteDuration delay = new FiniteDuration(delayNanos, TimeUnit.NANOSECONDS);
RunAsync message = new RunAsync(runAsync.getRunnable(), timeToRun);
final Object envelopedSelfMessage = envelopeSelfMessage(message);
getContext().system().scheduler().scheduleOnce(delay, getSelf(), envelopedSelfMessage,
getContext().dispatcher(), ActorRef.noSender());
}
}
// 同步RPC调用
private void handleRpcInvocation(RpcInvocation rpcInvocation) {
Method rpcMethod = null;
String methodName = rpcInvocation.getMethodName();
Class<?>[] parameterTypes = rpcInvocation.getParameterTypes();
if (rpcMethod != null) {
try {
// this supports declaration of anonymous classes
rpcMethod.setAccessible(true);
// 如果反射的方法是void,则直接执行
if (rpcMethod.getReturnType().equals(Void.TYPE)) {
// No return value to send back
rpcMethod.invoke(rpcEndpoint, rpcInvocation.getArgs());
}
else {// 反射的方法是有参的
final Object result;
try {
result = rpcMethod.invoke(rpcEndpoint, rpcInvocation.getArgs());
}
catch (InvocationTargetException e) {
log.debug("Reporting back error thrown in remote procedure {}", rpcMethod, e);
// tell the sender about the failure
getSender().tell(new Status.Failure(e.getTargetException()), getSelf());
return;
}
final String methodName = rpcMethod.getName();
// 异步Future结果
if (result instanceof CompletableFuture) {
final CompletableFuture<?> responseFuture = (CompletableFuture<?>) result;
sendAsyncResponse(responseFuture, methodName);
} else {// 同步结果
sendSyncResponse(result, methodName);
}
}
} catch (Throwable e) {
log.error("Error while executing remote procedure call {}.", rpcMethod, e);
// tell the sender about the failure
getSender().tell(new Status.Failure(e), getSelf());
}
}
}
// 处理同步调用响应,直接调actoir.tell()
private void sendSyncResponse(Object response, String methodName) {
if (isRemoteSender(getSender())) {
// 远程调用:序列化结果并验证大小
Either<SerializedValue<?>, AkkaRpcException> serializedResult = serializeRemoteResultAndVerifySize(response, methodName);
if (serializedResult.isLeft()) {
getSender().tell(new Status.Success(serializedResult.left()), getSelf());
} else {
getSender().tell(new Status.Failure(serializedResult.right()), getSelf());
}
} else {
// 本地调用:直接返回结果(无需序列化)
getSender().tell(new Status.Success(response), getSelf());
}
}
// 处理异步调用响应,需要注册回调;通过Promise包装结果,并由Patterns.pipe异步发送响应,避免阻塞
private void sendAsyncResponse(CompletableFuture<?> asyncResponse, String methodName) {
final ActorRef sender = getSender();
/*
promise是一个状态容器,有三种状态
1.Pending:初始状态,未完成
2.Success(value):操作成功,包含结果值
3.Failure(exception):操作失败,包含异常
*/
Promise.DefaultPromise<Object> promise = new Promise.DefaultPromise<>();
// 1. 注册回调(非阻塞)
asyncResponse.whenComplete(
(value, throwable) -> {
// 3. 异步任务完成后执行这里(可能在几秒后)
if (throwable != null) {
promise.failure(throwable);
} else {
if (isRemoteSender(sender)) {
// 远程调用:序列化结果
Either<SerializedValue<?>, AkkaRpcException> serializedResult = serializeRemoteResultAndVerifySize(value, methodName);
if (serializedResult.isLeft()) {
promise.success(serializedResult.left());
} else {
promise.failure(serializedResult.right());
}
} else {
// 本地调用:直接返回
promise.success(value);
}
}
});
// 2. 立即执行到这里,不会等待异步任务完成
/*
Patterns.pipe(future, dispatcher).to(recipient) 实现了延迟提交机制
1.最初promise.future()处于Pending状态,pipe机制不执行任何操作,只是等待状态变更
2.当 future(此处是 promise.future())完成时,自动将结果转换为消息
3.发送消息给 recipient(调用方 Actor)
4.整个过程由 Akka 底层监听 future 的状态变化,无需手动干预
*/
Patterns.pipe(promise.future(), getContext().dispatcher()).to(sender);
}