一个典型的HA集群,NameNode会被配置在两台独立的机器上.在任何的时间上,一个NameNode处于活动状态,而另一个在备份状态,活动状态的NameNode会响应集群中所有的客户端,同时备份的只是作为一个副本,保证在必要的时候提供一个快速的转移。
为了备份节点和活跃的节点状态同步,当前的实现要求两个节点可以连接到一个共享设备上的目录(例如网络存储的网络文件系统)。这个架构会在以后的版本中实现。
当任何的namespace被活动状态的节点修改,他会记录在共享文件夹的edit日志文件中。备份节点观察共享文件夹的edit,他允许拥有自己的namespace。在执行自动迁移之前,备份节点在转换为活动的节点之前确认已经读取到共享存储当中所有的edit。这将确保和迁移之前完全同步。
为了提供一个快速的转移,备份NameNode要求保存着最新的block在集群当中的信息。为了能够得到这个,DataNode都被配置了所有的NameNode的地址,并且发送block的地址信息和心跳给两个node。
保证只有一个活跃的NameNode在集群当中是一个十分重要的一步。否则namespace状态在两个节点间不同会导致数据都是或者其他一些不正确的结果。为了确保这个,防止所谓split - brain场景,管理员至少为共享存储配置一个过滤方法。故障转移期间,如果前一个活跃节点不再是活跃状态那么他将无法通过过滤,过滤过程负责切断前一活跃的节点写入共享存储edits的权限。这会有效防止其他NameNode持续处于活跃状态,允许新的活动节点安全进行故障转移。
为了部署一个HA集群,你应该按照以下准备:
NameNode机器:机器负责运行活动和和备份的NameNode,两台机器应该有着完全一样的硬件,同样的硬件应该和没有HA的硬件完全一致。
共享存储:需要有一个共享文件夹并且所有的NameNode机器都有读写权限。通常这是一个远程文件,支持NFS并安装挂载在每个NameNode机器。目前只有单一的共享edits文件夹被支持。因此,系统的可用性被共享的edits文件夹所限制,为了解决这个单点则需要edits文件夹的冗余。具体来说,多个网络的存储路径和冗余存储本身(磁盘、网络、和权力)。因为这个原因,建议共享存储服务器是一个高质量的专用NAS设备而不是一个简单的Linux服务器。
注意:在一个HA集群中备份的NameNode也要坚持namespace的状态,那么就没有必要去运行一个Secondary NameNode, CheckpointNode, 或者是BackupNode在集群当中,事实上这么做的话有可能会是一个错误。为了允许有一个重新配置的非HA的集群可以实现HA,并且实现硬件的重用,所以把以前的secondary NameNode的机器作为这样一个机器。
配置综述
HA的配置向后兼容允许既存的单NameNode配置在没有任何改动的情况下工作,新的配置被设计成集群当中的所有节点拥有着相同的配置并且并不需要为不同的机器设置不同的配置文件。
如HDFS Federation,HA集群重用nameserviceID去标示一个HDFS实例这个实例可能实际上包含了很多的HA NameNodes。另外一个新的抽象叫做NameNode ID被添置到了HA。在集群中每个不同的NameNode有着不同的NameNode ID 去标示他,所有的NameNode采用同一个配置文件,相关的配置参数都被用nameservice ID和NameNode ID作为后缀。
配置细节
dfs.ha.namenodes.[nameservice ID] -为在nameservice中的每一个NameNode设置唯一标示符。
配置一个逗号分隔的 NameNode ID列表。这将是被DataNode识别为所有的NameNode。例如,如果使用“mycluster”作为nameservice ID,并且使用“nn1”和“nn2”作为NameNodes标示符,你应该如下配置: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 - 共享存储文件夹的地址。
这里配置一个edits的远程共享目录,该目录用于备份节点获取所有活跃节点造成的文件系统的更新,你应该配置只配置一个这样的目录。这个目录应该可以被所有的NameNode读写,这个应该是一个绝对路径,例如:
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>file:///mnt/filer1/dfs/ha-name-dir-shared</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 都是系统所需的。我们需要确认活跃的NameNode转换为备份的状态,或者防止在其他NameNode转换为活跃状态之前,进程被终结。为了这个目的我们需要配置至少一个过滤方法(fencing method)。
过滤方法被配置为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>
这是你需要启动两个HA的NameNode作为你日常的NameNode启动。
你能访问每一个NameNode的web页面通过配置的HTTP地址,你应该注意到在配置的地址旁边就是HA的状态(‘active’或者‘standby’)什么时候一个HA NameNode 启动它会被初始化为备份状态。
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
验证自动迁移
BookKeeper是NameNode共享存储的一个选择。BookKeeper通过早多个高性能存储节点之间复制edit的日志实现了一个高可用。edit的日志可以通过高性能的节点实现高性能。过滤方法也可以被支持,例如BookKeeper 不允许同时有两个对象可以写入同一个edit日志。
BookKeeper的元数据被储存在ZooKeeper中。在HA架构中,ZKFC需要Zookeeper集群支持,这个集群同样也为BookKeeper元数据服务。
更多的细节可以参考BookKeeper文档。
BookKeeperJournalManager 是一个HDFS JournalManager的实现接口,运行用户自定义实现日志写入HDFS的NameNode当中.
BookKeeper Journal Manager
使用BookKeeperJournalManager应该添加下面的内容到hdfs-site.xml
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>bookkeeper://zk1:2181;zk2:2181;zk3:2181/hdfsjournal</value>
</property>
<property>
<name>dfs.namenode.edits.journal-plugin.bookkeeper</name>
<value>org.apache.hadoop.contrib.bkjournal.BookKeeperJournalManager</value>
</property>
bookkeeper 的URI形式是bookkeeper://[zkEnsemble]/[rootZnode] [zookkeeper ensemble] 是一个分号分隔的列表,与zookeeper 的host:port 匹配。例如上面是3个服务zk1, zk2 & zk3,每一个都是监听2181端口。[root znode]是zookeeper的znode的路径用来储存edit日志信息
必须在NameNode的类路径下为ournal-plugin指定类。会解释如何使用journal manager生成一个jar文件,并把它引入classpath下。
更多的配置选项.
dfs.namenode.bookkeeperjournal.output-buffer-size - bookkeeper journal流缓冲的字节数。默认是1024
<property>
<name>dfs.namenode.bookkeeperjournal.output-buffer-size</name>
<value>1024</value>
</property>
dfs.namenode.bookkeeperjournal.ensemble-size - bookkeeper服务在edit日志的数量,这个数量是可以写入edit日志的服务的数量,默认是3
<property>
<name>dfs.namenode.bookkeeperjournal.ensemble-size</name>
<value>3</value>
</property>
dfs.namenode.bookkeeperjournal.quorum-size - bookkeeper 可以写入的数量。这个数量用来认定写入。默认是2
<property>
<name>dfs.namenode.bookkeeperjournal.quorum-size</name>
<value>2</value>
</property>
dfs.namenode.bookkeeperjournal.digestPw - 当创建edit日志段的时候需要的密码
<property>
<name>dfs.namenode.bookkeeperjournal.digestPw</name>
<value>myPassword</value>
</property>
dfs.namenode.bookkeeperjournal.zk.session.timeout - Zookeeper 客户端从BookKeeper Journal Manager的session超时时间。Hadoop要求这个值应该小于ZKFC 的session超时时间默认为3000
<property>
<name>dfs.namenode.bookkeeperjournal.zk.session.timeout</name>
<value>3000</value>
</property>
构建BookKeeper Journal Manager plugin jar
为BK journal生成一个分布的包应该这么做:
$ mvn clean package -Pdist
这会生成一个BookKeeperJournalManager的jar,all the dependencies needed by the journal manager, hadoop-hdfs/src/contrib/bkjournal/target/hadoop-hdfs-bkjournal-VERSION.jar
注意:-Pdist的部分很重要,否则将无法形成一个jar的包。这个依赖包含进jar路径避免与其他的NameNode依赖冲突
放置BookKeeperJournalManager到NameNode classpath
运行一个HDFSNameNode并使用BookKeeper,需要拷贝bkjournal jar到hdfs的lib目录。在HDFS的文件系统中,他应该是
$HADOOP_HDFS_HOME/share/hadoop/hdfs/lib/
当前的限制
1)NameNode格式自动命令不会格式化BookKeeper数据。我们不得不手动清空BookKeeper集群数据,在Zookeeper创建/ledgers/available路径
---- $ zkCli.sh create /ledgers 0 $ zkCli.sh 创建 /ledgers/available 0
注意:bookkeeper://zk1:2181;zk2:2181;zk3:2181/hdfsjournal最后部分的/hdfsjournal指明在zookeeper的znode,储存元数据的储存路径,管理员可以设置喜欢的值。
2)BookKeeper不支持SASL和SSL连接
3)自动恢复存储节点的失败。只能参考下面的文章(BOOKKEEPER-237),手动恢复数据。