SpringBoot 中 Tomcat 添加 AJP 协议 Connector 和优雅停机

2.2.4.RELEASE 版本

  1. spring-boot 版本设置为 2.2.4.RELEASE.
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
  1. 新增 ajpConnector 配置。
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AjpAutoConfig {
    private int ajpPort = 38001;

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainer() {
        return tomcat -> {
            if (tomcat instanceof TomcatServletWebServerFactory) {
                Connector ajpConnector = new Connector("AJP/1.3");
                ajpConnector.setPort(ajpPort);
                tomcat.addAdditionalTomcatConnectors(ajpConnector);
            }
        };
    }
}

启动服务,启动日志如下所示:

启动日志
  1. 通过 CPing 测试 ajpConnector 配置是否已经生效。

Maven 依赖:

    <!-- https://mvnrepository.com/artifact/com.github.jrialland/ajpclient -->
    <dependency>
        <groupId>com.github.jrialland</groupId>
        <artifactId>ajpclient</artifactId>
        <version>1.11</version>
    </dependency>

测试代码:

package com.github.jrialland.ajpclient;

import com.github.jrialland.ajpclient.pool.Channels;
import io.netty.channel.Channel;

import java.util.concurrent.TimeUnit;

public class CPingDemo {

    public static void main(String[] args) throws Exception {
        //get a tcp connection
        final Channel channel = Channels.connect("localhost", 38001);
        //will try a cping/cpong exchange on the opened tcp connection
        boolean success = new CPing(2, TimeUnit.SECONDS).execute(channel);
        System.out.println(success);
    }
}

运行结果如下图所示:

CPing 测试结果

优雅关闭 AJP Connector

  1. 新增一个 DelayShutdown,来模拟关闭耗时。
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class DelayShutdown implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        log.info("start destroy....");

        for (int i = 1; i < 20; ++ i) {
            log.info("this is {} count.", i);
            Thread.sleep(1000);
        }

        log.info("end destroy....");
    }
}

从关闭 Spring 容器日志可以看出,在关闭过程中,CPing 请求还是会返回对应 CPong 响应。

说明关闭 Spring 容器过程中, ajpConnector 还可以接收请求。

关闭 Spring 容器日志
  1. 新增 /shutdown 接口,来优雅关闭 ajpConnector
@Slf4j
@RestController
public class DemoController {

    @Autowired
    private ApplicationContext context;
    @Autowired
    private TomcatServletWebServerFactory tomcatServletWebServerFactory;

    @GetMapping("shutdown")
    public void shutdown() throws Exception {
        Connector ajpConnector = tomcatServletWebServerFactory.getAdditionalTomcatConnectors().get(0);
        close(ajpConnector);
        Thread.sleep(1000 * 10);
        SpringApplication.exit(context);
    }

    private void close(Connector connector) {
        connector.pause();
        connector.getProtocolHandler().closeServerSocketGraceful();
    }
}
  • pause() 方法:
    • (1)设置 Endpointpaused 状态为 true
    • (2)AcceptorEndpointpaused=true 时不再建立新的 socket 链接(即不再建立新的 socket 连接)
    • (3)AjpProcessorEndpointpaused=true 时,service() 不再处理当前 socket 的新请求,会在上一个请求处理完毕后关闭当前 socket 链接(即历史 socket 不再接受新的请求)。
  • closeServerSocketGraceful() 方法:
    • 因为其 EndpointbindState 等于 BOUND_ON_INIT,所以 do nothing
关闭 Spring 容器日志

从关闭 Spring 容器日志可以看出,在关闭过程中,CPing 请求不会再返回对应 CPong 响应。从而实现在关闭 Spring 容器过程中不能接受新请求。

2.3.10.RELEASE 版本

  1. AjpAutoConfig 调整。
@Configuration
public class AjpAutoConfig {
    private int ajpPort = 38001;

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainer() {
        return tomcat -> {
            if (tomcat instanceof TomcatServletWebServerFactory) {
                Connector ajpConnector = new Connector("AJP/1.3");
                ajpConnector.setPort(ajpPort);
                ((AbstractAjpProtocol) ajpConnector.getProtocolHandler()).setSecretRequired(false); // 2.3.10.RELEASE 新增
                tomcat.addAdditionalTomcatConnectors(ajpConnector);
            }
        };
    }
}
  1. 优雅关机通过 application.properties 中配置即可实现:
## 开启优雅停机, 如果不配置是默认IMMEDIATE, 立即停机
server.shutdown=graceful
## 优雅停机宽限期时间
spring.lifecycle.timeout-per-shutdown-phase=20s

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容