SpringBoot项目使用Nacos作为配置中心
前置条件: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作为配置中心,动态管理配置完成。