zookeeper学习和简单使用总结

一、zk简介

ZooKeeper是java写的一个开源的分布式协调服务框架,一主多从架构,理想情况下,每个节点数据一致,数据存储模型为树型结构,节点znode可存放数据,数据存放在内存和磁盘中。通过对结点znode状态管理,可用于其它分布式组件的服务注册和发现,参与组件的管理,分布式框架比如kafka,hadoop等。zookeeper的锁机制,保证写安全和读高性能平衡,高并发情况下保证数据安全。

二、zk框架应用场景

  • 分布式存储,基于内存,数据磁盘持久化,保证高可用和高性能以及数据安全。
  • 数据一致性,在保证高并发的同时,zk客户端的修改可以对其它客户端来说有一致性
  • Watch机制,通过对znode节点状态监控,对注册的服务节点状态进行管理。
  • 分布式锁,读锁和写锁,可以保证读的高性能的同时保证并罚下数据修改的安全
  • ZAB协议数据广播和选举机制,保证服务崩溃恢复和主从数据同步问题。

三、zk集群框架介绍

1.架构图

一主多从的架构,保证高可用和数据安全

2. 集群心跳机制

监控集群节点状态,并把状态同步给子节点,leader挂掉后,从节点发起选举机制

3.集群选举机制

3.1 ZAB协议四种节点状态:
  • Looking:选举状态
  • Following:follower节点所处的状态
  • Leading:leader节点所处的状态
  • Observing:观察者节点所处的状态
3.2 集群启动时选举

启动时都是LOOKING状态,有两台开始发起选举,分两轮投票,第一轮两个节点互相投自己生成的票(myid/zxid),分别选出最大的票投到投票箱,之后更新自己的选票为投票箱最大的选票。

第二轮投票,将自己最大选票投给彼此,得到选票后再投到箱内,更新最新的选票号。

当超过一半的节点参与选举后,得到的节点作为主节点leader。

3.3 集群恢复时选举

利用心跳机制,监测到leader节点挂点,follower节点状态变为Looking选举状态,发起选举。

4.zk内部数据保存和操作

4.1 数据模型,树形结构
  • 每个节点称为znode,znode可以存放数据
  • znode节点分为持久化节点和临时节点(和client会话生命周期一样,同时会生成sessionid)
  • znode节点,利用watch机制,可设立监听,管理节点以及子节点状态信息
  • 在高并发时,提供序列节点,保证服务可用和安全,分布式锁也会用到
  • znode节点满足zk分布式锁机制,客户端可以数据同步安全
  • znode节点有acl权限机制,保证数据安全
  • znode节点delete删除提供乐观锁删除,类似cas算法

4.2 内部nio和bio机制

zk集群服务端连接client后,会建立tcp长连接,client会定时发送心跳上报给服务端,同时服务端利用nio来管理多个client的channel;同时client建立的监听,可以对多个znode监听,也是利用nio机制。bio是服务节点间内部选举的方式。

5.集群数据同步

5.1 写入事务

客户端写入数据时,如果连接的时follower,则follower会将写请求转发给leader节点处理,leader节点会生成事务编号,写入数据到本地数据文件,返回自己ack,并发送给follower节点,follower节点写入本地文件成功后会返回ack给leader,leader收集ack并过半数节点写入成功,向各节点和自己发送commit命令,节点将数据刷新到内存中。

5.2 高并发下写事务,分布式锁
  • 读锁,客户端都可以读,上读锁的前提是,之前没有上写锁。
  • 写锁,拿到写锁才可以写,之前没有任何锁。
5.2.1 羊群效应

读锁和写锁,在节点释放锁后,所有锁会激活,争抢节点使用权,资源消耗出现峰值,可能会导致集群雪崩。

5.2.2 链式监听

为了防止羊群效应,使用链式监听,后一个锁对象监听上一个锁对象,如果当前是读锁,上一个读锁已使用,则可获取;如果当前是写锁,则上一个锁对象被释放后才能使用,后面所有对象锁都不能使用。

6.集群搭建

leaderfollowerfollower
ip192.168.47.128192.168.47.129192.168.47.130
client_port218121822183
server_data_port200120022003
server_elect_port300130023003
myid123
6.1 准备环境

搭建三台虚拟机,安装好jdk环境,下载apache-zookeeper-3.8.3-bin.tar.gz并解压。

6.2 修改配置文件
  • 修改 vi /etc/profile  source /etc/profile                       
  • 修改zk包下conf/zoo.cfg文件
  • 在/usr/local/zks/zkdata下新建myid文件,每个节点从1开始编号
6.3 启动集群
  • 启动三台虚拟机节点的zk,zkServer.sh start ../conf/zoo.cfg
  • 观察节点状态 
