Zookeeper实践----shock wave 2

208 阅读6分钟

环境

  • zookeeper
  • docker
  • springcloud

安装

#搜索镜像
docker search zookeeper
#拉取镜像
docker pull zookeeper
#运行镜像
docker run -itd -p 2181:2181 2e2

常用实践

配置yml

server:
  port: 8602
spring:
  application:
    name: client2-service
  cloud:
    nacos:
      #192.168.82.83:8848
      server-addr: 127.0.0.1:8848
    zookeeper:
      connect-string: 127.0.0.1:2181
      session-timeout: 2000

基础应用

引入依赖
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.2</version>
</dependency>
ZookeeperConfig
package org.example.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.CountDownLatch;

/**
 * @author shock wave 2
 * @version 1.0
 * @description: TODO
 * @date 2021/8/10 16:34
 */
@Configuration
@Slf4j
public class ZookeeperConfig {
    @Value("${spring.cloud.zookeeper.connect-string}")
    private String connectString;

    @Value("${spring.cloud.zookeeper.session-timeout}")
    private int sessionTimeout;
    public String getConnectString() {
        return connectString;
    }

    public void setConnectString(String connectString) {
        this.connectString = connectString;
    }

    public int getSessionTimeout() {
        return sessionTimeout;
    }

    public void setSessionTimeout(int sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
    }

    @Bean(name = "zkClient")
    public ZooKeeper zkClient() {
        ZooKeeper zooKeeper = null;
        try {
            final CountDownLatch countDownLatch = new CountDownLatch(1);
            zooKeeper = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    // 如果收到了服务端的响应事件,说明连接成功
                    if (Event.KeeperState.SyncConnected == event.getState()) {
                        countDownLatch.countDown();
                    }
                }
            });
            countDownLatch.await();
            log.info("  初始化ZooKeeper连接状态: {}",zooKeeper.getState());
        } catch (Exception e) {
            log.error(" 初始化Zookeeper连接状态异常: {}",e.getMessage());
        }
        return  zooKeeper;
    }
}
ZoopeekerController
package org.example.controller;

import org.apache.commons.lang.NullArgumentException;
import org.example.config.CustomWatcher;
import org.example.entity.ZoopeekerNode;
import org.example.utils.Result;
import org.example.utils.ZkUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author shock wave 2
 * @version 1.0
 * @description: TODO
 * @date 2021/8/10 16:33
 */
@RestController
@RequestMapping("zoopeeker")
public class ZoopeekerController {
    @Autowired
    private ZkUtil zkUtil;
    @PostMapping("createNode")
    public void createNode(@RequestBody ZoopeekerNode zoopeekerNode) {
        switch (zoopeekerNode.getType()){
            case "per":
                zkUtil.createPerNode(zoopeekerNode.getPath(),zoopeekerNode.getData());
                break;
            case "tmp":
                zkUtil.createTmpNode(zoopeekerNode.getPath(),zoopeekerNode.getData());
                break;
            case  "custom":
                zkUtil.createNode(zoopeekerNode.getPath(),zoopeekerNode.getData(),zoopeekerNode.getAcl(),zoopeekerNode.getCreateMode());
                break;
            default:
                throw new NullArgumentException("缺少主要参数");
        }
    }
    @GetMapping("getNode")
    public Result getNode(@RequestParam String path) {
        CustomWatcher customWatcher = new CustomWatcher();
        return Result.success(zkUtil.getData(path,customWatcher));
    }
    @PostMapping("updateNode")
    public Result updateNode(@RequestBody ZoopeekerNode zoopeekerNode) {
        return Result.success(zkUtil.updateNode(zoopeekerNode.getPath(),zoopeekerNode.getData()));
    }
}
ZkUtil
package org.example.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author shock wave 2
 * @version 1.0
 * @description: TODO
 * @date 2021/8/10 17:18
 */
@Slf4j
@Component
public class ZkUtil {
    @Autowired
    private org.apache.zookeeper.ZooKeeper zkClient;
    /**
     * 创建持久化节点
     * -- 客户端断开连接后,节点数据持久化在磁盘上,不会被删除。
     *
     * @param path 路径
     * @param data 数据
     */
    public boolean createPerNode(String path, String data) {
        try {
            // 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
            zkClient.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            return true;
        } catch (Exception e) {
            log.error("创建持久化节点异常,路径: {}, 数据: {}, 异常: {}", path, data, e);
            return false;
        }
    }

    /**
     * 创建临时节点
     * -- 客户端断开连接后,节点将被删除。
     *
     * @param path 路径
     * @param data 数据
     */
    public boolean createTmpNode(String path, String data) {
        try {
            // 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
            zkClient.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            return true;
        } catch (Exception e) {
            log.error("创建临时节点异常,路径: {}, 数据: {}, 异常: {}", path, data, e);
            return false;
        }
    }

    /**
     * 创建自定义节点
     *
     * @param path       路径
     * @param data       数据
     * @param acl        节点权限
     * @param createMode 节点类型
     */
    public boolean createNode(String path, String data, List<ACL> acl, CreateMode createMode) {
        try {
            // 参数1:要创建的节点的路径; 参数2:节点数据 ; 参数3:节点权限 ;参数4:节点的类型
            zkClient.create(path, data.getBytes(), acl, createMode);
            return true;
        } catch (Exception e) {
            log.error("创建节点异常,路径: {}, 数据: {}, 异常: {}", path, data, e);
            return false;
        }
    }


