目录
[使用java代码操作zk 3.5以下版本,使用的技术还是Curator](#使用java代码操作zk 3.5以下版本,使用的技术还是Curator)
[1) 搭建namenode的高可用](#1) 搭建namenode的高可用)
[Java 代码操作 HA 的 hdfs 代码演示:](#Java 代码操作 HA 的 hdfs 代码演示:)
一、Zk是用java代码进行操作(了解)
有很多的软件都是可以使用命令,也可以使用代码进行操作:
MySQL -- SQL语句、使用java代码(JDBC)
HDFS -- shell、Java代码
zookeeper -- shell、Java代码
有两种操作方案:
JDBC 的代码--原生的
DBUtils 工具包
1、原生的API进行操作
2、Curator 工具包进行操作(建议同学们学习这个东西)
第一步:导入Curator工具包
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
package com.bigdata;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
public class TestZk {
CuratorFramework curator =null;
@Before
public void init(){
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,2);
curator = CuratorFrameworkFactory.newClient(
"bigdata01:2181", retryPolicy
);
curator.start();
}
@After
public void destory(){
curator.close();
}
@Test //
public void testCreate() throws Exception {
// 一个类的对象如果是null, 使用null调用任何方法,都报空指针异常
// 创建一个临时的节点,节点的名字是/hello1
curator.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/xihu/abcd");
//Thread.sleep(20000);
}
@Test //
public void testLs() throws Exception {
// 一个类的对象如果是null, 使用null调用任何方法,都报空指针异常
// 创建一个临时的节点,节点的名字是/hello1
//2、查询子节点:ls
List<String> list = curator.getChildren().forPath("/");
System.out.println(list);
}
@Test //
public void testSetData() throws Exception {
curator.setData().forPath("/xihu/abcd","西湖的水,我的泪".getBytes());
}
@Test
public void testGetData() throws Exception{
byte[] bytes = curator.getData().forPath("/xihu");
System.out.println(new String(bytes));
}
@Test
public void testString(){
// 只学两个东西: 集合(ArrayList HashSet HashMap) 工具类的API (String) 二八原则
// 想将一个字符串变为byte数组
String a = "hello";
byte[] arr = a.getBytes();
System.out.println(Arrays.toString(arr));
// 想将一个byte数组变为字符串
String str = new String(arr);
System.out.println(str);
}
@Test
public void testDelete() throws Exception{
curator.delete().forPath("/xihu/abc");
}
// 演示watch 监听
@Test
public void testWatch() throws Exception{
TreeCache treeCache = new TreeCache(curator, "/xihu");
// 一个接口中只有一个未实现的方法,该接口是函数式接口,函数式接口:lambda
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
TreeCacheEvent.Type type = treeCacheEvent.getType();
ChildData data = treeCacheEvent.getData();
if(type == TreeCacheEvent.Type.NODE_ADDED){
System.out.println("/xihu 下创建了一个新的节点");
// data.getData() 如果节点没有数据,返回的是IP;如果节点上有数据,返回的是节点的值
System.out.println("该节点上的数据是:"+new String(data.getData()));
System.out.println("新增了个路径:"+data.getPath());
}
if(type == TreeCacheEvent.Type.NODE_UPDATED){
System.out.println("/xihu 下修改了一个节点");
// data.getData() 返回的是IP
System.out.println("该节点上的数据是:"+new String(data.getData()));
System.out.println("修改的路径:"+data.getPath());
}
if(type == TreeCacheEvent.Type.NODE_REMOVED){
System.out.println("删除了个路径:"+data.getPath());
}
}
});
treeCache.start();
Thread.sleep(1000*100);
}
@Test
public void testSwith(){
String season= "冬";
switch (season){
case "冬":
System.out.println("冬天冷...");
break;
case "春":
System.out.println("春天来了,冬天还会源码?");
break;
}
}
@Test
public void testEnum(){
// WINTER 是枚举类的一个实例化对象
Season season = Season.WINTER;
switch (season){
case WINTER:
System.out.println("冬天冷...");
break;
case SPRING:
System.out.println("春天来了,冬天还会源码?");
break;
}
}
}
使用java代码操作zk 3.5以下版本,使用的技术还是Curator
第一步:创建项目
第二步:导入包:
<!--zk的客户端( curator )-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
package com.bigdata;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
public class TestZk {
CuratorFramework curator = null;
@Before
public void init() {
// 1. 创建一个连接(自动重连)
RetryNTimes retry = new RetryNTimes(2, 5);// 重连10次,每次间隔1000秒
// 2. 创建一个客户端对象。
curator = CuratorFrameworkFactory.newClient("bigdata01:2181,bigdata02:2181,bigdata03:2181", retry);
// 3. 启动客户端
curator.start();
}
// 新增节点 永久的
@Test
public void testAddNode() throws Exception {
// 这个里面可以创建四种类型的节点 永久节点 临时节点 永久序列节点 临时序列节点
String s = curator.create().withMode(CreateMode.PERSISTENT)
.forPath("/a1", "测试".getBytes());
System.out.println(s);
}
// 获取zk的数据
@Test
public void testGetData() throws Exception {
byte[] bytes = curator.getData().forPath("/a1");
// byte数组如何变为字符串,使用 String 的构造方法
System.out.println(new String(bytes));
}
// 修改数据
@Test
public void testSetData() throws Exception {
curator.setData().forPath("/a1", "我是打水哥".getBytes());
}
// 删除节点
@Test
public void testDeleteNode() throws Exception {
curator.delete().forPath("/a1");
}
// 判断一个节点是否存在
@Test
public void testIsExists() throws Exception {
// Stat 表示一个节点信息,如果获取不到节点信息,为null
Stat stat = curator.checkExists().forPath("/a1");
System.out.println(stat == null ? "该节点不存在" : "存在");
}
// 获取子节点信息,只能获取一层
@Test
public void testGetChild() throws Exception {
List<String> list = curator.getChildren().forPath("/");
for (String fileName : list) {
System.out.println(fileName);
}
}
// 给一个节点添加监听器 get /abc watch
@Test
public void testSetWatch() throws Exception {
NodeCache nodeCache = new NodeCache(curator, "/a1");
nodeCache.start();
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
byte[] data = nodeCache.getCurrentData().getData();
System.out.println(new String(data));
}
});
// 这个地方添加这句话的意思是不让程序跑结束,否则一切都白搭了
Thread.sleep(Integer.MAX_VALUE);
}
// 给一个节点的子节点添加监听器 ls /abc watch
@Test
public void testSetWatchChild() throws Exception {
PathChildrenCache cache = new PathChildrenCache(curator, "/a1", true);
cache.start();
// 添加监听
cache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
// 需要知道是新增了还是修改了还是删除了
System.out.println(event.getType());
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("添加了一个节点");
System.out.println(event.getData().getPath());
System.out.println(new String(event.getData().getData()));
break;
case CHILD_UPDATED:
System.out.println("数据更新了");
System.out.println(event.getData().getPath());
System.out.println(new String(event.getData().getData()));
break;
case CHILD_REMOVED:
System.out.println("节点被删除了");
System.out.println(event.getData().getPath());
break;
default:
System.out.println("节点发生了其他变化");
break;
}
}
});
// 这个地方添加这句话的意思是不让程序跑结束,否则一切都白搭了
Thread.sleep(Integer.MAX_VALUE);
}
}
什么是API?
一般是一个技术诞生后,为了方便开发人员通过代码获取其对应的服务,就会开发出来一些方法,开发人员只需要知道调用这个方法有什么效果即可,不需要懂内部的原理,这样的一系列方法就是该技术的API。
什么是接口?
接口一般存在于web端,规定只要传递响应的参数,我就完成对应的功能,并且返回结果数据。一般是java工程师和web工程师配合工作时使用,很多开源的技术, 为了方便别人使用,会对外开放很多接口,供开发人员进行调用。
枚举类:
package com.bigdata;
// 枚举类
public enum Season {
SPRING, // Season SPRING = new Season("春");
SUMMER,
AUTUMN,
WINTER;
// 以下这四个,分别是Season类的四个实例化对象
Season(String a){
}
Season(){
}
}
枚举类的使用:
@Test
public void testEnum(){
// WINTER 是枚举类的一个实例化对象
Season season = Season.WINTER;
switch (season){
case WINTER:
System.out.println("冬天冷...");
break;
case SPRING:
System.out.println("春天来了,冬天还会源码?");
break;
}
}
假如新增节点,报如下错误:当然这个错误也影响结果
main-EventThread\] ERROR org.apache.curator.framework.imps.EnsembleTracker - Invalid config event received: {server.1=bigdata01:2888:3888:participant, version=0, server.3=bigdata03:2888:3888:participant, server.2=bigdata02:2888:3888:participant}
\[main-EventThread\] ERROR org.apache.curator.framework.imps.EnsembleTracker - Invalid config event received: {server.1=bigdata01:2888:3888:participant, version=0, server.3=bigdata03:2888:3888:participant, server.2=bigdata02:2888:3888:participant}
请修改 zoo.cfg 配置文件:
老的版本的配置:
server.1=bigdata01:2888:3888
server.2=bigdata02:2888:3888
server.3=bigdata03:2888:3888
新的版本的配置:
server.1=bigdata01:2888:3888;2181
server.2=bigdata02:2888:3888;2181
server.3=bigdata03:2888:3888;2181
配置完成后,分发一下,然后重启zk集群即可。
## 二、zk的选举机制
zk中有两种角色:Leader 和 Fllower
Leader是自己的集群各台电脑投票选举出来的。
事务:一通操作,要么同时成立,要么都不成立。
举例:Jack 和 Rose
Rose 给 Jack(小李子) 转钱
Rose -100
Jack +100
如果中间数据有问题,直接回滚,回复到当初的样子
事务操作:新增,修改,删除等操作
非事务操作: 读数据
但凡跟数据相关的技术,都有事务。
mysql 有事务
jdbc 有事务
mybatis 有事务
zookeeper:
Zookeeper 集群工作的核心。
1、事务请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性;
举例 : 比如添加了一个节点,删除了节点,修改了数据 都是事务操作。
2、集群内部各个服务器的调度者。
3、对于 create, setData, delete 等有写操作的请求,需要统一转发给leader 处理, leader 需要决定编号、执行操作,这个过程称为一个事务。
类似于村长
- 处理客户端非事务(读操作)请求,转发事务请求(写操作)给 Leader;
- 参与集群 Leader 选举投票 2n-1台可以做集群投票。 1 3 5 7 9 ....
- 此外,针对访问量比较大的 zookeeper 集群, 还可新增观察者角色。
类似于村民
- 观察者角色,观察 Zookeeper 集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给 Leader服务器进行处理。
- 不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
类似于外来人员(喝汤了)
epoch > zxid > myid
先比较 epoch 谁大谁是领导 --资历
逻辑时钟 参与过的选举的次数 类似于老党员。
zxid 如果epoch 相等,就看谁的zxid大,谁就是领导
zxid 代表的是事务的次数 如果这个值很大,就表示这个机器上的数据比较的新
如果zxid 也相等,就看每台电脑上的myid了,如果myid谁大谁是领导。
我现在的集群中,有三台zk,为什么第二台是领导?
zk01 1
zk02 2
zk03 3
启动第一台,第一台开始选举,自己投自己一票,因为超过半数才有效,所以zk01不是领导,此时的状态是选举中。
启动第二台,先投自己一票,然后第一台开始投票,比较两者之间谁的myid大,谁的大,就投给谁,zk02再得一票,第二台两票,超过了半数,领导出现了zk02.
启动第三台:发现有领导了,投了吧,当fllower吧。
## 三、Hadoop集群的高可用(HA)
关于搭建软件的总结:需要拥有三项技能:
1、linux 命令
2、软件的架构以及运行原理
3、会看日志
HA: High Availability,高可用集群,指的是集群7\*24小时不间断服务。
Hadoop中存在单点故障问题:NameNode ResourceManager
问题:
NameNode 一个
DataNode 多个
集群中的NameNode挂掉了,整个集群都不可用了。
NameNode存在单点故障。
解决方案:使用zk集群搭建Hadoop的高可用。(NameNode的高可用)
我们可以启动两个NameNode ,其中一台Namenode起作用,另一台备用。
关于集群中的高可用的解决方案:
1、主从模式(冷备)
有一个正常工作,另一个闲置,当第一台服务停掉了,另一台直接顶上。
类似于有两个讲师,一个讲师讲课,另一个讲师在家闲着。
2、双主互备
两个都同时工作,一个挂掉了另一个还可以工作。
两个收银员,一个生病了,另一个继续收银。
3、多节点互备
多个收银员,同时收费(超市结账)

