Zookeeper
Zookeeper是什么
Zookeeper是什么
- Zookeeper是一个开源的分布式应用程序协调服务
Zookeeper能做什么
- Zookeeper是用来保证数据在集群间的事务一致性
Zookeeper应用场景
- 集群分布式锁
- 集群统一命名服务
- 分布式协调服务
角色与特性
Zookeeper角色与特性
- Leader:接受所有Follower的提案请求并统一协调发起提案的投票,负责与所有的Follower进行内部数据交换
- Follower:直接为客户端服务并参与提案的投票,同时与Leader进行数据交换
- Observer:直接为客户端服务但并不参与提案的投票,同时也与Leader进行数据交换
角色与选举
Zookeeper角色与选举
- 服务在启动的时候是没有角色的(LOOKING)
- 角色是通过选举产生的
- 选举产生一个Leader,剩下的是Follower
选举Leader原则
- 集群中超过半数机器投票选择Leader
- 假如集群中拥有n台服务器,那么Leader必须得到n/2+1台服务器的投票
- 如果Leader死亡,重新选举Leader
- 如果死亡的机器数量达到一半,则集群挂掉
- 如果无法得到足够的投票数量,就重新发起投票,如果参与投票的机器不足n/2+1,则集群停止工作
- 如果Follower死亡过多,剩余机器不足n/2+1,则集群也会停止工作
- Observer不计算在投票总设备数量里面
Zookeeper原理与设计
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收到该消息时,会把该操作更新到内存中并对Client的写请求做出回应
- ZooKeeper在上述协议中实际扮演了两个职能。一方面从客户端接受连接与操作请求,另一方面对操作结果进行投票。这两个职能在Zookeeper集群扩展的时候彼此制约
- 从Zab协议对写请求的处理过程中可以发现,增加Follower的数量,则增加了协议投票过程的压力。因为Leader节点必须等待集群中过半Server响应投票,是节点的增加使得部分计算机运行较慢,从而拖慢整个投票过程的可能性也随之提高,随着集群变大,写操作也会随之下降
- 所以,我们不得不在增加Client数量的期望和我们希望保持较好吞吐性能的期望间进行权衡。要打破这一耦合关系,我们引入了不参与投票的服务器Observer。Observer可以接受客户端的连接,并将写请求转发给Leader节点。但Leader节点不会要求Observer参加投票,仅仅在上述第3歩那样,和其他服务节点一起得到投票结果
- Observer的扩展,给Zookeeper的可伸缩性带来了全新的景象。加入很多Observer节点,无须担心严重影响写吞吐量。但并非是无懈可击,因为协议中的通知阶段,仍然与服务器的数量呈线性关系。但是这里的串行开销非常低。因此,我们可以认为在通知服务器
阶段的开销不会成为瓶颈 - Observer提升读性能的可伸缩性
- Observer提供了广域网能力
Zookeeper集群
Zookeeper集群的安装配置
- 配置文件改名zoo.cfg
# mv zoo_sample.cfg zoo.cfg
- zoo.cfg文件最后添加如下内容
server.1=node1:2888:3888
server.2=node2:2888:3888
server.3=node3:2888:3888
server.4=nn01:2888:3888:observer
zoo.cfg集群的安装配置
- 创建datadir指定的目录
# mkdir /tmp/zookeeper
- 在目录下创建id对应主机名的myid文件
关于myid文件
- myid文件中只有一个数字
- 注意:请确保每个server的myid文件中id数字不同
- server.id中的id与myid中的id必须一致
- id的范围是1~255
(1)安装zookeeper并修改配置文件
[root@had-node1 ~]# cd /usr/local/zookeeper/conf/
[root@had-node1 conf]# mv zoo_sample.cfg zoo.cfg
[root@had-node1 conf]# vim zoo.cfg
... ...
server.1=had-node2:2888:3888
server.2=had-node3:2888:3888
server.3=had-node4:2888:3888
server.4=had-node1:2888:3888:observer //申明observer
(2)同步到所有节点
[root@had-node1 ~]# for i in {71..73}
> do
> rsync -av --delete /usr/local/zookeeper root@192.168.1.$i:/usr/local/
> done
(3)每个节点(had-node1~had-node4)创建id目录并创建id
[root@had-node1 conf]# mkdir /tmp/zookeepermkdir /tmp/zookeeper
[root@had-node1 ~]# echo 4 > /tmp/zookeeper/myid
[root@had-node2 ~]# echo 1 > /tmp/zookeeper/myid
[root@had-node3 ~]# echo 2 > /tmp/zookeeper/myid
[root@had-node4 ~]# echo 3 > /tmp/zookeeper/myid
- 启动集群,查看验证(在所有集群节点执行)
# /usr/local/zookeeper/bin/zkServer.sh start
- 查看角色
# /usr/local/zookeeper/bin/zkServer.sh status
- Zookeeper管理文档
http://zookeeper.apache.org/doc/r3.4.10/zookeeperAdmin.html
Kafka集群
什么是Kafka
Kafka是什么
- Kafka是由LinkedIn开发的一个分布式的消息系统
- Kafka是使用Scala编写
- Kafka是一种消息中间件
为什么要使用Kafka
- 解耦、冗余、提高扩展性、缓冲
- 保证顺序,灵活,削峰填谷
- 异步通信
Kafka角色
Kafka角色与集群结构
- producer:生产者,负责发布消息
- consumer:消费者,负责读取处理消息
- topic:消息的类别
- Parition:每个Topic包含一个或多个Partition
- Broker:Kafka集群包含一个或多个服务器
Kafka通过Zookeeper管理集群配置,选举Leader
Kafka集群安装与配置
Kafka角色与集群结构
Kafka集群的安装配置
- Kafka集群的安装配置依赖Zookeeper,搭建Kafka集群之前,请先创建好一个可用的Zookeeper集群
- 安装OpenJDK运行环境
- 同步Kafka拷贝到所有集群主机
- 修改配置文件
- 启动与验证
Kafka集群的安装配置
- erver.properties
- broker.id
- 每台服务器的broker.id都不能相同
zookeeper.connect
- zookeeper集群地址,不用都列出,写一部分即可
Kafka集群的安装配置
- 在所有主机启动服务
# ./bin/kafka-server-start.sh -daemon config/server.properties
验证
- jps命令应该能看到Kafka模块
- netstat应该能看到9092在监听
集群验证与消息发布
- 创建一个 topic
# ./bin/kafka-topics.sh --create --partitions 2 --replication-factor 2 \ --zookeeper localhost:2181 --topic mymsg
- 生产者
# ./bin/kafka-console-producer.sh \ --broker-list localhost:9092 --topic mymsg
- 消费者
# ./bin/kafka-console-consumer.sh \ --bootstrap-server localhost:9092 --topic mymsg
(1)安装kafka安装包到/usr/local/kafka
[root@had-node1 ~]# tar -xf hadoop/kafka_2.12-2.1.0.tgz
[root@had-node1 ~]# mv kafka_2.12-2.1.0/ /usr/local/kafka
(2)修改配置文件
[root@had-node1 ~]# cd /usr/local/kafka/config/
[root@had-node1 config]# vim server.properties
... ...
broker.id=0
... ...
zookeeper.connect=had-node2:2181,had-node2:2181,had-node3:2181
... ...
(3)同步配置文件
[root@had-node1 ~]# for i in {71..73}
> do
> rsync -av /usr/local/kafka 192.168.1.$i:/usr/local/
> done
(4)修改每个节点的broker.id,不要重复
(5)在所有主机启动服务
/usr/local/kafka/bin/kafka-server-start.sh -daemon /usr/local/kafka/config/server.properties
(6)验证,jps,netstat查看9092端口
(7)集群消息验证
had-node2创建一个topic
[root@had-node2 ~]# cd /usr/local/kafka/
[root@had-node2 kafka]# ./bin/kafka-topics.sh --create --partitions 2 --replication-factor 2 --zookeeper localhost:2181 --topic mymsg
had-node3 生产者,生产者发送消息,消费者可以收到
[root@had-node3 ~]# cd /usr/local/kafka/
[root@had-node3 kafka]# ./bin/kafka-console-producer.sh --broker-list localhost:9092 --topic mymsg
had-node4 消费者
[root@had-node4 ~]# cd /usr/local/kafka/
[root@had-node4 kafka]# ./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic mymsg
Hadoop高可用
为什么需要NameNode
原因
- NameNode是HDFS的核心配置,HDFS又是Hadoop核心组件,NameNode在Hadoop集群中至关重要
- NameNode宕机,将导致集群不可用,如果NameNode数据丢失将导致整个集群的数据丢失,而NameNode的数据的更新又比较频繁,实现NameNode高可用势在必行
解决方案
官方提供了两种解决方案
- HDFS with NFS
- HDFS with QJM
两种方案异同
NFS | QJM |
NN | NN |
ZK | ZK |
ZKFailoverController | ZKFailoverController |
NFS | JournalNode |
HA方案对比
- 都能实现热备
- 都是一个Active NN和一个Standby NN
- 都使用Zookeeper和ZKFC来实现自动失效恢复
- 失效切换都使用Fencin配置的方法来Active NN
- NFS数据共享变更方案把数据存储在共享存储里,我们还需要考虑NFS的高可用设计
- QJM不需要共享存储,但需要让每一个DN都知道两个NN的位置,并把块信息和心跳包发送给Active和Standby这两个NN
使用方案
使用原因(QJM)
- 解决NameNode单点故障问题
- Hadoop给出了HDFS的高可用HA方案:HDFS通常由两个NameNode组成,一个处于Active状态,另一个处于Standby状态。Active NameNode对外提供服务,比如处理来自客户端的RPC请求,而Standby NameNode则不对外提供服务,仅同步Active NameNode的状态,以便能够在它失败时进行切换
典型的HA集群
- NameNode会被配置在两台独立的机器上,在任何时候 , 一 个 NameNode 处 于 活 动 状 态 , 而 另 一 个NameNode则处于备份状态
- 活动状态的NameNode会响应集群中所有的客户端,备份状态的NameNode只是作为一个副本,保证在必要的时候提供一个快速的转移
NameNode高可用
NameNode高可用架构
- 为了让Standby Node与Active Node保持同步,这两个Node 都 与 一 组 称 为 JNS 的 互 相 独 立 的 进 程 保 持 通 信(Journal Nodes)。当Active Node更新了namespace,它将记录修改日志发送给JNS的多数派。Standby Node将会从JNS中读取这些edits,并持续关注它们对日志的变更
- Standby Node将日志变更应用在自己的namespace中,当Failover发生时,Standby将会在提升自己为Active之前,确保能够从JNS中读取所有的edits,即在Failover发生之前Standy持有的namespace与Active保持完全同步
- NameNode更新很频繁,为了保持主备数据的一致性,为了支持快速Failover,Standby Node持有集群中blocks的最新位置是非常必要的。为了达到这一目的,DataNodes上需要同时配置这两个Namenode的地址,同时和它们都建立心跳连接,并把block位置发送给它们
- 任何时刻,只能有一个Active NameNode,否则会导致集群操作混乱,两个NameNode将会有两种不同的数据状态,可能会导致数据丢失或状态异常,这种情况通常称为"split-brain"(脑裂,三节点通讯阻断,即集群中不同的DataNode看到了不同的Active NameNodes)
- 对于JNS而言,任何时候只允许一个NameNode作为writer;在Failover期间,原来的Standby Node将会接管Active的所有职能,并负责向JNS写入日志记录,这种机制阻止了其他NameNode处于Active状态的问题
NameNode架构图
- NameNode高可用架构图
创建一台机器,192.168.1.77(had-node01-02),作为namenode的备用节点
(1)安装java-openjdk
[root@had-node01-02 ~]# yum -y install java-1.8.0-openjdk-devel
(2)配置本机可以ssh无密码登录所有主机,包括自己,配置had-node01可以ssh无密码登录本机
[root@had-node1 ~]# ssh-copy-id 192.168.1.77
[root@had-node01-02 ~]# ssh-keygen
[root@had-node01-02 ~]# for i in {70..77}
> do
> ssh-copy-id 192.168.1.$i
> done
(3)配置本机的hosts解析
[root@had-node1 ~]# vim /etc/hosts
... ...
192.168.1.70 had-node1
192.168.1.71 had-node2
192.168.1.72 had-node3
192.168.1.73 had-node4
192.168.1.74 had-node5
192.168.1.75 had-client
192.168.1.76 had-nfsgw
192.168.1.77 had-node01-02
[root@had-node1 ~]# for i in {70..77}
> do
> scp /etc/hosts root@192.168.1.$i:/etc/hosts
> done
(4)除了zookeeper服务,其他服务都停掉
[root@had-node1 ~]# rm -rf /var/hadoop/* //所有节点都执行
[root@had-node01-02 ~]# mkdir /var/hadoop //node01-02节点创建hadoop目录
[root@had-node1 ~]# cd /usr/local/hadoop/ //所有节点清除掉hadoop日志
[root@had-node1 hadoop]# rm -rf logs/*
core-site配置
- core-site.xml文件
<property>
<name>fs.defaultFS</name>
<value>hdfs://mycluster</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/var/hadoop</value>
</property>
<property>
<name>ha.zookeeper.quorum</name>
<value>node1:2181,node2:2181,node3:2181</value>
hdfs-site配置
- hdfs-site.xml文件
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
- SecondaryNameNode在高可用里没有用,这里把它关闭
- NameNode在后面定义hdfs-site配置
<!-- 指定hdfs的nameservices名称为mycluster -->
<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
</property>
- 指定集群的两个NaneNode的名称分别为nn1,nn2
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2</value>
</property>hdfs-site配置(续2)
- 配置nn1,nn2的rpc通信端口
<property>
<name>dfs.namenode.rpc-
address.mycluster.nn1</name>
<value>nn01:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-
address.mycluster.nn2</name>
<value>nn02:8020</value>
- 配置nn1,nn2的http通信端口
<property>
<name>dfs.namenode.http-
address.mycluster.nn1</name>
<value>nn01:50070</value>
</property>
<property>
<name>dfs.namenode.http-
address.mycluster.nn2</name>
<value>nn02:50070</value>
- 指定NameNode元数据存储在journalnode中的路径
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://node1:8485;node2:8485;node3:8485/my
cluster</value>
</property>
- 指定journalnode日志文件存储的路径
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/var/hadoop/journal</value>
- 指定HDFS客户端连接Active NameNode的java类
<property>
<name>dfs.client.failover.proxy.provider.mycluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.Confi
guredFailoverProxyProvider</value>
- 配置隔离机制为SSH
<property>
<name>dfs.ha.fencing.methods</name>
<value>sshfence</value>
</property>
- 指定密钥的位置
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/root/.ssh/id_rsa</value>
- 开启自动故障转移
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
yarn
高可用
ResourceManager高可用
- RM的高可用原理与NN一样,需要依赖ZK来实现,这里
配置文件的关键部分,感兴趣的同学可以自己学习和测试 - yarn.resourcemanager.hostname
- 同理因为使用集群模式,该选项应该关闭yarn-site配置
yarn-site.xml配置
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
<name>yarn.resourcemanager.recovery.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.store.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.re
covery.ZKRMStateStore</value>
<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>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>nn01</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>nn02</value>
</property>
(5)修改node1上的配置文件
[root@had-node1 ~]# cd /usr/local/hadoop/
[root@had-node1 hadoop]# vim etc/hadoop/core-site.xml
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://mynodes</value> //namenodes,namenode组的名称
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/var/hadoop</value>
</property>
<property>
<name>hadoop.proxyuser.nfsuser.groups</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.nfsuser.hosts</name>
<value>*</value>
</property>
<property>
<name>ha.zookeeper.quorum</name>
<value>had-node1:2181,had-node2:2181,had-node3:2181</value> //zookeeper地址
</property>
</configuration>
[root@had-node1 hadoop]# vim etc/hadoop/hdfs-site.xml
... ...
<configuration>
<property>
<name>dfs.namenode.http-address</name>
<value>had-node1:50070</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>had-node1:50090</value>
</property>
<property>
<name>dfs.replication</name>
<value>2</value>
</property>
<property>
<name>dfs.hosts.exclude</name>
<value>/usr/local/hadoop/etc/hadoop/exclude</value>
</property>
<property>
<name>dfs.nameservices</name>
<value>mynodes</value>
</property>
<property>
<name>dfs.ha.namenodes.mynodes</name>
<value>nn1,nn2</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mynodes.nn1</name>
<value>had-node1:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mynodes.nn2</name>
<value>had-node01-02:8020</value>
</property>
<property>
<name>dfs.namenode.http-address.mynodes.nn1</name>
<value>had-node1:50070</value>
</property>
<property>
<name>dfs.namenode.http-address.mynodes.nn2</name>
<value>had-node01-02:50070</value>
</property>
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://had-node1:8485;had-node2:8485;had-node3:8485/mynodes</value>
</property>
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/var/hadoop/journal</value>
</property>
<property>
<name>dfs.client.failover.proxy.provider.mynodes</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
<property>
<name>dfs.ha.fencing.methods</name>
<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>
[root@had-node1 hadoop]# vim etc/hadoop/yarn-site.xml
... ...
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<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>had-node1:2181,had-node2:2181,had-node3:2181</value>
</property>
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>yarn-ha</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>had-node1</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>had-node01-02</value>
</property>
</configuration>
(6)集群启动及验证
<1>将had-node1上的配置同步到had-node2~had-node3和had-node01-02节点上
[root@had-node1 ~]# for i in {71..73}
> do
> rsync -av --delete /usr/local/hadoop root@192.168.1.$i:/usr/local/
> done
[root@had-node1 ~]# scp -r /usr/local/hadoop root@192.168.1.77:/usr/local/
[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs zkfc -formatZK
<2>had-node2~had-node3都执行
[root@had-node2 ~]# /usr/local/hadoop/sbin/hadoop-daemon.sh start journalnode
<3>节点格式化
[root@had-node1 ~]# /usr/local/hadoop/sbin/start-all.sh
[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs namenode -format
<4>将had-node01上的/var/hadoop/dfs同步到had-node01-02
[root@had-node1 ~]# scp -r /var/hadoop/dfs root@192.168.1.77:/var/hadoop/
<5>初始化jns
[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs namenode -initializeSharedEdits //输入Y
<6>had-node2had-node3上停止journalnode服务.had-node2had-node3都执行
[root@had-node2 ~]# /usr/local/hadoop/sbin/hadoop-daemon.sh stop journalnode
<7>had-node1启动hdfs,yarn
[root@had-node1 ~]# /usr/local/hadoop/sbin/start-dfs.sh
[root@had-node1 ~]# /usr/local/hadoop/sbin/start-yarn.sh
<8>had-node01-02启动热备ResourceManager
[root@had-node01-02 ~]# /usr/local/hadoop/sbin/yarn-daemon.sh start resourcemanager
<9>查看集群状态
[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn1
standby
[root@had-node1 ~]# /usr/local/hadoop/bin/hdfs haadmin -getServiceState nn2
active
<10>获取ResourceManager状态
[root@had-node01-02 ~]# /usr/local/hadoop/sbin/start-all.sh
[root@had-node1 ~]# /usr/local/hadoop/bin/yarn rmadmin -getServiceState rm1
active
[root@had-node1 ~]# /usr/local/hadoop/bin/yarn rmadmin -getServiceState rm2
standby
查看集群状态
-
获取节点信息
# ./bin/hdfs dfsadmin -report
# ./bin/yarn node -list
-
访问集群文件
# ./bin/hadoop fs -mkdir /input
# ./bin/hadoop fs -ls hdfs://mycluster/
- 主从切换Activate
# ./sbin/hadoop-daemon.sh stop namenode