SpringBoot整合Zookeeper客户端Curator

本文档介绍了如何在SpringBoot项目中整合Zookeeper实现分布式锁,并利用Swagger2构建API文档。首先,展示了项目依赖的引入,包括Zookeeper、Curator和Swagger的相关库。接着,配置了Zookeeper的连接信息,并定义了Swagger的基本信息。接着,通过代码展示了创建、更新、删除Zookeeper节点的方法,以及分布式锁的使用。最后,提供了异步操作、监听和缓存监听等高级功能的实现,确保了服务的高可用性和数据一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 引入依赖

  • 这里springboot使用 2.5.4 版本,zk使用 3.7.0 版本,curator使用 5.2.1 版本

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>zookeeper-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>zookeeper-demo</name>
    <description>zookeeper-demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!--需要和zookeeper服务端版本保持一致-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>5.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.2.1</version>
        </dependency>

        <!-- Swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2. 配置文件

zookeeper.host=127.0.0.1:2181

3. Zookeeper配置文件类

package com.example.zookeeperdemo.config;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ZookeeperConfig {

    @Value("${zookeeper.host}")
    private String host;

    @Bean
    public CuratorFramework curatorFramework() {
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(host)
                .sessionTimeoutMs(5000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        curatorFramework.start();
        return curatorFramework;
    }

}

4. swagger配置类

package com.example.zookeeperdemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
                .apis(RequestHandlerSelectors.basePackage("com.example")).build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().version("1.0").build();
    }

}

5. 消息返回体

  • 用来包装返回数据
package com.example.zookeeperdemo.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AjaxResult<T> {

    private Integer code;

    private String message;

    private T data;

    public static <T> AjaxResult<T> ok(T data) {
        return new AjaxResult<>(0, "success", data);
    }

    public static <T> AjaxResult<T> error(T data) {
        return new AjaxResult<>(1, "error", data);
    }

}

6. 创建订单service

  • 为后续测试分布式锁方法使用
package com.example.zookeeperdemo.service;

import org.springframework.stereotype.Component;

@Component
public class OrderService {

    public static Integer i = 0;

    public Integer createOrder() {
        return ++i;
    }

}

7. 创建controller

  • 包括对zk节点的常用操作,创建、更新、删除、监听及分布式锁
package com.example.zookeeperdemo.controller;

import com.alibaba.fastjson.JSONObject;
import com.example.zookeeperdemo.dto.AjaxResult;
import com.example.zookeeperdemo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.framework.api.CuratorListener;
import org.apache.curator.framework.api.CuratorWatcher;
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.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
@RequestMapping("/zk")
@RestController
public class ZkController {

    @Resource
    private CuratorFramework curatorFramework;

    @Resource
    private OrderService orderService;

