如果Spark的部署方式选择Standalone,一个采用Master/Slaves的典型架构,那么Master是有SPOF(单点故障,Single Point of Failure)。Spark可以选用ZooKeeper来实现HA。

ZooKeeper提供了一个Leader Election机制,利用这个机制可以保证虽然集群存在多个Master但是只有一个是Active的,其他的都是Standby,当Active的Master出现故障时,另外的一个Standby Master会被选举出来。由于集群的信息,包括Worker, Driver和Application的信息都已经持久化到文件系统,因此在切换的过程中只会影响新Job的提交,对于正在进行的Job没有任何的影响。加入ZooKeeper的集群整体架构如下图所示。

1. Master的重启策略

Master在启动时,会根据启动参数来决定不同的Master故障重启策略:

  1. ZOOKEEPER实现HA
  2. FILESYSTEM:实现Master无数据丢失重启,集群的运行时数据会保存到本地/网络文件系统上
  3. 丢弃所有原来的数据重启

Master::preStart()可以看出这三种不同逻辑的实现。

  1. override def preStart() {
  2. logInfo("Starting Spark master at " + masterUrl)
  3. ...
  4. //persistenceEngine是持久化Worker,Driver和Application信息的,这样在Master重新启动时不会影响
  5. //已经提交Job的运行
  6.  persistenceEngine = RECOVERY_MODE match {
  7. case "ZOOKEEPER" =>
  8. logInfo("Persisting recovery state to ZooKeeper")
  9. new ZooKeeperPersistenceEngine(SerializationExtension(context.system), conf)
  10. case "FILESYSTEM" =>
  11. logInfo("Persisting recovery state to directory: " + RECOVERY_DIR)
  12. new FileSystemPersistenceEngine(RECOVERY_DIR, SerializationExtension(context.system))
  13. case _ =>
  14. new BlackHolePersistenceEngine()
  15. }
  16. //leaderElectionAgent负责Leader的选取。
  17. leaderElectionAgent = RECOVERY_MODE match {
  18. case "ZOOKEEPER" =>
  19. context.actorOf(Props(classOf[ZooKeeperLeaderElectionAgent], self, masterUrl, conf))
  20. case _ => // 仅仅有一个Master的集群,那么当前的Master就是Active的
  21.  context.actorOf(Props(classOf[MonarchyLeaderAgent], self))
  22. }
  23. }

RECOVERY_MODE是一个字符串,可以从spark-env.sh中去设置。

  1. val RECOVERY_MODE = conf.get("spark.deploy.recoveryMode", "NONE")

如果不设置spark.deploy.recoveryMode的话,那么集群的所有运行数据在Master重启是都会丢失,这个结论是从BlackHolePersistenceEngine的实现得出的。

  1. private[spark] class BlackHolePersistenceEngine extends PersistenceEngine {
  2. override def addApplication(app: ApplicationInfo) {}
  3. override def removeApplication(app: ApplicationInfo) {}
  4. override def addWorker(worker: WorkerInfo) {}
  5. override def removeWorker(worker: WorkerInfo) {}
  6. override def addDriver(driver: DriverInfo) {}
  7. override def removeDriver(driver: DriverInfo) {}
  8.  
  9. override def readPersistedData() = (Nil, Nil, Nil)
  10. }

