javascript
Spring Cloud 入门 之 Config 篇(六)
一、前言
隨著業(yè)務(wù)的擴(kuò)展,為了方便開發(fā)和維護(hù)項(xiàng)目,我們通常會(huì)將大項(xiàng)目拆分成多個(gè)小項(xiàng)目做成微服務(wù),每個(gè)微服務(wù)都會(huì)有各自配置文件,管理和修改文件起來也會(huì)變得繁瑣。而且,當(dāng)我們需要修改正在運(yùn)行的項(xiàng)目的配置時(shí),通常需要重啟項(xiàng)目后配置才能生效。
上述的問題將是本篇需要解決的問題。
二、介紹
#?2.1 簡(jiǎn)單介紹
? ? Spring Cloud Config 用于為分布式系統(tǒng)中的基礎(chǔ)設(shè)施和微服務(wù)應(yīng)用提供集中化的外部配置支持,它分為服務(wù)端和客戶端兩部分。服務(wù)端(config server)也稱為分布式配置中心,是一個(gè)獨(dú)立的微服務(wù)應(yīng)用,用來連接配置倉(cāng)庫(kù)并為客戶端提供獲取配置信息,加密/解密信息等訪問接口。而客戶端(config client)則是微服務(wù)架構(gòu)中各微服務(wù)應(yīng)用或基礎(chǔ)設(shè)施,通過指定的配置中心來管理應(yīng)用資源與業(yè)務(wù)相關(guān)的配置內(nèi)容,并在啟動(dòng)的時(shí)候從配置中心獲取和加載配置信息。
#?2.2 運(yùn)行原理
? ? 如上圖,當(dāng) Config Client 首次啟動(dòng)時(shí)會(huì)向 Config Server 獲取配置信息,Config Server 接收到請(qǐng)求再?gòu)倪h(yuǎn)程私有倉(cāng)庫(kù)獲取配置(連接不上項(xiàng)目會(huì)報(bào)錯(cuò)),并保存到本地倉(cāng)庫(kù)中。
? ? 當(dāng) Config Client 再次啟動(dòng)時(shí)會(huì)向 Config Server 獲取配置信息,Config Server 還是會(huì)先從遠(yuǎn)程私有倉(cāng)庫(kù)拉去數(shù)據(jù)。如果網(wǎng)絡(luò)問題或認(rèn)證問題導(dǎo)致無法連接遠(yuǎn)程私有庫(kù),Config Server 才會(huì)從本地倉(cāng)庫(kù)獲取配置信息返回給 Config Client。
三、Config 實(shí)戰(zhàn)
? ? 本次實(shí)戰(zhàn)基于 Eureka 篇的項(xiàng)目進(jìn)行擴(kuò)展演練。不清楚的讀者請(qǐng)先轉(zhuǎn)移至?《Spring Cloud 入門 之 Eureka 篇(一)》?進(jìn)行瀏覽。
? ? 我們使用配置中心來維護(hù) order-server 的配置數(shù)據(jù)(application.yml)。
? ? 測(cè)試場(chǎng)景:由于配置中心服務(wù)本身也是一個(gè)微服務(wù),因此我們需要將配置中心注冊(cè)到 Eureka 上,當(dāng) order-server 啟動(dòng)時(shí)先向 Eureka 獲取配置中心的訪問地址,然后從配置中心獲取相應(yīng)的配置信息進(jìn)行正常啟動(dòng)。
? ? 本篇實(shí)戰(zhàn)用到的項(xiàng)目列表:
| eureka-server | 9000 | 注冊(cè)中心(Eureka 服務(wù)端) |
| config-server | 10000 | 配置中心(Eureka 客戶端、Config 服務(wù)端) |
| order-server | 8100 | 訂單服務(wù)(Eureka 客戶端、Config 客戶端) |
#?3.1 上傳配置
? ? 在 GitHub 上新建一個(gè)私有倉(cāng)庫(kù),名為 spring-cloud-config。
? ? 我們將 order-server 項(xiàng)目的配置文件放到改倉(cāng)庫(kù)中,如下圖:
? ? 新建 2 個(gè) yml 文件,內(nèi)容為:
server: port: 8100 eureka: instance: instance-id: order-api-8100 prefer-ip-address: true # 訪問路徑可以顯示 IP env: devORDER-dev.yml 和 ORDER-test.yml 不同之處在于 env 的值,其中一個(gè)是 dev ,另一個(gè)是 test。
#?3.2 config 服務(wù)端
新建一個(gè) spring boot 項(xiàng)目,名為 config-server(任意名字)。
?
? ? 2. application.yml
server: port: 10000spring: application: name: CONFIG cloud: config: server: git: uri: https://github.com/moonlightL/spring-cloud-config.git username: moonlightL password: xxx basedir: d:/data # 本地庫(kù)目錄 eureka: instance: instance-id: config-api client: service-url: defaultZone: http://localhost:9000/eureka/ # 注冊(cè)中心訪問地址? ? 3. 啟動(dòng)類添加?@EnableConfigServer:
@EnableConfigServer @EnableEurekaClient @SpringBootApplication public class ConfigApplication {public static void main(String[] args) { SpringApplication.run(ConfigApplication.class, args); } }啟動(dòng)成功后,我們打開瀏覽器訪問?http://localhost:10000/order-dev.yml?和?http://localhost:10000/order-test.yml,結(jié)果如下圖:
config-server 服務(wù)成功拉去遠(yuǎn)程私有倉(cāng)庫(kù)的配置數(shù)據(jù)。
其中,訪問規(guī)則如下:
<IP:PORT>/{name}-{profiles}.yml <IP:PORT>/{label}/{name}-{profiles}.yml- name:文件名,可當(dāng)作服務(wù)名稱
- profiles: 環(huán)境,如:dev,test,pro
- lable: 分支,指定訪問某分支下的配置文件,默認(rèn)拉去 master 分支。
#?3.3 config 客戶端
在 order-server 項(xiàng)目中。
? ? 2.刪除 application.yml,并新建 bootstrap.yml,保存如下內(nèi)容:
spring: application: name: ORDER cloud: config: discovery: enabled: true service-id: CONFIG # config-server 在注冊(cè)中心的名稱 profile: dev # 指定配置文件的環(huán)境 eureka: client: service-url: defaultZone: http://localhost:9000/eureka/ # 注冊(cè)中心訪問地址配置中,通過 spring.cloud.config.discovery.service-id 確定配置中心,再通過 spring.application.name 的值拼接 spring.cloud.config.profile 的值,從而確定需要拉去從配置中心獲取的配置文件。(如:ORDER-dev)
注意:必須保留 eureka 注冊(cè)中心的配置,否則 order-server 無法連接注冊(cè)中心,也就無法獲取配置中心(config-server)的訪問信息。
? ? 3.測(cè)試
新建一個(gè)測(cè)試類:
@RestController @RequestMapping("/test") public class TestController {@Value("${env}") private String env; // 從配置中心獲取@RequestMapping("/getConfigInfo") public String getConfigInfo() { return env; } }打開瀏覽器訪問?http://localhost:8100/test/getConfigInfo,結(jié)果如下圖:
成功獲取 config-server 從遠(yuǎn)程私有倉(cāng)庫(kù)拉去的數(shù)據(jù),由于在 bootstrap.yml 中配置了 spring.cloud.config.profile=dev,因此拉取到的數(shù)據(jù)就是 ORDER-dev.yml 中的數(shù)據(jù)。
引申問題:
當(dāng)我們修改遠(yuǎn)程私有倉(cāng)庫(kù)的配置文件時(shí),Config Server 如何知道是否該重新獲取遠(yuǎn)程倉(cāng)庫(kù)數(shù)據(jù)呢?
現(xiàn)在已知唯一的解決方式就是重啟 Config Client 項(xiàng)目,在項(xiàng)目啟動(dòng)時(shí)會(huì)請(qǐng)求 Config Server 重新拉去遠(yuǎn)程私有倉(cāng)庫(kù)數(shù)據(jù)。但是,如果是在生產(chǎn)環(huán)境下隨便重啟項(xiàng)目必定會(huì)影響系統(tǒng)的正常運(yùn)行,那有沒有更好的方式解決上述的問題呢?請(qǐng)讀者繼續(xù)閱讀下文。
四、整合 Bus
Spring Cloud Bus 是 Spring Cloud 家族中的一個(gè)子項(xiàng)目,用于實(shí)現(xiàn)微服務(wù)之間的通信。它整合 Java 的事件處理機(jī)制和消息中間件消息的發(fā)送和接受,主要由發(fā)送端、接收端和事件組成。針對(duì)不同的業(yè)務(wù)需求,可以設(shè)置不同的事件,發(fā)送端發(fā)送事件,接收端接受相應(yīng)的事件,并進(jìn)行相應(yīng)的處理。
#?4.1 配置中心
在 config-server 項(xiàng)目中:
? ? 2.修改 application.yml,添加如下配置:
server: port: 10000spring: application: name: CONFIG cloud: config: server: git: uri: https://github.com/moonlightL/spring-cloud-config.git username: moonlightL password: shijiemori960 rabbitmq: host: 192.168.2.13 port: 5672 username: light password: lighteureka: instance: instance-id: config-api client: service-url: defaultZone: http://localhost:9000/eureka/ # 注冊(cè)中心訪問地址management: endpoints: web: exposure: include: "*" # 暴露接口添加了 rabbitmq 配置和 management 的配置。
#?4.2 訂單服務(wù)
在 order-server 項(xiàng)目中:
? ? 2. 修改 bootstrap.yml:
spring: application: name: ORDER cloud: config: discovery: enabled: true service-id: CONFIG # config-server 在注冊(cè)中心的名稱 profile: dev # 指定配置文件的環(huán)境 rabbitmq: host: 192.168.2.13 port: 5672 username: light password: light eureka: client: service-url: defaultZone: http://localhost:9000/eureka/ # 注冊(cè)中心訪問地址添加 rabbitmq 配置。
? ? 3. 獲取數(shù)據(jù)的類上添加?@RefreshScope?注解:
@RestController @RequestMapping("/test") @RefreshScope public class TestController {@Value("${env}") private String env; // 從配置中心獲取@RequestMapping("/getConfigInfo") public String getConfigInfo() { return env; } }整合 Bus 后的原理圖如下:
? ? 當(dāng)我們修改遠(yuǎn)程私有倉(cāng)庫(kù)配置信息后,需要向 Config Server 發(fā)起?actuator/bus-refresh?請(qǐng)求。然后, Config Server 會(huì)通知消息總線 Bus,之后 Bus 接到消息并通知給其它連接到總線的 Config Client。最后,Config Client 接收到通知請(qǐng)求 Config Server 端重新訪問遠(yuǎn)程私有倉(cāng)庫(kù)拉去最新數(shù)據(jù)。
? ? 4.測(cè)試:
? ? 修改遠(yuǎn)程私有倉(cāng)庫(kù)配置文件,使用 Postman 發(fā)起 POST 請(qǐng)求?http://localhost:10000/actuator/bus-refresh,最終配置中心重新拉去數(shù)據(jù),最后再訪問 order-server?http://localhost:8100/test/getConfigInfo?獲取最新數(shù)據(jù),運(yùn)行結(jié)果如下圖:
? ? 如上圖,我們實(shí)現(xiàn)了在不重啟項(xiàng)目的情況下,獲取變更數(shù)據(jù)的功能。
引申問題:
? ? 每次更新私有倉(cāng)庫(kù)中的配置文件都需要手動(dòng)請(qǐng)求?actuator/bus-refresh,還是不夠自動(dòng)化。
? ? 下邊我們來解決該問題。
五、集成 WebHook
? ? 遠(yuǎn)程私有倉(cāng)庫(kù)的提供 WebHook 配置,我們將?actuator/bus-refresh?配置上去,當(dāng)遠(yuǎn)程私有倉(cāng)庫(kù)中的配置信息發(fā)生變動(dòng)時(shí),就會(huì)自動(dòng)調(diào)用該接口最終實(shí)現(xiàn)自動(dòng)刷新目的。
#?5.1 配置 WebHook 地址
? ? 登錄 GitHub,點(diǎn)擊 GitHub 的 WebHook 菜單,右側(cè)面板中 Payload URL 填寫 <配置中心 url>/actuator/bus-refresh, Content-type 選擇 applicaton/json,保存即可。
? ? 由于筆者是本地測(cè)試,沒有外網(wǎng)域名,因此借助?https://natapp.cn?做外網(wǎng)映射(操作簡(jiǎn)單,詳情看官網(wǎng)教程),以下是筆者的外網(wǎng)信息:
設(shè)置 WebHook 操作如下圖:
#?5.2 測(cè)試
? ? 預(yù)期效果:當(dāng)我們修改 GitHub 上私有倉(cāng)庫(kù)的配置數(shù)據(jù)后,我們?cè)僭L問?http://localhost:8100/test/getConfigInfo?應(yīng)該展示最新的數(shù)據(jù)。
但是結(jié)果失敗了。
原因:?回到 config-server 控制臺(tái)查看日志發(fā)現(xiàn)報(bào)錯(cuò)了:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token at [Source: (PushbackInputStream); line: 1, column: 68] (through reference chain: java.util.LinkedHashMap["hook"])]這是因?yàn)?#xff0c;GitHub 在調(diào)用 <配置中心 url>/actuator/bus-refresh 時(shí),往請(qǐng)求體添加了 payload 數(shù)據(jù),但它不是一個(gè)標(biāo)準(zhǔn)的 JSON 數(shù)據(jù)。因此,config-server 在接收 GitHub 發(fā)送的請(qǐng)求獲取,從請(qǐng)求體數(shù)據(jù)做轉(zhuǎn)換時(shí)就報(bào)錯(cuò)了。
解決方案:
在 config-server 項(xiàng)目中,新建一個(gè)過濾器,用于過濾?actuator/bus-refresh?請(qǐng)求,將其請(qǐng)求體置空:
@Component public class WebHookFilter implements Filter {@Override public void init(FilterConfig filterConfig) throws ServletException {}@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String url = new String(httpServletRequest.getRequestURI());// 只過濾 /actuator/bus-refresh 請(qǐng)求 if (!url.endsWith("/actuator/bus-refresh")) { chain.doFilter(request, response); return; }// 使用 HttpServletRequest 包裝原始請(qǐng)求達(dá)到修改 post 請(qǐng)求中 body 內(nèi)容的目的 CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);chain.doFilter(requestWrapper, response);}@Override public void destroy() {}private class CustometRequestWrapper extends HttpServletRequestWrapper { public CustometRequestWrapper(HttpServletRequest request) { super(request); }@Override public ServletInputStream getInputStream() throws IOException { byte[] bytes = new byte[0]; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);return new ServletInputStream() { @Override public boolean isFinished() { return byteArrayInputStream.read() == -1 ? true : false; }@Override public boolean isReady() { return false; }@Override public void setReadListener(ReadListener readListener) {}@Override public int read() throws IOException { return byteArrayInputStream.read(); } }; } } }完成如上配置后,再次測(cè)試,結(jié)果如下:
搞定! 由于網(wǎng)絡(luò)問題,拉去最新數(shù)據(jù)時(shí)有點(diǎn)慢,需要多刷新幾次。。。
六、案例源碼
config demo 源碼
總結(jié)
以上是生活随笔為你收集整理的Spring Cloud 入门 之 Config 篇(六)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 万字长文精华之数据中台构建五步法
- 下一篇: Ubuntu 安装firefox中文版