自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何實(shí)現(xiàn)一個(gè)分布式配置中心?

開(kāi)發(fā) 架構(gòu)
配置中心在大廠系統(tǒng)開(kāi)發(fā)中是一個(gè)非常常用的功能,它的核心功能在于不需要上線系統(tǒng)的情況下,改變系統(tǒng)中對(duì)象或者屬性的值。是屬性的值,也就是你在通過(guò)類獲取某個(gè)屬性,判斷;功能開(kāi)關(guān)、渠道地址、人群名單、息費(fèi)費(fèi)率、切量占比等等,這些可能隨時(shí)動(dòng)態(tài)調(diào)整的值,都是通過(guò)配置中心實(shí)現(xiàn)的。

本文的宗旨在于通過(guò)簡(jiǎn)單干凈實(shí)踐的方式,向讀者介紹 Zookeeper 的安裝配置,學(xué)習(xí) SpringBoot 整合使用,以及基于 Zookeeper 開(kāi)發(fā)一個(gè)簡(jiǎn)單的配置中心功能內(nèi)核。通過(guò)這樣的實(shí)踐方式,讓讀者入門和掌握 Zookeeper 以應(yīng)對(duì)后續(xù)需要此技術(shù)棧的相關(guān)開(kāi)發(fā)項(xiàng)目。

本文的重點(diǎn)是基于 Zookeeper 實(shí)現(xiàn)的配置中心,那配置中心是啥呢?

配置中心在大廠系統(tǒng)開(kāi)發(fā)中是一個(gè)非常常用的功能,它的核心功能在于不需要上線系統(tǒng)的情況下,改變系統(tǒng)中對(duì)象或者屬性的值。是屬性的值,也就是你在通過(guò)類獲取某個(gè)屬性,判斷;功能開(kāi)關(guān)、渠道地址、人群名單、息費(fèi)費(fèi)率、切量占比等等,這些可能隨時(shí)動(dòng)態(tài)調(diào)整的值,都是通過(guò)配置中心實(shí)現(xiàn)的。所以在本章節(jié)的案例中,小傅哥基于 Zookeeper 組件的功能特性,來(lái)設(shè)計(jì)這樣一個(gè)配置中心,方便大家學(xué)習(xí)。

本文涉及的工程:

  • xfg-dev-tech-zookeeper:https://gitcode.net/KnowledgePlanet/road-map/xfg-dev-tech-connection-pool - docs/dev-ops 提供了 Zookeeper 安裝腳本。

一、配置中心

Zookeeper 有什么特性,動(dòng)態(tài)配置中心怎么做?

技術(shù)是支撐解決方案實(shí)現(xiàn)的,有了各個(gè)技術(shù)棧組件的自身特點(diǎn),才好實(shí)現(xiàn)出我們所需的各類功能。那么這樣的一個(gè)能讓,各個(gè)服務(wù)都可以動(dòng)態(tài)變更配置的配置中心,就要用到 Zookeeper 的節(jié)點(diǎn)監(jiān)聽(tīng)和節(jié)點(diǎn)值的變化來(lái)動(dòng)態(tài)設(shè)置 Java 類中屬性的變化。如圖:

圖片圖片

  • 首先,我們需要定義出一個(gè) Zookeeper 監(jiān)聽(tīng)的配置路徑,一般這個(gè)路徑在配置中心中是申請(qǐng)的系統(tǒng)使用地址,以確保值的唯一。
  • 之后,每個(gè)類對(duì)應(yīng)的屬性,需要映射出一個(gè)監(jiān)聽(tīng)的節(jié)點(diǎn)。比如;Zookeeper 監(jiān)聽(tīng)了 /xfg-dev-tech/config 那么類中 a 屬性可以是 /xfg-dev-tech/config/a 這對(duì)這個(gè)路徑設(shè)置的值,就可以被監(jiān)聽(tīng)拿到了。
  • 最后,把獲取到的監(jiān)聽(tīng)值,通過(guò) Java 反射操作,把值設(shè)置到對(duì)應(yīng)的屬性上。這樣在 SpringBoot 應(yīng)用程序中,使用某個(gè)類的屬性值的時(shí)候,就可以動(dòng)態(tài)的獲取到變化的屬性值了。

二、環(huán)境配置

