一个典型的HA集群,NameNode会被配置在两台独立的机器上.在任何的时间上,一个NameNode处于活动状态,而另一个在备份状态,活动状态的NameNode会响应集群中所有的客户端,同时备份的只是作为一个副本,保证在必要的时候提供一个快速的转移。
为了使备份的节点和活动的节点保持一致,两个节点通过一个特殊的守护线程相连,这个线程叫做“JournalNodes”(JNs)。当活动状态的节点修改任何的命名空间,他都会通过这些JNs记录日志,备用的节点可以监控edit日志的变化,并且通过JNs读取到变化。备份节点查看edits可以拥有专门的namespace。在故障转移的时候备份节点将在切换至活动状态前确认他从JNs读取到的所有edits。这个确认的目的是为了保证Namespace的状态和迁移之前是完全同步的。
为了提供一个快速的转移,备份NameNode要求保存着最新的block在集群当中的信息。为了能够得到这个,DataNode都被配置了所有的NameNode的地址,并且发送block的地址信息和心跳给两个node。
保证只有一个活跃的NameNode在集群当中是一个十分重要的一步。否则namespace状态在两个节点间不同会导致数据都是或者其他一些不正确的结果。为了确保这个,防止所谓split - brain场景,JournalNodes将只允许一个NameNode进行写操作。故障转移期间,NameNode成为活跃状态的时候会接管JournalNodes的写权限,这会有效防止其他NameNode持续处于活跃状态,允许新的活动节点安全进行故障转移。
为了部署一个HA集群,你应该按照以下准备:
NameNode机器:机器负责运行活动和和备份的NameNode,两台机器应该有着完全一样的硬件,同样的硬件应该和没有HA的硬件完全一致。
JournalNode机器:这个机器用来运行JNs,JNs的守护线程相对较轻,所以可以和Hadoop的其他守护线程放到一起,比如NameNodes, the JobTracker, 或者 YARN ResourceManager。注意至少需要3个JNs的守护线程,因为edit日志的编辑和修改必须写入大多数的JNs。这将允许系统在单机上失败。你可能运行多余3个的jns,但是为了能够判定失败的数目,应该运行一个单数的JNs(比如3,5,7等)。注意当运行N个jns,系统最多可以容忍(N - 1)/ 2失败,并继续正常运转。
dfs.ha.namenodes.[nameservice ID] -为在nameservice中的每一个NameNode设置唯一标示符。
配置一个逗号分隔的 NameNode ID列表。这将是被DataNode识别为所有的NameNode。例如,如果使用“mycluster”作为nameservice ID,并且使用“nn1”和“nn2”作为NameNodes标示符,你应该如下配置:
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2</value>
</property>
注意:在每一个nameservice中最多只能有两个NameNode可以被配置
dfs.namenode.rpc-address.[nameservice ID].[name node ID] - 每个NameNode监听的完整正确的RPC地址
对于先前配置的NameNode ID,设置全地址和IP端口的NameNode进程,注意配置两个独立的配置选项例如:
<property>
<name>dfs.namenode.rpc-address.mycluster.nn1</name>
<value>machine1.example.com:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn2</name>
<value>machine2.example.com:8020</value>
</property>
注意:如果您愿意,您可能同样配置“servicerpc-address”设置
dfs.namenode.http-address.[nameservice ID].[name node ID] -每个NameNode监听的完整正确的HTTP地址
和上面设置rpc-address一样,设置NameNode的HTTP服务,例如:
<property>
<name>dfs.namenode.http-address.mycluster.nn1</name>
<value>machine1.example.com:50070</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn2</name>
<value>machine2.example.com:50070</value>
</property>
注意:如果Hadoop的安全特征被开启,你应该类似的设置https-address为每一个NameNode
dfs.namenode.shared.edits.dir - NameNode读/写edits的URL,为JNs服务
这个配置是为了JournalNodes配置,提供edits的共享存储地址,这个地址被活动状态的nameNode写入被备份状态的nameNode读取用来保存活动状态写入的整个文件系统的最新的改变。尽管你必须指定几个JournalNode地址,但是你只要一个配置为这些URL,这个URI应该是这样的“qjournal://host1:port1;host2:port2;host3:port3/journalId”,Journal ID是这个nameservice的唯一标示。它允许一套JournalNodes为多个namesystems提供存储。虽然不是必须的,但是建议重用nameservice ID 作为journal 的标示。
例如如果JournalNodes正运行在"node1.example.com", "node2.example.com", 和"node3.example.com",并且集群的nameservice ID是 mycluster,你应该用下面作为设置(JournalNode 默认的端口是8485)
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://node1.example.com:8485;node2.example.com:8485;node3.example.com:8485/mycluster</value>
</property>
dfs.client.failover.proxy.provider.[nameservice ID] -HDFS客户端使用的Java类与活跃的NameNode联系
配置一个类的名字用来被DFS客户端确定那个NameNode是目前活跃的那个NameNode现在正在提供响应。目前唯一的实现是已经附带的ConfiguredFailoverProxyProvider,除非自己实现一个,例如:
<property>
<name>dfs.client.failover.proxy.provider.mycluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
dfs.ha.fencing.methods -一个脚本或者java累的列表用来筛选活动NameNode在故障转移的期间
在任何时间只有一个活动的NameNode 都是系统所需的。当使用Quorum Journal管理器时,只有一个NameNode将被允许写入到JournalNodes,所以没有可能破坏文件系统的元数据从一个split-brain的场景。但是当一个迁移法伤的时候,前一个活动的NameNode还是可能读取客户端的请求并为其提供服务,当他企图写入JournalNodes时候他可能会超时,直到这个NameNode停止。因为这个原因,所以需要配置几个过滤的方法当使用Quorum Journal管理器时候使用。但是为了提高当一个过滤机制失败时系统的可用性,建议配置一个过滤方法保证返回成果的作为这个过滤方法列表的最后一项,助于如果你选择使用一个没有实际效果的过滤方法你也必须配置一些东西为这个设置比如“shell(/bin/true)”。
过滤方法被配置为carriage-return-separated列表,会在故障转移的时候被调用,直到一个过滤返回success。有两个方法比Hadoop使用:
shell和sshfence。如果想自定义可以看theorg.apache.hadoop.ha.NodeFencer类。
<property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value> </property> <property> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/home/exampleuser/.ssh/id_rsa</value> </property>
可以通过不标准username或者port来实现SSH,我们也可以配置一个超时时间(毫秒),如果这个时间没有连接上那么会返回一个失败。例如:
<property> <name>dfs.ha.fencing.methods</name> <value>sshfence([[username][:port]])</value> </property> <property> <name>dfs.ha.fencing.ssh.connect-timeout</name> <value>30000</value> </property>
shell - 运行任何的shell命令去过滤活动的NameNode<property> <name>dfs.ha.fencing.methods</name> <value>shell(/path/to/my/script.sh arg1 arg2 ...)</value> </property>
$target_host | hostname of the node to be fenced |
$target_port | IPC port of the node to be fenced |
$target_address | the above two, combined as host:port |
$target_nameserviceid | the nameservice ID of the NN to be fenced |
$target_namenodeid | the namenode ID of the NN to be fenced |
<property> <name>dfs.ha.fencing.methods</name> <value>shell(/path/to/my/script.sh --nameservice=$target_nameserviceid $target_host:$target_port)</value> </property>
如果返回0,过滤方法并认为是成功,其他则认为不成功会调用下一个过滤方法。
<property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value> </property>
dfs.journalnode.edits.dir -JournalNode守护线程报错他本地状态的路径<property> <name>dfs.journalnode.edits.dir</name> <value>/path/to/journal/node/local/data</value> </property>
Usage: DFSHAAdmin [-ns <nameserviceId>] [-transitionToActive <serviceId>] [-transitionToStandby <serviceId>] [-failover [--forcefence] [--forceactive] <serviceId> <serviceId>] [-getServiceState <serviceId>] [-checkHealth <serviceId>] [-help <command>]
<property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property>
<property> <name>ha.zookeeper.quorum</name> <value>zk1.example.com:2181,zk2.example.com:2181,zk3.example.com:2181</value> </property>
这是一个主机端口的匹配在ZooKeeper 服务中。
如先前的文档中关于参数的描述,这些设置可能需要被配置先前的nameservice并用nameservice ID作为后缀,比如在集群中,你可能期望自动故障转移只是发生在一个nameservice上那么可以设置dfs.ha.automatic-failover.enabled.my-nameservice-id.
还有一些参数也可以设置并且对于自动迁移或一些影响,但是他们对于绝大多数的安装来说不是必要的。详细内容请参考配置主键key文档。
在ZooKeeper初始化HA状态
在配置的主键被添加之后,下一步就是在ZooKeeper中初始化需要的状态,你可以在一个NameNode的主机上运行下面的命令:
$ hdfs zkfc -formatZK
他会在ZooKeeper 创建一个Znode代替用来为自动迁移储存数据。
使用start-dfs.sh启动集群
因为自动迁移被配置在文件中,start-dfs.sh脚本会自动启动一个ZKFC守护线程在NameNode运行的机器上,当ZKFC启动时,他会自动选择一个NameNode作为活动的。
手动启动集群
如果你手动管理你的集群中的服务,你需要手动的为每一个NameNode的机器去启动zkfc,你可以运行下面的命令:
$ hadoop-daemon.sh start zkfc
安全的进入ZooKeeper
如果你运行的是一个安全的集群,你会希望在ZooKeeper储存的信息也是安全的这会防止恶意的客户端改变ZooKeeper的元数据或者触发错误的故障转移。
为了实现安全的信息,首先需要在core-site.xml添加下面的内容:
<property> <name>ha.zookeeper.auth</name> <value>@/path/to/zk-auth.txt</value> </property> <property> <name>ha.zookeeper.acl</name> <value>@/path/to/zk-acl.txt</value> </property>
digest:hdfs-zkfcs:mypassword
--hdfs-zkfcs是一个ZooKeeper用户名,mypassword是一个密码
$ java -cp $ZK_HOME/lib/*:$ZK_HOME/zookeeper-3.4.2.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider hdfs-zkfcs:mypassword
output: hdfs-zkfcs:mypassword->hdfs-zkfcs:P/OQvnYyU/nF/mGYvB/xurX8dYs=
复制粘贴->后面的字符串到zk-acls.tx,“digest:”作为前缀,比如:
digest:hdfs-zkfcs:vlUvLnd8MlacsE80rDuu6ONESbM=:rwcda
为了这些ACL起作用,你应该运行zkfc -format命令如上描述。
[zk: localhost:2181(CONNECTED) 1] getAcl /hadoop-ha
'digest,'hdfs-zkfcs:vlUvLnd8MlacsE80rDuu6ONESbM=
: cdrwa
验证自动迁移