spring cloud config使用etcd存儲

ETCD作為云原生的一大基礎(chǔ)項(xiàng)目,其在k8s中的應(yīng)用讓它得到了很大關(guān)注,自己當(dāng)時在學(xué)習(xí)k8s的時候也就簡單的了解一下捅膘,并沒有過多關(guān)注翘地,后來學(xué)習(xí)raft算法又對其產(chǎn)生了興趣申尤。都說其可以作為服務(wù)注冊中心和配置中心使用,但是目前好像并沒有看到有什么java項(xiàng)目使用ETCD衙耕,也可能是spring cloud整體生態(tài)比較成熟昧穿,不管是配置中心還是注冊中心都有很多優(yōu)秀的解決方案。但是基于好奇心我還是準(zhǔn)備摸索以下如何使用ETCD作為配置中心橙喘,這里只是做一個基礎(chǔ)的demo时鸵,不會太深入。

一厅瞎、ETCD

首先我們需要一個ETCD服務(wù)端饰潜,我直接本地啟動,因?yàn)槲抑氨镜卮罱?code>ETCD集群本地有部署過和簸,本次就直接使用單節(jié)點(diǎn)啟動彭雾。另外為了更方便查看數(shù)據(jù),我IDEA安裝了EtcdHelper插件锁保。
啟動使用默認(rèn)配置薯酝,然后通過etcdctl創(chuàng)建角色、用戶爽柒,并通過key前綴給角色授權(quán)吴菠。這些可以參考官方文檔。這里我簡單說下我在項(xiàng)目中會使用到的一些配置信息霉赡。
端口:2379橄务,角色:admin,并給這個角色授予key前綴權(quán)限/spring_etcd/穴亏、/etcd_demo/蜂挪、/etcd_config/重挑,用戶etcdAdmin,密碼:123456棠涮,這些配置在項(xiàng)目中會用到谬哀,這里就先介紹一下。

二严肪、config server

首先我們新建一個spring boot項(xiàng)目史煎。引入相關(guān)的依賴,這里額外引入了JPASecurity驳糯,項(xiàng)目pom.xml如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-security</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>io.etcd</groupId>
            <artifactId>jetcd-core</artifactId>
            <version>0.7.5</version>
        </dependency>

項(xiàng)目的配置文件application.properties如下:

spring.application.name=spring-etcd
server.port=9090
spring.profiles.active=etcd

## mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.boot.allow_jdbc_metadata_access=false
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=none

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456

spring.cloud.config.server.etcd.endpoints=http://127.0.0.1:2379
spring.cloud.config.server.etcd.user-name=etcdAdmin
spring.cloud.config.server.etcd.password=123456
#spring.cloud.config.server.etcd.name-space=/spring_etcd/

logging.level.root=info

接下來我們新增一個ETCD配置類EtcdConfigProperties篇梭,代碼如下:

@Data
@Configuration
@ConfigurationProperties(prefix = "spring.cloud.config.server.etcd")
public class EtcdConfigProperties implements EnvironmentRepositoryProperties {

    private int order = Ordered.LOWEST_PRECEDENCE;

    private List<String> endpoints;

    private boolean enable;

    private String nameSpace;

    private String userName;

    private String password;

    @Override
    public void setOrder(int order) {
        this.order = order;
    }
}

這里主要定義了ETCD的endpoint地址、用戶名酝枢、密碼恬偷、命名空間(本項(xiàng)目沒用上)。接下來我們根據(jù)配置文件創(chuàng)建一個Client帘睦,代碼如下:

@Configuration
@ConditionalOnBean({EtcdConfigProperties.class})
public class EtcdClientConfig {


    @Bean
    public Client createClient(EtcdConfigProperties etcdConfigProperties) {
        List<String> endpoints = etcdConfigProperties.getEndpoints();

        return Client.builder().endpoints(endpoints.toArray(new String[endpoints.size()]))
//                .namespace(ByteSequence.from(etcdConfigProperties.getNameSpace(), StandardCharsets.UTF_8))
                .user(ByteSequence.from(etcdConfigProperties.getUserName(),StandardCharsets.UTF_8))
                .password(ByteSequence.from(etcdConfigProperties.getPassword(), StandardCharsets.UTF_8)).build();
    }
}