它把所有的接口实现为空。PersistenceEngine是一个trait。作为对比,可以看一下ZooKeeper的实现。

  1. class ZooKeeperPersistenceEngine(serialization: Serialization, conf: SparkConf)
  2. extends PersistenceEngine
  3. with Logging
  4. {
  5. val WORKING_DIR = conf.get("spark.deploy.zookeeper.dir", "/spark") + "/master_status"
  6. val zk: CuratorFramework = SparkCuratorUtil.newClient(conf)
  7.  
  8. SparkCuratorUtil.mkdir(zk, WORKING_DIR)
  9. // 将app的信息序列化到文件WORKING_DIR/app_{app.id}中
  10. override def addApplication(app: ApplicationInfo) {
  11. serializeIntoFile(WORKING_DIR + "/app_" + app.id, app)
  12. }
  13.  
  14. override def removeApplication(app: ApplicationInfo) {
  15. zk.delete().forPath(WORKING_DIR + "/app_" + app.id)
  16. }

Spark使用的并不是ZooKeeper的API,而是使用的org.apache.curator.framework.CuratorFramework 和 org.apache.curator.framework.recipes.leader.{LeaderLatchListener, LeaderLatch} 。Curator在ZooKeeper上做了一层很友好的封装。

2. 集群启动参数的配置

简单总结一下参数的设置,通过上述代码的分析,我们知道为了使用ZooKeeper至少应该设置一下参数(实际上,仅仅需要设置这些参数。通过设置spark-env.sh:

  1. spark.deploy.recoveryMode=ZOOKEEPER
  2. spark.deploy.zookeeper.url=zk_server_1:2181,zk_server_2:2181
  3. spark.deploy.zookeeper.dir=/dir
  4. // OR 通过一下方式设置
  5. export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER "
  6. export SPARK_DAEMON_JAVA_OPTS="${SPARK_DAEMON_JAVA_OPTS} -Dspark.deploy.zookeeper.url=zk_server1:2181,zk_server_2:2181"

各个参数的意义:

参数 默认值 含义
spark.deploy.recoveryMode NONE 恢复模式(Master重新启动的模式),有三种:1, ZooKeeper, 2, FileSystem, 3 NONE
spark.deploy.zookeeper.url ZooKeeper的Server地址
spark.deploy.zookeeper.dir /spark ZooKeeper 保存集群元数据信息的文件目录,包括Worker,Driver和Application。

3. CuratorFramework简介

CuratorFramework极大的简化了ZooKeeper的使用,它提供了high-level的API,并且基于ZooKeeper添加了很多特性,包括

  • 自动连接管理:连接到ZooKeeper的Client有可能会连接中断,Curator处理了这种情况,对于Client来说自动重连是透明的。
  • 简洁的API:简化了原生态的ZooKeeper的方法,事件等;提供了一个简单易用的接口。
  • Recipe的实现(更多介绍请点击Recipes):
    • Leader的选择
    • 共享锁
    • 缓存和监控
    • 分布式的队列
    • 分布式的优先队列

CuratorFrameworks通过CuratorFrameworkFactory来创建线程安全的ZooKeeper的实例。

CuratorFrameworkFactory.newClient()提供了一个简单的方式来创建ZooKeeper的实例,可以传入不同的参数来对实例进行完全的控制。获取实例后,必须通过start()来启动这个实例,在结束时,需要调用close()。

  1. /**
  2. * Create a new client
  3. *
  4. *
  5. * @param connectString list of servers to connect to
  6. * @param sessionTimeoutMs session timeout
  7. * @param connectionTimeoutMs connection timeout
  8. * @param retryPolicy retry policy to use
  9. * @return client
  10. */
  11. public static CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy)
  12. {
  13. return builder().
  14. connectString(connectString).
  15. sessionTimeoutMs(sessionTimeoutMs).
  16. connectionTimeoutMs(connectionTimeoutMs).
  17. retryPolicy(retryPolicy).
  18. build();
  19. }

需要关注的还有两个Recipe:org.apache.curator.framework.recipes.leader.{LeaderLatchListener, LeaderLatch}。

首先看一下LeaderlatchListener,它在LeaderLatch状态变化的时候被通知:

  1. 在该节点被选为Leader的时候,接口isLeader()会被调用
  2. 在节点被剥夺Leader的时候,接口notLeader()会被调用

由于通知是异步的,因此有可能在接口被调用的时候,这个状态是准确的,需要确认一下LeaderLatch的hasLeadership()是否的确是true/false。这一点在接下来Spark的实现中可以得到体现。

  1. /**
  2. * LeaderLatchListener can be used to be notified asynchronously about when the state of the LeaderLatch has changed.
  3. *
  4. * Note that just because you are in the middle of one of these method calls, it does not necessarily mean that
  5. * hasLeadership() is the corresponding true/false value. It is possible for the state to change behind the scenes
  6. * before these methods get called. The contract is that if that happens, you should see another call to the other
  7. * method pretty quickly.
  8. */
  9. public interface LeaderLatchListener
  10. {
  11. /**
  12. * This is called when the LeaderLatch's state goes from hasLeadership = false to hasLeadership = true.
  13. *
  14. * Note that it is possible that by the time this method call happens, hasLeadership has fallen back to false. If
  15. * this occurs, you can expect {@link #notLeader()} to also be called.
  16. */
  17. public void isLeader();
  18.  
  19. /**
  20. * This is called when the LeaderLatch's state goes from hasLeadership = true to hasLeadership = false.
  21. *
  22. * Note that it is possible that by the time this method call happens, hasLeadership has become true. If
  23. * this occurs, you can expect {@link #isLeader()} to also be called.
  24. */
  25. public void notLeader();
  26. }

LeaderLatch负责在众多连接到ZooKeeper Cluster的竞争者中选择一个Leader。Leader的选择机制可以看ZooKeeper的具体实现,LeaderLatch这是完成了很好的封装。我们只需要要知道在初始化它的实例后,需要通过

  1. public class LeaderLatch implements Closeable
  2. {
  3. private final Logger log = LoggerFactory.getLogger(getClass());
  4. private final CuratorFramework client;
  5. private final String latchPath;
  6. private final String id;
  7. private final AtomicReference<State> state = new AtomicReference<State>(State.LATENT);
  8. private final AtomicBoolean hasLeadership = new AtomicBoolean(false);
  9. private final AtomicReference<String> ourPath = new AtomicReference<String>();
  10. private final ListenerContainer<LeaderLatchListener> listeners = new ListenerContainer<LeaderLatchListener>();
  11. private final CloseMode closeMode;
  12. private final AtomicReference<Future<?>> startTask = new AtomicReference<Future<?>>();
  13. .
  14. .
  15. .
  16. /**
  17. * Attaches a listener to this LeaderLatch
  18. * <p/>
  19. * Attaching the same listener multiple times is a noop from the second time on.
  20. * <p/>
  21. * All methods for the listener are run using the provided Executor. It is common to pass in a single-threaded
  22. * executor so that you can be certain that listener methods are called in sequence, but if you are fine with
  23. * them being called out of order you are welcome to use multiple threads.
  24. *
  25. * @param listener the listener to attach
  26. */
  27. public void addListener(LeaderLatchListener listener)
  28. {
  29. listeners.addListener(listener);
  30. }

通过addListener可以将我们实现的Listener添加到LeaderLatch。在Listener里,我们在两个接口里实现了被选为Leader或者被剥夺Leader角色时的逻辑即可。

4. ZooKeeperLeaderElectionAgent的实现

实际上因为有Curator的存在,Spark实现Master的HA就变得非常简单了,ZooKeeperLeaderElectionAgent实现了接口LeaderLatchListener,在isLeader()确认所属的Master被选为Leader后,向Master发送消息ElectedLeader,Master会将自己的状态改为ALIVE。当noLeader()被调用时,它会向Master发送消息RevokedLeadership时,Master会关闭。

  1. private[spark] class ZooKeeperLeaderElectionAgent(val masterActor: ActorRef,
  2. masterUrl: String, conf: SparkConf)
  3. extends LeaderElectionAgent with LeaderLatchListener with Logging {
  4.   val WORKING_DIR = conf.get("spark.deploy.zookeeper.dir", "/spark") + "/leader_election"
  5.   // zk是通过CuratorFrameworkFactory创建的ZooKeeper实例
  6.   private var zk: CuratorFramework = _
  7.   // leaderLatch:Curator负责选出Leader。
  8.   private var leaderLatch: LeaderLatch = _
  9.   private var status = LeadershipStatus.NOT_LEADER
  10.  
  11.   override def preStart() {
  12.  
  13.     logInfo("Starting ZooKeeper LeaderElection agent")
  14.     zk = SparkCuratorUtil.newClient(conf)
  15.     leaderLatch = new LeaderLatch(zk, WORKING_DIR)
  16.     leaderLatch.addListener(this)
  17.  
  18.     leaderLatch.start()
  19.   }

在prestart中,启动了leaderLatch来处理选举ZK中的Leader。就如在上节分析的,主要的逻辑在isLeader和noLeader中。

  1. override def isLeader() {
  2. synchronized {
  3. // could have lost leadership by now.
  4. //现在leadership可能已经被剥夺了。。详情参见Curator的实现。
  5. if (!leaderLatch.hasLeadership) {
  6. return
  7. }
  8.  
  9. logInfo("We have gained leadership")
  10. updateLeadershipStatus(true)
  11. }
  12. }
  13.  
  14. override def notLeader() {
  15. synchronized {
  16. // 现在可能赋予leadership了。详情参见Curator的实现。
  17. if (leaderLatch.hasLeadership) {
  18. return
  19. }
  20.  
  21. logInfo("We have lost leadership")
  22. updateLeadershipStatus(false)
  23. }
  24. }

updateLeadershipStatus的逻辑很简单,就是向Master发送消息。

  1. def updateLeadershipStatus(isLeader: Boolean) {
  2. if (isLeader && status == LeadershipStatus.NOT_LEADER) {
  3. status = LeadershipStatus.LEADER
  4. masterActor ! ElectedLeader
  5. } else if (!isLeader && status == LeadershipStatus.LEADER) {
  6. status = LeadershipStatus.NOT_LEADER
  7. masterActor ! RevokedLeadership
  8. }
  9. }

5. 设计理念

为了解决Standalone模式下的Master的SPOF,Spark采用了ZooKeeper提供的选举功能。Spark并没有采用ZooKeeper原生的Java API,而是采用了Curator,一个对ZooKeeper进行了封装的框架。采用了Curator后,Spark不用管理与ZooKeeper的连接,这些对于Spark来说都是透明的。Spark仅仅使用了100行代码,就实现了Master的HA。当然了,Spark是站在的巨人的肩膀上。谁又会去重复发明轮子呢?

请您支持:

如果你看到这里,相信这篇文章对您有所帮助。如果是的话,请为本文投一下票吧: 点击投票,多谢。如果您已经在投票页面,请点击下面的投一票吧!

BTW,即使您没有CSDN的帐号,可以使用第三方登录的,包括微博,QQ,Gmail,GitHub,百度,等。

Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源码实现的更多相关文章

  1. Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源代码实现

    假设Spark的部署方式选择Standalone,一个採用Master/Slaves的典型架构,那么Master是有SPOF(单点故障,Single Point of Failure).Spark能够 ...

  2. Spark技术内幕:Master的故障恢复

    Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源码实现  详细阐述了使用ZK实现的Master的HA,那么Master是如何快速故障恢复的呢? 处于 ...

  3. Spark技术内幕:Client,Master和Worker 通信源码解析

    http://blog.csdn.net/anzhsoft/article/details/30802603 Spark的Cluster Manager可以有几种部署模式: Standlone Mes ...

  4. Spark技术内幕:Stage划分及提交源码分析

    http://blog.csdn.net/anzhsoft/article/details/39859463 当触发一个RDD的action后,以count为例,调用关系如下: org.apache. ...

  5. Spark技术内幕: Task向Executor提交的源码解析

    在上文<Spark技术内幕:Stage划分及提交源码分析>中,我们分析了Stage的生成和提交.但是Stage的提交,只是DAGScheduler完成了对DAG的划分,生成了一个计算拓扑, ...

  6. Spark技术内幕: Task向Executor提交的源代码解析

    在上文<Spark技术内幕:Stage划分及提交源代码分析>中,我们分析了Stage的生成和提交.可是Stage的提交,仅仅是DAGScheduler完毕了对DAG的划分,生成了一个计算拓 ...

  7. Spark技术内幕:Shuffle的性能调优

    通过上面的架构和源码实现的分析,不难得出Shuffle是Spark Core比较复杂的模块的结论.它也是非常影响性能的操作之一.因此,在这里整理了会影响Shuffle性能的各项配置.尽管大部分的配置项 ...

  8. linux -- 基于zookeeper搭建yarn的HA高可用集群

    linux -- 基于zookeeper搭建yarn的HA高可用集群 实现方式:配置yarn-site.xml配置文件 <configuration> <property> & ...

  9. 基于Eclipse IDE的Ardupilot飞控源码阅读环境搭建

    基于Eclipse IDE的Ardupilot飞控源码阅读环境搭建 作者:Awesome 日期:2017-10-21 需准备的软件工具 Ardupilot飞控源码 PX4 toolchain JAVA ...

随机推荐

  1. bzoj2655calc 容斥+dp

    2655: calc Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 322  Solved: 197[Submit][Status][Discuss] ...

  2. Gradle学习之基础篇

    一.gradle基础概念 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具.Gradle抛弃了基于各种繁琐的XML,使用一种基于Groovy的特定领域语言( ...

  3. Python作业之工资管理

    作业之工资管理 工资管理实现要求: 工资管理系统 Alex 100000 Rain 80000 Egon 50000 Yuan 30000 -----以上是info.txt文件----- 实现效果: ...

  4. Tinychain 是比特币的一个简易口袋实现

    Putting the rough in "rough consensus" Tinychain is a pocket-sized implementation of Bitco ...

  5. Miox带你走进动态路由的世界——51信用卡前端团队

    写在前面: 有的时候再做大型项目的时候,确实会被复杂的路由逻辑所烦恼,会经常遇到权限问题,路由跳转回退逻辑问题.这几天在网上看到了51信用卡团队开源了一个Miox,可以有效的解决这些痛点,于是乎我就做 ...

  6. JavaScript判断不同平台

    function getPlatformType() { let UA = navigator.userAgent; if(/MicroMessenger/i.test(UA)){ return 'w ...

  7. 补充Mysql5.7用法

    下面简单介绍一下安装: [root@MySQL soft]# tar xf mysql-5.7.10-linux-glibc2.5-x86_64.tar.gz -C /data/service/ [r ...

  8. mongo数据更新(修改器)

    数据更新简单的做法是删除重新插入update()函数语法 db.集合.update(更新条件,新的对象数据(更新操作符),upsert,multi)upsert如果要更新的数据不存在,则增加一条新的内 ...

  9. js判断奇偶数实现隐藏显示功能 与css立体按钮

      hello!   好久不见了 ,今天也没准备什么技术,知识想和大家就见个面,一个js判断奇数偶数来实现css样式 ,感觉最大的用途就是页面的导航.就这么一个小小的技术. 劳动快乐   当!当!当! ...

  10. 关于ubuntu14.04LTS 64位 播放优酷视频

    起因:chrome无法播放优酷视频,然后换firefox发现居然没有装flash 插件. 解释:关于chrome在网上看到了不少说法,说chrome新版本的不支持adobe flash之类的,但是这些 ...