SpringBoot專案使用Nacos作為配置中心

竹風有點甜發表於2021-12-25

前置條件:jdk、SpringBoot專案、Nacos、Linux伺服器(可無)
具體版本:jdk11、SpringBoot 2.3.5.RELEASE、Nacos 2.0.3、Centos 6
目標:SpirngBoot專案使用Nacos作為配置中心動態管理專案配置
相關問題及解答參考本文末尾
原文首發:chenetchen.ltd,個人部落格網站。

前言

使用SringBoot框架開發的專案,雖然免去了在Tomcat上的配置,可以將專案打成jar包後在伺服器上釋出,但是如果需要修改配置檔案,需要停下專案,使用vim開啟jar包修改配置檔案,然後重啟專案。

過程不免繁雜,而且需要啟停專案,需要專業人員在伺服器上操作。在之前的工作中,學習到的JMX,可以動態獲取或修改引數,但是JMX的主要作用是用於監控,而不是作為一個配置,且呼叫JMX連線也麻煩,學習成本較大,使用JMX檢視並管理引數需要使用到jconsole工具,也存在學習成本,一樣需要專業人員來操作。

於是乎,回想起之前學習的分散式元件中,Nacos進入了我的選擇範圍

Nacos

Nacos是阿里旗下的一款開源軟體,支援服務註冊與發現、配置管理以及微服務管理的元件。Nacos的目標是為了取代過去常用的註冊中心(Zookeeper、Eureka等),以及配置中心(Spring Cloud Config等)。Nacos整合了註冊中心和配置中心的功能。

基於此,雖然是SpirngBoot專案,服務註冊與發現用不上,但是可以使用Nacos配置中心的功能,對專案的配置檔案進行動態管理。

SpringBoot使用Nacos

Maven配置

根據官方文件——Nacos Spring Boot 快速開始,在Spirng Boot專案的Maven中引入nacos-config-spring-boot-starter依賴

<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>nacos-config-spring-boot-starter</artifactId>
    <version>${latest.version}</version>
</dependency>

需要注意的是,網上對於Spring Boot專案使用的Nacos依賴是哪一個,存在錯誤的版本,相當一部分的帖子和部落格,提到的是spring-cloud-starter-alibaba-nacos-discovery和spring-cloud-starter-alibaba-nacos-config,雖然這兩個也能在Spring Boot中使用,但是配置更加繁瑣。可以參考SpringBoot整合nacos實現配置中心(配置動態更新)

Nacos配置

啟動Nacos

對於本文中的環境,即單個Spirng Boot非分散式叢集的專案而言,Nacos需要以單機的形式啟動。

在Windows下修改/nacos/bin目錄下的startup.cmd指令碼檔案,將其中的 set MODE="cluster"修改為 set MODE="standalone",即可實現Nacos的單機啟動。

配置管理

Nacos啟動成功後(本文預設在測試情況下都在Windows環境下單機啟動Nacos,保持預設配置),訪問 localhost:8848/nacos,使用naocs/nacos登入Nacos平臺。

在左側的配置管理中點選配置列表,再點選右側頁面主體列表上方的加號按鈕,新增配置,Data Id為 test,分組預設,格式選擇properties,配置內容為

useLocalCache=true

釋出成功後,返回。

專案配置

Nacos配置

專案配置有兩種方法,一種是使用註解,在SpringBoot專案的主啟動類上使用

@NacosPropertySource(dataId = "test", autoRefreshed = true)

dataId:Nacos中配置的配置ID
autoRefreshed:開啟自動重新整理

另一種方式是使用配置檔案,因為前文中提到了Spring Boot中使用的是nacos-config-spring-boot-starter,而非Cloud中難道依賴,所以此處是無法使用bootstrap配置檔案的(如要使用bootstrap配置,需要引入cloud相關的配置檔案),直接在application配置檔案中配置好nacos相關的配置。

nacos:
  config:
    type: yaml
    server-addr: 127.0.0.1:8848
    context-path: nacos
    data-id: test
    auto-refresh: true
    bootstrap:
      enable: true

需要注意的是,在以上配置中,有個別配置需要注意,關於此問題,參看SpringBoot2整合nacos(一)

測試程式碼

參看Nacos官方提供的demo,編寫如下測試類(SpringBoot專案需要引入web依賴)

