欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot實(shí)現(xiàn)動(dòng)態(tài)端口切換黑魔法

 更新時(shí)間:2024年12月17日 09:14:33   作者:小小工匠  
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何實(shí)現(xiàn)動(dòng)態(tài)端口切換黑魔法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

關(guān)鍵技術(shù)點(diǎn)

利用 Spring Boot 內(nèi)嵌 Servlet 容器 和 動(dòng)態(tài)端口切換 的方式實(shí)現(xiàn)平滑更新的方案,關(guān)鍵技術(shù)點(diǎn)如下:

Servlet 容器重新綁定端口:Spring Boot 使用 ServletWebServerFactory 動(dòng)態(tài)設(shè)置新端口。

零停機(jī)切換:通過(guò)先啟動(dòng)備用服務(wù)、釋放主端口,再切換新服務(wù)到主端口,實(shí)現(xiàn)服務(wù)的無(wú)縫切換。

端口檢測(cè)和進(jìn)程終止:使用 ServerSocket 和系統(tǒng)命令來(lái)檢測(cè)和操作端口。

這種設(shè)計(jì)允許服務(wù)在不完全停止的情況下切換到更新的版本,從而極大地縮短了不可用時(shí)間,實(shí)現(xiàn)了接近于零停機(jī)的效果。

核心原理

1.內(nèi)嵌 Tomcat 容器動(dòng)態(tài)啟動(dòng):

使用 TomcatServletWebServerFactory 實(shí)現(xiàn)容器的動(dòng)態(tài)創(chuàng)建和啟動(dòng)。

動(dòng)態(tài)綁定 DispatcherServlet 通過(guò) ServletContextInitializer 集合完成 Servlet 注冊(cè)。

2.端口檢查和動(dòng)態(tài)切換:

通過(guò) ServerSocket 判斷端口是否占用。

如果占用,則先用備用端口啟動(dòng)新服務(wù),再通過(guò)關(guān)閉老服務(wù)釋放主端口,最后切換新服務(wù)到主端口。

3.運(yùn)行時(shí)自動(dòng)處理:

利用 Runtime.exec 執(zhí)行系統(tǒng)命令,釋放端口并終止舊進(jìn)程。

在極短時(shí)間內(nèi)完成新舊服務(wù)切換,避免長(zhǎng)時(shí)間的停機(jī)。

Code

package com.artisan;

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.ServletContextInitializerBeans;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.Collections;

@SpringBootApplication()
public class BootMainApplication {
    public static void main(String[] args) {
        // 默認(rèn)端口設(shè)置
        int defaultPort = 8080;
        // 備選端口設(shè)置
        int alternativePort = 9090;
        // 檢查默認(rèn)端口是否已被占用
        boolean isPortOccupied = isPortInUse(defaultPort);

        // 動(dòng)態(tài)端口分配
        int portToUse = isPortOccupied ? alternativePort : defaultPort;
        // 創(chuàng)建Spring Boot應(yīng)用實(shí)例
        SpringApplication app = new SpringApplication(WebMainApplication2.class);
        // 設(shè)置端口配置
        app.setDefaultProperties(Collections.singletonMap("server.port", portToUse));
        // 運(yùn)行應(yīng)用并獲取上下文
        ConfigurableApplicationContext context = app.run(args);

        // 如果默認(rèn)端口被占用,則嘗試切換回默認(rèn)端口
        if (isPortOccupied) {
            switchToDefaultPort(context, defaultPort, portToUse);
        }
    }

