Key technical points
Using Spring Boot embedded Servlet containers and dynamic port switching to achieve a smooth update solution. The key technical points are as follows:
Servlet container rebind port: Spring Boot uses ServletWebServerFactory to dynamically set up new ports.
Zero downtime switching: seamless service switching is achieved by first starting the backup service, releasing the main port, and then switching to the new service to the main port.
Port Detection and Process Termination: Use ServerSocket and system commands to detect and operate ports.
This design allows services to switch to a newer version without completely stopping, greatly reducing unavailability time and achieving near zero downtime.
Core Principle
1. Dynamic startup of the embedded Tomcat container:
Use TomcatServletWebServerFactory to implement dynamic creation and startup of containers.
Dynamic binding DispatcherServlet Complete Servlet registration through the ServletContextInitializer collection.
2. Port check and dynamic switching:
Use ServerSocket to determine whether the port is occupied.
If it is occupied, first use the alternate port to start the new service, then release the main port by closing the old service, and finally switch to the new service to the main port.
3. Automatic processing during runtime:
Use system commands to release the port and terminate the old process.
Complete the switching between old and new services in a very short time to avoid long-term downtime.
Code
package ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; @SpringBootApplication() public class BootMainApplication { public static void main(String[] args) { // Default port settings int defaultPort = 8080; // Alternative port settings int alternativePort = 9090; // Check whether the default port has been occupied boolean isPortOccupied = isPortInUse(defaultPort); // Dynamic port allocation int portToUse = isPortOccupied ? alternativePort : defaultPort; // Create Spring Boot application instance SpringApplication app = new SpringApplication(); // Set port configuration (("", portToUse)); // Run the application and get the context ConfigurableApplicationContext context = (args); // If the default port is occupied, try to switch back to the default port if (isPortOccupied) { switchToDefaultPort(context, defaultPort, portToUse); } } /** * Switch to default port * * When the default port is occupied by other processes, this method tries to release the port and starts a new web server instance to bind to the default port * At the same time, it will stop the current web server instance * * @param context Current application context, used to access the web server factory and stop the current web server * @param defaultPort default port number, the target port you want to switch to * @param currentPort The port number currently being used by the web server */ private static void switchToDefaultPort(ConfigurableApplicationContext context, int defaultPort, int currentPort) { try { // Release the default port terminateProcessUsingPort(defaultPort); // Wait for the port to be released while (isPortInUse(defaultPort)) { (100); } // Start a new container to bind the default port ServletWebServerFactory webServerFactory = getWebServerFactory(context); ((TomcatServletWebServerFactory) webServerFactory).setPort(defaultPort); WebServer newServer = (getServletContextInitializers(context)); (); // Stop the current container ((ServletWebServerApplicationContext) context).getWebServer().stop(); } catch (Exception e) { (); } } /** * Check whether the specified port is in use * * @param port The port number to be checked * @return Return true if the port is in use; otherwise return false */ private static boolean isPortInUse(int port) { try (ServerSocket serverSocket = new ServerSocket(port)) { // If you can successfully create a ServerSocket instance, it means that the port is available and return false return false; } catch (IOException e) { // If an IOException is thrown when creating a ServerSocket instance, it means that the port has been occupied and returns true return true; } } /** * Terminate the process using the specified port * * @param port The port number that needs to be released * @throws IOException If an error occurs when executing the command * @throws InterruptedException if thread is interrupted */ private static void terminateProcessUsingPort(int port) throws IOException, InterruptedException { // Build a command to terminate a process using the specified port String command = ("lsof -i :%d | grep LISTEN | awk '{print $2}' | xargs kill -9", port); // Execute the command and wait for the command to be executed ().exec(new String[]{"sh", "-c", command}).waitFor(); } /** * Get the ServletContextInitializer instance * This method is used to transfer all ServletContextInitializerBeans instances in Spring application context * Convert to implementation of ServletContextInitializer interface to initialize ServletContext at application startup * * @param context Spring's application context, used to get BeanFactory * @return Return an instance that implements the ServletContextInitializer interface */ private static ServletContextInitializer getServletContextInitializers(ConfigurableApplicationContext context) { // Create a ServletContextInitializerBeans instance using BeanFactory in ApplicationContext // Here we return ServletContextInitializerBeans as the implementation class of ServletContextInitializer // ServletContextInitializerBeans will be responsible for collecting all implementations of ServletContextInitializer in the application context // and call their onStartup methods in turn when the application starts to initialize the ServletContext return (ServletContextInitializer) new ServletContextInitializerBeans(()); } /** * Get the Servlet Web Server Factory * * @param context Configurable application context for obtaining bean factories * @return ServletWebServerFactory instance, used to configure and create web servers */ private static ServletWebServerFactory getWebServerFactory(ConfigurableApplicationContext context) { // Get the Bean factory from the application context and get the ServletWebServerFactory instance from it return ().getBean(); } }
test
import ; import ; import ; @RestController() @RequestMapping("port/") public class TestPortController { @GetMapping("test") public String test() { return "artisan-old"; } }
After startup, visit http://localhost:8080/port/test
Modify the return value of TestPortController, type a jar package, start a new jar package,
Revisit http://localhost:8080/port/test to observe whether the return result is the modified return value
This is the article about SpringBoot's dynamic port switching black magic. For more related SpringBoot dynamic port content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!