6.4 新建客户端,连接zk集群

7. java客户端

7.1 依赖

7.2 配置zk客户端
#### ZK ####
curator.retryCount=5
curator.elapsedTimeMs=5000
curator.server=192.168.47.128:2181,192.168.47.129:2182,192.168.47.130:2183
curator.sessionTimeoutMs=600000
curator.connectTimeoutMs=5000
package com.spring.zkkafka.conf;

import lombok.Data;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(ConfigZK.ZkProperties.class)
public class ConfigZK {

    @Bean(initMethod = "start")
    CuratorFramework curatorFramework(ZkProperties properties) {
        ZkProperties zkProperties = new ZkProperties();

        return CuratorFrameworkFactory.builder()
                .connectString(properties.getServer())
                .connectionTimeoutMs(properties.getConnectTimeoutMs())
                .sessionTimeoutMs(properties.getSessionTimeoutMs())
                .retryPolicy(new RetryNTimes(properties.getRetryCount(), properties.getElapsedTimeMs()))
                .build();
    }

    @ConfigurationProperties(prefix = "curator")
    @Data
    class ZkProperties {
        private int retryCount;
        private int elapsedTimeMs;
        private String server;
        private int sessionTimeoutMs;
        private int connectTimeoutMs;
    }
}
7.3 java客户端用例
package com.spring.zkkafka;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.zookeeper.CreateMode;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

@SpringBootTest
@Slf4j
public class zkClientTest {

    @Autowired
    private CuratorFramework curator;

    @Test
    public void createNode() throws Exception {
        // 持久节点
//        String res = curator.create().forPath("/client1", "abc".getBytes());
        String res = curator.create().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath("/per", "abcd".getBytes());
        // 临时节点(和会话生命周期一样)
//        String res = curator.create().withMode(CreateMode.EPHEMERAL).forPath("/tempSeq", "abc".getBytes());
        System.out.printf("create node : %s successful.%n", res);

        System.in.read();
    }

    @Test
    public void getNodeData() throws Exception {
        byte[] bytes = curator.getData().forPath("/per0000000014");
        System.out.printf("get node data : %s successful.%n", new String(bytes));
    }

    @Test
    public void createNodeWithParent() throws Exception {
        String res = curator.create().creatingParentsIfNeeded().forPath("/ttt/per1", "aaaaa".getBytes());
        System.out.printf("create node : %s successful.%n", res);
    }

    @Test
    public void deleteNodeWithParent() throws Exception {
        curator.delete().guaranteed().deletingChildrenIfNeeded().forPath("/ttt");
        System.out.printf("delete node : %s successful.%n", "/ttt");
    }

    @Test
    public void addNodeListener() throws IOException {
        CuratorCache curatorCache = CuratorCache.builder(curator, "/test1").build();
        curatorCache.listenable().addListener(CuratorCacheListener.builder().forCreates((d) -> {
            System.out.printf("create operation, data : %s%n", d.getData() == null ? null : new String(d.getData()));
        }).forChanges((ori_d, cur_d) -> {
            System.out.printf("data change operation, ori_data : %s, cur_data : %s%n",
                    ori_d.getData() == null ? null : new String(ori_d.getData()), new String(cur_d.getData()));
        }).forDeletes((d) -> {
            System.out.printf("delete node operation : %s%n", d.getPath());
        }).build());

        curatorCache.start();

        System.in.read(); // 主线程不关闭
    }

    @Test
    public void getReadLock() throws Exception {
        InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(curator, "/rwlock");
        InterProcessMutex readLock = readWriteLock.readLock(); // 非阻塞
        System.out.println("等待获取锁");
        readLock.acquire(); // 阻塞直到获取锁
        System.out.println("获取到锁");
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            Thread.sleep(3000);
        }
        readLock.release(); // 释放
        System.out.println("释放锁");
    }

    @Test
    public void getReadLock1() throws Exception {
        InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(curator, "/rwlock");
        InterProcessMutex readLock = readWriteLock.readLock(); // 非阻塞
        System.out.println("等待获取锁");
        readLock.acquire(); // 阻塞直到获取锁
        System.out.println("获取到锁");
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            Thread.sleep(3000);
        }
        readLock.release(); // 释放
        System.out.println("释放锁");
    }

    @Test
    public void getWriteLock() throws Exception {
        InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(curator, "/rwlock");
        InterProcessMutex writeLock = readWriteLock.writeLock(); // 非阻塞
        System.out.println("等待获取锁");
        writeLock.acquire(); // 阻塞直到获取锁
        System.out.println("获取到锁");
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            Thread.sleep(3000);
        }
        writeLock.release(); // 释放
        System.out.println("释放锁");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值