    /**
     * 切換到默認(rèn)端口
     *
     * 當(dāng)默認(rèn)端口被其他進(jìn)程占用時(shí),此方法嘗試釋放該端口,并啟動(dòng)一個(gè)新的Web服務(wù)器實(shí)例綁定到默認(rèn)端口
     * 同時(shí),它會(huì)停止當(dāng)前的Web服務(wù)器實(shí)例
     *
     * @param context 當(dāng)前應(yīng)用上下文,用于訪問(wèn)Web服務(wù)器工廠和停止當(dāng)前Web服務(wù)器
     * @param defaultPort 默認(rèn)端口號(hào),希望切換到的目標(biāo)端口
     * @param currentPort 當(dāng)前Web服務(wù)器正在使用的端口號(hào)
     */
    private static void switchToDefaultPort(ConfigurableApplicationContext context, int defaultPort, int currentPort) {
        try {
            // 釋放默認(rèn)端口
            terminateProcessUsingPort(defaultPort);

            // 等待端口釋放
            while (isPortInUse(defaultPort)) {
                Thread.sleep(100);
            }

            // 啟動(dòng)新容器綁定默認(rèn)端口
            ServletWebServerFactory webServerFactory = getWebServerFactory(context);
            ((TomcatServletWebServerFactory) webServerFactory).setPort(defaultPort);

            WebServer newServer = webServerFactory.getWebServer(getServletContextInitializers(context));
            newServer.start();

            // 停止當(dāng)前容器
            ((ServletWebServerApplicationContext) context).getWebServer().stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 檢查指定的端口是否正在使用
     *
     * @param port 要檢查的端口號(hào)
     * @return 如果端口正在使用,則返回true;否則返回false
     */
    private static boolean isPortInUse(int port) {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            // 如果能夠成功創(chuàng)建ServerSocket實(shí)例,說(shuō)明端口可用,返回false
            return false;
        } catch (IOException e) {
            // 如果創(chuàng)建ServerSocket實(shí)例時(shí)拋出IOException,說(shuō)明端口已被占用,返回true
            return true;
        }
    }

    /**
     * 終止使用指定端口的進(jìn)程
     *
     * @param port 需要釋放的端口號(hào)
     * @throws IOException 如果執(zhí)行命令發(fā)生錯(cuò)誤
     * @throws InterruptedException 如果線程被中斷
     */
    private static void terminateProcessUsingPort(int port) throws IOException, InterruptedException {
        // 構(gòu)建終止使用指定端口的進(jìn)程的命令
        String command = String.format("lsof -i :%d | grep LISTEN | awk '{print $2}' | xargs kill -9", port);
        // 執(zhí)行命令并等待命令執(zhí)行完成
        Runtime.getRuntime().exec(new String[]{"sh", "-c", command}).waitFor();
    }

    /**
     * 獲取ServletContextInitializer實(shí)例
     * 該方法用于將Spring應(yīng)用上下文中的所有ServletContextInitializerBeans實(shí)例
     * 轉(zhuǎn)換為ServletContextInitializer接口的實(shí)現(xiàn),以便在應(yīng)用啟動(dòng)時(shí)初始化ServletContext
     *
     * @param context Spring的應(yīng)用上下文,用于獲取BeanFactory
     * @return 返回一個(gè)實(shí)現(xiàn)了ServletContextInitializer接口的實(shí)例
     */
    private static ServletContextInitializer getServletContextInitializers(ConfigurableApplicationContext context) {
        // 使用ApplicationContext中的BeanFactory創(chuàng)建ServletContextInitializerBeans實(shí)例
        // 這里將ServletContextInitializerBeans作為ServletContextInitializer的實(shí)現(xiàn)類(lèi)返回
        // ServletContextInitializerBeans將會(huì)負(fù)責(zé)收集應(yīng)用上下文中所有ServletContextInitializer的實(shí)現(xiàn)
        // 并在應(yīng)用啟動(dòng)時(shí)依次調(diào)用它們的onStartup方法來(lái)初始化ServletContext
        return (ServletContextInitializer) new ServletContextInitializerBeans(context.getBeanFactory());
    }

    /**
     * 獲取Servlet Web服務(wù)器工廠
     *
     * @param context 可配置的應(yīng)用上下文,用于獲取Bean工廠
     * @return ServletWebServerFactory實(shí)例,用于配置和創(chuàng)建Web服務(wù)器
     */
    private static ServletWebServerFactory getWebServerFactory(ConfigurableApplicationContext context) {
        // 從應(yīng)用上下文中獲取Bean工廠,并從中獲取ServletWebServerFactory實(shí)例
        return context.getBeanFactory().getBean(ServletWebServerFactory.class);
    }
}

測(cè)試

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController()
@RequestMapping("port/")
public class TestPortController {
    @GetMapping("test")
    public String test() {
        return "artisan-old";
    }
}

啟動(dòng)后,訪問(wèn) http://localhost:8080/port/test

修改TestPortController 的返回值, 打個(gè)jar包, 啟動(dòng)新的jar包,

重新訪問(wèn) http://localhost:8080/port/test ,觀察返回結(jié)果是否是修改后的返回值

到此這篇關(guān)于SpringBoot實(shí)現(xiàn)動(dòng)態(tài)端口切換黑魔法的文章就介紹到這了,更多相關(guān)SpringBoot動(dòng)態(tài)端口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論