public class TestZK {
// zookeeper集群的ip和端口
private String connectString = "192.168.204.141:2181,192.168.204.142:2181,192.168.204.143:2181";
/*
session超时的时间: 时间不宜设置太小。因为zookeeper和加载集群环境会因为性能等原因而延迟略高,
如果时间太少,还没有创建好客户端,就开始操作节点。会报错的。
(心急吃不了热豆腐)
*/
private int sessionTimeout = 60 * 1000;
//zookeeper客户端对象
private ZooKeeper zkClient;
@Before
public void init() throws Exception {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
System.out.println("得到监听反馈,再进行的业务处理代码!");
System.out.println(watchedEvent.getType());
}
});
}
分布式和集群的区别
分布式和集群,都是很多人在做
- 分布式:负责工作不一样,但是最终目的都是一样的
- 集群:负责工作一样
概述
是一个开源的分布式(多台服务器干一件事)的,为分布式应用提供“协调服务”的Apache项目
服务平台,利用zookeeper部署技术
最初的运用领域在大数据技术生态圈当中。
工作机制
Zookeeper从设计模式角度来理解;是一个基于观察者模式(一个人干活,有人盯着)设计的分布式服务管理框架。他负责 存储 和 管理 大家都关心的数据。
- 然后接受观察者的注册,一旦这些数据发生变化
- Zookeeper就将负责通知已经注册的那些观察者做出相应的反应
- 从而实现集群中类似Master/Slave管理模式
Zookeeper=文件系统+通知机制
机制(例:美团原理):
- 商家营业并入驻
- 获取当前营业的饭店列表
- 服务器节点下线
- 服务器节点上下线事件通知
- 重新再获取服务器列表,并注册监听
特点
- 是一个leader和多个follower来组成的集群
- 集群中只要有半数以上的节点存活,Zookeeper就能正常工作(5台服务器挂2台,没问题;4台服务器挂2个,就停止)
- 全局数据一致,每台服务器都保存一份相同的数据副本,无论clien连接哪台server,数据都是一致的。
- 数据更新原子性,一次数据要么成功,要么失败
- 实时性,在一定时间范围内,client能读取到最新数据
- 更新的请求按照顺序执行,会按照发送过来的顺序,逐一执行
数据结构
Zookeeper数据模型的结构与linux文件系很类似,整体上可以看作是一棵数,每个节点称作一个ZNode。
每一个ZNode默认能够存储1MB的数据(元数据),每个ZNode的路径都是唯一的
- 元数据,又称中介数据,中继数据,为描述数据的数据,主要是描述数据属性的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能
应用场景
提供的服务包括:统一命名服务,统一配置管理,统一集群管理,服务器节点动态上下线,负载均衡等
统一命名服务
- 在分布式环境下,通常需要对应用或服务进行统一命名,便于识别
- 例如:服务器的IP地址不容易记,但域名相比之下却很容易记住
统一配置管理
- 分布式环境下,配置文件做到同步是必经之路
- 1000台服务器,如果配置文件做出修改,那一台一台的修改,运维人员肯定会疯,如何做到修改一处就快速同步到每台服务器上
- 将配置管理交给Zookeeper
- 将配置信息写入到Zookeeper的某个节点上
- 每个客户端都监听这个节点
- 一旦节点中的数据文件被修改,Zookeeper这个话匣子就会通知每台客户服务器
服务器节点动态上下线
- 客户端能够实现获取服务器上下线的变化
软负载均衡
- Zookeeper会记录每台服务器的访问次数,让访问次数最少的服务器去处理最新的客户请求
下载地址
镜像库地址:http://archive.apache.org/dist/zookeeper/
Zookeeper内部原理
选举机制
- 半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器
- 虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的
- 例:假如有5台服务器,第一台服务器投票与自己,如果没超过半数,无法成为leader,则将票数推给第二台服务器,此时,第二台服务算上自己就有两个票数,但是票数任未过半。第二台将两个票数推给第三台服务器,此时,第三台服务器拥有三个票数,成为leader。第四五台服务器则不继续进行投票选举(已经出现leader时,其它的服务器就是Follower)
节点类型
持久型:
- 持久化目录节点:客户端与zookeeper断开连接后,该节点依旧存在
- 持久化顺序编号目录节点:客户端与zookeeper断开连接后,该节点依旧存在,创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护,例如:Znode001,Znode002...
短暂型:
- 临时目录节点:客户端和服务器断开连接后,创建的节点自动删除
- 临时顺序编号目录节点:客户端和zookeeper断开连接后,该节点被删除,创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护,例如:Znode001,Znode002...
注意:序号是相当于i++,和数据库中的自增长类似
监听器原理
- 在main方法中创建Zookeeper客户端的同时就会创建两个线程,一个负责网络连接通信,一个负责监听
- 监听事件就会通知网络通信发送给zookeeper
- zookeeper获取注册的监听事件后,立刻将监听事件添加到监听列表里
- zookeeper监听到 数据变化 或 路径变化,就会将这个消息发送给监听线程(1.监听节点数据变化:get path [watch];2.监听子节点增减的变化:Is path [watch])
- 监听线程就会在内部调用process方法(需要我们实现process方法内容)
写数据流程
- Client想想Zookeeper的Server1 上写数据,必须得先发送一个写的请求
- 如果Server1不是Leader,那么Server1 会把接收到的请求进一步转发给Leader
- 这个Leader会将写请求广播给各个Server,各个Server写成功后就会通知Leader
- 当Leader收到半数以上的Server数据写成功了,那么就说明数据写成功了
- 随后,Leader会告诉Server1数据写成功了
- Server1会反馈通知Client数据写成功了,整个流程结束
Zookeeper实战
分布式安装部署
集群思路:先搞定一台服务器,再克隆出两台,形成集群
安装zookeeper
配置服务器编号
- 在/opt/zookeeper/zkData创建myid文件 : [root@localhost zkData]# vim myid
- 在文件中添加与server对应的编号:1
- 其余两台服务器分别对应2和3
配置zoo.cft文件
- 打开zoo.cfg文件,增加如下配置
- 配置参数解读 server.A=B:C:D
- A:一个数字,表示第几号服务器。集群模式下配置的/opt/zookeeper/zkData/myid文件里面的数据就是A的值
- B:服务器的ip地址
- C:与集群中Leader服务器交换信息的端口
- D:选举时专用端口,万一集群中的Leader服务器挂了,需要一个端口重新进行选举,选出一个新的Leader,二这个端口就是用来执行选举时服务器相互通信的端口
配置其余两台服务器
- 在虚拟机数据目录vms下,创建zk02
- 将本台服务器数据目录下的.vmx文件和所有的.vmdk文件分别拷贝到zk02下
- 虚拟机->文件->打开(选择zk02下的.vmx文件)
- 开启此虚拟机,弹出对话框,选择“我已复制该虚拟机”
- 进入系统后,修改linux中的ip,修改/opt/zookeeper/zkData/myid中的数值为2.第三台服务器zk03,重复以上的步骤
集群操作
客户端命令行操作
API应用
IDEA环境搭建
- 创建一个,maven工程
- 添加pom文件
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
在resources下创建log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/zk.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
创建Zookeeper客户端
public class TestZK {
// zookeeper集群的ip和端口
private String connectString = "192.168.204.141:2181,192.168.204.142:2181,192.168.204.143:2181";
/*
session超时的时间: 时间不宜设置太小。因为zookeeper和加载集群环境会因为性能等原因而延迟略高,
如果时间太少,还没有创建好客户端,就开始操作节点。会报错的。
(心急吃不了热豆腐)
*/
private int sessionTimeout = 60 * 1000;
//zookeeper客户端对象
private ZooKeeper zkClient;
@Before
public void init() throws Exception {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
System.out.println("得到监听反馈,再进行的业务处理代码!");
System.out.println(watchedEvent.getType());
}
});
}
创建节点
一个ACL对象就是一个Id和permission对
- 表示哪个/哪些范围的Id(Who)在通过了怎样的鉴权(How)之后,就允许进行那些操作 (What):Who How What;
- permission(What)就是一个int表示的位码,每一位代表一个对应操作的允许状态。
- 类似linux的文件权限,不同的是共有5种操作:CREATE、READ、WRITE、DELETE、 ADMIN(对应更改ACL的权限)
- OPEN_ACL_UNSAFE:创建开放节点,允许任意操作 (用的最少,其余的权限用的很 少)
- READ_ACL_UNSAFE:创建只读节点
- CREATOR_ALL_ACL:创建者才有全部权限
//创建节点
@Test
public void createNode() throws Exception {
String str = zkClient.create("/lagou", "laosun".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//参数1:要创建的节点的路径
//参数2:节点数据
//参数3:节点权限
//参数4:节点的类型
System.out.println("已创建节点: " + str);
}
查询节点的值
@Test
public void getNodeData() throws Exception {
byte[] bytes = zkClient.getData("/lagou", false, new Stat());
String str = new String(bytes);
System.out.println("/lagou节点的数据:" + str);
}
修改节点的值
//修改节点上的数据
@Test
public void updateData() throws Exception {
Stat stat = zkClient.setData("/lagou", "laosunA".getBytes(), 0);
System.out.println(stat);
}
删除节点
//删除节点
@Test
public void delete() throws Exception {
zkClient.delete("/lagou", 1);
System.out.println("删除成功!");
}
获取子节点
//获取子节点
@Test
public void getChildren() throws Exception {
List<String> list = zkClient.getChildren("/china", false);
for (String child : list) {
System.out.println(child);
}
}
监听子节点的变化
//监听根节点下面的变化
@Test
public void watchNode() throws Exception{
List<String> list = zkClient.getChildren("/", true);
for (String s : list)
System.out.println(s);
// 让线程无限的等待下去
System.in.read();
}
判断ZNode是否变化
@Test
public void exists()throws Exception{
Stat stat = zkClient.exists("/lagou", false);
if(stat==null)
System.out.println("不存在");
else
System.out.println("存在");
}
分布式锁
- 锁:我们在多线程中接触过,作用就是让当前的资源不会被其他线程访问!
- 我的日记本,不可以被别人看到。所以要锁在保险柜中
- 当我打开锁,将日记本拿走了,别人才能使用这个保险柜
- 在zookeeper中使用传统的锁引发的 “羊群效应” :1000个人创建节点,只有一个人能成功,999 人需要等待!
- 羊群是一种很散乱的组织,平时在一起也是盲目地左冲右撞,但一旦有一只头羊动起来,其他的羊 也会不假思索地一哄而上,全然不顾旁边可能有的狼和不远处更好的草。羊群效应就是比喻人都有 一种从众心理,从众心理很容易导致盲从,而盲从往往会陷入骗局或遭到失败。
- 避免“羊群效应”,zookeeper采用分布式锁
- 所有请求进来,在/lock下创建 临时顺序节点 ,放心,zookeeper会帮你编号排序
- 判断自己是不是/lock下最小的节点 1. 是,获得锁(创建节点) 2. 否,对前面小我一级的节点进行监听
- 获得锁请求,处理完业务逻辑,释放锁(删除节点),后一个节点得到通知
- 重复步骤2