Flink RPC

247 阅读15分钟

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 等构成。

  1. RpcEndpoint 定义了一个 Actor 的路径;

  2. RpcService 提供了启动 RpcServer 、执行代码体等方法;

  3. 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 的执行图如下所示:

image.png

(3) 异步和同步消息通信

在任何地方,Flink 尝试使用异步消息和通过 Futures(用来获取异步的响应)来处理响应。Futures 和很少的几个阻塞调用有一个超时时间,以防操作失败。这是为了防止死锁,当消息丢失或者分布式组件 crash。但是,如果在一个大集群或者慢网络的情况下,超时可能会使得情况更糟。因此,操作的超时时间可以通过“akka.timeout.timeout”来配置。

在两个 actor 可以通信之前,需要获取一个 ActorRef。这个操作的查找同样需要一个超时。为了使得系统尽可能快速的失败,如果一个 actor 还没开始,超时时间需要被设置的比较小。为了以防经历查询超时,可以通过“akka.lookup.timeout”配置增加查询时间。

Akka 的另一个特点是限制发送的最大消息大小。原因是它保留了同样数据大小的序列化 buffer 和不想浪费空间。如果你曾经遇到过传输失败,因为消息超过了最大大小,你可以增加akka.framesize配置来增加大小。

二.Flink RPC怎么实现的?

1.流程及基本概念

image.png

组件作用
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 通信时,必须让其提供对应地址。

image.png

从类继承图可以看到基本上所有组件都实现了 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--连接,本地和远端都干啥了

这里涉及一个本地和远端的关系,可以简单理解为:

  • 本地负责 “调用转换” 和 “结果转换”
  • 远程负责 “实际执行”

image.png

@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 的具体实现,主要负责处理如下类型消息:

  1. 本地的 Rpc 调用: LocalRpcInvocation会指派给 RpcEndpoint 进行处理,如果有响应结果,则将响应结果返还给 Sender。
  2. RunAsync & CallAsync:这类消息带有可执行的代码,直接在 Actor 的线程中执行。
  3. 控制消息 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);
	}

7.最后总结一张图

FlinkRpc.png