SpringBoot 服务升级零停机的方案

设计思路

  1. 检查有没有端口占用
  2. 如果有端口占用可以先使用其他端口及逆行启动
  3. 等到启动完毕后终止老进程
  4. 重新创建容器并关联DispatcherServlet

先看代码

package com.test.port;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;


/**
 * spring boot 实现代码更新,零停机更新
 *
 * @author 邢鑫
 * @date 2024/09/12
 * @Description 实现springboot 代码更新零停机更新的思路主要分为以下4步骤
 * 1.检查有没有端口占用
 * 2.如果有占用可以先使用其他端口进行启动
 * 3.等待关闭老版本的进程
 * 4.重新创建新的容器,并且关联老版本容器 DispatcherServlet
 */

@SpringBootApplication
@EnableScheduling
public class Main {

    public static void main(String[] args) {
        String[] newArgs = args; //
        int defaultPort = 8080;    // 对应老版本的启动端口
        boolean needChangePort = false;
        //判断是否有端口占用
        if (isPortInUse(defaultPort)) {
            newArgs = new String[args.length + 1];
            System.arraycopy(args, 0, newArgs, 0, args.length);
            newArgs[newArgs.length - 1] = "--server.port=" + 9090;   //设置临时端口
            needChangePort = true;  //
        }
        //这里先用临时端口先把程序启动起来
        ConfigurableApplicationContext run = SpringApplication.run(Main.class, newArgs);

        if (needChangePort) {
            String command = String.format("lsof -i :%d | grep LISTEN | awk '{print $2}' | xargs kill -9", defaultPort);
            try {
                Runtime.getRuntime().exec(new String[]{"sh", "-c", command}).waitFor();
                while (isPortInUse(defaultPort)) {
                }
                ServletWebServerFactory servletWebServerFactory = getServletWebServerFactory(run);
                ((TomcatServletWebServerFactory) servletWebServerFactory).setPort(defaultPort);
                WebServer webServer = servletWebServerFactory.getWebServer(invokeSelfInitialize((ServletWebServerApplicationContext) run));
                webServer.start();
                ((ServletWebServerApplicationContext) run).getWebServer().stop();
            } catch (IOException | InterruptedException e) {
                throw new RuntimeException(e);
            }
        }


    }

    /**
      * 反射调用 ServletWebServerApplicationContext 类中的私有方法 getSelfInitializer,
      * 以获取 ServletContextInitializer 对象
      */
    private static ServletContextInitializer invokeSelfInitialize(ServletWebServerApplicationContext context) {
        try {

            Method method = ServletWebServerApplicationContext.class.getDeclaredMethod("getSelfInitializer");
            method.setAccessible(true);
            return (ServletContextInitializer) method.invoke(context);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 判断端口是否占用
     *
     * @param port 是否占用的目标端口
     * @return  如果占用,返回true,如果没有占用返回false
     */
    private static boolean isPortInUse(int port) {
        try (ServerSocket ignored = new ServerSocket(port)) {
            return false;
        } catch (Exception e) {
            return true;
        }
    }


    private static ServletWebServerFactory getServletWebServerFactory(ConfigurableApplicationContext context) {
        String[] beanNames = context.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        return context.getBean(beanNames[0], ServletWebServerFactory.class);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Monkey@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值