@Controller
public class Test {
    @NacosValue(value = "${useLocalCache:false}", autoRefreshed = true)
    private boolean useLocalCache;

    @RequestMapping(value = "/get", method = RequestMethod.GET)
    @ResponseBody
    public boolean get() {
        return useLocalCache;
    }
}

啟動專案(如果配置檔案中——Nacos配置和專案配置中均未指定專案埠,且80埠被佔用,需要注意新增額為專案埠配置,server.prot=xxx,配置在Nacos或application配置檔案中均可)

訪問/get測試,返回為true即表示SpringBoot專案正確獲取Nacos中的配置。

版本問題

如果上方的基本流程一切順利,那麼恭喜你沒有遇到版本問題;如果Nacos啟動失敗、專案啟動失敗、獲取不到值等,那麼接下來的部分應該可以解決你的問題。

SpirngBoot和Nacos Spring Boot

首先是SpringBoot的版本和Nacos的pom檔案版本,此問題一般出現在專案啟動失敗,這是因為SpringBoot的版本和nacos-config-spring-boot-starter的版本問題。一般會出現如下錯誤:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nacosConfigurationPropertiesBinder': Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.alibaba.boot.nacos.config.binder.NacosBootConfigurationPropertiesBinder]: Constructor threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:315) ~[spring-beans-5.3.2.jar:5.3.2]
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:296) ~[spring-beans-5.3.2.jar:5.3.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1356) ~[spring-beans-5.3.2.jar:5.3.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1206) ~[spring-beans-5.3.2.jar:5.3.2]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.alibaba.boot.nacos.config.binder.NacosBootConfigurationPropertiesBinder]: Constructor threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:225) ~[spring-beans-5.3.2.jar:5.3.2]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:117) ~[spring-beans-5.3.2.jar:5.3.2]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:311) ~[spring-beans-5.3.2.jar:5.3.2]
    ... 20 common frames omitted
Caused by: java.lang.NoClassDefFoundError: org/springframework/boot/context/properties/ConfigurationBeanFactoryMetadata
    at com.alibaba.boot.nacos.config.binder.NacosBootConfigurationPropertiesBinder.<init>(NacosBootConfigurationPropertiesBinder.java:51) ~[nacos-config-spring-boot-autoconfigure-0.2.7.jar:0.2.7]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_181]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_181]
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_181]
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_181]
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:212) ~[spring-beans-5.3.2.jar:5.3.2]
    ... 22 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_181]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_181]
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) ~[na:1.8.0_181]
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_181]
    ... 28 common frames omitted

錯誤提示建立名為'nacosConfigurationPropertiesBinder'的bean失敗。這是因為nacos-config-spring-boot-starter支援的SpringBoot版本比專案當前使用的版本低,而SpringBoot在2.4之後刪掉了ConfigurationBeanFactoryMetadata,需要將SpringBoot的版本降級到2.3.x。

Nacos Spring Boot和JDK

是的,沒錯,JDK此處也插了一腳,是可能存在JDK的版本問題的。
在JDK11的的情況下,nacos-config-spring-boot-starter版本過低,專案啟動不過丟擲異常,但是訪問測試程式碼時,無法獲取Nacos配置中心中配置的值。

經由網友@yvioo測試,在JDK11下,使用nacos-config-spring-boot-starter版本為0.2.7及以上,可以成功獲取Nacos配置中心的值(我測試的時候依舊為未成功獲取),建議使用0.2.10版本。

部署專案到Linux上

經過上方的一系列測試,SpringBoot專案已經能完整的獲取Nacos配置中心中值,現在,需要將環境部署到Linux伺服器上使用。

Linux安裝Nacos

相信網上關於在Linux上部署Nacos的教程數不勝數了,但是大部分的教程在我眼裡還是不夠細緻,並沒有提到各種各樣的bug,在經由我八哥小王子的測試下,果不其然觸發了各式各樣的bug,為此,下文中不過多描述如何安裝nacos,而是如何解決bug。

簡單概述一下Linux安裝Nacos,從官方下載nacos.tar.gz安裝包後,解壓到/usr/local目錄下,便安裝成功了,一切順利的話,像Windows下修改啟動模式,從叢集修改為單機後,直接 ./startup.sh -m standalone即可啟動Nacos了。

Java環境問題

