spring boot + Eureka如何平滑上下线服务

目录

“ 系统正常运行,如果要新发版程序,如何保证程序平滑上线,不影响前端的请求?使用Eureka作为注册中心时,会有哪些地方会导致新服务上下线延迟?如何优化并解决服务的正常上下线?”

技术版本信息:

spring-boot-starter-parent-2.2.4-RELEASE
spring-boot-starter-web-2.2.4-RELEASE
spring-boot-starter-undertow-2.2.4-RELEASE
spring-cloud-starter-netflix-eureka-server-2.2.1.RELEASE
spring-cloud-starter-netflix-eureka-client-2.2.1.RELEASE
spring-boot-starter-openfeign-2.2.1-RELEASE
spring-cloud.version-Hoxton.SR1

服务平滑上下线-单机版

场景描述:
对于传统的单机JAVA WEB程序部署新的服务,就是停止当前的服务,然后部署新的服务,这样的操作会导致一个问题,就是在旧服务下线前,新服务上线前的这段时间,服务是不可用的,而且在服务下线前,如果当前还有请求没有执行完毕,也有可能会被异常中止。

可能有些同学会想到,部署多个服务在一个SLB下,暴露SLB供前端调用,这样后端就可以部署好了新的服务之后,然后在停掉旧的服务。这个解决办法,的确可以保证服务的上下线之间的时差为0,但是这个办法还不能解决当前旧服务还有部分请求没有执行完毕,就被中止的问题。
对于上述问题,还有些聪明的同学会思考到,是不是可以监控容器中是否还有请求的方式,保证容器中所有请求都执行完毕之后,在执行服务停止呢?
答案,当然是可以的。那具体如何操作呢?接下来就以undewtow容器作为例子给大家演示一下监听方法。

解决方案:

1、引入undertow容器pom文件,这里需要去掉web自带的tomcat容器

<!-- 移除掉默认支持的 Tomcat -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.2.4.RELEASE</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    
    <!-- 添加 undertow -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>

2、增加undertow配置文件


@Configuration
public class UndertowConfig {
    @Autowired
    private UndertowGracefulShutdownWrapper gracefulShutdownWrapper;

    @Bean
    public UndertowServletWebServerFactory servletWebServerFactory() {
        UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
        factory.addDeploymentInfoCustomizers(deploymentInfo -> deploymentInfo.addOuterHandlerChainWrapper(gracefulShutdownWrapper));
        factory.addBuilderCustomizers(builder -> builder.setServerOption(UndertowOptions.ENABLE_STATISTICS, true));
        return factory;
    }
}

3、增加undertow操作程序链类

@Component
public class UndertowGracefulShutdownWrapper implements HandlerWrapper {

    private GracefulShutdownHandler gracefulShutdownHandler;

    @Override
    public HttpHandler wrap(HttpHandler handler) {
        if (null == gracefulShutdownHandler) {
            this.gracefulShutdownHandler = new GracefulShutdownHandler(handler);
        }
        return gracefulShutdownHandler;
    }

    public GracefulShutdownHandler getGracefulShutdownHandler() {
        return gracefulShutdownHandler;
    }
}

4、增加undertow优雅停止核心处理类

@Component
public class UndertowGracefulShutdown implements ApplicationListener<ContextClosedEvent> {

    private static final Logger LOGGER = LoggerFactory.getLogger(UndertowGracefulShutdown.class);

    @Autowired
    private UndertowGracefulShutdownWrapper gracefulShutdownWrapper;

    @Autowired
    private ServletWebServerApplicationContext context;

    @Override
    public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
        gracefulShutdownWrapper.getGracefulShutdownHandler().shutdown();
        try {
            UndertowServletWebServer servletContainer = (UndertowServletWebServer)context.getWebServer();
            Field field = servletContainer.getClass().getDeclaredField("undertow");
            field.setAccessible(true);

            // API活跃连接统计
            Undertow undertow = (Undertow) field.get(servletContainer);
            List<Undertow.ListenerInfo> listenerInfo = undertow.getListenerInfo();
            Undertow.ListenerInfo listener = listenerInfo.get(0);
            ConnectorStatistics connectorStatistics = listener.getConnectorStatistics();
            // 每隔1秒检测是否已经处理完停止服务之前接收的request
            while (!gracefulShutdownWrapper.getGracefulShutdownHandler().awaitShutdown(1000)) {
                if (null != connectorStatistics) {
                    LOGGER.info("Can't shutdown undertow, requests still processing. And there are {} activeConnections...", connectorStatistics.getActiveConnections());
                } else {
                    LOGGER.info("Can shutdown undertow.");
                }
            }
        } catch (Exception e) {
            LOGGER.error("undertow graceful shutdown error!", e);
        }
    }

undertow官网文档

服务平滑上下线-微服务版

场景描述:

在微服务的环境下,spring boot 常与 Eureka注册中心一起使用,从CAP理论来分析Eureka注册中心,它是一个满足AP模式的注册中心,底层通过Eureka服务端以及客户端的缓存的方式来保证服务的高可用性。本文主要以spring boot+openfegin+eureka来演示,微服务环境下,服务平滑上下线的问题以及优化方案。

下面简单先来描述一下Eureka服务的注册方式,客户端启动之后会发送一条注册的指令到服务端,服务端收到指令之后,刷新本地的服务列表信息,客户端默认30秒一次,来获取服务列表信息,这里使用的openfeign默认是在集成ribbon组件上,并且ribbon每次选择负载均衡的目的机器信息时,是从ribbon缓存中获取,所以总结一下整个微服务架构环境中,存在Eureka客户端缓存、ribbon缓存、服务端缓存,具体缓存见下图:
在这里插入图片描述

通过上述分析可以简单统计一下服务正常上下线需要多久时间:
在这里插入图片描述
优化方案:

1、增加undertow优雅停止方法,见第一点单机版解决方案

2、增加eureka client下线接口类,执行服务下线前调用一下,等待服务过期时间(比如优化后需要等待10秒),然后在停止服务


 @Resource
 private EurekaClient eurekaClient;
 @PostMapping("/eureka/stop/client")
  public ResponseEntity stopEurekaClient(HttpServletRequest request) {
      eurekaClient.shutdown();
      return ResponseEntity.ok("ok");
  }

3、优化eureka client 、eureka server、ribbon的参数

#eureka server端
eureka:
  server:
    #取消二级缓存
    use-read-only-response-cache: false 
    #集群里eureka节点的变化信息更新的时间间隔,默认60*10*1000
    peer-eureka-nodes-update-interval-ms: 10000 

#eureka client端
eureka:
  client:
  #eureka client间隔多久去拉取服务注册信息,默认为30秒
    registry-fetch-interval-seconds: 5
 
#ribbon本地服务列表缓存时间,默认30S
ribbon:       
  ServerListRefreshInterval: 5000 

在这里插入图片描述
在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