今天聊聊正常关闭服务时如何让微服务优雅下线。程序员
为何说是优雅下线?咱们知道在分布式应用中为了知足CAP原则中的A(可用性),像Nacos、Eureka等注册中心的客户端都会进行实例列表的缓存。当正常关闭应用时,虽然能够主动调用注册中心进行注销,但这些客户端缓存的实例列表仍是要等一段时间才会失效。web
上述状况就有可能致使服务请求到已经被关闭的实例上,虽然经过重试机制能够解决掉这个问题,但这种解决方案会出现重试,在必定程度上会致使用户侧请求变慢。这时就须要进行优雅的下线操做了。面试
下面咱们先从一般关闭进程的几种方式聊起。spring
Spring Cloud自己对关闭服务是有支持的,当经过kill命令关闭进程时会主动调用Shutdown hook来进行当前实例的注销。使用方式:json
kill Java进程ID
这种方式是借助Spring Cloud的Shutdown hook机制(本质是Spring Boot提供,Spring Cloud服务发现功能进行具体注销实现),在关闭服务以前会对Nacos、Eureka等服务进行注销。但这个注销只是告诉了注册中心,客户端的缓存可能须要等几秒(Nacos默认为5秒)以后才能感知到。缓存
这种Shutdown hook机制不只适用于kill命令,还适用于程序正常退出、使用System.exit()、终端使用Ctrl + C等。但不适用于kill -9 这样强制关闭或服务器宕机等场景。服务器
这种方案虽然比直接挂掉要等15秒缩短了时间,相对好一些,但本质上并无解决客户端缓存的问题,不建议使用。app
在Spring Boot中,提供了/shutdown端点,基于此也能够实现优雅停机,但本质上与第一种方式相同,都是基于Shutdown hook来实现的。在处理完基于Shutdown hook的逻辑以后,也会进行服务的关闭,但一样面临客户端缓存的问题,所以,也不推荐使用。curl
这种方式首先须要在项目中引入对应的依赖:分布式
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
而后在项目中配置开启/shutdown端点:
management: endpoint: shutdown: enabled: true endpoints: web: exposure: include: shutdown
而后停服时请求对应的端点,这里采用curl命令示例:
curl -X http://实例服务地址/actuator/shutdown
Spring Boot一样提供了/pause端点(Spring Boot Actuator提供),经过/pause端点,能够将/health为UP状态的实例修改成Down状态。
基本操做就是在配置文件中进行pause端点的开启:
management: endpoint: # 启用pause端点 pause: enabled: true # pause端点在某些版本下依赖restart端点 restart: enabled: true endpoints: web: exposure: include: pause,restart
而后发送curl命令,便可进行服务的终止。注意这里须要采用POST请求。
关于/pause端点的使用,不一样的版本差别很大。笔者在使用Spring Boot 2.4.2.RELEASE版本时发现根本没法生效,查了Spring Boot和Spring Cloud项目的Issues发现,这个问题从2.3.1.RELEASE就存在。目前看应该是在最新版本中Web Server的管理改成SmartLifecycle的缘由,而Spring Cloud对此貌似放弃了支持(有待考察),最新的版本调用/pause端点无任何反应。
鉴于上述版本变更过大的缘由,不建议使用/pause端点进行微服务的下线操做,但使用/pause端点的整个思路仍是值得借鉴的。
基本思路就是:当调用/pause端点以后,微服务的状态会从UP变为DOWN,而服务自己仍是能够正常提供服务。当微服务被标记为DOWN状态以后,会从注册中心摘除,等待一段时间(好比5秒),当Nacos客户端缓存的实例列表更新了,再进行停服处理。
这个思路的核心就是:先将微服务的流量切换掉,而后再关闭或从新发布。这就解决了正常发布时客户端缓存实例列表的问题。
基于上述思路,其实本身也能够实现相应的功能,好比提供一个Controller,先调用该Controller中的方法将当前实例从Nacos中注销,而后等待5秒,再经过脚本或其余方式将服务关闭掉。
方式三中提到的方案若是Spring Cloud可以直接支持,那就更好了。这不,Spring Cloud提供了/service-registry端点。但从名字就能够知道专门针对服务注册实现的一个端点。
在配置文件中开启/service-registry端点:
management: endpoints: web: exposure: include: service-registry base-path: /actuator endpoint: serviceregistry: enabled: true
访问端点能够查看到开启了以下端点:
{ "_links": { "self": { "href": "http://localhost:8081/actuator", "templated": false }, "serviceregistry": { "href": "http://localhost:8081/actuator/serviceregistry", "templated": false } } }
经过curl命令来进行服务状态的修改:
curl -X "POST" "http://localhost:8081/actuator/serviceregistry?status=DOWN" -H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"
执行上述命令以前,查看Nacos对应实例状态为:
能够看到实例详情中的按钮为“下线”也就是说目前处于UP状态。当执行完上述curl命令以后,实例详情中的按钮为“上线”,说明实例已经下线了。
上述命令就至关于咱们在Nacos管理后台手动的操做了实例的上下线。
固然,上述状况是基于Spring Cloud和Nacos的模式实现的,本质上Spring Cloud是定义了一个规范,好比全部的注册中心都须要实现ServiceRegistry接口,同时基于ServiceRegistry这个抽象还定义了通用的Endpoint:
@Endpoint(id = "serviceregistry") public class ServiceRegistryEndpoint { private final ServiceRegistry serviceRegistry; private Registration registration; public ServiceRegistryEndpoint(ServiceRegistry<?> serviceRegistry) { this.serviceRegistry = serviceRegistry; } public void setRegistration(Registration registration) { this.registration = registration; } @WriteOperation public ResponseEntity<?> setStatus(String status) { Assert.notNull(status, "status may not by null"); if (this.registration == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found"); } this.serviceRegistry.setStatus(this.registration, status); return ResponseEntity.ok().build(); } @ReadOperation public ResponseEntity getStatus() { if (this.registration == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found"); } return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration)); } }
咱们上面调用的Endpoint即是经过上面代码实现的。因此不只Nacos,只要基于Spring Cloud集成的注册中心,本质上都是支持这种方式的服务下线的。
不少项目都逐步在进行微服务化改造,但一旦由于微服务系统,将面临着更复杂的状况。本篇文章重点基于Nacos在Spring Cloud体系中优雅下线来为你们剖析了一个微服务实战中常见的问题及解决方案。你是否在使用微服务,你又是否注意到这一点了?想学更多微服务实战,啥也不说,关注吧。
最近我整理了整套《JAVA核心知识点总结》,说实话 ,做为一名Java程序员,不论你需不须要面试都应该好好看下这份资料。拿到手老是不亏的~个人很多粉丝也所以拿到腾讯字节快手等公司的Offer
进【Java进阶之路群】,找管理员获取哦-!