這里主要是根據(jù)endpoint袍患、user、password創(chuàng)建了一個Client竣付,這里都要根據(jù)ByteSequence來轉(zhuǎn)換感覺不是很方便诡延。這里其實(shí)我開始是準(zhǔn)備用namespace的,但是自己對這個用法還不是很了解古胆,而且文檔也沒找到對應(yīng)的內(nèi)容肆良,所以暫時就沒用上。我理解應(yīng)該就是一個key或者前綴赤兴,這個等我有時間再來研究一下吧妖滔。
接下來就是很關(guān)鍵的一步,就是提供一個供客戶端獲取配置文件的接口桶良。通過spring cloud config文檔我們知道config server其實(shí)是通過http為外部提供配置信息的,如果想使用自定義的存儲沮翔,其實(shí)只要我們實(shí)現(xiàn)EnvironmentRepository接口即可陨帆,當(dāng)然也要實(shí)現(xiàn)Ordered并重寫getOrdered方法,不然的話我們的配置優(yōu)先級默認(rèn)會是最低的采蚀,因此我們新創(chuàng)建一個EtcdEnvironmentRepository類疲牵,代碼如下:

@Slf4j
@Configuration
@Profile("etcd")
public class EtcdEnvironmentRepository implements EnvironmentRepository, Ordered {

    private final Client client;

    private final EtcdConfigProperties configProperties;

    public EtcdEnvironmentRepository(Client client, EtcdConfigProperties configProperties) {
        this.client = client;
        this.configProperties = configProperties;
    }

    @SneakyThrows
    @Override
    public Environment findOne(String application, String profile, String label) {
        String queryKey = "/" + application + "/" + profile;
        GetOption option = GetOption.newBuilder().isPrefix(true).build();
        GetResponse response = client.getKVClient().get(ByteSequence.from(queryKey, StandardCharsets.UTF_8), option).get();
        List<KeyValue> list = response.getKvs();
        // 配置文件
        Map<String, String> properties = new HashMap<>();
        for (KeyValue kv : response.getKvs()) {
            String key = kv.getKey().toString(StandardCharsets.UTF_8);
            String value = kv.getValue().toString(StandardCharsets.UTF_8);
            key = key.replace(queryKey, "").replace("/", "");
            log.info("key={} ,value={}", key, value);
            properties.put(key, value);
        }
        Environment environment = new Environment(application, profile);
        //
        String propertyName = application + "-" + profile + ".properties";
        environment.add(new PropertySource(propertyName, properties));
        propertyName = "application-" + profile + ".properties";
//        environment.add(new PropertySource(propertyName, properties));

//        environment.add(new PropertySource(application + ".properties", properties));
        return environment;
    }

    @Override
    public Environment findOne(String application, String profile, String label, boolean includeOrigin) {
        return EnvironmentRepository.super.findOne(application, profile, label, includeOrigin);
    }

    @Override
    public int getOrder() {
        return configProperties.getOrder();
    }
}

因?yàn)樵谂渲妙惱锩嫖覍?code>order的值設(shè)為最小值,這樣我們整個配置的優(yōu)先級最高榆鼠。簡單的解釋一下代碼纲爸,首先就是根據(jù)客戶端的應(yīng)用名稱和配置文件名稱作為key前綴,查詢所有的key-value鍵值對妆够,并將這些鍵值對放入配置文件Map中识啦,然后將其放入到Environment中负蚊。這里我當(dāng)時測試了以下application、profile具體的值颓哮,這里我們可以等會debug看下家妆。
好了到這里我們的server端已經(jīng)完成,接下來我們創(chuàng)建client端項(xiàng)目冕茅。

三 client server

同上伤极,這里我們創(chuàng)建一個springf clound config client項(xiàng)目,pom.xml引入依賴如下:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