    /**
     * 检查节点是否存在
     */
    @GetMapping("/isExistNode")
    public AjaxResult isExistNode(@RequestParam String nodeName) {
        try {
            return AjaxResult.ok(Objects.nonNull(curatorFramework.checkExists().forPath(nodeName)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 创建节点
     */
    @GetMapping("/createNode")
    public AjaxResult createNode(@RequestParam String nodeName) {
        try {
            return AjaxResult.ok(curatorFramework.create()
                    // 如果有子节点会递归创建
                    .creatingParentsIfNeeded()
                    // 设置为持久节点
                    .withMode(CreateMode.PERSISTENT).forPath(nodeName));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 更新节点数据
     */
    @GetMapping("/setNodeValue")
    public AjaxResult setNodeValue(@RequestParam String nodeName, @RequestParam String nodeValue) {
        try {
            return AjaxResult.ok(curatorFramework.setData().forPath(nodeName, nodeValue.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 创建节点并设置数据
     */
    @GetMapping("/createNodeAndValue")
    public AjaxResult createNodeAndValue(@RequestParam String nodeName, @RequestParam String nodeValue) {
        try {
            return AjaxResult.ok(curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
                    .forPath(nodeName, nodeValue.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 查询节点数据
     */
    @GetMapping("/queryNodeValue")
    public AjaxResult queryNodeValue(@RequestParam String nodeName) {
        try {
            return AjaxResult.ok(new String(curatorFramework.getData().storingStatIn(new Stat()).forPath(nodeName)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 查询节点下的所有子节点
     */
    @GetMapping("/queryNodeChild")
    public AjaxResult queryNodeChild(@RequestParam String nodeName) {
        try {
            return AjaxResult.ok(curatorFramework.getChildren().forPath(nodeName));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 异步执行
     */
    @GetMapping("/asyncCreateNode")
    public AjaxResult asyncCreateNode(@RequestParam String nodeName) {
        try {
            return AjaxResult.ok(curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
                    // 异步执行方法,可以设置回调方法
                    .inBackground((curatorFramework1, curatorEvent) ->
                            log.info(curatorEvent.getPath() + ":" + JSONObject.toJSONString(curatorEvent.getStat())))
                    .forPath(nodeName));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 递归删除
     */
    @GetMapping("/deleteNode")
    public AjaxResult deleteNode(@RequestParam String nodeName) {
        try {
            // guaranteed 用来保证如果删除失败,则会话有效期内一直尝试删除
            // deletingChildrenIfNeeded 表示如果有子节点,则递归删除
            return AjaxResult.ok(curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath(nodeName));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 单次注册监听,返回当前节点的内容信息;使用Watcher对节点进行监听,但只能监听一次;被监听的节点需要存在,否则会提示节点不存在
     */
    @GetMapping("/watchNode")
    public AjaxResult watchNode(@RequestParam String nodeName) {
        try {
            byte[] bytes = curatorFramework.getData().usingWatcher((CuratorWatcher) watchedEvent -> log
                    .info(watchedEvent.getPath() + ":" + JSONObject.toJSONString(watchedEvent))).forPath(nodeName);
            return AjaxResult.ok(new String(bytes));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.error(null);
    }

    /**
     * 单次监听异步操作,即调用inBackground()方法会异步获得监听,同步操作则不会触发监听事件
     */
    @GetMapping("/asyncListener")
    public AjaxResult asyncListener(@RequestParam String nodeName) {
        try {
            CuratorListener curatorListener = new CuratorListener() {
                @Override
                public void eventReceived(CuratorFramework curatorFramework, CuratorEvent curatorEvent)
                        throws Exception {
                    log.info(curatorEvent.getPath() + ":" + JSONObject.toJSONString(curatorEvent));
                }
            };
            curatorFramework.getCuratorListenable().addListener(curatorListener);
            log.info(new String(curatorFramework.getData().forPath(nodeName)));
            log.info(JSONObject.toJSONString(curatorFramework.setData().forPath(nodeName, "123".getBytes())));
            // 异步操作
            curatorFramework.delete().inBackground().forPath(nodeName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return AjaxResult.ok(null);
    }

    /**
     * 反复注册监听,Cache事件监听可以理解为一个本地缓存视图与远程Zookeeper视图的对比过程,可以监听节点的创建,修改和删除
     */
    @GetMapping("/nodeCache")
    public AjaxResult nodeCache(@RequestParam String nodeName) {
        CuratorCache curatorCache = CuratorCache.build(curatorFramework, nodeName);
        CuratorCacheListener listener = CuratorCacheListener.builder().forNodeCache(
                        () -> log.info("nodeCache监听 " + nodeName + ":" + new String(curatorFramework.getData().forPath(nodeName))))
                .build();
        curatorCache.listenable().addListener(listener);
        curatorCache.start();
        return AjaxResult.ok(null);
    }

    /**
     * 子节点监听
     */
    @GetMapping("/childNodeCache")
    public AjaxResult childNodeCache(@RequestParam String nodeName) {
        CuratorCache curatorCache = CuratorCache.build(curatorFramework, nodeName);
        CuratorCacheListener childNodeCache = CuratorCacheListener.builder()
                .forPathChildrenCache(nodeName, curatorFramework, (curatorFramework1, pathChildrenCacheEvent) -> log
                        .info("childNodeCache监听 " + JSONObject.toJSONString(pathChildrenCacheEvent.getType())))
                .build();
        curatorCache.listenable().addListener(childNodeCache);
        curatorCache.start();
        return AjaxResult.ok(null);
    }

    /**
     * 分布式锁
     */
    @GetMapping("/lock")
    public AjaxResult lock(@RequestParam Integer count) throws Exception {
        String nodeName = "/lock";
        if (Objects.isNull(curatorFramework.checkExists().forPath(nodeName))) {
            curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(nodeName);
        }
        InterProcessMutex lock = new InterProcessMutex(curatorFramework, nodeName);

        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            list.add(i);
        }
        List<Integer> collect =
            list.parallelStream().map(integer -> createOrder(lock)).sorted().collect(Collectors.toList());
        for (Integer str : collect) {
            log.info(str.toString());
        }
        return AjaxResult.ok(null);
    }

    public Integer createOrder(InterProcessMutex lock) {
        try {
            lock.acquire();
            Integer order = orderService.createOrder();
            lock.release();
            return order;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

8. 分布式锁使用

  • 分别使用InterProcessMutex的acquire和release方法,来获取和释放锁

  • 不使用分布式锁的结果,会有重复的值

在这里插入图片描述

  • 使用锁,调用100次,没有重复的值

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fisher3652

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值