1.概要
Flink1.19RPC通信是基于Pekko+Netty实现的
这里主要讲解Flink各组件之间的是如何通信的,原理是Pekko+动态代理
以ResourceManager和TaskManager通信为例,底层TaskManager(实际上是TaskExecutor)要向ResourceMananger发送消息,首先要获取到rm的网关(动态代理对象),然后调用动态代理对象的invoke方法,将
消息发送到rm对应的Actor中(Actor就是pekko的一个类,实际接受处理rpc请求消息的),actor会处理该RPC请求
2.前置知识
抽象类RpcEndpoint
所有能进行RPC通信的组件都需要实现RpcEndpoint这个抽象类,才能实现通信
public abstract class RpcEndpoint implements RpcGateway, AutoCloseableAsync {
//获取自身的网关对象(动态代理对象)
public <C extends RpcGateway> C getSelfGateway(Class<C> selfGatewayType) {
return rpcService.getSelfGateway(selfGatewayType, rpcServer);
}
//获取rpc服务
public RpcService getRpcService() {
return rpcService;
}
}
RpcService接口
两个重要的方法:
1.connect()这个方法就是用来获取通信组件的动态代理对象,后续通过这个获取的代理对象进行rpc通信(自己和他人进行rpc通信)
2.startServer() 这个方法是创建一个自身的动态代理对象,并将该对象赋值给rpcserver(自己与自己进行rpc通信)
public interface RpcService extends AutoCloseableAsync {
<F extends Serializable, C extends FencedRpcGateway<F>> CompletableFuture<C> connect(
String address, F fencingToken, Class<C> clazz);
<C extends RpcEndpoint & RpcGateway> RpcServer startServer(C rpcEndpoint);
}
PekkoInvocationHandler
动态代理类
class PekkoInvocationHandler implements InvocationHandler, PekkoBasedEndpoint, RpcServer {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> declaringClass = method.getDeclaringClass();
Object result;
if (declaringClass.equals(PekkoBasedEndpoint.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(
"InvocationHandler 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 {
result = invokeRpc(method, args);
}
return result;
}
}
PekkoRpcActor
最终rpc请求都会发送到这个actor中处理
class PekkoRpcActor<T extends RpcEndpoint & RpcGateway> extends AbstractActor {
//接受消息,根据消息类型的不同走不同的方法
@Override
public Receive createReceive() {
return ReceiveBuilder.create()
.match(RemoteHandshakeMessage.class, this::handleHandshakeMessage)
.match(ControlMessages.class, this::handleControlMessage)
.matchAny(this::handleMessage)
.build();
}
//处理rpc消息
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);
} else {
log.warn(
"Received message of unknown type {} with value {}. Dropping this message!",
message.getClass().getName(),
message);
sendErrorIfSender(
new UnknownMessageException(
"Received unknown message "
+ message
+ " of type "
+ message.getClass().getSimpleName()
+ '.'));
}
}
}
3.整体架构流程
集群启动时,TaskManager(本质是TaskExecutor)向ResouceManager注册就用到了RPC通信,以此为例,讲解一下RPC通信的整体流程
1.通过rpcservice接口的connect方法获取rm的动态代理对象
2.这里的resoucemanager就是rm的代理对象(本质是FencedPekkoInvocationHandler),最终目的是为了调用到resourcemanager的registerTaskExecutor方法
3.接下来会调用到代理对象的invoke方法(动态代理相关的知识不多讲了),然后调用父类的invoke方法
4.判断是否为本地,这里不是本地,走invokePpc()方法
5.将数据封装,发送rpc消息
6.匹配消息类型,调用handlePpcMessage处理请求
7.继续匹配数据类型
8.从rpc请求中获取method和参数,调用method.invoke()方法
9.最终,流转到resourcemanager的registerTaskExecutor方法中,完成注册
小结
Rpc通信是flink的重要机制之一,在底层很多地方都用到了上面的内容,例如RM和TM的通信,RM和JobMaster的通信,心跳机制,组件的启动等等