這里除了引入spring-cloud-starter-config還引入了JPA姨伤、mysql的依賴哨坪,我的目的很簡單,就是數(shù)據(jù)庫的用戶名乍楚、密碼這些敏感信息從config server獲取齿税。然后通過一個簡單的查詢接口保證從server端獲取到正確的配置信息。
client項(xiàng)目的application.properties如下:

spring.application.name=etcd_demo
server.port=6060
spring.profiles.active=dev
spring.config.import=optional:configserver:http://127.0.0.1:9090
# config server user and password
#spring.cloud.config.username=etcdAdmin
#spring.cloud.config.password=123456
# config name default value application.name
spring.cloud.config.name=etcd_config
spring.cloud.refresh.enabled=true

spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.boot.allow_jdbc_metadata_access=false
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=none

## mysql properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true

這里注意以下就是spring.config.import的使用炊豪,這個是Spring Boot 2.4 引入的一種新的引入配置文件的方式凌箕,它是綁定到Config Server的默認(rèn)配置。這里我們指向server端地址即可词渤。通過上面的配置文件也發(fā)現(xiàn)我沒有配置數(shù)據(jù)庫用戶名和密碼牵舱。
另外我寫了兩個簡單的接口用于測試,代碼如下:

@Slf4j
@Service
public class UserServiceImpl implements IUserService {


    @Value("${dynamic.value}")
    private String configValue;

    private final UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserEntity queryById(Integer id) {
        return userRepository.findById(id).get();
    }

    @Override
    public String queryConfig() {
        log.info("動態(tài)配置的值:{}", configValue);
        return configValue;
    }
}

一個是查詢用戶缺虐,一個是獲取通過@Value注入的配置信息芜壁。等會我們通過接口分別測試以下兩個接口能否按照期望返回相應(yīng)的結(jié)果。

四 測試

先后啟動serverclient項(xiàng)目高氮,server端啟動成功慧妄,client啟動出錯了,信息如下:

2024-09-03T21:23:12.922+08:00  INFO 15273 --- [etcd_demo] [           main] c.y.s.etcdclient.EtcdClientApplication   : The following 1 profile is active: "dev"
2024-09-03T21:23:12.964+08:00  INFO 15273 --- [etcd_demo] [           main] o.s.c.c.c.ConfigServerConfigDataLoader   : Fetching config from server at : http://127.0.0.1:9090
2024-09-03T21:23:12.964+08:00  WARN 15273 --- [etcd_demo] [           main] o.s.c.c.c.ConfigServerConfigDataLoader   : Could not locate PropertySource ([ConfigServerConfigDataResource@6c451c9c uris = array<String>['http://127.0.0.1:9090'], optional = true, profiles = 'default']): Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.cloud.config.environment.Environment] and content type [text/html;charset=UTF-8]
2024-09-03T21:23:12.964+08:00  INFO 15273 --- [etcd_demo] [           main] o.s.c.c.c.ConfigServerConfigDataLoader   : Fetching config from server at : http://127.0.0.1:9090
2024-09-03T21:23:12.965+08:00  WARN 15273 --- [etcd_demo] [           main] o.s.c.c.c.ConfigServerConfigDataLoader   : Could not locate PropertySource ([ConfigServerConfigDataResource@cc62a3b uris = array<String>['http://127.0.0.1:9090'], optional = true, profiles = 'dev']): Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.cloud.config.environment.Environment] and content type [text/html;charset=UTF-8]
2024-09-03T21:23:12.965+08:00  INFO 15273 --- [etcd_demo] [           main] o.s.c.c.c.ConfigServerConfigDataLoader   : Fetching config from server at : http://127.0.0.1:9090
2024-09-03T21:23:12.965+08:00  WARN 15273 --- [etcd_demo] [           main] o.s.c.c.c.ConfigServerConfigDataLoader   : Could not locate PropertySource ([ConfigServerConfigDataResource@6cc0bcf6 uris = array<String>['http://127.0.0.1:9090'], optional = true, profiles = 'default']): Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.cloud.config.environment.Environment] and content type [text/html;charset=UTF-8]
2024-09-03T21:23:13.521+08:00  INFO 15273 --- [etcd_demo] [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.

沒有從遠(yuǎn)端獲取到PropertySource剪芍,因?yàn)?code>server端引入了spring security這個好像是默認(rèn)開啟的塞淹,所以先注掉依賴,如果要開啟的話一定要在server端和client端都配置好Security用戶名和密碼罪裹,不然client端通過http接口無法獲取到Environment饱普。再次啟動serverclient,這個時候server端的斷點(diǎn)生效了状共,如下圖:

