前置條件: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作為配置中心,動態管理配置完成。