使用Akka,所有远程过程调用现在都实现为异步消息。 这主要影响JobManager,TaskManager和JobClient的组件。 将来,甚至可能会将更多的组件转换为参与者,从而允许它们发送和处理异步消息。
Akka and the Actor Model
Akka是开发并发,容错和可扩展应用程序的框架。 它是参与者模型的实现,因此类似于Erlang的并发模型。 在参与者模型的上下文中,所有代理实体都被视为独立的参与者。 角色通过彼此发送异步消息与其他角色进行通信。 参与者模型的优势在于这种异步性。 也可以显式等待响应,以便您执行同步操作。 但是,强烈建议不要使用同步消息,因为它们会限制系统的可伸缩性。 每个参与者都有一个邮箱,其中存储了收到的消息。 此外,每个参与者都保持自己的孤立状态。 下面是几个参与者的示例网络。
角色具有单个处理线程,该线程轮询角色的邮箱并连续处理接收到的消息。 作为已处理消息的结果,参与者可以更改其内部状态,发送新消息或生成新参与者。 如果一个actor的内部状态是从其处理线程内部专门操纵的,则无需使actor的状态线程安全。 即使单个参与者本质上是顺序的,由多个参与者组成的系统也是高度并发且可扩展的,因为处理线程在所有参与者之间共享。 这种共享也是为什么从不应该从参与者线程内部调用阻塞调用的原因。 这样的调用将阻止该线程被其他参与者用来处理自己的消息。
Actor Systems
Actor系统是所有演员生活的容器。 它提供共享服务,例如计划,配置和日志记录。 参与者系统还包含线程池,所有参与者线程都从该线程池中募集。
多角色系统可以共存于一台机器上。 如果actor系统以RemoteActorRefProvider启动,则可以从可能驻留在远程计算机上的另一个actor系统进行访问。 演员系统自动识别演员消息是发给同一个演员系统中还是远程演员系统中的演员的。 在本地通信的情况下,可以使用共享内存有效地传输消息。 在远程通信的情况下,消息是通过网络堆栈发送的。
所有参与者都按层次结构组织。 每个新创建的actor都会将其创建actor作为父级分配。 该层次结构用于监督。 每个父母都有对其子女的监护权。 如果其中一个子项发生错误,则会通知他。 如果演员可以解决问题,那么他可以继续或重新开始孩子。 如果问题超出了他的处理范围,他可以将错误上报给自己的父母。 逐步升级错误仅表示当前层之上的层次结构层现在负责解决问题。
系统创建的第一个参与者由系统提供的守护者参与者/用户监督。 角色层次在此进行了详细说明。
Actors in Flink
Actors本身就是状态和行为的容器。 它是actor线程顺序处理传入的消息。 因为一个actor一次仅活动一个线程,所以它使用户摆脱了易于出错的锁定和线程管理任务。 但是,必须确保仅从此参与者线程访问参与者的内部状态。 actor的行为由接收函数定义,该函数为每个消息包含在接收到此消息时执行的某些逻辑。
Flink系统由三个必须通信的分布式组件组成:JobClient,JobManager和TaskManager。 JobClient从用户那里获取Flink作业,并将其提交给JobManager。 然后JobManager负责编排作业执行。 首先,它分配所需的资源量。 这主要包括TaskManager上的执行插槽。
分配资源后,JobManager将作业的各个任务部署到相应的TaskManager中。一旦收到任务,TaskManager会生成一个执行任务的线程。 状态更改(例如开始计算或完成计算)将发送回JobManager。 根据这些状态更新,JobManager将引导作业执行直到完成。 作业完成后,其结果将发送回给JobClient,以告知用户相关信息。 下图描述了作业执行过程。
JobManager & TaskManager
JobManager是中央控制单元,负责执行Flink作业。 因此,它控制着资源分配,任务调度和状态报告。
必须先启动一个JobManager和一个或多个TaskManager,然后才能执行任何Flink作业。 然后TaskManager通过向JobManager发送RegisterTaskManager消息在JobManager上注册。 JobManager通过``确认注册''消息确认注册成功。 如果TaskManager已在JobManager上注册,则由于发送了多个RegisterTaskManager消息,则JobManager返回一个AlreadyRegistered消息。 如果注册被拒绝,则JobManager将以RefuseRegistration消息作为响应。
通过向作业管理器发送带有相应JobGraph的SubmitJob消息向作业管理器提交作业。 收到JobGraph后,JobManager将在JobGraph中创建一个ExecutionGraph,作为分布式执行的逻辑表示。 ExecutionGraph包含有关必须执行才能部署到TaskManager的任务的信息。
JobManager的调度程序负责在可用TaskManager上分配执行插槽。 在TaskManager上分配执行插槽后,带有执行任务所需的所有必要信息的SubmitTask消息将发送到相应的TaskManager。 TaskOperationResult确认任务部署成功。 一旦部署并运行了提交作业的源,作业提交也被认为是成功的。 JobManager通过发送带有相应作业ID的成功消息来通知JobClient此状态。
在TaskManager上运行的单个任务的状态更新通过UpdateTaskExecutionState消息发送回JobManager。 使用这些更新消息,可以更新ExecutionGraph以反映执行的当前状态。
JobManager还充当数据源的输入拆分分配器。 它负责在所有TaskManager之间分配工作,以便在可能的情况下保留数据局部性。 为了动态平衡负载,任务在完成对旧输入的处理后,会请求新的输入拆分。 该请求是通过将RequestNextInputSplit发送到JobManager来实现的。 JobManager用NextInputSplit消息响应。 如果没有更多输入拆分,则消息中包含的输入拆分为null。
任务被延迟部署到任务管理器。 这意味着消耗数据的任务仅在其生产者之一完成生产某些数据之后才部署。 生产者执行此操作后,就会将ScheduleOrUpdateConsumers消息发送到JobManager。 此消息表明,消费者现在可以读取新生成的数据。 如果使用任务尚未运行,它将被部署到TaskManager。
JobClient
JobClient代表分布式系统的面向用户的组件。 它用于与JobManager进行通信,因此它负责提交Flink作业,查询已提交作业的状态并接收当前正在运行的作业的状态消息。
JobClient还是您通过消息与之通信的参与者。 存在与作业提交有关的两条消息:SubmitJobDetached和SubmitJobWait。 第一条消息提交作业,并从接收任何状态消息和最终作业结果中注销。 如果您想以丢脸的方式将作业提交到Flink群集,则分离模式非常有用。
SubmitJobWait消息将作业提交到JobManager并注册以接收该作业的状态消息。 在内部,这是通过生成辅助角色来完成的,该辅助角色用作状态消息的接收者。 作业终止后,由JobManager将带有持续时间和累加器结果的JobResultSuccess发送给产生的助手角色。 收到此消息后,辅助角色将消息转发给客户端,该客户端最初发出了SubmitJobWait消息,然后终止。
Asynchronous vs. Synchronous Messages
Flink尽可能尝试使用异步消息并将响应作为将来处理。 期货和少数现有的阻塞调用都有一个超时,在此之后该操作将被视为失败。 这样可以防止消息丢失或分布式组件崩溃时系统陷入僵局。 但是,如果您碰巧拥有非常大的群集或缓慢的网络,则可能会错误地触发超时。 因此,可以通过配置中的“ akka.ask.timeout”指定这些操作的超时时间。
演员可以与其他演员交谈之前,必须为其检索ActorRef。 此操作的查找也需要超时。 为了使Actor未启动时系统快速故障,将查找超时设置为比常规超时更小的值。 如果遇到查找超时的情况,可以通过配置中的“ akka.lookup.timeout”来增加查找时间。
Akka的另一个特点是它设置了可以发送的最大邮件大小的限制。 原因是它保留了相同大小的序列化缓冲区,并且不想浪费内存。 如果由于消息超出最大大小而遇到传输错误,则可以通过配置中的“ akka.framesize”来增加帧大小。
Failure Detection
分布式系统中的故障检测对其鲁棒性至关重要。 在商品集群上运行时,总是会发生某些组件发生故障或无法再访问的情况。 此类故障的原因是多态的,从硬件故障到网络中断都可能造成故障。 一个强大的分布式系统应该能够检测出故障的组件并从中恢复。
Flink通过使用Akka的DeathWatch机制来检测故障组件。 即使没有受到该演员的监督,甚至不在另一个演员系统中,DeathWatch也可以让演员观看其他演员。 一旦被观看的演员死亡或无法联系,终止消息就会发送给观看的演员。 因此,在接收到这样的消息时,系统可以针对它采取步骤。 在内部,DeathWatch被实现为心跳和故障检测器,它基于心跳间隔,听音暂停和故障阈值来估计演员何时可能死亡。 可以通过在配置中设置“ akka.watch.heartbeat.interval”值来控制心跳间隔。 可以通过“ akka.watch.heartbeat.pause”指定可接受的心跳暂停。 心跳暂停应为心跳间隔的倍数,否则丢失的心跳将直接触发DeathWatch。 可以通过“ akka.watch.threshold”指定故障阈值,它可以有效地控制故障检测器的灵敏度。 您可以在此处找到有关DeathWatch机制和故障检测器的更多详细信息。
在Flink中,JobManager监视所有已注册的TaskManager,而TaskManager监视JobManager。 这样,两个组件都知道何时不再可访问另一个组件。 JobManager的反应是将各个TaskManager标记为已死,以防止将来的任务部署到该TaskManager。 此外,它将使当前正在此任务管理器上运行的所有任务失败,并在其他TaskManager上重新安排其执行时间。 如果TaskManager仅因暂时的连接丢失而被标记为死,那么一旦重新建立连接,它就可以在JobManager中简单地重新注册自己。
TaskManager还监视JobManager。 此监视允许TaskManager在检测到JobManager失败时通过使所有当前正在运行的任务失败来进入清除状态。 此外,如果触发的死亡仅由网络拥塞或连接丢失引起,TaskManager将尝试重新连接到JobManager。