截圖 2024-09-03 21-37-47.png

通過上圖我們可以看到application變量值就是spring.cloud.config.name配置的值套耕,而profile則是默認(rèn)配置文件default,但是我們實(shí)際配置的spring.profiles.active值是dev峡继,跳過斷點(diǎn)冯袍,如下圖:

截圖 2024-09-03 21-48-24.png

通過兩次debug斷點(diǎn)我們發(fā)現(xiàn)client端獲取配置文件是根據(jù)config.name + profile來獲取的,所以我們可以在Etcd中設(shè)置好我們需要的配置信息碾牌,這也是為什么一開始我就要給admin角色按照key前綴授權(quán)的原因康愤。我們通過EtcdHelper新增幾項(xiàng)配置儡循,如下圖:
截圖 2024-09-03 21-53-43.png

EtcdEnvironmentRepository類中,我們是根據(jù)applicationprofile組合翘瓮,作為key前綴的贮折,然后根據(jù)從Etcd返回的key、value組裝成我們最終需要的配置信息资盅。
client啟動成功调榄,如下圖:
截圖 2024-09-03 21-58-56.png

最后我們通過測試接口測試一下能否達(dá)到我們期望的結(jié)果。
根據(jù)id查詢用戶信息呵扛,接口如下:
截圖 2024-09-03 22-01-10.png

查詢@Value注解注入的值每庆,如下:
截圖 2024-09-03 22-01-26.png

都沒有問題,說明clientserver端獲取的配置是正確的今穿,關(guān)于client查詢配置可以看下ConfigServerConfigDataLoader這個類的源碼缤灵。

五 最后

關(guān)于使用Etcd做為配置中心的內(nèi)容先到這里,但是在這次學(xué)習(xí)中我也發(fā)現(xiàn)了一個問題就是配置如何自動刷新蓝晒,我嘗試了一下沒有成功腮出,這個等我有時間再研究研究,如果成功了我再來單獨(dú)開一篇芝薇。本次項(xiàng)目的源碼放在我的github胚嘲。歡迎點(diǎn)贊評論轉(zhuǎn)發(fā)~~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市洛二,隨后出現(xiàn)的幾起案子馋劈,更是在濱河造成了極大的恐慌,老刑警劉巖晾嘶,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妓雾,死亡現(xiàn)場離奇詭異,居然都是意外死亡垒迂,警方通過查閱死者的電腦和手機(jī)械姻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娇斑,“玉大人策添,你說我怎么就攤上這事『晾拢” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵乐导,是天一觀的道長苦丁。 經(jīng)常有香客問我,道長物臂,這世上最難降的妖魔是什么旺拉? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任产上,我火速辦了婚禮,結(jié)果婚禮上蛾狗,老公的妹妹穿的比我還像新娘晋涣。我一直安慰自己,他們只是感情好沉桌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布谢鹊。 她就那樣靜靜地躺著,像睡著了一般留凭。 火紅的嫁衣襯著肌膚如雪佃扼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蔼夜,我揣著相機(jī)與錄音兼耀,去河邊找鬼。 笑死求冷,一個胖子當(dāng)著我的面吹牛瘤运,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播匠题,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拯坟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梧躺?” 一聲冷哼從身側(cè)響起似谁,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掠哥,沒想到半個月后巩踏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡续搀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年塞琼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片禁舷。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡彪杉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牵咙,到底是詐尸還是另有隱情派近,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布洁桌,位于F島的核電站渴丸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谱轨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一戒幔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧土童,春花似錦诗茎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雀瓢,卻和暖如春枢析,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刃麸。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工醒叁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泊业。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓把沼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吁伺。 傳聞我的和親對象是個殘疾皇子饮睬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容