Spring Boot整合Zookeeper实现配置中心

简介

使用背景

说到配置中心,目前市面上用的较多的配置中心都广为人知,比如百度的Disconf、Spring Cloud Config、携程的Apollo、阿里的Nacos等。由于项目组一直是使用的zookeeper作为配置中心,所以来学习使用。

实现原理

在Zookeeper建立一个根节点,比如/CONFIG,代表某个配置文件。将配置文件中的信息作为根节点的子节点存储,比如配置项timeout=3000,在Zookeeper中展现为:/CONFIG/timeout
,节点内容是3000。然后让所有使用到该配置信息的应用机器集成Zookeeper并监控/CONFIG的状态,一旦配置信息也就是子节点发生变化,每台应用机器就会收到ZK的通知,然后从ZK中获取新的配置信息应用到系统中。
实现原理细节可参考:https://cloud.tencent.com/developer/news/365803

Spring Boot整合Zookeeper

引入依赖

		 <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.3.0</version>
        </dependency>

Curator是Apache软件基金会下的一个开源框架,目前是Apache下的顶级项目。Curator是ZooKeeper的一个Java客户端库,包括一个高层API框架和一些实用程序,使得开发人员在使用ZooKeeper时更容易,更可靠。
参考:https://blog.csdn.net/hzygcs/article/details/88171620

修改配置application.properties

由于只是简单使用,配置连接即可

zookeeper.url=192.168.1.106:2181

定义配置类

ZookeeperConfig.java

@Configuration
public class ZookeeperConfig {

    Properties properties = new Properties();
    //可以理解curatorFramework为客户端,基本操作都由它完成
    CuratorFramework curatorFramework = null;
    TreeCache treeCache = null;

    @Value("${zookeeper.url}")
    private String zkUrl;
    //定义顶级
    private final String CONFIG_NAME = "/zookeeper";

    //初始化
    private void init() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        curatorFramework = CuratorFrameworkFactory.newClient(zkUrl, retryPolicy);
        treeCache = new TreeCache(curatorFramework, CONFIG_NAME);
    }

    /**
     * 设置属性
     * @param key
     * @param value
     * @throws Exception
     */
    public void setProperties(String key, String value) throws Exception {
        String propertiesKey = CONFIG_NAME + "/" + key;
        Stat stat = curatorFramework.checkExists().forPath(propertiesKey);
        if(stat == null) {
            curatorFramework.create().forPath(propertiesKey);
        }
        curatorFramework.setData().forPath(propertiesKey, value.getBytes());
    }

    /**
     * 获取属性
     * @param key
     * @return
     */
    public String getProperties(String key) {
        return properties.getProperty(key);
    }

    @PostConstruct
    public void loadProperties() {
        try {
            init();
            curatorFramework.start();
            treeCache.start();

            // 从zk中获取配置放入本地配置中
            Stat stat = curatorFramework.checkExists().forPath(CONFIG_NAME);
            if(stat == null) {
                curatorFramework.create().forPath(CONFIG_NAME);
            }
            List<String> configList = curatorFramework.getChildren().forPath(CONFIG_NAME);
            for (String configName : configList) {
                byte[] value = curatorFramework.getData().forPath(CONFIG_NAME + "/" + configName);
                properties.setProperty(configName, new String(value));
            }

            // 监听属性值变更
            treeCache.getListenable().addListener(new TreeCacheListener() {
                @Override
                public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
                    if (Objects.equals(treeCacheEvent.getType(), TreeCacheEvent.Type.NODE_ADDED) ||
                            Objects.equals(treeCacheEvent.getType(), TreeCacheEvent.Type.NODE_UPDATED)) {
                        String updateKey = treeCacheEvent.getData().getPath().replace(CONFIG_NAME + "/", "");
                        properties.setProperty(updateKey, new String(treeCacheEvent.getData().getData()));
                        System.out.println("数据更新: "+treeCacheEvent.getType()+", key:"+updateKey+",value:"+new String(treeCacheEvent.getData().getData()));
                    }
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

配置测试接口

ZkController.java

@RestController
public class ZkController {

    @Autowired
    private ZookeeperConfig zookeeperConfig;


    @GetMapping("get/{key}")
    public String getProperties(@PathVariable String key) {
        return zookeeperConfig.getProperties(key);
    }

    @GetMapping("set/{key}/{value}")
    public String setProperties(@PathVariable String key, @PathVariable String value) throws Exception {
        zookeeperConfig.setProperties(key, value);
        return "配置成功";
    }
}

验证

zookeeper节点情况如下:
在这里插入图片描述

浏览器中输入:http://localhost:8080/get/hehe 可以获取到,使用工具对其他两个node进行更新,程序控制台也可以查看到更新数据信息:

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

当验证set的时候,程序报错:
在这里插入图片描述

org.apache.zookeeper.KeeperException$UnimplementedException: KeeperErrorCode = Unimplemented for /zookeeper/123
	at org.apache.zookeeper.KeeperException.create(KeeperException.java:106) ~[zookeeper-3.5.7.jar:3.5.7]
	at org.apache.zookeeper.KeeperException.create(KeeperException.java:54) ~[zookeeper-3.5.7.jar:3.5.7]
	at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:1637) ~[zookeeper-3.5.7.jar:3.5.7]
	at org.apache.curator.framework.imps.CreateBuilderImpl$17.call(CreateBuilderImpl.java:1180) ~[curator-framework-4.3.0.jar:4.3.0]
	at org.apache.curator.framework.imps.CreateBuilderImpl$17.call(CreateBuilderImpl.java:1156) ~[curator-framework-4.3.0.jar:4.3.0]
	at org.apache.curator.connection.StandardConnectionHandlingPolicy.callWithRetry(StandardConnectionHandlingPolicy.java:67) ~[curator-client-4.3.0.jar:na]
	at org.apache.curator.RetryLoop.callWithRetry(RetryLoop.java:81) ~[curator-client-4.3.0.jar:na]
	at org.apache.curator.framework.imps.CreateBuilderImpl.pathInForeground(CreateBuilderImpl.java:1153) ~[curator-framework-4.3.0.jar:4.3.0]
	at org.apache.curator.framework.imps.CreateBuilderImpl.protectedPathInForeground(CreateBuilderImpl.java:607) ~[curator-framework-4.3.0.jar:4.3.0]
	at org.apache.curator.framework.imps.CreateBuilderImpl.forPath(CreateBuilderImpl.java:597) ~[curator-framework-4.3.0.jar:4.3.0]
	at org.apache.curator.framework.imps.CreateBuilderImpl.forPath(CreateBuilderImpl.java:575) ~[curator-framework-4.3.0.jar:4.3.0]
	at org.apache.curator.framework.imps.CreateBuilderImpl.forPath(CreateBuilderImpl.java:51) ~[curator-framework-4.3.0.jar:4.3.0]
	at com.springboot.springbootzookeeper.config.ZookeeperConfig.setProperties(ZookeeperConfig.java:55) ~[classes/:na]
	at com.springboot.springbootzookeeper.controller.ZkController.setProperties(ZkController.java:28) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_101]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_101]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_101]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_101]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_101]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_101]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.35.jar:9.0.35]
	at java.lang.Thread.run(Thread.java:745) [na:1.8.0_101]