    /**
     * 修改节点
     *
     * @param path 路径
     * @param data 数据
     */
    public boolean updateNode(String path, String data) {
        try {
            // zk的数据版本是从0开始计数的。如果客户端传入的是-1,则表示zk服务器需要基于最新的数据进行更新。如果对zk的数据节点的更新操作没有原子性要求则可以使用-1.
            // version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
            zkClient.setData(path, data.getBytes(), -1);
            return true;
        } catch (Exception e) {
            log.error("修改节点异常,路径: {}, 数据: {}, 异常: {}", path, data, e);
            return false;
        }
    }

    /**
     * 删除节点
     *
     * @param path 路径
     */
    public boolean deleteNode(String path) {
        try {
            // version参数指定要更新的数据的版本, 如果version和真实的版本不同, 更新操作将失败. 指定version为-1则忽略版本检查
            zkClient.delete(path, -1);
            return true;
        } catch (Exception e) {
            log.error("删除节点异常,路径: {}, 异常: {}", path, e);
            return false;
        }
    }

    /**
     * 判断指定节点是否存在
     *
     * @param path      路径
     * @param needWatch 指定是否复用zookeeper中默认的Watcher
     * @return 结果
     */
    public Stat exists(String path, boolean needWatch) {
        try {
            return zkClient.exists(path, needWatch);
        } catch (Exception e) {
            log.error("判断指定节点是否存在异常,路径: {}, 异常: {}", path, e);
            return null;
        }
    }

    /**
     * 检测结点是否存在 并设置监听事件
     * 三种监听类型: 创建,删除,更新
     *
     * @param path    路径
     * @param watcher 传入指定的监听类
     */
    public Stat exists(String path, Watcher watcher) {
        try {
            return zkClient.exists(path, watcher);
        } catch (Exception e) {
            log.error("判断指定节点是否存在异常,路径: {}, 异常: {}", path, e);
            return null;
        }
    }


    /**
     * 获取当前节点的子节点(不包含孙子节点)
     *
     * @param path 父节点path
     */
    public List<String> getChildren(String path) throws KeeperException, InterruptedException {
        List<String> list = zkClient.getChildren(path, false);
        return list;
    }

    /**
     * 获取指定节点的值
     *
     * @param path 路径
     */
    public String getData(String path, Watcher watcher) {
        try {
            Stat stat = new Stat();
            byte[] bytes = zkClient.getData(path, watcher, stat);
            return new String(bytes);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

curator分布式锁

依赖
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.2.0</version>
</dependency>
CuratorFrameworkConfig
package org.example.config;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.api.CuratorEventType;
import org.apache.curator.framework.api.CuratorListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.WatchedEvent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author shock wave 2
 * @version 1.0
 * @description: TODO
 * @date 2021/8/12 17:32
 */
@Configuration
public class CuratorFrameworkConfig {
    @Value("${spring.cloud.zookeeper.connect-string}")
    private String connectString;
    @Bean
    public CuratorFramework curatorFramework(){
        // ExponentialBackoffRetry是种重连策略,每次重连的间隔会越来越长,1000毫秒是初始化的间隔时间,3代表尝试重连次数。
        ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
        // 创建client
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(connectString, retry);
        // 添加watched 监听器
        curatorFramework.getCuratorListenable().addListener(new MyCuratorListener(){

        });
        curatorFramework.start();
        return curatorFramework;
    }


    public class MyCuratorListener implements CuratorListener {
        @Override
        public void eventReceived(CuratorFramework client, CuratorEvent event) throws Exception {
            CuratorEventType type = event.getType();
            if(type == CuratorEventType.WATCHED){
                WatchedEvent watchedEvent = event.getWatchedEvent();
                String path = watchedEvent.getPath();
                System.out.println(watchedEvent.getType()+" -- "+ path);
                // 重新设置改节点监听
                if(null != path){
                    client.checkExists().watched().forPath(path);
                }
            }
        }
    }
}
api
@GetMapping("curatorTest")
public Result curatorTest() {
    //大概原理是,使用zookeeper生成有序临时结点,每次判断是否是序号最小的结点,是的话获取锁
    //不是的话,需要获取当前序号的上一个结点
    String lockPath = "/lock/test/";
    String lockName = lockPath + UUID.randomUUID().toString();
    //TODO 获取分布式锁
    InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex (curatorFramework, lockName);
    try{
        //获取锁资源
        boolean flag = lock.acquire(10, TimeUnit.HOURS);
        if(flag){
            log.info("线程:{},获取到了锁",Thread.currentThread().getName());
            //TODO 获得锁之后可以进行相应的处理  睡一会
            Thread.sleep(500);
            log.info("======获得锁后进行相应的操作======" + Thread.currentThread().getName());
        }
    }catch (Exception e){
        log.info("错误信息:{}",e.getMessage());
    }finally {
        try {
            lock.release();
            log.info("=========lockName:{}==============={}释放了锁",lockName,Thread.currentThread().getName());
        } catch (Exception e) {
            log.info("错误信息:{}",e.getMessage());
        }
    }
    return Result.success();
}

zookeeper作为注册中心

  • zookeeper可以充当一个服务注册表(Service Registry),让多个服务提供者形成一个集群,让服务消费者通过服务注册表获取具体的服务访问地址(IP+端口)去访问具体的服务提供者。
  • zookeeper提供了“心跳检测”功能,它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其剔除。
  • 服务消费者会去监听相应路径(/HelloWorldService/1.0.0),一旦路径上的数据有任务变化(增加或减少),zookeeper都会通知服务消费方、服务提供者地址列表已经发生改变,从而进行更新。
依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
启动类
package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @author shock wave 2
 * @version 1.0
 * @description: TODO
 * @date 2021/8/9 17:14
 */
@SpringBootApplication
//会自动生成相应的结点
@EnableDiscoveryClient
public class Client2ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(Client2ServerApplication.class, args);
    }
}

结束