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的屬性值。

 

 

 

 

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