Nacos啟動失敗,首先排查一下Java環境,Nacos實際上為jar包,需要使用Java來啟動。

如果熟悉shell指令碼,可以看到在nacos/bin目錄下的startup.sh指令碼中,是有獲取Java環境的。

[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$JAVA_HOME/java/jdk-11.0.11
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/opt/taobao/java
[ ! -e "$JAVA_HOME/bin/java" ] && unset JAVA_HOME

首先可以先使用命令 echo $JAVA_HOME 檢視在/etc/profile中定義的JAVA_HOME路徑,對比Nacos啟動指令碼中獲取的JAVA_HOME路徑。

JDK11問題

是的,沒錯,JDK11的問題又來了,在JDK11的環境下啟動Nacos也可能失敗,參考Linux下使用JDK11部署Nacos啟動報錯:Could not find or load main class,需要將啟動指令碼startup.sh中的如下配置進行替換

JAVA_OPT_EXT_FIX="-Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${JAVA_HOME}/lib/ext"
替換為
JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${JAVA_HOME}/jre/lib/ext:${JAVA_HOME}/lib/ext"

echo "$JAVA $JAVA_OPT_EXT_FIX ${JAVA_OPT}"
替換為
echo "$JAVA ${JAVA_OPT}"

echo "$JAVA $JAVA_OPT_EXT_FIX ${JAVA_OPT}" > ${BASE_DIR}/logs/start.out 2>&1 &
nohup "$JAVA" "$JAVA_OPT_EXT_FIX" ${JAVA_OPT} nacos.nacos >> ${BASE_DIR}/logs/start.out 2>&1 &
替換為
echo "$JAVA ${JAVA_OPT}" > ${BASE_DIR}/logs/start.out 2>&1 &
nohup $JAVA ${JAVA_OPT} nacos.nacos >> ${BASE_DIR}/logs/start.out 2>&1 &

即可成功啟動。

JVM記憶體問題

替換完後,進行啟動,此時應該已經可以成功啟動了,ps -ef|grep nacos命令也能成功看到nacos的pid,但是當你訪問伺服器的nacos地址時,發現無法請求(預設排除了埠占用,防火牆未開放埠等問題),且重新使用ps命令檢視,會發現nacos自動停止了。

這是因為nacos2.0版本,在配置檔案中預設指定了jvm的大小。同樣是在Nacos的啟動指令碼startup.sh檔案中。

#===========================================================================================
# JVM Configuration
#===========================================================================================
if [[ "${MODE}" == "standalone" ]]; then
    JAVA_OPT="${JAVA_OPT} -Xms512m -Xmx512m -Xmn256m"
    JAVA_OPT="${JAVA_OPT} -Dnacos.standalone=true"
else
    if [[ "${EMBEDDED_STORAGE}" == "embedded" ]]; then
        JAVA_OPT="${JAVA_OPT} -DembeddedStorage=true"
    fi
    JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
    JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${BASE_DIR}/logs/java_heapdump.hprof"
    JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"

fi

if [[ "${FUNCTION_MODE}" == "config" ]]; then
    JAVA_OPT="${JAVA_OPT} -Dnacos.functionMode=config"
elif [[ "${FUNCTION_MODE}" == "naming" ]]; then
    JAVA_OPT="${JAVA_OPT} -Dnacos.functionMode=naming"
fi

其中的JAVA_OPT="${JAVA_OPT} -Xms512m -Xmx512m -Xmn256m"一行指定了JVM的初始容量和最大容量。

-Xms: 設定程式啟動時佔用記憶體大小
-Xmx: 設定程式執行期間最大可佔用的記憶體大小
-Xmn:新生代大小

針對Linux系統容量手動修改即可成功啟動。

JAVA_OPT="${JAVA_OPT} -Xms100m -Xmx256m -Xmn200m"

至此,SpirngBoot專案使用Nacos作為配置中心,動態管理配置完成。

參考資料

  1. Nacos Spring Boot 快速開始

  2. SpringBoot2整合nacos(一)

  3. nacos-spring-boot-config-example

  4. spring boot使用nacos作為配置中心實踐

  5. SpringBoot bootstrap 配置檔案沒有生效

  6. Linux下使用JDK11部署Nacos啟動報錯:Could not find or load main class

  7. centos或者linux的雲伺服器總是啟動不了nacos的問題解決

相關文章