Zookeeper系统详解

什么是Zookeeper?

        zookeeper是一个高效的分布式系统协调服务。他暴露了一些公共服务,比如命名/配置管理/同步控制/群组服务等。我们可以使用ZK来实现比如打成共识/集群管理/leader选举的。

        Zookeeper是一个高可用的分布式管理和协调框架,通过Paxos算法(实现主从选举的算法)以及基于ZAB协议(原子消息广播协议)的实现。该框架能够很好地保证分布式环境中数据的一致性。也正是基于这样的特性,使得ZooKeeper成为了解决分布式一致性问题的利器(集群中其节点最好为奇数个,利于其算法的实现)。

        当客户端对一个服务器执行了一次DML操作,这个期间是不允许其他客户端访问的,就是通过ZAB算法的实现的,相当于在这个集群中加了一把锁,当所有服务器节点数据都同步后(数据同步的算法就是paxos),再允许客户端访问。

        顺序一致性:从一个客户端发起的事务请求,最终会严格的按照发起的顺序被应用到zookeeper中去。

        原子性:所有事物请求的处理结果在整个集群中所有的机器上的应用情况是一致的。也就是说,要么整个集群中所有的机器都成功应用了某一事物,要么都没有应用。一定不会出现部分机器应用了该事务,另一部分没有应用的情况。

        单一视图:无论客户端连接的是哪个zookeeper服务器,其看到的服务器端数据模型都是一致的。

        可靠性:一旦服务器成功应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务器端状态将会被一致保留下来,除非有另外一个事务对其更改。

        实时性:通常所说的实时性就是指一旦事务被成功应用,那么客户端就可以立刻从服务端获取变更后的新数据,zookeeper仅仅能保证在一段时间内,客户端一定能从服务端读取最新的数据状态。

 zoo.cfg详解:

        tickTime: 基本事件单元,以毫秒为单位。这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔 tickTime时间就会发送一个心跳。                

        dataDir:存储内存中数据库快照的位置,顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。               

        clientPort: 这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。                

        initLimit: 这个配置项是用来配置 Zookeeper 接受客户端初始化连接时最长能忍受多少个心跳时间间隔数,        当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒。        

        syncLimit: 这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10 秒                

        server.A = B:C:D :         
        A表示这个是第几号服务器,       
        B 是这个服务器的 ip 地址;       
        C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;       

        D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader

zookeeper实现分布式锁

当一个用户发送请求后首先到zookeeper目录下查找有无id=1的临时节点(创建什么节点任意只是打个比方,临时节点仅在一次会话中有效,当这次会话完成,临时节点就会消失),如果有则等待,如果没有则创建该临时节点(zookeeper中如果节点一旦存在,就无法再次创建),这样就可以保证每次仅执行一个用户的请求,并按照zookeeper的顺序一致性挨个执行。

为什么不直接创建,如果抛出异常后(也就是该节点已经被创建)则等待呢?

因为zookeeper中get节点的效率是远远高于create一个节点的。

Watcher(观察者)

zookeeper有watch事件,是一次性触发的,当watch监视的数据发生变化时,通知设置了该watch的client,即watcher。

同样watcher的作用是监听了数据发生了某些变化,那就一定会有对应的事件类型和状态类型。
事件类型:(znode节点相关的)
    EventType.NodeCreated
    EventType.NodeDataChanged
    EventType.NodeChildrenChanged
    EventType.NodeDelete
状态类型:(是跟客户端实例相关的)
    KeeperState.Disconnected
    KeeperState.SyncConnected
    KeeperState.AuthFailed

    KeeperState.Expired

watcher的特性:一次性,客户端串行执行,轻量。
一次性:对于ZK的watcher只需要记住一点:zookeeper有watch事件,是一次性触发的,当watch监视的数据发生变化时,通知设置了该watch的client,即watcher,由于zookeeper的监控都是一次性的,所以必须每次都设置监控。
客户端串行执行:客户端watcher回调过程是一个串行同步的过程,这为我们保证的顺序,同时需要开发人员注意一点,千万不要因为一个watcher的处理逻辑影响了整个客户端的watcher回调。
轻量:WatchedEvent是zookeeper整个Watcher通知机制的最小通知单元,整个结构只包含三部分:通知状态,事件类型和节点路径。也就是说watcher通知非常的简单,只会告诉客户端发生了事件而不会告知其具体内容,需要客户自己去获取,比如NodeDataChanged事件,Zookeeper只会通知客户端指定节点的数据发生了变更,而不会直接提供具体的数据内容,这也体现了zookeeper轻量的设计思想,因为获取值会耗费更多的性能。

