同步通信和异步通信:回调函数存于异步通信,异步通信至少两个线程起步
1、Zookeeper监听器原理
1)、监听原理
①、首先要有一个main()线程
②、在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connet) ,一个负责监听( listener) 。
③、通过connect线程将注册的监听事件发送给Zookeeper。
④、在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中。
⑤、Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程。
⑥ 、listener线程内部调用了process()方法。
2)、常见的监听
①、监听节点数据的变化
get path[watch]
②、监听子节点增减的变化
ls path [watch]
2、ZooKeeperAPI
/**
* @author xiaoyoupei
* @date 2021-01-07 19:15
* @Description:
*/
public class ZkClient {
private ZooKeeper zooKeeper;
@Before
public void before() throws IOException {
//zookeeper连接地址
String connectString = "test:2181,test1:2181,test2:2181";
//超时时间
int sessionTimeout = 2000;
//1、创建一个Zookeeper对象
zooKeeper = new ZooKeeper(connectString, sessionTimeout
, new Watcher() {
@Override
//zookeeper监听的回调函数
public void process(WatchedEvent watchedEvent) {
}
});
}
/**
* 创建新节点
*/
@Test
public void create() throws IOException, KeeperException, InterruptedException {
//2、搞事情
zooKeeper.create(
"/testAPI",//路径名
"123".getBytes(),//节点的值(字节数组)
ZooDefs.Ids.OPEN_ACL_UNSAFE,//访问控制列表
CreateMode.PERSISTENT//永久有效(节点数据类型)异步通信
);
}
/**
* 查看目录下所有子节点,并且自定义监听(如果有什么操作会调用回调函数,比如删除节点)
*/
@Test
public void ls() throws KeeperException, InterruptedException {
List<String> children = zooKeeper.getChildren("/",//查看的路径
new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("自定义回调函数");
}
}
);
for (String child : children) {
System.out.println(child);
}
//阻塞主进程,因为后台的守护线程盯着主线程,主线程一结束,守护线程也结束
Thread.sleep(Long.MAX_VALUE);
}
/**
* 查询节点的值
*/
@Test
public void get() throws KeeperException, InterruptedException, IOException {
//节点状态(stat结构体)
Stat stat = new Stat();
byte[] data = zooKeeper.getData("/testAPI", false, stat);
System.out.println(stat.toString());//节点的状态信息
System.out.write(data);
System.out.println();
}
/**
* 查询一个节点的状态
*/
@Test
public void stat() throws KeeperException, InterruptedException {
//查看节点是否存在
Stat stat = zooKeeper.exists("/testAPI", false);
if (stat == null) {
System.out.println("节点不存在");
} else {
System.out.println(stat);
}
}
/**
* 设置节点的具体值
*/
@Test
public void set() throws KeeperException, InterruptedException {
String node = "/testAPI";
Stat stat = zooKeeper.exists(node, false);
if (stat == null) {
System.out.println("节点不存在");
} else {
zooKeeper.setData(
"/testAPI",//节点位置
"xyp".getBytes(),//要改成的具体值
/**
* 数据变化号
* (在我们进行此操作时很有可能有人正在修改,数据产生变化,
* 因此设置一个数据的变化号(数据的第几个版本))
*/
stat.getVersion()
);
}
}
/**
* 删除节点
*/
@Test
public void delete() throws KeeperException, InterruptedException {
zooKeeper.delete(
"/testAPI",
1);
}
@After
public void after() throws InterruptedException {
//3、关闭资源
zooKeeper.close();
}
}
3、Zookeeper的ZAB协议(半数协议)
zookeeper自己开发的一套协议。首先集群里面都是地位平等的,如果没有Leader,就在集群中选出Leader,如果有Leader就正常操作。
1)、选举机制(没有Leader选Leader)
①、过程
a、服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING;
b、服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的ID比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING
c、服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
d、服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;
e、服务器5启动,同4一样当小弟。
②、备注:
id先看每个服务器的zxid(每次进行操作的变动号),看谁的大,谁的大就是数据新,如果zxid相等就进一步比较myid(myid不可能相等的);找Leader和选举是同时进行的,只有Looking状态的机器才是“墙头草”,如果想优先让谁成为leader可以把myid调大。
③、通俗易懂:
5台机器群起就是最后一个当Leader,单个启动就是第3个当Leader;5台机器,第3个为Leader,第3个挂了,选举第5个当Leader。
④、提问:如果后面的有更厉害的怎么办?
回答:不会,已经选好了Leader就不会改动,后面的就只会同步数据作为Following。
2)、写数据流程
①、补充:
当有个跟从者提出不同意的时候,他会“原地自杀”,然后重启,再向Leader同步数据
②、提问:什么情况下节点不同意?
当Leader发布写请求的时候server一定有一个zxid(Leader发布请求的时候编写好的)但是每个server都有自己的最新的zxid,只要Leader发来的zxid比自己的zxid大,他就同意,反之则不同意。
③、提问:如果Leader提出,半数不同意怎么办?
Leader自杀,重选Leader(概率很小)
4、zookeeper群起脚本
1)、备注:
$#表示所有参数的个数;ssh $i表示免密登录;2表示标准错误(stderr);/dev/null是一个特殊的设备文件,这个文件接收到任何数据都会被丢弃。因此,null这个设备通常也被称为位桶(bit bucket)或黑洞;2>/dev/null的意思就是将标准错误stderr删掉。