什么是zk
定义与作用
定义:
是树形目录服务,Haddoop下的一个子项目
分布式,开源的分布式应用程序的协调服务(中间件)
【redis也是一个缓存中间件】
作用:
管理大数据下的框架
支持功能
1.配置管理(注册中心)
获取提供方和使用方的信息,整体提供所有服务器的配置功能 如:ip,端口,服务提供的功能接口等
2.分布式锁
当集群使用分布式开发的时候,用于控制服务的资源,锁住后只可一个服务对某一功能可操作
3.集群管理
管理服务的节点(多台服务器提供统一的功能,组成集群。保证服务可以一直运转。)
命令操作
数据模型
每个节点不仅可以有子节点还可以存储1M的数据 每个节点的名字都是/开头
Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M。
默认有一个根节点,每个子节点被称为ZNode
节点分类
【临时节点只要重启就消失】
1.持久化节点 客户端断开,节点仍然存在
2.临时节点 客户端断开,节点自动删除
3.持久化顺序节点 -s 【每创建一个,系统自动增长+1】
4.临时顺序节点 -es 创建时 顺序+1 ,客户端断开,节点自动删除
服务端
在linux中生效
API | 作用 |
---|---|
./zkServer.sh start | 开启 |
./zkServer.sh status | 查看状态 |
./zkServer.sh stop | 创建节点 |
./zkServer.sh | 重启 |
客户端
使用时,必须先开启服务端
连接 zkCli -server IP:PORT
默认端口号2181。配置文件中可改
【只要是写具体某一节点,都是从根开始写】
API | 作用 |
---|---|
ls /节点 | 查看当前节点下的子字节点 |
ls -s /节点 | 查看该节点的详细信息 |
create /节点 | 创建节点 |
get /节点 | 获取节点的值 |
set /节点 值 | 设置节点值 |
delete /节点 | 删除节点 |
deleteall /节点 | 递归删除节点 |
Curator的API
Curator三方 apache的Java客户端库
操作步骤
1.导入依赖
2.导入编译插件
两种方式建立连接获得curatorFramwork
第二种方式可以指定命名空间,默认创建在该命名空间下
注意:如果该空间没有子节点,过一定时间该命名空间的节点会消失
@Before
public void testConnection(){
//第一种方式获取连接
//创建重连策略对象
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 2); 重连间隔,次数
//创建连接对象
/**
* 需要:
* 1.连接地址和端口号 ,多个以逗号隔开
* 2.会话超时时间
* 3.连接超时时间
* 4.重试策略
* 5.设置命名空间(可选设置)
*/
cf = CuratorFrameworkFactory.newClient("localhost:2181",
30000, 15000, retryPolicy);
//开启连接
cf.start();
//第二种方式获取连接【链式编程】
// CuratorFramework build = CuratorFrameworkFactory.builder()
// .connectString("localhost:2181")
// .sessionTimeoutMs(30000)
// .connectionTimeoutMs(15000)
// .retryPolicy(retryPolicy)
// .build();
// build.start();
}
添加节点
默认类型为持久化
如果没有指定值, 会有一个默认值,默认值为自己电脑IP
client 是一次会话,每次创建的连接也是一次会话
提供方法,支持跨级创建节点【本质上如果没有父节点是不允许创建子节点的】
1.基本创建
2.带参数创建
3.设置类型
4.创建多级节点
当系统异常连接中断,zk有一个缓冲机制,不会立即将临时节点清除,如果重连不成功,隔一段时间再自动清除
/**
创建一个节点
如果在创建时没有设置值,那么就以当前主机ip作为数据进行存储
*/
@Test
public void show() throws Exception {
//创建一个节点
String s = cf.create().forPath("/c");
System.out.println(s); //返回路径
}
/**
创建一个节点,同时存储数据
存储时 使用的是字节进行存储 所以需要用getBytes()转码
*/
@Test
public void show1() throws Exception {
//创建一个节点
String s = cf.create().forPath("/d","23".getBytes());
System.out.println(s); //返回路径
}
/**
创建时修改存储类型。
默认类型:持久化
*/
@Test
public void show2() throws Exception {
//创建一个节点
String s = cf.create().withMode(CreateMode.PERSISTENT_SEQUENTIAL_WITH_TTL).forPath("/c/cpp1","23".getBytes());
System.out.println(s); //返回路径
// String path = cf.create().withMode(CreateMode.EPHEMERAL).forPath("/a/app2");
// System.out.println(path);
}
/**
curator提供方法 可以多级创建
*/
@Test
public void show3() throws Exception {
//创建一个节点
String s = cf.create().creatingParentContainersIfNeeded().forPath("/a/app1/app2/app3","12".getBytes());
System.out.println(s);
//使用回调,相当于是监听,当创建时就会显示回调函数内容
cf.create().creatingParentContainersIfNeeded().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("我被填满了~");
System.out.println(curatorEvent);
}
}).forPath("/a/app1/app2/app3");
}
查询节点
查询数据 get
getData().forpath()
查询子节点 ls
getchildren().forpath()
使用方法 getchildren如果设置了命名空间,使用/ 是在命名空间下开始查找,需要加上命名空间
查询状态信息 ls -s
getData().storingtatuin().forpath()
/**
获取某一节点的数据
如果某节点为null则报错
*/
@Test
public void show4() throws Exception {
byte[] bytes = cf.getData().forPath("/b/bpp1");
System.out.println(new String(bytes));
}
/**
获取某一节点的子节点
*/
@Test
public void show5() throws Exception {
List<String> strings = cf.getChildren().forPath("/a/app1");
System.out.println(strings);
}
/**
获取某一节点的状态信息
*/
@Test
public void show6() throws Exception {
Stat status = new Stat(); //创建状态对象
System.out.println(status); //首次输出 全是默认值
//3. 查询节点状态信息命令:ls -s /路径
cf.getData().storingStatIn(status).forPath("/a/app1");
System.out.println(status); //再次输出 已经进行了封装
}
修改节点
基本修改setData().forpath(key,value.getBytes)
根据版本修改 目的:其他线程不可干扰。保证原子性
new Statu . getVersion;
setData().withVersion(版本号)forpath(key,value.getBytes)
不支持重命名节点 【删除后重新添加】
/**
* 基础设置数据
*/
@Test
public void show7() throws Exception {
cf.setData().forPath("/a/app1", "itcast".getBytes());
}
/**
* 通过版本限定(类似于锁机制,当前时刻其他人不可同时修改),修改数据
*/
@Test
public void show8() throws Exception {
//新建状态对象
Stat stat = new Stat();
//获取当前节点的状态信息
byte[] bytes = cf.getData().storingStatIn(stat).forPath("/a/app1");
//获取版本号
int version = stat.getVersion();
//通过该版本号 锁定当前节点进行修改
Stat stat1 = cf.setData().withVersion(version).forPath("/a/app1", "hha".getBytes());
}
删除节点
1.删除单个节点
2.删除含字节点的节点
3.必定成功删除
4.回调【增删改查均有】
/**
* 基础删除功能
* @throws Exception
*/
@Test
public void show9() throws Exception {
// 1. 删除单个节点
cf.delete().forPath("/d");
}
/**
* 多级目录删除
* @throws Exception
*/
@Test
public void show10() throws Exception {
cf.delete().deletingChildrenIfNeeded().forPath("/a/app1");
}
/**
* 防止网络波动,必定删除成功(底层是如果失败,则多次重连。)
* @throws Exception
*/
@Test
public void show11() throws Exception {
//3. 必须成功的删除
cf.delete().guaranteed().forPath("/c");
}
/**
* 回调
* @throws Exception
*/
@Test
public void show12() throws Exception {
//4. 回调
cf.delete().guaranteed().inBackground(new BackgroundCallback(){
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("我被删除了~");
System.out.println(curatorEvent);
}
}).forPath("/f");
==================================
//4. 回调
cf.delete().guaranteed().inBackground(new BackgroundCallback(){
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println("我被删除了~");
System.out.println(curatorEvent);
}
}).forPath("/f");
while (true){
}
}
监听
作用:可以获取节点的各种信息。
zk的监听机制(增删改事件):
1.nodecache 监听单个节点
.start (true) :如果设置为true,表示 开启缓存功能。在本地生成一个缓存区。保存在本地缓存区
不设置true,默认开启。
/**
* 监控节点
* @throws Exception
*/
@Test
public void show13() throws Exception {
//创建节点监听对象
NodeCache nodeCache = new NodeCache(cf,"/a");
//注册监听
nodeCache.getListenable().addListener(new NodeCacheListener(){
//指定监听后的事件
@Override
public void nodeChanged() throws Exception {
System.out.println("节点变动了");
byte[] data = nodeCache.getCurrentData().getData(); //获取节点中的信息
System.out.println(data);
}
});
//3. 开启
nodeCache.start(true);
while (true){
}
}
2.pathchildrencache 监听特定节点的子节点 (不包含本身)
在构造器中指定是否起开缓存功能 【通过变量event可以获取该节点的状态信息】
测试:是否可以监听孙节点? 测试结果:不可以
//2. 绑定监听器
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
System.out.println("子节点变化了~");
System.out.println(event);
//监听子节点的数据变更,并且拿到变更后的数据
//1.获取类型
PathChildrenCacheEvent.Type type = event.getType();
//2.判断类型是否是update
if(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
System.out.println("数据变了!!!");
byte[] data = event.getData().getData();
System.out.println(new String(data));
}
}
});
//3. 开启
pathChildrenCache.start();
while (true){
}
}
3.treeCache 监听该节点的所有子孙节点
上述两种的结合。
场景发布/订阅功能:关注/提醒
【通过变量event可以获取该节点的状态信息】
public void testTreeCache() throws Exception {
//1. 创建监听器
TreeCache treeCache = new TreeCache(client,"/app2");
//2. 注册监听
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
System.out.println("节点变化了");
System.out.println(event);
}
});
//3. 开启
treeCache.start();
while (true){
}
}
分布式锁
定义:协调跨机器进程之间的同步问题
出现的原因: 由于分布式架构,导致在进行操作的时候已经不再是对同一个JVM进行操作,所以单纯的使用多线程锁机制在控制原子性已经不能满足需求。于是就出现了分布式锁组件 此处是使用的ZK的分布式锁组件,可以很好的解决跨机器进程之间的数据同步问题。
拥有分布式机制的技术:
1.基于缓存的分布式锁 redis、memcache【redis性能超好,缺点是如果redis集群 主服务器挂掉,可能从服务器都能获取到锁】
2.zookeeper分布式锁 curator (相对较高、但是性能稳定)
3.数据库层面分布式锁 悲观、乐观锁
(不会使用,因为数据库本身性能就低【修改的时候 对某表进行添加一条数据,第二个进程访问时查看,有拦截,无通行】)
案例 :卖票
package com.itheima.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class Ticket12306 implements Runnable{
private int tickets = 10;//数据库的票数
private InterProcessMutex lock ; //定义分布式锁
//连接zk
public Ticket12306(){
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//2.第二种方式
//CuratorFrameworkFactory.builder();
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.149.135:2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.build();
//开启连接
client.start();
lock = new InterProcessMutex(client,"/lock"); //初始化锁
}
@Override
//重写run方法开启线程
public void run() {
while(true){
//获取锁
try {
//设置锁的每一次的等待时间和时间单位
lock.acquire(3, TimeUnit.SECONDS);
if(tickets > 0){
System.out.println(Thread.currentThread()+":"+tickets);
Thread.sleep(100);
tickets--;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
分布式锁原理
(Curator已经对分布式锁进行了完全封装)
-
定义:协调跨机器的进程之间的数据同步
-
原理:
客户端获取到锁的时候,会在对应的根节点下创建临时顺序节点
- 临时:是为了防止偶发宕机情况下,lock对象不能释放,形成死锁。所以不是持久的 而是临时的
- 顺序:是提供了对象的排队功能(按照顺序依次获得锁),为了找到最小节点!
假设此时有三个客户端访问lock节点。
1.每一个线程到达lock节点下,都会生成一个自己的临时顺序节点,并且获取当前节点下的所有临时节点。按照先后顺序依次获得该节点的锁。
2.如果发现自己没有拿到锁,就会对前一个临时顺序节点进行监听。监听其删除事件,一旦删除就再次进行判断自己是否是最小的节点,如果是获得锁,如果不是重新找到比自己小的临时节点注册监听事件
zk集群搭建
搭建集群要保证所有机器都是zk服务器
leader选举机制
在搭建集群的过程中,zk提供了自动选举机制。当集群中的主服务器出现问题不能正常使用的时候,那么启动选举机制。
选举机制是超过半数的服务器会作为新的主服务器使用。
规则:
1. 依次按照自己启动的顺序来进行选举,当选举的票数达到半数就会使起当做新的leader 结束本次选举
(简而言之就是 基数为中间(5台选第三台) 偶数为中间+1(四台选第三台))
#注意: 故障之后 只要zk服务器数量超过2 那么就会自动选举 如果小于 那么leader会修休眠直到其他zk服务器重现
角色职责
主机写 辅机同步读取 保证一致性
领导者: 只负责事务请求和数据同步的调度工作
跟随者: 处理查询请求 如果是事务请求则进行转发 将该请求转发给领导者
观察者: 作用与跟随者一致 只是不会进行投票选举