2020-06-29 16:57:18.173  INFO 13664 --- [168.1.106:2181)] org.apache.zookeeper.ClientCnxn          : Unable to read additional data from server sessionid 0x10000654e3b001a, likely server has closed socket, closing socket connection and attempting reconnect
2020-06-29 16:57:18.273  INFO 13664 --- [ain-EventThread] o.a.c.f.state.ConnectionStateManager     : State change: SUSPENDED

感谢
https://blog.csdn.net/forget_me_not1991/article/details/80902055
https://www.jianshu.com/p/57ac51e9987f

将curator-recipes版本调整为2.8.0后,可以将节点添加成功。
在这里插入图片描述

2020-06-29 17:07:11.622  INFO 14628 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-06-29 17:07:11.625  INFO 14628 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 3 ms
数据更新: NODE_ADDED, key:123,value:192.168.137.1
数据更新: NODE_UPDATED, key:123,value:123

在这里插入图片描述

热门文章

暂无图片
编程学习 ·

命令模式

菜鸟教程中代理模式总结)1.定义:将请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令 的合适的对象,并把该命令传给相应的对象,该对象执行命令。 2.主要解决:行为请求者与行为实现者通常是一种紧耦合的关系,为了消除这种耦合关系 3.何时使用:命…
暂无图片
编程学习 ·

人脸活体离线识别摇头点头张嘴眨眼动作活体算法源码解析

活体识别要求最近我们公司的项目需要做一个活体识别的功能,要求如下: 1.离线识别,本地识别活体,这样速度快1s内完成。需要识别出人脸,并判断是否在摇头 ,点头,张嘴,眨眼等动作,进而判断是否活体,准确率要求90%即可,可以去破解相信没有任何一个项目能完全规避的,哪怕…
暂无图片
编程学习 ·

c++ string操作

c++ string操作 #include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std;void test01() {///*string& operator=(const char* s)* string& operator=(const string &s)* string& operato…
暂无图片
编程学习 ·

智能电视良心分享:沉浸式音画享受旗舰级品质做工

