Springboot2.x+Zookeeper实现分布式配置中心

市面上的配置中心产品

说到配置中心,大家应该也了解目前市面上用的较多的配置中心:

百度的Disconf、Spring Cloud Config、携程的Apollo、阿里的Nacos等。

由于Disconf不再维护,以下对Spring Cloud Config、Apollo、Nacos的功能点做的对比

640?wx_fmt=png

640?wx_fmt=png

 

大家平时在开发时,项目中会用到很多配置,有些配置还和环境有关,开发、测试和生产的配置也不一样;

或者在做某一个功能时,上线后配置有可能更改,即使我们将变量写在配置中,但还是需要重新发布服务,

所以,如果有个配置中心,当更新某个配置时,可以实时生效,岂不快哉!

 

如果做一个分布式配置中心的产品,所需要实现的功能很多,如配置存储、配置实时推送、权限校验、版本管理&回滚、配置格式校验等。

本文介绍使用Springboot2.x+Zookeeper实现简易的分布式配置中心,使用Zookeeper存储配置,本地缓存配置,监听zookeeper的配置更新,本地实时更新。

 

1、引入依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <zk.curator.version>2.12.0</zk.curator.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>${zk.curator.version}</version>
        </dependency>


    </dependencies>

 

2、配置中心类

@Component
public class PropertiesCenter {

    /**
     * 配置中心
     */
    Properties properties = new Properties();
    CuratorFramework client = null;
    TreeCache treeCache = null;

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

    private final String CONFIG_NAME = "/config-center";

    public PropertiesCenter() {
    }

    private void init() {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        client = CuratorFrameworkFactory.newClient(zkUrl, retryPolicy);
        treeCache = new TreeCache(client, CONFIG_NAME);
    }

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

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

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

            // 从zk中获取配置放入本地配置中
            Stat stat = client.checkExists().forPath(CONFIG_NAME);
            if(stat == null) {
                client.create().forPath(CONFIG_NAME);
            }
            List<String> configList = client.getChildren().forPath(CONFIG_NAME);
            for (String configName : configList) {
                byte[] value = client.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();
        }
    }
}

 

3、测试的set/get接口

@RestController
public class PropertiesController {

    @Autowired
    PropertiesCenter propertiesCenter;

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

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

 

4、启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConfigCenterApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigCenterApplication.class, args);
    }
}

 

5、配置application.properties

增加如下内容:

server.port=8080
zookeeper.url=127.0.0.1:2181

 

6、首先启动本地的zookeeper,再启动ConfigCenterApplication

启动成功后,测试:

浏览器访问:http://127.0.0.1:8080/set/book/java, 设置book的值为java,

再访问 http://127.0.0.1:8080/get/book,获取book的属性值。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章