在搭建hadoop集群的高可用之前,先将集群进行快照。
### 1) 搭建namenode的高可用
先将三台服务器拍摄快照,便于后面的恢复!!!
第一步:bigdata01和bigdata02都能免密登录到其他三台
因为以前bigdata01 已经免密登录其他三台,不需要做。
进入到**bigdata02**中:
ssh-keygen -t rsa
# 发送公钥(所有NameNode节点都要发送)
ssh-copy-id bigdata01
ssh-copy-id bigdata02
ssh-copy-id bigdata03
第二步:三台电脑上都同时安装psmisc
# ZKFC远程杀死假死NN使用的killall namenode命令属于该软件中的。
# 建议所有节点都安装psmisc
以下命令是在bigdata01上运行的,因为只有01 配置了xcall命令
xcall.sh yum install -y psmisc
第三步:检查jdk以及zk 三台是否都安装完毕
第四步:检查是否安装了hadoop集群,如果以前安装过了,清空数据
先停止集群:stop-all.sh
清理集群中的data数据:
xcall.sh rm -rf /opt/installs/hadoop/tmp/ /opt/installs/hadoop/logs/
第五步:配置hadoop-env.sh
export JAVA_HOME=/opt/installs/jdk/
export HDFS_NAMENODE_USER=root
export HDFS_DATANODE_USER=root
export HDFS_SECONDARYNAMENODE_USER=root
export YARN_RESOURCEMANAGER_USER=root
export YARN_NODEMANAGER_USER=root
**export HDFS_JOURNALNODE_USER=root**
**export HDFS_ZKFC_USER=root**
修改完毕之后,记得同步给bigdata02和03
xsync.sh hadoop-env.sh
第六步:修改core-site.xml