我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧
写在前面
大家好,今天我们来聊一个有意思的话题,大家都知道流式处理系统flink对于数据的顺序性和正确性并不是保证的。flink处理的数据可能会丢失,前后颠倒。那么导致这样的问题的原因是什么?flink 所有的job manager ,task manager之间互相是依靠什么rpc框架进行互相交互的? 每个slot算子之间如果有消息需要发送给彼此,那么它们是靠什么样的机制发送的?实际上这些问题都与flink使用的异步消息分布式rpc通信系统Akka息息相关的。
Actor模型
首先我们来讨论一下什么是actor模型,actor模型本质是一种利用actor作为基本计算单元的并行计算模型,在利用actor模型计算的程序中,所有的一切都是在acotr中执行的,就像面向对象编程中,一切都是在对象之中执行的。
相对的,actor模型中一切都是互相隔离的actor,它们也不会共享任何的变量。在java代码中并不存在原生的actor模型功能的类库,只能使用第三方的类库AKKA来实现actor模型。flink也是利用AKKA来实现actor模型。
actor中的消息机制其实更像是一个单线程的邮箱系统,大概类似于下图。调用actor发送消息就等于发送了一封邮件,发送消息以后,接收到该消息的acotr并不一定会立马去执行或者处理。就像日常打开邮箱,大家往往会顺序的浏览未读的邮件,而别的未读邮件就会积压在邮箱中等待处理。所以acotr模型的消息机制是完全异步的,发送消息和接受消息的acotr不需要在同一线程,甚至不需要在同一台机器上,所有actor都是单独处理和决策自己的邮件处理。这对与flink的分布式多线程计算提供了重要的支持,对于flink集群的搭建也是一种重要的支撑。
Actor Systems在 flink中的实现
由于本文的关键在于actor模型的在flink中的实现,这里就不再赘言AKKA的部分.
在最新的版本我们可以看到 flink的rpc的代码不再放在runTime包下,而是单独的抽出来一个rpc来安置这些部分的代码,flink-rpc-core中包括了非常重要的RpcEndpoint等等.
class TaskExecutor extends RpcEndpoint
public TaskManagerRunner(
Configuration configuration,
PluginManager pluginManager,
TaskExecutorServiceFactory taskExecutorServiceFactory)
throws Exception {
this.configuration = checkNotNull(configuration);
//通过配置启动rpcSystem
rpcSystem = RpcSystem.load(configuration);
)
我们首先从 TaskManagerRunner这个flink非常重要的基础类来看,在taskMannger初始化的时候的最初就会出初始化整个RpcSystem。
/**
* 循环初始化 RpcSystem.
*/
static RpcSystem load(Configuration config) {
final Iterator<RpcSystemLoader> iterator =
ServiceLoader.load(RpcSystemLoader.class).iterator();
Exception loadError = null;
while (iterator.hasNext()) {
final RpcSystemLoader next = iterator.next();
try {
return next.loadRpcSystem(config);
} catch (Exception e) {
loadError = ExceptionUtils.firstOrSuppressed(e, loadError);
}
}
throw new RpcLoaderException("Could not load RpcSystem.", loadError);
}
AkkaRpcSystemLoader的具体实现
public class AkkaRpcSystemLoader implements RpcSystemLoader {
//akkajar的地址
private static final String FLINK_RPC_AKKA_FAT_JAR = "flink-rpc-akka.jar";
static final String HINT_USAGE =
"mvn clean package -pl flink-rpc/flink-rpc-akka,flink-rpc/flink-rpc-akka-loader -DskipTests";
@Override
public RpcSystem loadRpcSystem(Configuration config) {
try {
final ClassLoader flinkClassLoader = RpcSystem.class.getClassLoader();
final Path tmpDirectory = Paths.get(ConfigurationUtils.parseTempDirectories(config)[0]);
Files.createDirectories(tmpDirectory);
final Path tempFile =
Files.createFile(
tmpDirectory.resolve("flink-rpc-akka_" + UUID.randomUUID() + ".jar"));
final InputStream resourceStream =
flinkClassLoader.getResourceAsStream(FLINK_RPC_AKKA_FAT_JAR);
if (resourceStream == null) {
throw new RpcLoaderException(
String.format(
"Akka RPC system could not be found. If this happened while running a test in the IDE, "
+ "run '%s' on the command-line, "
+ "or add a test dependency on the flink-rpc-akka-loader test-jar.",
HINT_USAGE));
}
IOUtils.copyBytes(resourceStream, Files.newOutputStream(tempFile));
final SubmoduleClassLoader submoduleClassLoader =
new SubmoduleClassLoader(
new URL[] {tempFile.toUri().toURL()}, flinkClassLoader);
return new CleanupOnCloseRpcSystem(
ServiceLoader.load(RpcSystem.class, submoduleClassLoader).iterator().next(),
submoduleClassLoader,
tempFile);
} catch (IOException e) {
throw new RuntimeException("Could not initialize RPC system.", e);
}
}
}
动态代理
借助 AkkaInvocationHandler 进行动态代理
参考文献: