1.引言
nacos是一个非常优秀的服务注册与发现组件,实际项目中,我们可能会用到它的常用高级特性,比如说
- 实现服务优雅上下线
- 服务领域模型
2.服务优雅上下线
在服务化架构体系下,服务优雅下线是重要且必要的一个能力。比如说线上服务升级,用户7*24小时在使用线上服务,我们不能简单粗暴的停机升级,停机意味着
- 服务不可用,影响用户体验
- 停机升级,时间成本高,时间就是金钱
因此线上服务,是不允许停机升级的。那么怎么去解决线上服务升级的问题呢?增加了新的特性、或者修复了线上bug,又不得不升级!
这个时候就显得服务化架构体系下,流量治理的重要性了。在实现流量治理的前提下,实现服务的灰度发布、或者蓝绿发布,当然这需要有一整套的服务治理体系,对研发团队的实力要求比较高!
如果我们是中小企业的研发团队,建立这样一套完整的服务治理体系,可能不太现实,可问题还是需要解决的,怎么办呢?有没有成本相对小的方案?
有,其实借助springboot actuator组件,以及服务注册发现组件Eureka、nacos,我们就可以很好的实现服务优雅下线,实现线上服务不停机升级。
这里,我们结合nacos服务注册发现,看如何来实现线上服务优雅下线。
2.1.准备环境
通过actuator组件实现线上服务下线,需要引入actuator组件依赖,以及服务注册发现组件依赖
xml
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--nacos discovery依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
默认情况下,actuator组件只开放health端点、以及info端点
json
// 20210814155152
// http://127.0.0.1:8080/actuator
{
"_links": {
"self": {
"href": "http://127.0.0.1:8080/actuator",
"templated": false
},
"health": {
"href": "http://127.0.0.1:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://127.0.0.1:8080/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://127.0.0.1:8080/actuator/info",
"templated": false
}
}
}
我们需要增加配置,开放服务下线端点
yaml
#健康检查端点配置
management:
endpoints:
web:
exposure:
#暴露所有端点
include: '*'
endpoint:
shutdown:
#激活shutdown端点
enabled: true
health:
show-details: always
2.2.优雅下线相关服务端点
引入actuator组件、nacos服务发现组件依赖后,重新启动服务,访问端点:http://127.0.0.1:8080/actuator
- service-registry端点:通过该端点请求服务上线、或者下线
- shutdown端点:通过该端点,停止服务
json
/ 20210814155436
// http://127.0.0.1:8080/actuator
{
"_links": {
...省略其它端点...
"shutdown": {
"href": "http://127.0.0.1:8080/actuator/shutdown",
"templated": false
},
...省略其它端点...
"service-registry": {
"href": "http://127.0.0.1:8080/actuator/service-registry",
"templated": false
}
}
}
启动服务follow-me-springloud-nacos-provider,在nacos控制台,我们在服务列表看到服务在线,看到操作栏显示"下线",说明此时服务在线


请求端点:http://127.0.0.1:8080/actuator/shutdown,停止服务节点应用
应用控制台,服务停止

nacos控制台服务列表,服务节点已经不在服务注册表中

2.3.完整演示服务上下线
案例演示步骤:
- 服务提供者服务:follow-me-springloud-nacos-provider,启动两个实例,分别使用8080、8081端口
- 服务消费者服务:follow-me-springloud-nacos-consumer,启动一个实例,使用8090端口
- 服务消费者调用服务提供者,通过ribbon实现负载均衡,使用默认轮询策略
- 正常启动服务,请求服务消费者端点:http://127.0.0.1:8090/consumer/test,观察流量会轮询打到8080、8081实例
- 请求端点:http://127.0.0.1:8080/actuator/service-registry?status=DOWN,将服务提供者8080实例下线
- 再次请求服务消费者端点:http://127.0.0.1:8090/consumer/test,观察流量只会打到8081实例
2.3.1.直接通过端点实现服务下线
服务都正常启动后,请求服务消费者端点:http://127.0.0.1:8090/consumer/test
服务消费者控制台

服务提供者8080实例控制台

服务提供者8081实例控制台

下线服务提供者8080实例,请求端点:http://127.0.0.1:8080/actuator/service-registry?status=DOWN

再次请求服务消费者端点:http://127.0.0.1:8090/consumer/test
服务消费者控制台

服务提供者8080实例控制台

服务提供者8081实例控制台

2.3.2.nacos控制台实现服务上下线
通过上面案例中service-registry端点实现服务上下线
- 服务上线:http://127.0.0.1:8080/actuator/service-registry?status=UP
- 服务下线:http://127.0.0.1:8080/actuator/service-registry?status=DOWN
如果我们使用的服务注册发现组件是Eureka,那就只能通过service-retistry端点来实现服务上下线控制了。
如果使用nacos,则方便了很多,通过nacos管理控制台面板,即可控制服务的上下线,比如通过控制台面板,我们再将8081服务提供者实例下线
下线前

下线后

请求服务消费者端点:http://127.0.0.1:8090/consumer/test,报错!因为已经没有可用的服务提供者节点了!
shell
2021-08-14 16:35:32.790 ERROR 10012 --- [nio-8090-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No instances available for follow-me-springloud-nacos-provider] with root cause

2.3.3.项目实践建议
在实际项目中,要实现线上服务不停机升级,比如说有a/b/c三个节点实例,可以分步实现
- 将a节点下线,升级完成上线
- 将b节点下线,升级完成上线
- 将c节点下线,升级完成上线
这样一来,即实现了线上服务不停机升级,且技术成本不高,正好借助springboot actuator组件、ribbon组件、服务注册发现组件完美整合,实现流量切分,实现灰度发布。
2.服务领域模型
nacos提供的服务领域模型,很好的满足实现了服务之间的隔离,领域模型范围从大到下依次是
- 命名空间
- 分组
- 服务
- 集群
- 实例
用一个图来描述,是这样的

通过命名空间,可以实现不同环境的隔离,类似与profile特性,比如说:生产环境、测试环境、开发环境之间的隔离。
分组、服务是逻辑上的概念,暂时还没有用。
集群可用于实现同城双活、异地容灾。比如说为了实现异地容灾,将服务部署到了两个城市的两个机房中
- 广州、深圳都部署了完整的服务集群,实现异地容灾
- 这个时候,为了用户体验,以及性能,我们需要实现同集群中服务优先调用
3.1.环境之间隔离
nacos提供了默认的名称空间public

我们可以创建新的名称空间,我创建两个名称空间,分别表示开发环境、生产环境
- 开发环境:dev
- 生产环境:prod

将服务提供者follow-me-springloud-nacos-provider,配置为生产环境
yaml
spring:
application:
name: follow-me-springloud-nacos-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#配置名称空间 prod,配置值命名空间Id
namespace: 206760b2-a6f4-4da3-bbe1-8c4c75dc7b2e
启动服务提供者,它注册prod环境下

将服务消费者follow-me-springloud-nacos-consumer,配置为开发环境
yaml
spring:
application:
name: follow-me-springloud-nacos-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#配置名称空间 dev,配置值命名空间Id
namespace: 1a500714-6fdc-410c-8743-ae30d0edf92e
启动服务消费者,它注册在dev环境下

此时,请求服务消费者端点:http://127.0.0.1:8090/consumer/test,报错!因为服务消费者,与服务提供者不在一个命名空间下,彼此之间隔离

3.2.同集群优先调用
演示完成不同命名空间隔离,我们将服务提供者,与服务消费者都恢复到dev环境,来看同集群优先调用。
增加集群配置
- 服务提供者8080实例,部署到广州机房,即cluster-name:GZ
- 服务提供者8081实例,部署到深圳机房,即cluster-name:SZ
- 服务消费者实例,部署到广州机房,即cluster-name:GZ
配置案例,其它实例可参考修改配置
yaml
spring:
application:
name: follow-me-springloud-nacos-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#配置名称空间 prod,配置值命名空间Id
#namespace: 206760b2-a6f4-4da3-bbe1-8c4c75dc7b2e
#配置名称空间 dev
namespace: 1a500714-6fdc-410c-8743-ae30d0edf92e
#服务提供者8080实例,部署在广州机房
cluster-name: GZ
配置ribbon负载均衡策略
将ribbon默认的轮询负载均衡策略,指定服务提供者配置为nacos提供的NacosRule,NacosRule实现了同机房优先调用
yaml
#ribbon配置
ribbon:
eager-load:
#开启ribbon饥饿加载
enabled: true
#服务提供者,使用nacos提供的负载均衡策略:同集群优先调用
follow-me-springloud-nacos-provider:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
启动服务提供者8080实例,它在GZ机房
启动服务提供者8081实例,它在SZ机房
启动服务消费者实例,它在GZ机房,并访问端点:http://127.0.0.1:8090/consumer/test,观察到一直调用到服务提供者8080实例

下线服务提供者8080实例

再次请求服务消费者端点:http://127.0.0.1:8090/consumer/test,观察到此时会调用到服务提供者8081,部署在SZ机房的实例