上周,我家工作了五年的电视结束了它的“职业生涯”。面对市面上林林总总的智能电视品牌,我在纠结良久之后,最终将目光锁定在荣耀智慧屏X1身上,趁着618打折,入手了一台。不得不说这款电视最近挺火,但是性能咋样,还得亲身体验过才有发言权。买电视我最关心还是音画质。很多…
暂无图片
编程学习 ·

springboot aop 切到service层,不生效

今天发现一个问题,使用aop切到service层方法上,idea会有切成功的标志,编译也不报错,但就是不生效。研究了下发现,是因为我切的方法被同一个service中的其他方法调用,这样的话就不生效了,暂不清楚原因,解决方法时切到调用它的方法上,这只是切点不生效的一种情况,希望能…
暂无图片
郑州普通话 ·

学习笔记六——循环神经网络

一、RNN 前馈神经网络&#xff1a;信息往一个方向流动。包括MLP和CNN 循环神经网络&#xff1a;信息循环流动&#xff0c;网络隐含层输出又作为自身输入&#xff0c;包括RNN、LSTM、GAN等。 RNN模型结构如下图所示&#xff1a; 展开之后相当于堆叠多个共享隐含层参数的前馈…
暂无图片
代理记账 ·

学习笔记六——循环神经网络

一、RNN 前馈神经网络&#xff1a;信息往一个方向流动。包括MLP和CNN 循环神经网络&#xff1a;信息循环流动&#xff0c;网络隐含层输出又作为自身输入&#xff0c;包括RNN、LSTM、GAN等。 RNN模型结构如下图所示&#xff1a; 展开之后相当于堆叠多个共享隐含层参数的前馈…
暂无图片
cgfy ·

学习笔记六——循环神经网络

一、RNN 前馈神经网络&#xff1a;信息往一个方向流动。包括MLP和CNN 循环神经网络&#xff1a;信息循环流动&#xff0c;网络隐含层输出又作为自身输入&#xff0c;包括RNN、LSTM、GAN等。 RNN模型结构如下图所示&#xff1a; 展开之后相当于堆叠多个共享隐含层参数的前馈…
暂无图片
coreui ·

Heap Sort 讲解

Heap Sort sorts a group of unordered elements using the Heap data structure. The sorting algorithm using a Min Heap is as follows: Heapify all elements into a Min HeapRecord and delete the top elementPut to top element into an array T that stores all so
暂无图片
未来博客 ·

Heap Sort 讲解

Heap Sort sorts a group of unordered elements using the Heap data structure. The sorting algorithm using a Min Heap is as follows: Heapify all elements into a Min HeapRecord and delete the top elementPut to top element into an array T that stores all so
暂无图片
建站日记 ·

[react] 你觉得react上手快不快?它有哪些限制?

[react] 你觉得react上手快不快&#xff1f;它有哪些限制&#xff1f; 相对vue来说不快。 限制 需要学习JSX需要工程化的配置需要对原生JavaScript有相当的掌握react只是一个UI层面的库&#xff0c;像vue内置了动画处理、keep-alive等功能&#xff0c;react则需要去找第三方库…
暂无图片
mfbz ·

学习笔记六——循环神经网络

一、RNN 前馈神经网络&#xff1a;信息往一个方向流动。包括MLP和CNN 循环神经网络&#xff1a;信息循环流动&#xff0c;网络隐含层输出又作为自身输入&#xff0c;包括RNN、LSTM、GAN等。 RNN模型结构如下图所示&#xff1a; 展开之后相当于堆叠多个共享隐含层参数的前馈…
暂无图片
mfbz ·

AOV网是否存在回路-拓扑排序-C++

拓扑排序是对测试AOV网是否存在回路的方法&#xff01; 拓扑排序的过程中&#xff0c;由于需要查找所有以某顶点为尾的弧&#xff0c;即找到该顶点的所有出边&#xff0c;故图要采用邻接表的存储方式。但拓扑排序较邻接表的存储方式有一点不同&#xff0c;由于要查找入度为0的点…
暂无图片
珊珊日记 ·

学习笔记六——循环神经网络

一、RNN 前馈神经网络&#xff1a;信息往一个方向流动。包括MLP和CNN 循环神经网络&#xff1a;信息循环流动&#xff0c;网络隐含层输出又作为自身输入&#xff0c;包括RNN、LSTM、GAN等。 RNN模型结构如下图所示&#xff1a; 展开之后相当于堆叠多个共享隐含层参数的前馈…
暂无图片
珊珊日记 ·

AOV网是否存在回路-拓扑排序-C++

拓扑排序是对测试AOV网是否存在回路的方法&#xff01; 拓扑排序的过程中&#xff0c;由于需要查找所有以某顶点为尾的弧&#xff0c;即找到该顶点的所有出边&#xff0c;故图要采用邻接表的存储方式。但拓扑排序较邻接表的存储方式有一点不同&#xff0c;由于要查找入度为0的点…