原生的zookeeper中对节点所有的增删改查方法中都有一个boolean类型的watch,这体现了watcher的一次性,但实际开发中相当麻烦,每一次监控过后都要重新设置watch。zkClient中对其进行的封装,取消了每个方法中的watch,将watch单独提取了出来:
                zkClient.subscribeChildChanges("/super", new IZkChildListener() {
@Override
public void handleChildChange(String parent, List<String> childrens)
throws Exception {
}
});

温馨提示:客户端操作zookeeper版本的jar包,千万不要和linux上的zookeeper版本不同,否则会出现可能可以连接,但是会花费非长久的时间才能发送一个请求的状况,深坑误入!!!

Curator框架

如果要使用类似Watcher的监听功能Curator必须依赖一个jar包:curator-recipes
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.8.0</version>

</dependency>

有了这个依赖包,我们使用NodeCache的方式去客户端实例中注册一个监听缓存,然后实现对应的监听方法即可,主要有两种个监听方式:
1 NodeCacheListner:监听节点的新增,修改操作。

2 PathChildrenCacheListner:监听子节点的新增,修改,删除操作。

Curator的思想并不是重复注册watch,而是在Client端加一个cache,每次Client端的数据发生变更时,就将缓存的内容与服务端的内容进行对比,如果有变化就将变化的内容从服务端取出再放进缓存。

public class CuratorWatcher {
private static final String ADDRESS = "192.168.25.129:2181,192.168.25.130:2181,192.168.25.131:2181";
public static void main(String[] args) throws Exception {
// 通过工厂创建连接
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString(ADDRESS)
// 重试策略:初试时间1S,重复10次
.sessionTimeoutMs(1000)
.retryPolicy(new ExponentialBackoffRetry(1000, 10)).build();
// 建立连接
cf.start();
// 建立一个缓存,点个参数为是否接受节点变更后的数据,如果false表明不接受
PathChildrenCache cache = new PathChildrenCache(cf, "/super", true);
// 在初始化的时候就进行监听缓存
cache.start(StartMode.POST_INITIALIZED_EVENT);
cache.getListenable().addListener(new PathChildrenCacheListener() {

@Override
public void childEvent(CuratorFramework cf, PathChildrenCacheEvent event)
throws Exception {
switch (event.getType()) {
case CHILD_ADDED:
System.out.println("child add:"+event.getData().getPath());
System.out.println("child add:"+new String(event.getData().getData(),"UTF-8"));
break;
case CHILD_UPDATED:
System.out.println("child update:"+event.getData().getPath());
System.out.println("child update:"+new String(event.getData().getData(),"utf-8"));
break;
case CHILD_REMOVED:
System.out.println("child remove:"+event.getData().getPath());
System.out.println("child remove:"+new String(event.getData().getData(),"utf-8"));
break;
default:
break;
}
}
});
//创建本身节点不返生变化
cf.create().forPath("/super","init".getBytes());
Thread.sleep(1000);
//添加子节点
Thread.sleep(1000);
cf.create().forPath("/super/c1", "c1内容".getBytes("iso8859-1"));
Thread.sleep(1000);
cf.create().forPath("/super/c2", "c2内容".getBytes());
//变更子节点
Thread.sleep(1000);
cf.setData().forPath("/super/c1", "c1内容变更".getBytes());
Thread.sleep(1000);
cf.setData().forPath("/super/c2", "c2内容变更".getBytes());
//删除子节点
Thread.sleep(1000);
cf.delete().forPath("/super/c1");
//删除所有super节点
Thread.sleep(1000);
cf.delete().deletingChildrenIfNeeded().forPath("/super");
}
}


备注:子节点只能监听一级,也就是如果孙子节点发生变化时无反应(不会抛异常)。

Curator实现分布式锁功能

在分布式场景中,我们为了保证数据的一致性,经常在程序运行的某一个点需要进行同步操作。

在这里使用Curator基于zookeeper的特性提供的分布式锁来处理分布式场景的数据一致性,zookeeper本身的分布式是有写问题的,强烈推荐Curator的分布式锁。

