Zookeeper
前面说了Hadoop的三大核心组件:HDFS分布式文件系统,MapReduce分布式计算框架,Yarn集群资源管理系统
今天我们来看一下另一个组件Zookeeper,这是一个开源的分布式应用程序协调服务,可以保证数据在集群间的事务一致性
Zookeeper的应用场景
集群分布式锁,集群统一命名服务,分布式协调服务
Zookeeper角色和选举
服务在启动的时候是没有角色的(LOOKING),通过选举产生角色,选举会产生一个Leader,剩下的是Follower
Leader | 接受所有Follower的提案请求并统一协调发起提案的投票,负责与所有的Follower进行内部数据交换 |
Follower | 直接为客户端服务并参与提案的投票,同时与Leader进行数据交换 |
Observer | 直接为客户端服务但并不参与提案的投票,同时也与Leader进行数据交换 |
原则:超过半数机器投票选择Leader,假如集群中拥有n台服务器,那么Leader必须得到n/2 + 1 台服务器的投票
注意:如果Leader死亡,重新选举Leader,如果死亡的数量达到一半,则集群挂掉
如果无法得到足够的投票数量,重新投票,如果参与投票的机器不足n/2 + 1 ,则集群停止工作
如果Follower死亡过多,剩余机器不足n/2 + 1 ,集群也会停止工作
Observer不计算在投票总设备数量里面
Zookeeper原理
Leader所有写相关操作,Follower读操作与响应Leader提议
在Observer出现以前,Zookeeper的伸缩性由Follower来实现,我们可以通过添加Follower节点的数量来保证Zookeeper服务的读性能,但是随着Follower节点数量的增加,Zookeeper服务的写性能受到了影响
客户端提交一个请求,若是读请求,则由每台Server的本地副本数据库直接响应,若是写请求,需要通过一致性协议(Zab)来处理
Zab协议规定:来自client的所有写请求都要转发给ZK服务总唯一的Leader,由Leader根据该请求发起一个Proposal。然后其他的Server对改Proposal进行Vote。之后Leader对Vote进行收集,当Vote数量过半时Leader会向所有的Server发送一个通知消息,最后当client所链接的Server收到该消息时,会把该操作更新到内存中并对Cliet的写请求做出回应
client—> server1—>leader step1 client sends write request to its local server | | V V server1 server2 step2 server sends request to leader which | | retransmits to all servers in the cluster to vote V V leader—— server2 | step3 V leader collects votes and informs all servers of results server1 | V client step4 server sends results of operation to the client |
Zookeeper在上述协议有两个职能,一方面从客户端接受链接与操作请求,另一方面对操作结果进行投票。这两个职能在Zookeeper集群扩展的时候彼此制约。
我们可以看到,Zab协议对写请求的处理过程中,增加Follower数量,则增加了协议投票过程中的压力。随着集群变大,写操作也会下降。所以为了解决这一问题,我们可以增加不参与投票的Observer服务器。Observer可以接受客户端的链接,也将写请求发送给leader节点。
Observer好处:
加入很多Observer节点,基本不会影响吞吐量,虽然在协议的通知阶段,仍然会与服务器的数量呈线性关系,但是由于串 行开销非常低,我们基本可以人为,不会成为瓶颈。Observer还可以替身读写性能的可伸缩性,并且还提供了广域网能力
部署Zookeeper集群
运用之前的node1,2,3将内存调整为4G。
[root@mynn01 hadoop]# ./sbin/stop-all.sh
[root@mynn01 hadoop]# jps
1299 Jps
[root@mynn01 hadoop]# ansible all -m shell -a 'jps'
node1 | SUCCESS | rc=0 >>
940 Jps
node2 | SUCCESS | rc=0 >>
971 Jps
node3 | SUCCESS | rc=0 >>
870 Jps
安装zookeeper
[root@mynn01 ~]# tar -zxf zookeeper-3.4.10.tar.gz
[root@mynn01 ~]# mv zookeeper-3.4.10 /usr/local/zookeeper
[root@mynn01 ~]# cd /usr/local/zookeeper/conf/
[root@mynn01 conf]# ls
configuration.xsl log4j.properties zoo_sample.cfg
[root@mynn01 conf]# cp zoo_sample.cfg zoo.cfg //将配置文件改名
[root@mynn01 conf]# ls -ld zoo.cfg
-rw-r--r-- 1 root root 922 1月 24 10:34 zoo.cfg
[root@mynn01 conf]# vim zoo.cfg
...
server.1=node1:2888:3888
server.2=node2:2888:3888
server.3=node3:2888:3888
server.4=mynn01:2888:3888:observer
拷贝/usr/local/zookeeper 到其他集群主机
[root@mynn01 conf]# for i in 192.168.1.{11,12,13}
> do
> rsync -aSH --delete /usr/local/zookeeper/ $i:/usr/local/zookeeper/
> done
[root@mynn01 conf]# mkdir /tmp/zookeeper //创建datadir指定的目录
[root@mynn01 conf]# ansible all -m shell -a 'mkdir /tmp/zookeeper' //其余三台节点创建目录
创建myid文件,id必须与配置文件里主机名对应的sever.(id)一致,id数字不一致,范围在1~255
[root@mynn01 conf]# echo 4 > /tmp/zookeeper/myid
[root@mynn01 conf]# ssh node1 'echo 1 > /tmp/zookeeper/myid'
[root@mynn01 conf]# ssh node2 'echo 2 > /tmp/zookeeper/myid'
[root@mynn01 conf]# ssh node3 'echo 3 > /tmp/zookeeper/myid'
启动服务,需要集群全部启动 (刚启动查看状态报错,启动半数以上,就成功了)
[root@mynn01 conf]# /usr/local/zookeeper/bin/zkServer.sh start
[root@mynn01 conf]# /usr/local/zookeeper/bin/zkServer.sh status //查看角色
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Error contacting service. It is probably not running. //会报错,在启动集群的其他主机
[root@mynn01 conf]# ansible all -m shell -a '/usr/local/zookeeper/bin/zkServer.sh start'
node3 | SUCCESS | rc=0 >>
Starting zookeeper ... STARTEDZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
node2 | SUCCESS | rc=0 >>
Starting zookeeper ... STARTEDZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
node1 | SUCCESS | rc=0 >>
Starting zookeeper ... STARTEDZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
[root@mynn01 conf]# /usr/local/zookeeper/bin/zkServer.sh status //隔差不多10s左右再看状态
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
Mode: observer
Zookeeper管理文档 http://zookeeper.apache.org/doc/r3.4.10/zookeeperAdmin.html
[root@mynn01 conf]# yum -y install socat
[root@mynn01 conf]# socat - TCP:node1:2181
stat
...
Mode: follower
[root@mynn01 consocat - TCP:node2:2181
stat
...
Mode: follower
[root@mynn01 conf]# socat - TCP:node3:2181
stat
...
Mode: leader
可以利用api查看状态(mynn01上操作)
[root@mynn01 conf]# vim api.sh
#!/bin/bash
function get_zkstat(){
exec 2</dev/null 8<>/dev/tcp/$1/2181
echo "stat" >&8
_S=$(cat <&8|grep -Po "^Mode:.*")
echo -e "$1\t${_S:-Mode: NULL}"
exec 8<&-
}
if (( $# == 0 ));then
echo "Usage: $0 host1 host2 host3 ... ..."
else
for i in $@;do get_zkstat ${i};done
fi
[root@mynn01 conf]# chmod 755 api.sh
[root@mynn01 conf]# ./api.sh node{1..3}
node1 Mode: follower
node2 Mode: follower
node3 Mode: leader
[root@mynn01 conf]# ./api.sh mynn01
mynn01 Mode: observer
Kafka集群
由LinkedIn开发的一个分布式的消息系统,由Scala编写(Scala由java编写),是一种消息中间件
好处:解耦,冗余,提高扩展性,缓冲;保证顺序,灵活,削峰填谷;异步通信
角色:
producer: 生产者,负责发布消息
consumer:消费者,负责读取处理消息
topic: 消息的类别
Partition: 每个Topic包含一个或多个Partition
Broker: kafka集群包含一个或多个服务器
注:kafka通过Zookeeper管理集群配置,选取leader
部署Kafka
环境和上面一样
安装Kafka(需要OpenJDK运行环境)
[root@node1 ~]# tar -zxf kafka_2.10-0.10.2.1.tgz
[root@node1 ~]# mv kafka_2.10-0.10.2.1 /usr/local/kafka
修改配置文件
[root@node1 ~]# cd /usr/local/kafka/
[root@node1 kafka]# vim config/server.properties
...
broker.id=5
...
zookeeper.connect=node1:2181,node2:2181,node3:2181 //集群地址,不用全部写出
同步文件到node2,node3
[root@node1 conf]# rsync -aSH --delete /usr/local/kafka/ node2:/usr/local/kafka/
[root@node1 conf]# rsync -aSH --delete /usr/local/kafka/ node3:/usr/local/kafka/
修改node2,node3上的配置文件,修改broker.id,每一台都不能相同
[root@node2 kafka]# vim config/server.properties
...
broker.id=12
[root@node3 kafka]# vim config/server.properties
...
broker.id=13
启动kafka集群(node1,node2,node3)
[root@node1 kafka]# ./bin/kafka-server-start.sh -daemon /usr/local/kafka/config/server.properties
[root@node2 kafka]# ./bin/kafka-server-start.sh -daemon /usr/local/kafka/config/server.properties
[root@node3 kafka]# ./bin/kafka-server-start.sh -daemon /usr/local/kafka/config/server.properties
[root@node1 kafka]# jps
1175 QuorumPeerMain
1611 Jps
1551 Kafka
[root@node1 kafka]# netstat -pntul | grep 90
tcp6 0 0 :::9092 :::* LISTEN 1551/java
kafka验证
在node1创建一个topic,node2生产者,node3消费者
[root@node1 kafka]# ./bin/kafka-topics.sh --create --partitions 1 --replication-factor 1 --zookeeper localhost:2181 --topic mymsg
Created topic "mymsg".
[root@node2 kafka]# ./bin/kafka-console-producer.sh --broker-lislocalhost:9092 --topic mymsg
1122 //输入内容
[root@node3 kafka]# ./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic mymsg
1122 //这边会直接同步
Hadoop高可用
运用NameNode原因
NameNode是HDFS的核心配置,HDFS又是Hadoop核心组件,所以说NameNode在Hadoop集群中至关重要
NameNode宕机,将会导致集群不可用,如果NameNode数据丢失将导致整个集群的数据丢失,而NameNode的数据更新又繁琐,所以采用NameNode的高可用
解决方案
第一种 HDFS with NFS 组成:NameNode(两台) ZK ZKFailoverController NFS
第二种 HDFS with QJM 组成:NameNode(两台) ZK ZKFailoverController JournalNode
两种方案都可以实现热备,一个NN(active) 一个NN(standby),均使用zookeepe和zkfc来实现自动失效恢复,失效使用fencin配置方法来启动NN(active)
NFS数据共享变更方案把数据存储在共享存储里,我们还需要考虑NFS的高可用设计
QJM不需要共享存储,但需要让每一个DN都知道两个NNDE位置,并把块信息和心跳包发送给active和standby这两个NN
使用QJM原因:
解决NameNode单点故障问题
HDFS的高可用通常由两个NameNode组成(会被配置在两台独立的机器上),一个active(处于活动状态),一个standby(处于备份状态)。其中activeNN对外提供服务(相应集群中所有的客户端),standbyNN不对外提供服务(只作为一个副本),仅同步activeNN状态,方便在失败时可以进行切换(提供一个快速的转移)
NameNode高可用架构
为了让standby Node和active Node保持同步,这两个Node都与一组称为JNS(journal Nodes)d的互相独立的进程保持通信。当activeNode更新了namespace,他将记录修改日志发给JNS。standbyNode将会从JNS中读取这些edits,并持续关注他们对日志的变更
standbyNode将日志变更应用在自己的namespace中,当failover发生时,standby将会在提升自己为active之前,确保能够从JNS中读取所有的edits,即在failover发生之前standby持有的namespace与active保持完全同步
namenode更新很频繁,为了保持主备数据的一致性,为了支持快速failover,standbynode持有集群中blocks的最新位置是十分重要的。为了达到这一目的,datanodes上需要同时配置两个namenode的地址,同时和它们都建立心跳链接,并把block位置发送给它们
任何时候,只能有一个active namenode,否则会导致集群操作混乱,两个namenode将会有两种不同的数据状态,可以会导致数据丢失或状态异常,这种情况通常称为“split-brain”(脑裂,三节点通讯阻断)
对于JNS而言,任何时候只允许一个namenode作为writer;在failover期间,原来的standbynode将会接管active的所有职能,并负责向JNS写入日志记录,这种机制阻止了其他namenode处于active状态的问题
Zookeeper Zookeeper Zookeeper /\ /\ | Heartbeat | Heartbeat | | | —— FailoverController Active FailoverController Standby —| | | | | | monitor | / ——> JournalNodes — \ | | monitor | health | Cmds / \ | | health | of NN.OS | / Shared NN state wit \ | | of | HW | / single writer(fenced) \ | | NN.OS,HW | \/ / \/ \/ | |————> NameNode Active NameNode Standby <— | /\ /\ \ / block reports to active & stanby \ / DN fencing:update cmds from one DataNode \ DataNode DataNode |
部署NameNode
基于前面NameNode实验(其中除了192.168.1.20 mynn02 没有配置namenode,其余已经配置,稍作变更添加即可)
192.168.1.10 mynn01 | namenode resourcemanager |
192.168.1.20 mynn02 | namenode resourcemanager |
192.168.1.11 node1 | ZK datanode nodemanager JNS |
192.168.1.12 node2 | ZK datanode nodemanager JNS |
192.168.1.13 node3 | ZK datanode nodemanager JNS |
域名需要全部可以解析(所有节点同步)
[root@mynn01 conf]# vim /etc/hosts
192.168.1.10 mynn01
192.168.1.11 node1
192.168.1.12 node2
192.168.1.13 node3
192.168.1.20 mynn02
[root@mynn01 conf]# for i in 192.168.1.{11,12,13,20}
> do
> scp /etc/hosts $i:/etc/hosts
> done
启动192.168.1.20 mynn02主机,安装java-1.8.0-openjdk-devel
[root@mynn02 ~]# yum -y install java-1.8.0-openjdk-devel
SSH免密码登录
[root@mynn02 ~]# vim /etc/ssh/ssh_config
Host *
...
StrictHostKeyChecking no
[root@mynn01 ~]# scp -r /root/.ssh root@192.168.1.20:/root/
//拷贝公钥和私钥到mynn02 使其可以无密钥链接其他四台主机,mynn01也可以无密钥链接它
[root@mynn02 .ssh]# rm -rf known_hosts
停止所有服务,包括kafka(因为这个比较吃内存)
[root@mynn01 hadoop]# ./sbin/stop-all.sh
[root@mynn01 hadoop]# ansible all -m shell -a '/usr/local/kafka/bin/kafka-server-stop.sh'
node2 | SUCCESS | rc=0 >>
node1 | SUCCESS | rc=0 >>
node3 | SUCCESS | rc=0 >>
[root@mynn01 zookeeper]# ./bin/zkServer.sh stop //可以把mynn01上的zookeeper停掉,因为node1,node2,node3就可以实现zookeeper集群
[root@mynn01 conf]# ./api.sh node{1..3} //查看zookeeper集群状态
node1 Mode: follower
node2 Mode: follower
node3 Mode: leader
删除hdfs数据
[root@mynn01 hadoop]# ansible all -m shell -a 'rm -rf /var/hadoop/*'
[root@mynn01 hadoop]# rm -rf /var/hadoop/*
修改配置文件 core-site.xml
[root@mynn01 hadoop]# vim etc/hadoop/core-site.xml
...
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://nsd1809</value> //集群组名字
</property>
<property>
<name>ha.zookeeper.quorum</name>
<value>node1:2181,node2:2181,node3:2181</value> //zookeeper集群主机
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/var/hadoop</value>
</configuration>
[root@mynn01 hadoop]# vim etc/hadoop/core-site.xml
hdfs-site的配置
[root@mynn01 hadoop]# vim etc/hadoop/exclude //之前定义的删除节点的文件,可以把文件内容删除
[root@mynn01 hadoop]# vim etc/hadoop/hdfs-site.xml
<configuration>
<property>
<name>dfs.nameservices</name> //指定hdfs的nameservices名称
<value>nsd1809</value>
</property>
<property>
<name>dfs.ha.namenodes.nsd1809</name> //指定集群的两个NameNode的名称
<value>nn1,nn2</value>
</property>
<property>
<name>dfs.namenode.http-address.nsd1809.nn1</name> //配置nn1的http通信端口
<value>mynn01:50070</value>
</property>
<property>
<name>dfs.namenode.http-address.nsd1809.nn2</name>
<value>mynn02:50070</value>
</property>
<property>
<name>dfs.namenode.rpc-address.nsd1809.nn1</name> //配置nn1的rpc端口
<value>mynn01:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.nsd1809.nn2</name>
<value>mynn02:8020</value>
</property>
<property>
<name>dfs.namenode.shared.edits.dir</name> //指定NameNode元数据存储在journalnode中的路径
<value>qjournal://node1:8485;node2:8485;node3:8485/nsd1809</value>
</property>
<property>
<name>dfs.journalnode.edits.dir</name> //指定journalnode日志文件存储的路径
<value>/var/hadoop/journal</value>
</property>
<property>
<name>dfs.client.failover.proxy.provider.nsd1809</name> //指定HDFS客户端链接Active NameNode的java类
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
<property>
<name>dfs.ha.fencing.methods</name> //配置隔离机制为SSH
<value>sshfence</value>
</property>
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name> //指定密钥的位置
<value>/root/.ssh/id_rsa</value>
</property>
<property>
<name>dfs.ha.automatic-failover.enabled</name> //开启自动故障转移
<value>true</value>
</property>
...
</configuration>
yarn高可用
ResourceManager高可用:RM的高可用原理与NN一样,需要依赖ZK来实现,yarn.resourcemanager.hostname关闭(因为集群模式)
<configuration>
<!-- Site specific YARN configuration properties -->
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<property>
<name>yarn.resourcemanager.recovery.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.store.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
</property>
<property>
<name>yarn.resourcemanager.zk-address</name>
<value>node1:2181,node2:2181,node3:2181</value>
</property>
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>yarn-ha</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>mynn01</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>mynn02</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
</configuration>
同步/usr/local/hadoop到其余四台主机
[root@mynn01 hadoop]# for i in mynn02 node{1..3}
> do
> rsync -aSH --delete /usr/local/hadoop $i:/usr/local/ &
> done
[1] 2924
[2] 2925
[3] 2926
[4] 2927
[root@mynn01 hadoop]# wait
[1] 完成 rsync -aSH --delete /usr/local/hadoop $i:/usr/local/
[2] 完成 rsync -aSH --delete /usr/local/hadoop $i:/usr/local/
[3]- 完成 rsync -aSH --delete /usr/local/hadoop $i:/usr/local/
[4]+ 完成 rsync -aSH --delete /usr/local/hadoop $i:/usr/local/
高可用验证
初始化ZK集群
[root@mynn01 ~]# /usr/local/hadoop/bin/hdfs zkfc -formatZK
在node1,node2,node3上面启动journalnode服务
[root@node1 ~]# /usr/local/hadoop/sbin/hadoop-daemon.sh start journalnode
[root@node1 ~]# jps
29262 JournalNode
26895 QuorumPeerMain
29311 Jps
格式化,先在node1,node2,node3上面启动journalnode才能格式化
[root@mynn01 ~]# /usr/local/hadoop//bin/hdfs namenode -format
[root@mynn01 hadoop]# ls /var/hadoop/
dfs
mynn02数据同步到本地 /var/hadoop/dfs
[root@mynn02 ~]# cd /var/hadoop/
[root@mynn02 hadoop]# rsync -aSH nn01:/var/hadoop/ /var/hadoop/
[root@mynn02 hadoop]# ls
dfs
初始化JNS
[root@mynn01 hadoop]# /usr/local/hadoop/bin/hdfs namenode -initializeSharedEdits //出现successfully才可以
停止journalnode服务(node1,node2,node3)
[root@node1 hadoop]# ./sbin/hadoop-daemon.sh stop journalnode
[root@node1 hadoop]# jps
29346 Jps
26895 QuorumPeerMain
启动集群
[root@mynn01 hadoop]# /usr/local/hadoop/sbin/start-all.sh //启动所有集群在01上
[root@mynn02 hadoop]# /usr/local/hadoop/sbin/yarn-daemon.sh start resourcemanager //启动热备
查看集群状态
[root@mynn01 hadoop]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn1
active
[root@mynn01 hadoop]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn2
standby
[root@mynn01 hadoop]# /usr/local/hadoop/bin/yarn rmadmin -getServiceState rm1
active
[root@mynn01 hadoop]# /usr/local/hadoop/bin/yarn rmadmin -getServiceState rm2
standby
查看节点
[root@mynn01 hadoop]# /usr/local/hadoop/bin/hdfs dfsadmin -report
...
Live datanodes (3): //会有三个节点
...
[root@mynn01 hadoop]# /usr/local/hadoop/bin/yarn node -list
Total Nodes:3
Node-Id Node-State Node-Http-Address Number-of-Running-Containers
node2:43307 RUNNING node2:8042 0
node1:34606 RUNNING node1:8042 0
node3:36749 RUNNING node3:8042 0
访问集群
[root@mynn01 hadoop]# ./bin/hadoop fs -ls /
[root@mynn01 hadoop]# ./bin/hadoop fs -mkdir hdfs://nsd1809/input
[root@nn01 hadoop]# /usr/local/hadoop/bin/hadoop fs -ls / //再次查看
input
关闭active namenode
[root@mynn01 hadoop]# /usr/local/hadoop/sbin/hadoop-daemon.sh stop namenode
[root@mynn01 hadoop]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn1
//再次查看会报错
[root@mynn01 hadoop]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn2
//nn02由之前的standby变为active
active
[root@nn01 hadoop]# /usr/local/hadoop/sbin/yarn-daemon.sh stop resourcemanager
//停止resourcemanager
[root@nn01 hadoop]# /usr/local/hadoop/bin/yarn rmadmin -getServiceState rm2
active
恢复节点
[root@nn01 hadoop]# /usr/local/hadoop/sbin/hadoop-daemon.sh start namenode
//启动namenode
[root@nn01 hadoop]# /usr/local/hadoop/sbin/yarn-daemon.sh start resourcemanager
//启动resourcemanager
[root@nn01 hadoop]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn1
standby
[root@nn01 hadoop]# /usr/local/hadoop/bin/yarn rmadmin -getServiceState rm1
standby