在安裝執(zhí)行 docker-compose.yml 腳本之前,你需要先在本地安裝 docker 之后 IntelliJ IDEA 打開(kāi) docker-compose.yml 文件,如圖操作即可安裝。

圖片圖片

圖片圖片

  • 另外,如果你是在服務(wù)器上安裝,則需要執(zhí)行 docker-compose -f docker-compose.yml up -d 并且是你已經(jīng)安裝了 Docker-Compose 包?!?這些內(nèi)容在小傅哥的《Java 簡(jiǎn)明教程》中都有講解,可以進(jìn)入學(xué)習(xí)。

三、基本使用

連接腳本:

docker exec -it zookeeper bash
zkCli.sh -server IP(替換為你自己的):2181

常用命令:

1. 創(chuàng)建節(jié)點(diǎn):create /path data
2. 創(chuàng)建臨時(shí)節(jié)點(diǎn):create -e /path data
3. 創(chuàng)建順序節(jié)點(diǎn):create -s /path data
4. 創(chuàng)建臨時(shí)順序節(jié)點(diǎn):create -e -s /path data
5. 獲取節(jié)點(diǎn)數(shù)據(jù):get /path
6. 獲取節(jié)點(diǎn)子節(jié)點(diǎn)列表:ls /path
7. 更新節(jié)點(diǎn)數(shù)據(jù):set /path data
8. 刪除節(jié)點(diǎn):delete /path
9. 刪除節(jié)點(diǎn)及其子節(jié)點(diǎn):deleteall /path
10. 監(jiān)聽(tīng)節(jié)點(diǎn)變化:get -w /path
11. 查看節(jié)點(diǎn)狀態(tài):stat /path
12. 查看節(jié)點(diǎn)ACL權(quán)限:getAcl /path
13. 設(shè)置節(jié)點(diǎn)ACL權(quán)限:setAcl /path acl
14. 查看節(jié)點(diǎn)子節(jié)點(diǎn)數(shù)量:count /path
15. 查看節(jié)點(diǎn)子節(jié)點(diǎn)數(shù)量并監(jiān)聽(tīng)變化:count -w /path
root@4365b68d50d6:/apache-zookeeper-3.9.0-bin# ls
bin  conf  docs  lib  LICENSE.txt  NOTICE.txt  README.md  README_packaging.md
root@4365b68d50d6:/apache-zookeeper-3.9.0-bin# zkCli.sh -server 10.253.6.71:2181

[zk: 192.168.1.101:2181(CONNECTED) 1] ls /xfg-dev-tech
[config, configdowngradeSwitch]
[zk: 192.168.1.101:2181(CONNECTED) 2]

執(zhí)行完鏈接 Zookeeper 以后,就可以執(zhí)行這些常用命令了。你也可以嘗試著練習(xí)下這些命令。

四、功能實(shí)現(xiàn)

1. 工程結(jié)構(gòu)

圖片圖片

工程結(jié)構(gòu)分為2個(gè)部分:

  • app 啟動(dòng)層的 config 包下,用于提供 Zookeeper 服務(wù)的啟動(dòng)配置。以及小傅哥在這里新添加的功能 DCCValue 配置中心模塊。
  • trigger 是觸發(fā)器,這里吧 http 請(qǐng)求、listener 監(jiān)聽(tīng),都是放到這里使用。另外像 MQ、JOB、RPC 也是放到這一層,以這一層觸發(fā),來(lái)調(diào)用我們的領(lǐng)域服務(wù)。

2. 啟動(dòng) Zookeeper 服務(wù)

2.1 自定配置

@Data
@ConfigurationProperties(prefix = "zookeeper.sdk.config", ignoreInvalidFields = true)
public class ZookeeperClientConfigProperties {

    private String connectString;
    private int baseSleepTimeMs;
    private int maxRetries;
    private int sessionTimeoutMs;
    private int connectionTimeoutMs;

}

2.2 使用配置

zookeeper:
  sdk:
    config:
      connect-string: 10.253.6.71:2181
      base-sleep-time-ms: 1000
      max-retries: 3
      session-timeout-ms: 1800000
      connection-timeout-ms: 30000

2.3 配置服務(wù)

@Configuration
@EnableConfigurationProperties(ZookeeperClientConfigProperties.class)
public class ZooKeeperClientConfig {

