前言
參考資料:
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服務原理與實戰》
《B站 尚矽谷 SpringCloud 框架開發教程 周陽》
《Nacos 官網》
Nacos 致力於解決微服務中的統一配置、服務註冊與發現等問題。它提供了一組簡單易用的特性集,幫助開發者快速實現動態服務發現、服務配置、服務後設資料及流量管理;
1. Nacos 配置中心基礎知識
1.1 Nacos 在配置中心中的功能
- CRUD、版本管理、灰度管理、監聽管理、推送軌跡、聚合資料等功能;
1.2 Nacos 配置管理 Data ID 的構成
${prefix}-${spring.profiles.active}.${file-extension}
;- 預設為:
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
;- prefix:預設為
spring.application.name
的值,也可以通過配置項spring.cloud.nacos.config.prefix
來配置; - spring.profiles.active:即為當前環境對應的
profile
。當 spring.profiles.active 為空時,對應的連線符 - 也將不存在,dataId 的拼接格式變成${prefix}.${file-extension}
; - file-exetension:為配置內容的資料格式,可以通過配置項
spring.cloud.nacos.config.file-extension
來配置。目前只支援 properties 和 yaml 型別;
- prefix:預設為
1.3 Nacos 配置的回滾機制
- Nacos 會記錄配置檔案的歷史版本預設保留 30 天,此外還有一鍵回滾功能,回滾操作將會觸發配置更新;
1.4 Nacos 配置的圖形化管理介面
- 配置管理;
- 名稱空間;
1.5 Namespace、Group、Data ID 三者的關係
-
類似 Java 裡面的 package 名和類名,最外層的 Namespace 是可以用於區分部署環境的,Group 和 Data ID 邏輯上區分兩個目標物件;
-
預設情況下,
Namespace=public
,Group=DEFAULT_GROUP
,Cluster=DEFAULT
;- Namespace 名稱空間:主要用來實現隔離,用於解決多環境及多租戶資料的隔離問題。比如有三個環境:開發、測試、生產環境,可以建立三個Namespace,不同的Namespace 之間是隔離的;
- Group 分組:可以把不同的微服務劃分到同一個分組裡面去,用來實現 Data ID 分組管理的機制;
- Data ID:通常用於組織劃分系統的配置集;
- Cluster 簇:對指定微服務的一個虛擬劃分;
- Instance 例項:微服務的例項;
-
官方的建議是,通過 Namespace 來區分不同的環境,而 Group 可以專注在業務層面的資料分組;
1.6 Nacos 對配置的 CRUD
- 主要通過提供 Open API 介面或 SDK 實現;
- 客戶端通過 Open API 或呼叫 SDK 介面傳送請求給伺服器,伺服器解析請求,並做相應的處理;
對配置的操作 | SDK | Open API | 說明 |
---|---|---|---|
釋出配置 | public boolean publishConfig(String dataId, String group, String content) throws NacosException | POST: /nacos/v1/cs/configs | 將配置儲存到 Nacos Config Server 中 |
刪除配置 | public boolean removeConfig(String dataId, String group)throws NacosException | DELETE: /nacos/v1/cs/configs | 刪除配置中心的指定配置 |
獲取配置 | public string getConfig(String dataId, String group, long timeoutMs) throws NacosException | GET: /nacos/v1/cs/configs | 從 Nacos Config Server 中讀取配置 |
監聽配置 | public void addListener(String dataId, String group, Listener listener) | POST: /nacos/v1/cs/configs/listener | 訂閱感興趣的配置,當配置發生變化時可以收到一個事件 |
1.7 Nacos 動態監聽的長輪詢機制
- 一般來說,動態監聽有兩種機制:
比較項 | Pull 機制 | Push 機制 |
---|---|---|
說明 | 客戶端從服務端主動拉取資料 | 服務端主動把資料推送到客戶端 |
缺點 | 不能保證資料實時性;在服務端配置長時間不更新的情況下,客戶端的定時任務會做一些無效的 Pull | 如果客戶端的數量比較多,服務端需要耗費大量的記憶體資源來儲存每個連線;需要心跳機制來維持每個連線狀態 |
- Nacos 的解決方案:長輪詢機制:
- 如果客戶端發起 Pull 請求,服務端收到請求之後,先檢查配置是否發生了變更:
- 變更:返回變更配置;
- 無變更:設定一個定時任務,延期 29.5s 執行,把當前的客戶端長輪詢連線加入 allSubs 佇列;
- 在這 29.5s 內的配置變化:
- 配置無變化:等待 29.5s 後觸發自動檢查機制,返回配置;
- 配置變化:在 29.5s 內任意一個時刻配置變化,會觸發一個事件機制,監聽到該事件的任務會遍歷 allSubs 佇列,找到發生變更的配置項對應的 ClientLongPolling 任務,將變更的資料通過該任務中的連線進行返回。相當於完成了一次 PUSH 操作;
- 長輪詢機制結合了 Pull 機制和 Push 機制的優點;
- 原始碼分析詳情請見:微服務架構 | *2.5 Nacos 長輪詢定時機制的原始碼分析;
1.8 Nacos 配置中心的原始碼分析
- 由於篇幅有限,該內容放在以下兩篇文章:
2. Nacos 基礎配置
以 Data ID 方案為例,更多方案詳情請見本篇第三點《3. Nacos 載入配置的三種方案》;
2.1 下載 Nacos 伺服器
- 詳情請見:《微服務架構 | 3.2 Alibaba Nacos 註冊中心》的 2. 安裝並執行 Nacos 伺服器;
2.2 引入 pom.xml 依賴檔案
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2.3 修改 yml 配置檔案
- bootstrap.yml:
# nacos配置
server:
port: 18082
spring:
application:
name: nacos-config-client #必須,構成 Nacos 配置管理 Data ID 欄位的一部分
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos 服務註冊中心地址
config:
server-addr: localhost:8848 #Nacos 作為配置中心地址
file-extension: yaml #指定 yaml 格式的配置
# prefix: hhh #Data ID 的字首,如果不指定,就是 nacos-config-client-dev.yaml。指定後,是 hhh-dev.yaml
# refresh: true #是否動態重新整理
# group: DEFAULT_GROUP #指定組
# namespace: PUBLIC #指定名稱空間 ID
- application.yml:
spring:
profiles:
active: dev # 表示開發環境
2.4 在主程式類上新增註解
- @EnableDiscoveryClient:使用其他元件(Nacos、zookeeper、Consul)作為註冊中心;
2.5 編寫業務類
- 這裡僅編寫一個 controller 作為示例:
@RestController
@RefreshScope //使當前類下的配置支援 Nacos 的動態重新整理功能
public class ConfigClientController{
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
2.6 在 Nacos 伺服器中新增配置資訊
- 在 Nacos 伺服器裡新建並新增配置:
- 啟動服務,呼叫介面:
http://localhost:18082/config/info
獲取配置資訊;
2.7 報錯無法裝配 bean
- 注意:如果
.yml
和 Nacos 伺服器配置的Data ID
匹配不上,將導致 ConfigClientController 類裡的${config.info}
找不到,最終報 ConfigClientController 無法裝配的錯誤,如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.configClientController': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'config.info' in value "${config.info}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:380) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$1(AbstractBeanFactory.java:356) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.cloud.context.scope.GenericScope$BeanLifecycleWrapper.getBean(GenericScope.java:389) ~[spring-cloud-context-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.cloud.context.scope.GenericScope.get(GenericScope.java:186) ~[spring-cloud-context-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:353) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
at org.springframework.cloud.context.scope.refresh.RefreshScope.eagerlyInitialize(RefreshScope.java:134) ~[spring-cloud-context-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.cloud.context.scope.refresh.RefreshScope.start(RefreshScope.java:125) ~[spring-cloud-context-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.cloud.context.scope.refresh.RefreshScope.onApplicationEvent(RefreshScope.java:119) ~[spring-cloud-context-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.cloud.context.scope.refresh.RefreshScope.onApplicationEvent(RefreshScope.java:73) ~[spring-cloud-context-2.1.2.RELEASE.jar:2.1.2.RELEASE]
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
......
3. Nacos 載入配置的三種方案
- 以下對 bootstrap.yml 的修改都可以在啟動時配置 JVM 環境替代,使用如下命令:
-Dspring.profiles.active=${profile}
3.1 Data ID 方案
- 新建三個 Data ID 的環境:
- 指定
spring.profile.active
中的配置,重啟服務,可以看到配置已經切換成其他環境了:
spring:
profiles:
active: prod
3.2 Group 方案
- 新建三個 Group 的環境:
- 指定
spring.profile.active
和spring.cloud.nacos.config.group
中的配置,重啟服務,可以看到配置已經切換成其他環境了: - application.yml:
spring:
profiles:
active: info #修改
- bootstrap.yml:
server:
port: 18082
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
group: PROD_GROUP #新增
3.3 Namespace 方案
- 新建兩個 Namespace 的環境:
- 克隆 / 新建幾份配置檔案到新的名稱空間,然後編輯修改;
- 指定
spring.profile.active
和spring.cloud.nacos.config.group
中的配置,重啟服務,可以看到配置已經切換成其他環境了: - application.yml:
spring:
profiles:
active: dev #修改
- bootstrap.yml:
server:
port: 18082
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
group: DEFAULT_GROUP #新增
namespace: 48b2da7d-0b26-4c15-907b-9a379db8f7de #新增,名稱空間的 ID,在新建名稱空間時會給出