zookeeper实现Hadoop的HA高可用
高可用的原理
hadoop中的namenode如果是单节点,那么就不满足高可用。实际设置为两个节点,其中一个为active状态,另一个为stand by,前者挂了,后者就把状态改为active,给客户端提供服务。那么两个节点之间需要有公共日志记录,stand by节点转变为active节点时,就把公共日志中的内容更新到数据中。那么公共服务日志中的节点也得是高可用的,就可以通过选举的方式来决定谁是leader。这就要引入zookeeper了。
zookeeper原理
1.可以创建节点,每个节点可以保存少量的数据。
2.如果节点发生了改变,能够通知到客户端。
基于这两点就可以为各种客户端实现高可用。
zookeeper本来就是高可用的,一般会部署奇数台机器,每个节点都是通过选举来寻找leader的。
5台机器为例,选举过程:3888端口用来选举通讯,每个节点都有自己的myid,刚开始第一台机器投自己myid1,第二台机器投自己myid2,这时候第一个节点会发现第二台机器的myid比自己大,所以会把自己的票给到第二台。第三台启动之后,会投自己,前两台的投票也会改成3 ,至此,3号机器的票数超过了半数,成为leader节点。4,5号机器发现有leader节点了,就不再投票了。其他节点自动变为follower节点。
zokeeper节点类型
zokeeper监听节点,可以监听数据的变化,还可以监听目录的变化
节点类型可分为,临时的和持久的,是否带序号。
临时的节点如果客户端断开了连接,那么创建的这个节点就删除了。
带序号的话,新增的节点后面会拼接00000001,序号是递增的。
利用zookeeper开发分布式应用系统
原理:生产者每次上线都在zk上注册一个带序号的临时节点,消费者每次启动都去zk上查询在线的生产者列表,并且对父节点注册监听,监听子节点的变化。每次生产者有上线或者下线的变化,消费者都能及时感知,重新去拉去生产者的列表。
代码展示
public class Consumer {
// 定义一个list用于存放最新的在线服务器列表
private volatile ArrayList<String> onlineServers = new ArrayList<>();
// 构造zk连接对象
ZooKeeper zk = null;
// 构造zk客户端连接
public void connectZK() throws Exception {
zk = new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected && event.getType() == EventType.NodeChildrenChanged) {
try {
// 事件回调逻辑中,再次查询zk上的在线服务器节点即可,查询逻辑中又再次注册了子节点变化事件监听
getOnlineServers();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
// 查询在线服务器列表
public void getOnlineServers() throws Exception {
List<String> children = zk.getChildren("/servers", true);
ArrayList<String> servers = new ArrayList<>();
for (String child : children) {
byte[] data = zk.getData("/servers/" + child, false, null);
String serverInfo = new String(data);
servers.add(serverInfo);
}
onlineServers = servers;
System.out.println("查询了一次zk,当前在线的服务器有:" + servers);
}
public void sendRequest() throws Exception {
Random random = new Random();
while (true) {
try {
// 挑选一台当前在线的服务器
int nextInt = random.nextInt(onlineServers.size());
String server = onlineServers.get(nextInt);
String hostname = server.split(":")[0];
int port = Integer.parseInt(server.split(":")[1]);
System.out.println("本次请求挑选的服务器为:" + server);
Socket socket = new Socket(hostname, port);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write("haha".getBytes());
out.flush();
byte[] buf = new byte[256];
int read = in.read(buf);
System.out.println("服务器响应的时间为:" + new String(buf, 0, read));
out.close();
in.close();
socket.close();
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
Consumer consumer = new Consumer();
// 构造zk连接对象
consumer.connectZK();
// 查询在线服务器列表
consumer.getOnlineServers();
// 处理业务(向一台服务器发送时间查询请求)
consumer.sendRequest();
}
}
public class TimeQueryServer {
ZooKeeper zk = null;
// 构造zk客户端连接
public void connectZK() throws Exception{
zk = new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, null);
}
// 注册服务器信息
public void registerServerInfo(String hostname,String port) throws Exception{
/**
* 先判断注册节点的父节点是否存在,如果不存在,则创建
*/
Stat stat = zk.exists("/servers", false);
if(stat==null){
zk.create("/servers", null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
// 注册服务器数据到zk的约定注册节点下
String create = zk.create("/servers/server", (hostname+":"+port).getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname+" 服务器向zk注册信息成功,注册的节点为:" + create);
}
public static void main(String[] args) throws Exception {
TimeQueryServer timeQueryServer = new TimeQueryServer();
// 构造zk客户端连接
timeQueryServer.connectZK();
// 注册服务器信息
timeQueryServer.registerServerInfo(args[0], args[1]);
// 启动业务线程开始处理业务
new TimeQueryService(Integer.parseInt(args[1])).start();
}
}
public class Consumer {
// 定义一个list用于存放最新的在线服务器列表
private volatile ArrayList<String> onlineServers = new ArrayList<>();
// 构造zk连接对象
ZooKeeper zk = null;
// 构造zk客户端连接
public void connectZK() throws Exception {
zk = new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected && event.getType() == EventType.NodeChildrenChanged) {
try {
// 事件回调逻辑中,再次查询zk上的在线服务器节点即可,查询逻辑中又再次注册了子节点变化事件监听
getOnlineServers();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
// 查询在线服务器列表
public void getOnlineServers() throws Exception {
List<String> children = zk.getChildren("/servers", true);
ArrayList<String> servers = new ArrayList<>();
for (String child : children) {
byte[] data = zk.getData("/servers/" + child, false, null);
String serverInfo = new String(data);
servers.add(serverInfo);
}
onlineServers = servers;
System.out.println("查询了一次zk,当前在线的服务器有:" + servers);
}
public void sendRequest() throws Exception {
Random random = new Random();
while (true) {
try {
// 挑选一台当前在线的服务器
int nextInt = random.nextInt(onlineServers.size());
String server = onlineServers.get(nextInt);
String hostname = server.split(":")[0];
int port = Integer.parseInt(server.split(":")[1]);
System.out.println("本次请求挑选的服务器为:" + server);
Socket socket = new Socket(hostname, port);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write("haha".getBytes());
out.flush();
byte[] buf = new byte[256];
int read = in.read(buf);
System.out.println("服务器响应的时间为:" + new String(buf, 0, read));
out.close();
in.close();
socket.close();
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
Consumer consumer = new Consumer();
// 构造zk连接对象
consumer.connectZK();
// 查询在线服务器列表
consumer.getOnlineServers();
// 处理业务(向一台服务器发送时间查询请求)
consumer.sendRequest();
}
}
hadoop利用zk实现高可用
在节点从stand by状态改为active状态时,为防止脑裂的发生,即acvive节点并不是真正地挂了,会发送ssh kill命令,如果没响应再发送ssh脚本,强行让原节点停止,之后才会安心地转为active节点。
yarn的节点转化会简单一些,因为不存在数据的同步问题。
Paxos协议可以参考:https://blog.csdn.net/asmfeng2/article/details/132851195
hadoop高可用系统搭建
搭建过程略过
在HA环境下使用hdfs时,需要将hadoop集群的配置信息放到系统classpath中,这样才能正常使用集群版。