    /**
     * 多參數(shù)構(gòu)建ZooKeeper客戶端連接
     *
     * @return client
     */
    @Bean(name = "zookeeperClient")
    public CuratorFramework createWithOptions(ZookeeperClientConfigProperties properties) {
        ExponentialBackoffRetry backoffRetry = new ExponentialBackoffRetry(properties.getBaseSleepTimeMs(), properties.getMaxRetries());
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString(properties.getConnectString())
                .retryPolicy(backoffRetry)
                .sessionTimeoutMs(properties.getSessionTimeoutMs())
                .connectionTimeoutMs(properties.getConnectionTimeoutMs())
                .build();
        client.start();
        return client;
    }

}
  • 這樣我們就可以啟動(dòng)一個(gè) Zookeeper 的客戶端了,自定義可以更好的控制和使用。

3. 定義注解

就功能來(lái)講,我們需要對(duì)類中的屬性進(jìn)行賦值操作。那么就需要使用自定義注解進(jìn)行標(biāo)記。所以這里我們先自定義一個(gè)注解。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
public @interface DCCValue {

    String value() default "";

}
  • 這樣所有使用了 @DCCValue 的注解的字段就都可以被我掃描到了。

4. 監(jiān)聽(tīng)變化

4.1 獲取屬性

源碼:cn.bugstack.xfg.dev.tech.config.DCCValueBeanFactory#postProcessAfterInitialization

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    Class<?> beanClass = bean.getClass();
    Field[] fields = beanClass.getDeclaredFields();
    for (Field field : fields) {
        if (field.isAnnotationPresent(DCCValue.class)) {
            DCCValue dccValue = field.getAnnotation(DCCValue.class);
            try {
                if (null == client.checkExists().forPath(BASE_CONFIG_PATH.concat("/").concat(dccValue.value()))) {
                    client.create().creatingParentsIfNeeded().forPath(BASE_CONFIG_PATH.concat("/").concat(dccValue.value()));
                    log.info("DCC 節(jié)點(diǎn)監(jiān)聽(tīng) listener node {} not absent create new done!", BASE_CONFIG_PATH.concat("/").concat(dccValue.value()));
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            dccObjGroup.put(BASE_CONFIG_PATH.concat("/").concat(dccValue.value()), bean);
        }
    }
    return bean;
}
  • DCCValueBeanFactory 實(shí)現(xiàn)了 BeanPostProcessor 接口的 postProcessAfterInitialization 方法。
  • 在實(shí)現(xiàn)中,通過(guò)對(duì) bean 對(duì)象的解析獲取到使用 DCCValue 注解屬性,并判斷這個(gè)屬性拼接的地址是否在 Zookeeper 中創(chuàng)建,如果沒(méi)有則創(chuàng)建。之后保存對(duì)象到內(nèi)存中。

4.2 設(shè)置屬性

源碼:cn.bugstack.xfg.dev.tech.config.DCCValueBeanFactory#DCCValueBeanFactory

curatorCache.listenable().addListener((type, oldData, data) -> {
    switch (type) {
        case NODE_CHANGED:
            String dccValuePath = data.getPath();
            Object objBean = dccObjGroup.get(dccValuePath);
            try {
                // 1. getDeclaredField 方法用于獲取指定類中聲明的所有字段,包括私有字段、受保護(hù)字段和公共字段。
                // 2. getField 方法用于獲取指定類中的公共字段,即只能獲取到公共訪問(wèn)修飾符(public)的字段。
                Field field = objBean.getClass().getDeclaredField(dccValuePath.substring(dccValuePath.lastIndexOf("/") + 1));
                field.setAccessible(true);
                field.set(objBean, new String(data.getData()));
                field.setAccessible(false);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            break;
        default:
            break;
    }
});
  • 基于 Zookeeper 對(duì)節(jié)點(diǎn)的監(jiān)聽(tīng),只要這個(gè)節(jié)點(diǎn)上有值發(fā)生變化。就可以立刻檢測(cè)到對(duì)應(yīng)的路徑信息和值信息。
  • 那么拿到這個(gè)值信息,就可以把值寫入到對(duì)應(yīng)的屬性上了。如類.A = Zookeeper 獲取到的值

五、功能使用

源碼:cn.bugstack.xfg.dev.tech.trigger.http.ConfigController

@RestController
public class ConfigController {

    @DCCValue("downgradeSwitch")
    private String downgradeSwitch;

    @DCCValue("userWhiteList")
    private String userWhiteList;

    @Resource
    private CuratorFramework curatorFramework;

    /**
     * curl http://localhost:8091/getConfig/downgradeSwitch
     */
    @RequestMapping("/getConfig/downgradeSwitch")
    public String getConfigDowngradeSwitch() {
        return downgradeSwitch;
    }

    /**
     * curl http://localhost:8091/getConfig/userWhiteList
     */
    @RequestMapping("/getConfig/userWhiteList")
    public String getConfigUserWhiteList() {
        return userWhiteList;
    }

    /**
     * curl -X GET "http://localhost:8091/setConfig?downgradeSwitch=false&userWhiteList=xfg,user2,user3"
     */
    @GetMapping("/setConfig")
    public void setConfig(Boolean downgradeSwitch, String userWhiteList) throws Exception {
        curatorFramework.setData().forPath("/xfg-dev-tech/config/downgradeSwitch", (downgradeSwitch ? "開(kāi)" : "關(guān)").getBytes(StandardCharsets.UTF_8));
        curatorFramework.setData().forPath("/xfg-dev-tech/config/userWhiteList", userWhiteList.getBytes(StandardCharsets.UTF_8));
    }
    
}

這里的核心驗(yàn)證就是讓 downgradeSwitch、userWhiteList 這2個(gè)屬性值可以動(dòng)態(tài)變化;

  1. 在兩個(gè)屬性上添加注解后,就會(huì)被掃描和管理。
  2. 獲取值方法:http://localhost:8091/getConfig/downgradeSwitch、http://localhost:8091/getConfig/userWhiteList
  3. 設(shè)置值方法:http://localhost:8091/setConfig?downgradeSwitch=false&userWhiteList=xfg,user2,user3 - 這里的設(shè)置值操作不非得在這里,可以是一個(gè)單獨(dú)的控制后臺(tái)來(lái)操作。這里的方式主要是演示作用

圖片圖片

你可以按照如圖的操作順序,進(jìn)行驗(yàn)證屬性值的變化。

六、其他測(cè)試

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiTest {

    @Resource
    private CuratorFramework curatorFramework;

    @Test
    public void test_all() throws Exception {
        String path = "/xfg-dev-tech/config/downgradeSwitch";
        String data = "0";
        curatorFramework.create().withMode(CreateMode.EPHEMERAL).forPath(path, data.getBytes(StandardCharsets.UTF_8));

        for (int i = 0; i < 2; i++) {
            curatorFramework.setData().forPath(path, String.valueOf(i).getBytes(StandardCharsets.UTF_8));
        }
    }

    /**
     * 創(chuàng)建永久節(jié)點(diǎn)
     */
    @Test
    public void createNode() throws Exception {
        String path = "/xfg-dev-tech/config/downgradeSwitch/test/a";
        String data = "0";
        if (null == curatorFramework.checkExists().forPath(path)) {
            curatorFramework.create().creatingParentsIfNeeded().forPath(path);
        }
    }

    /**
     * 創(chuàng)建臨時(shí)節(jié)點(diǎn)
     */
    @Test
    public void createEphemeralNode() throws Exception {
        String path = "/xfg-dev-tech/config/epnode";
        String data = "0";
        curatorFramework.create().withMode(CreateMode.EPHEMERAL).forPath(path, data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 創(chuàng)建臨時(shí)有序節(jié)點(diǎn)
     */
    @Test
    public void crateEphemeralSequentialNode() throws Exception {
        String path = "/xfg-dev-tech/config/epsnode";
        String data = "0";
        curatorFramework.create()
                .withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
                .forPath(path, data.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 往節(jié)點(diǎn)種設(shè)置數(shù)據(jù)
     */
    @Test
    public void setData() throws Exception {
        curatorFramework.setData().forPath("/xfg-dev-tech/config/downgradeSwitch", "111".getBytes(StandardCharsets.UTF_8));
        curatorFramework.setData().forPath("/xfg-dev-tech/config/userWhiteList", "222".getBytes(StandardCharsets.UTF_8));
    }

    @Test
    public void getData() throws Exception {
        String downgradeSwitch = new String(curatorFramework.getData().forPath("/xfg-dev-tech/config/downgradeSwitch"), StandardCharsets.UTF_8);
        log.info("測(cè)試結(jié)果: {}", downgradeSwitch);
        String userWhiteList = new String(curatorFramework.getData().forPath("/xfg-dev-tech/config/userWhiteList"), StandardCharsets.UTF_8);
        log.info("測(cè)試結(jié)果: {}", userWhiteList);
    }

    /**
     * 異步修改數(shù)據(jù)
     */
    @Test
    public void setDataAsync() throws Exception {
        String path = "/xfg-dev-tech/config/downgradeSwitch";
        String data = "0";
        CuratorListener listener = (client, event) -> {
            Stat stat = event.getStat();
            log.info("stat=" + JSON.toJSONString(stat));
            CuratorEventType eventType = event.getType();
            log.info("eventType=" + eventType.name());
        };
        curatorFramework.getCuratorListenable().addListener(listener);
        curatorFramework.setData().inBackground().forPath(path, data.getBytes(StandardCharsets.UTF_8));
    }


    /**
     * 刪除節(jié)點(diǎn)
     */
    @Test
    public void deleteData() throws Exception {
        String path = "/xfg-dev-tech/config/downgradeSwitch";
        curatorFramework.delete().deletingChildrenIfNeeded().forPath(path);
    }

    /**
     * 安全刪除節(jié)點(diǎn)
     */
    @Test
    public void guaranteedDeleteData() throws Exception {
        String path = "/xfg-dev-tech/config/downgradeSwitch";
        curatorFramework.delete().guaranteed().forPath(path);
    }

    /**
     * 獲取子節(jié)點(diǎn)下的全部子節(jié)點(diǎn)路徑集合
     */
    @Test
    public void watchedGetChildren() throws Exception {
        String path = "/xfg-dev-tech";
        List<String> children = curatorFramework.getChildren().watched().forPath(path);
        log.info("測(cè)試結(jié)果:{}", JSON.toJSONString(children));
    }


    /**
     * 獲取節(jié)點(diǎn)數(shù)據(jù)
     */
    @Test
    public void getDataByPath() throws Exception {
        String path = "/xfg-dev-tech/config/downgradeSwitch";
        String fullClassName = "";
        String jsonStr = new String(curatorFramework.getData().forPath(path), StandardCharsets.UTF_8);
        Class clazz = Class.forName(fullClassName);
        log.info("測(cè)試結(jié)果:{}", JSON.parseObject(jsonStr, clazz));
    }

}
  • 這些功能也都可以測(cè)試驗(yàn)證,也是平常用的較多的東西。

七、其他資料

  • Zookeeper Web UI:https://zoonavigator.elkozmon.com/en/latest/
  • 官網(wǎng)文檔:https://zookeeper.apache.org/doc/r3.5.0-alpha/zookeeperAdmin.html
責(zé)任編輯:武曉燕 來(lái)源: bugstack蟲(chóng)洞棧
相關(guān)推薦

2024-07-15 08:25:07

2020-07-30 09:35:09

Redis分布式鎖數(shù)據(jù)庫(kù)

2021-11-01 12:25:56

Redis分布式

2019-12-27 16:00:56

分布式事務(wù)框架Java

2022-06-13 10:01:36

Apollo攜程框架

2025-03-06 11:30:15

2022-04-14 07:56:30

公平鎖Java線程

2023-03-06 08:14:48

MySQLRedis場(chǎng)景

2022-06-27 08:36:27

分布式事務(wù)XA規(guī)范

2016-09-30 10:13:07

分布式爬蟲(chóng)系統(tǒng)

2022-08-01 08:01:04

ID發(fā)號(hào)器系統(tǒng)

2024-03-18 08:50:20

分布式系統(tǒng)機(jī)制

2024-02-19 00:00:00

Redis分布式

2021-02-10 09:54:15

分布式NacosApollo

2024-05-08 10:20:00

Redis分布式

2022-04-08 11:24:39

分布式配置中心服務(wù)端

2023-08-21 19:10:34

Redis分布式

2018-09-06 22:49:31

分布式架構(gòu)服務(wù)器

2022-09-29 08:28:57

SpringRedis分布式

2022-09-22 13:28:34

Redis分布式鎖
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)