项目在考虑引入注册中心和配置中心,对比了下当前较流行的配置中心和注册中心,发现Nacos比较适合我们这种私有化部署比较多的场景。本文将对其介绍,后续将会从源码实现层面进行研究分享。
当前比较流行的配置中心就是Apollo,因此与Apollo进行对比。
对比项目/配置中心 | Apollo | Nacos |
---|---|---|
高可用 | 基于Eureka | 基于Raft,不需要额外中间件 |
多环境 | 以项目粒度为单位,每个项目下多环境 | 以环境粒度为单位,每个环境下多个项目 |
配置生效 | 秒级别热更新生效 | 秒级别热更新生效 |
版本管理 | 自动管理 | 自动管理 |
配置回滚 | 支持 | 支持 |
配置继承 | 支持配置继承,可以有公共配置,其他都来继承,适合微服务的场景 | 不支持 |
灰度发布 | 支持 | 支持 |
配置格式校验 | 支持 | 支持 |
审计 | 支持审计日志,可以清晰查看每次操作时变更的配置条目和变更前后对比 | 支持审计日志,仅记录了变更前的原记录,未展示变更的配置条目以及变更前后对比 |
权限管理 | 以项目粒度为单位对UI登录用户/客户端调用鉴权,不支持只读/读写粒度权限控制 | 以环境粒度为单位对UI登录用户/客户端调用鉴权,支持只读/读写粒度权限控制 |
监听查询 | 支持 | 支持 |
多语言 | Go,C++,Python,Java,.net,OpenAPI | Python,Java,Nodejs,Go,OpenAPI |
分布式高可用最小集群数量 | Config2+Admin3+Portal*2+Mysql=8 | Nacos*3+MySql=4 |
通信协议 | HTTP | HTTP、gRPC |
从上面可以看出,配置中心该有的功能两者都有,Apollo在功能上比Nacos更加完善。但是,Apollo在部署上依赖的太多,运维成本较高。此外,Apollo只是一个配置中心,Nacos还有注册中心,如果服务同时依赖注册中心和配置中心,只需要部署一套Nacos就可以满足需求,运维成本较低。
对比项目\注册中心 | Nacos | Eureka | Consul |
---|---|---|---|
依赖 | 无 | Zookeeper | 无 |
CAP模型 | AP+CP | AP | CP |
伸缩性 | Raft选举算法性能、可用性、容错性均比较好 | 使用广播同步信息,集群超过1000台机器后对Eureka集群压力很大 | Raft选举算法 |
健康检查方式 | TPC/HTTP/SQL/Client Beat | Client Beat | TCP/HTTP/gRPC/CMD |
负载均衡策略 | 权重/MetaData/Selector | Ribbon | Fabio |
跨中心同步 | 支持 | 不支持 | 支持 |
版本迭代 | 正常迭代 | 已不升级 | 正常迭代 |
集成支持 | SpringCloud/K8S | SpringCloud | SpringCloud/K8S |
访问协议 | HTTP/DNS/gRPC | HTTP | HTTP/DNS |
雪崩保护 | 支持 | 支持 | 不支持 |
自动注销实例 | 支持 | 支持 | 不支持 |
监听支持 | 支持 | 支持 | 支持 |
界面 | 中文 | 英文 | 英文 |
因Eureka 2.x已不再迭代,不会选用。从功能上来看,Nacos注册中心会更加完善。从UI上看,Nacos为中文界面,更符合国人习惯。因此,Nacos是更好的选择。
Nacos系统架构设计如下:
逻辑架构及组件:
整体架构分为用户层、业务层、内核层和插件,用户层解决易用性问题,业务层解决服务发现和配置管理功能,内核层解决分布式系统一致性、存储、高可用等核心问题,插件解决扩展性问题。
一种典型使用场景如下:
配置中心在实现上采用AP一致性协议。
Nacos客户端会在本地生成配置的快照,当客户端无法连接到Nacos Server时,可以使用配置快照实现整体容灾能力,类似于缓存,会在适当的时机更新,但是并没有缓存过期的概念。
Nacos划分为服务、集群、实例三层。
Nacos支持AP和CP两种并存的一致性协议,实现上,一个是基于简化的Raft的CP一致性,一个是基于自研协议Distro(Gossip改进)的AP一致性。
Nacos实例支持两种,临时实例和持久化实例,两者区分的关键在与健康检查方式。临时实例使用客户端上报模式,而持久化实例使用服务端反向探测模式,开启上在1.x时是在实例级别,2.x之后在服务级别。
正如上面所说,Nacos既支持客户端的健康检查,也支持服务端的健康检查。对于临时实例,使用心跳上报方式维持活性,发送心跳的周期默认是5秒,Nacos服务端会在15秒没收到心跳后将实例设置为不健康,在30秒没收到心跳时将这个临时实例摘除。对于持久化实例,探测方式支持TCP端口、HTTP返回码检测方式,对于一些特殊场景,也支持如MySQL命令来进行检查。
Nacos在后期也会支持用户扩展机制,支持用户传入一条业务语义的请求,然后由Nacos去执行,做到健康检查的定制。
负载均衡其实并不属于注册中心的功能,完整的流程应该是先从注册中心获取服务的实例列表,再根据实际需求选择其中的部分实例或者按照一定的流量分配机制来访问不同的服务提供者。但Nacos恰好反过来,服务消费者并不关心负载均衡,只关心如何正确、高效访问服务,服务提供者则非常关注自身被访问的流量的调配。在实现上,Nacos将客户端好服务端的负载均衡相结合,提供了基于健康检查、权重、CMDB标签等策略。
Nacos在权限设计上采用RBAC体系,授权的资源知道命名空间,粒度很大,功能也相对较弱。
#!/bin/bash
# 参考地址
# https://github.com/nacos-group/nacos-docker
# web访问http://127.0.0.1:8848,账号/密码:nacos/nacos
# 8848为服务的端口
# 9848为客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求,固定偏移1000,即8848+1000
# 9849为服务端gRPC请求服务端端口,用于服务间同步等,固定偏移1001,即8848+1001
# 配置参数:
# SERVER_SERVLET_CONTEXTPATH:指定上下文前缀,默认nacos
# NACOS_APPLICATION_PORT:指定端口,默认8848
# NACOS_AUTH_ENABLE:是否开启权限,默认false
# NACOS_AUTH_TOKEN_EXPIRE_SECONDS:token失效时间,默认18000秒
# NACOS_AUTH_TOKEN:token,默认SecretKey012345678901234567890123456789012345678901234567890123456789
# NACOS_AUTH_CACHE_ENABLE:权限缓存开关,开启后权限缓存的更新默认有15秒的延迟,默认false
# SPRING_DATASOURCE_PLATFORM:指定数据源平台,如果使用MySQL,值配置成mysql
# MYSQL_DATABASE_NUM:指定MySQL数据库个数,默认1
# MYSQL_SERVICE_HOST:指定数据库主机
# MYSQL_SERVICE_PORT:指定数据库端口
# MYSQL_SERVICE_DB_NAME:指定库名
# MYSQL_SERVICE_DB_PARAM:指定数据库连接参数
# MYSQL_SERVICE_USER:指定用户名
# MYSQL_SERVICE_PASSWORD:指定密码
# NACOS_AUTH_TOKEN_EXPIRE_SECONDS:指定Token过期时间,默认1800秒
# NACOS_SERVER_IP:多网卡情况下,指定ip或网卡
# PREFER_HOST_MODE:如果支持主机名可以使用hostname,否则使用ip,默认也是ip
# JVM_XMS:-Xms
# JVM_XMX:-Xmx
# JVM_XMN:-Xmn
# JVM_MS:-XX:MetaspaceSize
# JVM_MMS:-XX:MaxMetaspaceSize
# TOMCAT_ACCESSLOG_ENABLED:是否开始tomcat访问日志的记录
# --add-host hostname 可以用来添加主机名
# -h hostname 用于指定主机名
docker run -d \
--name some-nacos \
-e MODE=standalone \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
nacos/nacos-server:2.0.3
复制代码
#!/bin/bash
docker run -d \
--name some-nacos-mysql \
--net common-network \
-e MODE=standalone \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=some-mysql \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_DB_NAME=nacos \
-e 'MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true' \
-e MYSQL_SERVICE_USER=nacos \
-e MYSQL_SERVICE_PASSWORD=nacos \
nacos/nacos-server:2.0.3
复制代码
从Nacos官网上下载发行包或者源码,进入nacos-2.0.3/distribution/conf
目录,执行SQL脚本:
source nacos-mysql.sql
复制代码
使用一个Docker实例节点进行模拟,端口依次使用8841,8842,8843,暂未使用Swarm,配置如下:
#!/bin/bash
docker run -d \
--name some-nacos-8841 \
--net common-network \
-h some-nacos-8841 \
-e MODE=cluster \
-e PREFER_HOST_MODE=hostname \
-e 'NACOS_SERVERS=some-nacos-8841:8841 some-nacos-8842:8842 some-nacos-8843:8843' \
-e NACOS_APPLICATION_PORT=8841 \
-p 8841:8841 \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=some-mysql \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_DB_NAME=nacos \
-e 'MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true' \
-e MYSQL_SERVICE_USER=nacos \
-e MYSQL_SERVICE_PASSWORD=nacos \
-e JVM_XMS=512m \
-e JVM_XMX=512m \
-v /Users/ginger/nacos/logs/8841:/home/nacos/logs \
nacos/nacos-server:2.0.3
复制代码
#!/bin/bash
docker run -d \
--name some-nacos-8842 \
--net common-network \
-h some-nacos-8842 \
-e MODE=cluster \
-e PREFER_HOST_MODE=hostname \
-e 'NACOS_SERVERS=some-nacos-8841:8841 some-nacos-8842:8842 some-nacos-8843:8843' \
-e NACOS_APPLICATION_PORT=8842 \
-p 8842:8842 \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=some-mysql \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_DB_NAME=nacos \
-e 'MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true' \
-e MYSQL_SERVICE_USER=nacos \
-e MYSQL_SERVICE_PASSWORD=nacos \
-e JVM_XMS=512m \
-e JVM_XMX=512m \
-v /Users/ginger/nacos/logs/8842:/home/nacos/logs \
nacos/nacos-server:2.0.3
复制代码
#!/bin/bash
docker run -d \
--name some-nacos-8843 \
--net common-network \
-h some-nacos-8843 \
-e MODE=cluster \
-e PREFER_HOST_MODE=hostname \
-e 'NACOS_SERVERS=some-nacos-8841:8841 some-nacos-8842:8842 some-nacos-8843:8843' \
-e NACOS_APPLICATION_PORT=8843 \
-p 8843:8843 \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=some-mysql \
-e MYSQL_SERVICE_PORT=3306 \
-e MYSQL_SERVICE_DB_NAME=nacos \
-e 'MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true' \
-e MYSQL_SERVICE_USER=nacos \
-e MYSQL_SERVICE_PASSWORD=nacos \
-e JVM_XMS=512m \
-e JVM_XMX=512m \
-v /Users/ginger/nacos/logs/8843:/home/nacos/logs \
nacos/nacos-server:2.0.3
复制代码
启动效果如下:
如果要查看Leader节点,可以点开节点元数据查看。注意,默认情况下Nacos分配的内存为2G,如果是单机多容器部署,注意要加上此环境变量的配置,否则会一直重启。如果是多机单容器部署则不需要。
可以给集群配置一个Nginx代理,作为统一入口,以便后续管理和维护。Nginx的部署不再多说,配置如下:
location / {
proxy_pass http://nacos-server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
}
upstream nacos-server {
server some-nacos-8841:8841 weight=1 max_fails=2 fail_timeout=10s;
server some-nacos-8842:8842 weight=1 max_fails=2 fail_timeout=10s;
server some-nacos-8843:8843 weight=1 max_fails=2 fail_timeout=10s;
}
复制代码
0.2.10
com.alibaba.boot
nacos-config-spring-boot-starter
${nacos-config.version}
复制代码
spring.profiles.active = dev
spring.application.name = easy-console
server.port = 8096
server.servlet.context-path = /console
复制代码
# Nacos配置中心服务端地址
nacos.config.server-addr = 172.17.0.26:8848
# 是否开启Nacos配置预加载
nacos.config.bootstrap.enable = true
# 是否开启Nacos预加载日志
nacos.config.bootstrap.log-enable = false
# 名称空间(只可使用名称空间Id,不可使用Name,两者在创建时可以使用同一个名字)
nacos.config.namespace = dev
# 配置分组
nacos.config.group = DEFAULT_GROUP
# 配置项Data Id
nacos.config.data-id = ${spring.application.name}-${spring.profiles.active}.${nacos.config.type}
# 配置格式
nacos.config.type = properties
# 是否开启自动刷新
nacos.config.auto-refresh = true
# 远程配置是否优先于本地配置
nacos.config.remote-first = false
# 长轮询的重试次数
nacos.config.max-retry = 3
# 长轮询任务重试时间,单位为毫秒
nacos.config.config-retry-time = 2000
# 长轮询的超时时间,单位为毫秒
nacos.config.config-long-poll-timeout = 30000
# 监听器首次添加时拉取远端配置
nacos.config.enable-remote-sync-config = false
复制代码
注意,nacos.config.bootstrap.enable
需要开启,Nacos的配置需要先加载,从远程拉取服务的配置。否则,服务的一些配置可能会先加载,导致因为配置不存在而报错。
[Nacos Config Boot] : The preload log configuration is enabled
. ____ _ __ _ _
/\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )___ | '_ | '_| | '_ / _` | \ \ \ \
\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.12.RELEASE)
[2021-12-19 17:24:24.261] [main] [INFO] NacosConfigPropertiesUtils.buildNacosConfigProperties:47 - nacosConfigProperties : NacosConfigProperties{serverAddr='172.17.0.26:8848', contextPath='null', encode='null', endpoint='null', namespace='null', accessKey='null', secretKey='null', ramRoleName='null', autoRefresh=true, dataId='easy-console-dev.properties', dataIds='null', group='DEFAULT_GROUP', type=PROPERTIES, maxRetry='3', configLongPollTimeout='30000', configRetryTime='2000', enableRemoteSyncConfig=false, extConfig=[], bootstrap=Bootstrap{enable=true, logEnable=true}}
[2021-12-19 17:24:24.270] [main] [INFO] ClientWorker.addCacheDataIfAbsent:384 - [config_rpc_client] [subscribe] easy-console-dev.properties+DEFAULT_GROUP
[2021-12-19 17:24:24.277] [main] [INFO] CacheData.addListener:169 - [config_rpc_client] [add-listener] ok, tenant=, dataId=easy-console-local.properties, group=DEFAULT_GROUP, cnt=1
[2021-12-19 17:24:24.285] [main] [INFO] StartupInfoLogger.logStarting:55 - Starting EasyConsoleApplication on pc.local with PID 88943
[2021-12-19 17:24:24.286] [main] [INFO] SpringApplication.logStartupProfileInfo:652 - The following profiles are active: dev
[2021-12-19 17:24:26.526] [main] [INFO] TomcatWebServer.initialize:108 - Tomcat initialized with port(s): 8096 (http)
[2021-12-19 17:24:26.534] [main] [INFO] DirectJDKLog.log:173 - Initializing ProtocolHandler ["http-nio-8096"]
[2021-12-19 17:24:26.534] [main] [INFO] DirectJDKLog.log:173 - Starting service [Tomcat]
[2021-12-19 17:24:26.534] [main] [INFO] DirectJDKLog.log:173 - Starting Servlet engine: [Apache Tomcat/9.0.46]
复制代码
2021-12-24 14:37:09.268 INFO [main :c.a.n.c.u.ParamUtil] [settings] [req-serv] nacos-server port:8848
2021-12-24 14:37:09.271 INFO [main :c.a.n.c.u.ParamUtil] [settings] [http-client] connect timeout:1000
2021-12-24 14:37:09.273 INFO [main :c.a.n.c.u.ParamUtil] PER_TASK_CONFIG_SIZE: 3000.0
2021-12-24 14:37:09.376 INFO [main :c.a.n.c.i.CredentialWatcher] null No credential found
2021-12-24 14:37:09.408 INFO [main :c.a.n.c.c.i.Limiter] limitTime:5.0
2021-12-24 14:37:09.904 INFO [main :c.a.n.c.c.i.LocalConfigInfoProcessor] LOCAL_SNAPSHOT_PATH:logs/nacos/config
2021-12-24 14:37:10.835 INFO [com.alibaba.nacos.client.remote.worker:c.a.n.c.c.i.ClientWorker] [e12d9f10-6594-4ddc-8dc2-fc4a6d48e5a9_config-0] Connected,notify listen context...
2021-12-24 14:37:10.891 INFO [main :c.a.n.c.c.u.JvmUtil] isMultiInstance:false
2021-12-24 14:37:10.906 INFO [main :c.a.n.c.c.i.ClientWorker] [config_rpc_client] [subscribe] easy-console-dev.properties+DEFAULT_GROUP+dev
2021-12-24 14:37:10.911 INFO [main :c.a.n.c.c.i.CacheData] [config_rpc_client] [add-listener] ok, tenant=dev, dataId=easy-console-local.properties, group=DEFAULT_GROUP, cnt=1
复制代码
实现有两种方式,第一种为使用一套配置中心,通过启动命令进行环境切换,如下:
-Dspring.profiles.active=online
复制代码
第二种方式为多环境配置,如下:
Nacos会在本地缓存远程的配置,默认目录为/用户目录
,Nacos会在默认目录后面拼接上/nacos/config
,具体如下:
ginger@pc:~ $ tree nacos
nacos
├── config
│ └── config_rpc_client_nacos
│ └── snapshot-tenant
│ └── local
│ └── DEFAULT_GROUP
│ └── easy-console-dev.properties
└── naming
└── local
└── failover
复制代码
可以在启动脚本中添加-D参数修改缓存文件的存储位置。
/用户目录
,因此实际目录为/${JM.SNAPSHOT.PATH}/nacos/config
。Nacos会记录客户端日志,默认目录为/用户目录
,Nacos会在默认目录后面拼接上/logs/nacos
,具体如下:
ginger@pc:~ $ tree logs
logs
└── nacos
├── config.log
├── naming.log
└── remote.log
复制代码
默认单个文件大小为10M,最多7个文件。可以在启动脚本中添加-D参数修改日志的存储位置以及文件大小和文件数。
/用户目录
,因此实际目录为/${JM.LOG.PATH}/logs/nacos
。0.2.10
com.alibaba.boot
nacos-discovery-spring-boot-starter
${nacos-discovery.version}
复制代码
spring.profiles.active = dev
spring.application.name = easy-console
server.port = 8096
server.servlet.context-path = /console
复制代码
# Nacos注册中心
# 服务端地址
nacos.discovery.server-addr = 172.17.0.26:8848
# 是否开启自动注册
nacos.discovery.auto-register = true
# 注册分组
nacos.discovery.register.group-name = DEFAULT_GROUP
# 注册命名空间
nacos.discovery.namespace = ${spring.profiles.active}
复制代码
. ____ _ __ _ _
/\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )___ | '_ | '_| | '_ / _` | \ \ \ \
\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.12.RELEASE)
[2021-12-24 14:37:09.229] [main] [INFO] NacosConfigPropertiesUtils.buildNacosConfigProperties:47 - nacosConfigProperties : NacosConfigProperties{serverAddr='172.17.0.26:8848', contextPath='null', encode='null', endpoint='null', namespace='local', accessKey='null', secretKey='null', ramRoleName='null', autoRefresh=true, dataId='easy-console-dev.properties', dataIds='null', group='DEFAULT_GROUP', type=PROPERTIES, maxRetry='3', configLongPollTimeout='30000', configRetryTime='2000', enableRemoteSyncConfig=false, extConfig=[], bootstrap=Bootstrap{enable=true, logEnable=false}}
[2021-12-24 14:37:09.974] [main] [INFO] Reflections.scan:232 - Reflections took 33 ms to scan 1 urls, producing 3 keys and 6 values
[2021-12-24 14:37:09.999] [main] [INFO] Reflections.scan:232 - Reflections took 14 ms to scan 1 urls, producing 4 keys and 9 values
[2021-12-24 14:37:10.014] [main] [INFO] Reflections.scan:232 - Reflections took 12 ms to scan 1 urls, producing 3 keys and 10 values
[2021-12-24 14:37:10.140] [main] [INFO] Reflections.scan:232 - Reflections took 122 ms to scan 126 urls, producing 0 keys and 0 values
[2021-12-24 14:37:10.155] [main] [INFO] Reflections.scan:232 - Reflections took 12 ms to scan 1 urls, producing 1 keys and 5 values
[2021-12-24 14:37:10.168] [main] [INFO] Reflections.scan:232 - Reflections took 10 ms to scan 1 urls, producing 1 keys and 7 values
[2021-12-24 14:37:10.177] [main] [INFO] Reflections.scan:232 - Reflections took 7 ms to scan 1 urls, producing 2 keys and 8 values
[2021-12-24 14:37:10.270] [main] [INFO] Reflections.scan:232 - Reflections took 88 ms to scan 126 urls, producing 0 keys and 0 values
[2021-12-24 14:37:10.901] [main] [INFO] NacosConfigLoader.reqNacosConfig:169 - load config from nacos, data-id is : easy-console-dev.properties, group is : DEFAULT_GROUP
[2021-12-24 14:37:10.918] [main] [INFO] StartupInfoLogger.logStarting:55 - Starting EasyConsoleApplication on pc.local with PID 13082
[2021-12-24 14:37:10.921] [main] [INFO] SpringApplication.logStartupProfileInfo:652 - The following profiles are active: local
[2021-12-24 14:37:12.854] [main] [INFO] TomcatWebServer.initialize:108 - Tomcat initialized with port(s): 8096 (http)
[2021-12-24 14:37:12.861] [main] [INFO] DirectJDKLog.log:173 - Initializing ProtocolHandler ["http-nio-8096"]
[2021-12-24 14:37:12.861] [main] [INFO] DirectJDKLog.log:173 - Starting service [Tomcat]
[2021-12-24 14:37:12.862] [main] [INFO] DirectJDKLog.log:173 - Starting Servlet engine: [Apache Tomcat/9.0.46]
[2021-12-24 14:37:12.924] [main] [INFO] DirectJDKLog.log:173 - Initializing Spring embedded WebApplicationContext
[2021-12-24 14:37:12.925] [main] [INFO] ServletWebServerApplicationContext.prepareWebApplicationContext:285 - Root WebApplicationContext: initialization completed in 1964 ms
[2021-12-24 14:37:15.823] [main] [INFO] DirectJDKLog.log:173 - Starting ProtocolHandler ["http-nio-8096"]
[2021-12-24 14:37:15.837] [main] [INFO] TomcatWebServer.start:220 - Tomcat started on port(s): 8096 (http) with context path '/console'
[2021-12-24 14:37:15.886] [main] [INFO] NacosDiscoveryAutoRegister.onApplicationEvent:89 - Finished auto register service : easy-console, ip : 10.242.44.123, port : 8096
复制代码
2021-12-24 14:37:14.700 INFO [main :c.a.n.c.naming] initializer namespace from System Property :null
2021-12-24 14:37:14.701 INFO [main :c.a.n.c.naming] initializer namespace from System Environment :null
2021-12-24 14:37:14.701 INFO [main :c.a.n.c.naming] initializer namespace from System Property :null
2021-12-24 14:37:15.839 INFO [main :c.a.n.c.naming] [REGISTER-SERVICE] local registering service easy-console with instance Instance{instanceId='', ip='10.242.44.123', port=8096, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='null', serviceName='null', metadata={preserved.register.source=SPRING_BOOT}}
2021-12-24 14:37:16.108 INFO [main :c.a.n.c.naming] [REGISTER-SERVICE] local registering service easy-console with instance Instance{instanceId='', ip='10.242.44.123', port=8096, weight=1.0, healthy=true, enabled=true, ephemeral=true, clusterName='null', serviceName='null', metadata={preserved.register.source=SPRING_BOOT}}
复制代码
spring.profiles.active = dev
spring.application.name = nacos-client
server.port = 8090
复制代码
# Nacos注册中心
# 服务端地址
nacos.discovery.server-addr = 172.17.0.26:8848
# 是否开启自动注册
nacos.discovery.auto-register = false
# 注册分组
nacos.discovery.register.group-name = DEFAULT_GROUP
# 注册命名空间
nacos.discovery.namespace = ${spring.profiles.active}
复制代码
@NacosInjected
private NamingService namingService;
// 获取服务的所有实例
namingService.getAllInstances("easy-console");
复制代码
@Slf4j
@Component
public class HealthCheckTiming {
@NacosInjected
private NamingService namingService;
@Scheduled(cron = "0/5 * * * * ?")
public void checkHealth() {
try {
Instance instance = namingService.selectOneHealthyInstance("easy-console", true);
String url = "http://" + instance.getIp() + ":" + instance.getPort() + "/console/api/system/status";
String rsp = HttpUtil.getMethod(url);
log.info("check health, success, rsp={}", rsp);
} catch (IllegalStateException e) {
log.info("check health, failed, {}", e.getMessage());
} catch (Exception e) {
log.error("check health, failed", e);
}
}
}
复制代码
使用时需要捕获IllegalStateException
异常,从Nacos源码上看,在获取不到服务实例时,Nacos会抛出IllegalStateException
异常。
public static List selectAll(ServiceInfo serviceInfo) {
List hosts = serviceInfo.getHosts();
if (CollectionUtils.isEmpty(hosts)) {
throw new IllegalStateException("no host to srv for serviceInfo: " + serviceInfo.getName());
}
return hosts;
}
复制代码
我们发现展示的应用为unknown,看下Nacos源码,Nacos优先会读取系统属性中的project.name
作为Consumer的应用名,如果获取不到则通过启动容器来获取,仍然获取不到则使用unknown
。因此,如果需要修改Consumer展示的应用名,只需要在服务启动参数添加相应参数,比如-Dproject.name=nacos-client
。
private static final String PARAM_MARKING_PROJECT = "project.name";
private static final String PARAM_MARKING_JBOSS = "jboss.server.home.dir";
private static final String PARAM_MARKING_JETTY = "jetty.home";
private static final String PARAM_MARKING_TOMCAT = "catalina.base";
private static final String DEFAULT_APP_NAME = "unknown";
public static String getAppName() {
String appName;
appName = getAppNameByProjectName();
if (appName != null) {
return appName;
}
appName = getAppNameByServerHome();
if (appName != null) {
return appName;
}
return DEFAULT_APP_NAME;
}
private static String getAppNameByProjectName() {
return System.getProperty(PARAM_MARKING_PROJECT);
}
private static String getAppNameByServerHome() {
String serverHome = null;
if (SERVER_JBOSS.equals(getServerType())) {
serverHome = System.getProperty(PARAM_MARKING_JBOSS);
} else if (SERVER_JETTY.equals(getServerType())) {
serverHome = System.getProperty(PARAM_MARKING_JETTY);
} else if (SERVER_TOMCAT.equals(getServerType())) {
serverHome = System.getProperty(PARAM_MARKING_TOMCAT);
}
if (serverHome != null && serverHome.startsWith(LINUX_ADMIN_HOME)) {
return StringUtils.substringBetween(serverHome, LINUX_ADMIN_HOME, File.separator);
}
return null;
}
复制代码
再次启动服务,此时应用名已修改。
将Provider提供的服务下线,此时没有可用的Provider实例,我们看到Consumer端会立即感知到,此时Nacos会抛出no host to srv for serviceInfo: easy-console
。而后,重新上限该实例,我们可以看到Provider已可以正常提供服务。
[2021-12-25 16:40:40.023] [taskScheduler-2] [INFO] HealthCheckTiming.checkHealth:34 - check health, success, rsp="{"status":"online"}"
[2021-12-25 16:40:45.022] [taskScheduler-2] [INFO] HealthCheckTiming.checkHealth:34 - check health, success, rsp="{"status":"online"}"
[2021-12-25 16:40:50.004] [taskScheduler-2] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:40:55.006] [taskScheduler-1] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:41:00.001] [taskScheduler-4] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:41:05.002] [taskScheduler-4] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:41:10.006] [taskScheduler-4] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:41:15.005] [taskScheduler-4] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:41:20.006] [taskScheduler-4] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:41:25.004] [taskScheduler-2] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:41:30.004] [taskScheduler-2] [INFO] HealthCheckTiming.checkHealth:36 - check health, failed, no host to srv for serviceInfo: easy-console
[2021-12-25 16:41:35.036] [taskScheduler-3] [INFO] HealthCheckTiming.checkHealth:34 - check health, success, rsp="{"status":"online"}"
[2021-12-25 16:41:40.025] [taskScheduler-3] [INFO] HealthCheckTiming.checkHealth:34 - check health, success, rsp="{