javascript
Spring Cloud 入门 之 Config 篇(六)
一、前言
隨著業務的擴展,為了方便開發和維護項目,我們通常會將大項目拆分成多個小項目做成微服務,每個微服務都會有各自配置文件,管理和修改文件起來也會變得繁瑣。而且,當我們需要修改正在運行的項目的配置時,通常需要重啟項目后配置才能生效。
上述的問題將是本篇需要解決的問題。
二、介紹
#?2.1 簡單介紹
? ? Spring Cloud Config 用于為分布式系統中的基礎設施和微服務應用提供集中化的外部配置支持,它分為服務端和客戶端兩部分。服務端(config server)也稱為分布式配置中心,是一個獨立的微服務應用,用來連接配置倉庫并為客戶端提供獲取配置信息,加密/解密信息等訪問接口。而客戶端(config client)則是微服務架構中各微服務應用或基礎設施,通過指定的配置中心來管理應用資源與業務相關的配置內容,并在啟動的時候從配置中心獲取和加載配置信息。
#?2.2 運行原理
? ? 如上圖,當 Config Client 首次啟動時會向 Config Server 獲取配置信息,Config Server 接收到請求再從遠程私有倉庫獲取配置(連接不上項目會報錯),并保存到本地倉庫中。
? ? 當 Config Client 再次啟動時會向 Config Server 獲取配置信息,Config Server 還是會先從遠程私有倉庫拉去數據。如果網絡問題或認證問題導致無法連接遠程私有庫,Config Server 才會從本地倉庫獲取配置信息返回給 Config Client。
三、Config 實戰
? ? 本次實戰基于 Eureka 篇的項目進行擴展演練。不清楚的讀者請先轉移至?《Spring Cloud 入門 之 Eureka 篇(一)》?進行瀏覽。
? ? 我們使用配置中心來維護 order-server 的配置數據(application.yml)。
? ? 測試場景:由于配置中心服務本身也是一個微服務,因此我們需要將配置中心注冊到 Eureka 上,當 order-server 啟動時先向 Eureka 獲取配置中心的訪問地址,然后從配置中心獲取相應的配置信息進行正常啟動。
? ? 本篇實戰用到的項目列表:
| eureka-server | 9000 | 注冊中心(Eureka 服務端) |
| config-server | 10000 | 配置中心(Eureka 客戶端、Config 服務端) |
| order-server | 8100 | 訂單服務(Eureka 客戶端、Config 客戶端) |
#?3.1 上傳配置
? ? 在 GitHub 上新建一個私有倉庫,名為 spring-cloud-config。
? ? 我們將 order-server 項目的配置文件放到改倉庫中,如下圖:
? ? 新建 2 個 yml 文件,內容為:
server: port: 8100 eureka: instance: instance-id: order-api-8100 prefer-ip-address: true # 訪問路徑可以顯示 IP env: devORDER-dev.yml 和 ORDER-test.yml 不同之處在于 env 的值,其中一個是 dev ,另一個是 test。
#?3.2 config 服務端
新建一個 spring boot 項目,名為 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 # 本地庫目錄 eureka: instance: instance-id: config-api client: service-url: defaultZone: http://localhost:9000/eureka/ # 注冊中心訪問地址? ? 3. 啟動類添加?@EnableConfigServer:
@EnableConfigServer @EnableEurekaClient @SpringBootApplication public class ConfigApplication {public static void main(String[] args) { SpringApplication.run(ConfigApplication.class, args); } }啟動成功后,我們打開瀏覽器訪問?http://localhost:10000/order-dev.yml?和?http://localhost:10000/order-test.yml,結果如下圖:
config-server 服務成功拉去遠程私有倉庫的配置數據。
其中,訪問規則如下:
<IP:PORT>/{name}-{profiles}.yml <IP:PORT>/{label}/{name}-{profiles}.yml- name:文件名,可當作服務名稱
- profiles: 環境,如:dev,test,pro
- lable: 分支,指定訪問某分支下的配置文件,默認拉去 master 分支。
#?3.3 config 客戶端
在 order-server 項目中。
? ? 2.刪除 application.yml,并新建 bootstrap.yml,保存如下內容:
spring: application: name: ORDER cloud: config: discovery: enabled: true service-id: CONFIG # config-server 在注冊中心的名稱 profile: dev # 指定配置文件的環境 eureka: client: service-url: defaultZone: http://localhost:9000/eureka/ # 注冊中心訪問地址配置中,通過 spring.cloud.config.discovery.service-id 確定配置中心,再通過 spring.application.name 的值拼接 spring.cloud.config.profile 的值,從而確定需要拉去從配置中心獲取的配置文件。(如:ORDER-dev)
注意:必須保留 eureka 注冊中心的配置,否則 order-server 無法連接注冊中心,也就無法獲取配置中心(config-server)的訪問信息。
? ? 3.測試
新建一個測試類:
@RestController @RequestMapping("/test") public class TestController {@Value("${env}") private String env; // 從配置中心獲取@RequestMapping("/getConfigInfo") public String getConfigInfo() { return env; } }打開瀏覽器訪問?http://localhost:8100/test/getConfigInfo,結果如下圖:
成功獲取 config-server 從遠程私有倉庫拉去的數據,由于在 bootstrap.yml 中配置了 spring.cloud.config.profile=dev,因此拉取到的數據就是 ORDER-dev.yml 中的數據。
引申問題:
當我們修改遠程私有倉庫的配置文件時,Config Server 如何知道是否該重新獲取遠程倉庫數據呢?
現在已知唯一的解決方式就是重啟 Config Client 項目,在項目啟動時會請求 Config Server 重新拉去遠程私有倉庫數據。但是,如果是在生產環境下隨便重啟項目必定會影響系統的正常運行,那有沒有更好的方式解決上述的問題呢?請讀者繼續閱讀下文。
四、整合 Bus
Spring Cloud Bus 是 Spring Cloud 家族中的一個子項目,用于實現微服務之間的通信。它整合 Java 的事件處理機制和消息中間件消息的發送和接受,主要由發送端、接收端和事件組成。針對不同的業務需求,可以設置不同的事件,發送端發送事件,接收端接受相應的事件,并進行相應的處理。
#?4.1 配置中心
在 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: 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/ # 注冊中心訪問地址management: endpoints: web: exposure: include: "*" # 暴露接口添加了 rabbitmq 配置和 management 的配置。
#?4.2 訂單服務
在 order-server 項目中:
? ? 2. 修改 bootstrap.yml:
spring: application: name: ORDER cloud: config: discovery: enabled: true service-id: CONFIG # config-server 在注冊中心的名稱 profile: dev # 指定配置文件的環境 rabbitmq: host: 192.168.2.13 port: 5672 username: light password: light eureka: client: service-url: defaultZone: http://localhost:9000/eureka/ # 注冊中心訪問地址添加 rabbitmq 配置。
? ? 3. 獲取數據的類上添加?@RefreshScope?注解:
@RestController @RequestMapping("/test") @RefreshScope public class TestController {@Value("${env}") private String env; // 從配置中心獲取@RequestMapping("/getConfigInfo") public String getConfigInfo() { return env; } }整合 Bus 后的原理圖如下:
? ? 當我們修改遠程私有倉庫配置信息后,需要向 Config Server 發起?actuator/bus-refresh?請求。然后, Config Server 會通知消息總線 Bus,之后 Bus 接到消息并通知給其它連接到總線的 Config Client。最后,Config Client 接收到通知請求 Config Server 端重新訪問遠程私有倉庫拉去最新數據。
? ? 4.測試:
? ? 修改遠程私有倉庫配置文件,使用 Postman 發起 POST 請求?http://localhost:10000/actuator/bus-refresh,最終配置中心重新拉去數據,最后再訪問 order-server?http://localhost:8100/test/getConfigInfo?獲取最新數據,運行結果如下圖:
? ? 如上圖,我們實現了在不重啟項目的情況下,獲取變更數據的功能。
引申問題:
? ? 每次更新私有倉庫中的配置文件都需要手動請求?actuator/bus-refresh,還是不夠自動化。
? ? 下邊我們來解決該問題。
五、集成 WebHook
? ? 遠程私有倉庫的提供 WebHook 配置,我們將?actuator/bus-refresh?配置上去,當遠程私有倉庫中的配置信息發生變動時,就會自動調用該接口最終實現自動刷新目的。
#?5.1 配置 WebHook 地址
? ? 登錄 GitHub,點擊 GitHub 的 WebHook 菜單,右側面板中 Payload URL 填寫 <配置中心 url>/actuator/bus-refresh, Content-type 選擇 applicaton/json,保存即可。
? ? 由于筆者是本地測試,沒有外網域名,因此借助?https://natapp.cn?做外網映射(操作簡單,詳情看官網教程),以下是筆者的外網信息:
設置 WebHook 操作如下圖:
#?5.2 測試
? ? 預期效果:當我們修改 GitHub 上私有倉庫的配置數據后,我們再訪問?http://localhost:8100/test/getConfigInfo?應該展示最新的數據。
但是結果失敗了。
原因:?回到 config-server 控制臺查看日志發現報錯了:
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"])]這是因為,GitHub 在調用 <配置中心 url>/actuator/bus-refresh 時,往請求體添加了 payload 數據,但它不是一個標準的 JSON 數據。因此,config-server 在接收 GitHub 發送的請求獲取,從請求體數據做轉換時就報錯了。
解決方案:
在 config-server 項目中,新建一個過濾器,用于過濾?actuator/bus-refresh?請求,將其請求體置空:
@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 請求 if (!url.endsWith("/actuator/bus-refresh")) { chain.doFilter(request, response); return; }// 使用 HttpServletRequest 包裝原始請求達到修改 post 請求中 body 內容的目的 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(); } }; } } }完成如上配置后,再次測試,結果如下:
搞定! 由于網絡問題,拉去最新數據時有點慢,需要多刷新幾次。。。
六、案例源碼
config demo 源碼
總結
以上是生活随笔為你收集整理的Spring Cloud 入门 之 Config 篇(六)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 万字长文精华之数据中台构建五步法
- 下一篇: Springboot瘦身(lib和程序分