package com.baidu;
import java.util.concurrent.CountDownLatch;
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;


public class CuratorLock {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.25.129:2181,192.168.25.130:2181,192.168.25.131:2181";
/** session超时时间 */
static final int SESSION_OUTTIME = 5000;// ms
static int count = 10;

public static void decriment() {
count--;
System.out.println(count);
}

public static void main(String[] args) throws InterruptedException {

final CountDownLatch condown = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
                                        // 通过工厂创建连接,模拟十个不同的用户new了10个不同的连接
CuratorFramework cf = createCuratorFramework();
                                        // 开启连接
cf.start();
                                        //模拟十个不同的用户new了10个不同的分布式锁
InterProcessMutex lock = new InterProcessMutex(cf, "/super");
System.out.println("线程" + Thread.currentThread().getName()
+ "进入等待");
try {
                                                //使用CountDownLatch ,让主线程休息一秒,new出所有的线程,让线程同时开启
condown.await();
                                                //拿到锁允许进入
lock.acquire();
System.out.println(Thread.currentThread().getName()
+ "业务处理开始");
Thread.sleep(1000);
decriment();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (lock != null) {
try {
// 释放锁
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}


}
}).start();
condown.countDown();
}
                Thread.sleep(1000);
}
private static CuratorFramework createCuratorFramework() {
return CuratorFrameworkFactory.builder().connectString(CONNECT_ADDR)
// 重试策略:初试时间1S,重复10次
.retryPolicy(new ExponentialBackoffRetry(1000, 10))
.sessionTimeoutMs(1000).build();
}

}



curator实现分布式计数器功能可以使用Curator框架提供的DistributedAtomicInteger。

分布式Barrier

分布式线程同时启动退出可以有两个类(相当于java中的cyclicbarrier和countdownlatch):DistributedDoubleBarrier和DistributedBarrier。

DistributedDoubleBarrier用法:
package com.baidu;
import java.util.Random;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.barriers.DistributedDoubleBarrier;

import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorBarrier {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.25.129:2181,192.168.25.130:2181,192.168.25.131:2181";
/** session超时时间 */
static final int SESSION_OUTTIME = 5000;// ms

public static void main(String[] args) throws Exception {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
CuratorFramework cf = null;
try {
cf = createCuratorFramework();
cf.start();
DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(
cf, "/super", 5);
Thread.sleep(1000 * new Random().nextInt(4));
System.out.println(Thread.currentThread().getName()
+ "开始运行");
barrier.enter();
Thread.sleep(1000 * new Random().nextInt(3));
System.out.println(Thread.currentThread().getName()
+ "运行完毕");
barrier.leave();
System.out.println(Thread.currentThread().getName()
+ "同时退出");
} catch (Exception e) {
e.printStackTrace();
}finally{
cf.close();
}
}
},"t"+i).start();
}
}
private static CuratorFramework createCuratorFramework() {
return CuratorFrameworkFactory.builder().connectString(CONNECT_ADDR)
// 重试策略:初试时间1S,重复10次
.retryPolicy(new ExponentialBackoffRetry(1000, 10))
.sessionTimeoutMs(1000).build();
}
}

注意:作为锁的/super节点一定要为空节点,如果下面有子节点会出问题

DistributedBarrier的用法:
package com.baidu;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.barriers.DistributedBarrier;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorBarrier1 {
static final String CONNECT_ADDR = "192.168.25.129:2181,192.168.25.130:2181,192.168.25.131:2181";
static DistributedBarrier barrier = null;
public static void main(String[] args) throws Exception {
for(int i=0;i<5;i++){
new Thread(new Runnable(){
@Override
public void run() {
CuratorFramework cf = createCuratorFrameworkFactory();
cf.start();
barrier = new DistributedBarrier(cf, "/super");
try {
barrier.setBarrier();
System.out.println(Thread.currentThread().getName()+"设置barrier");
barrier.waitOnBarrier();
System.out.println("执行完毕");
} catch (Exception e) {
e.printStackTrace();
}
}},"t"+i).start();
}
Thread.sleep(5000);
barrier.removeBarrier();
}

private static CuratorFramework createCuratorFrameworkFactory(){
return CuratorFrameworkFactory.builder().connectString(CONNECT_ADDR)
.retryPolicy(new ExponentialBackoffRetry(1000, 10))
.build();
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值