Spring boot隨機端口你都不會,怎麼動態擴容?

一般情況下每個spring boot工程啓動都有固定的端口,但是固定端口不利用服務的動態擴容,如果在一臺服務器上需要對同一個服務進行多實例部署,很容易出現端口衝突,那麼怎麼解決這個問題呢?

random隨機端口

在spring boot中,可以通過${random}來生成隨機數字,我們可以在配置文件中,這麼設置端口:

server.port=${random.int(2000,8000)}

通過random.int方法,指定生成2000~8000的隨機端口。這樣每次啓動的端口都不一樣。

多次啓動,發現每次的端口都不一致說明配置成功。
在這裏插入圖片描述
在這裏插入圖片描述
注意事項:
這裏需要注意spring boot項目啓動屬性文件的加載順序,spring boot的屬性是由裏向外加載,所以最外層的最後被加載,會覆蓋裏層的屬性。
所以如果主動在啓動命令中使用–server.port配置了項目的端口號,那麼屬性文件中配置的隨機端口屬性就不會生效。

通過System.setProperty設置有效隨機端口

上面的方法雖然暫時達到了想要的效果,但是有個問題:如果生成的這個隨機端口已經被使用了,那麼項目啓動就會出現端口衝突。
那麼,我們能否通過一個檢測機制,讓生成的隨機端口一定是一個沒有被佔用的有效的隨機端口呢?

有效端口檢測原理:
通過建立socket連接,Socket socket = new Socket(Address,port);#address代表主機的IP地址,port代表端口號
如果對該主機的特定端口號能建立一個socket,則說明該主機的該端口在使用。
Socket socket = new Socket(Address,port);#address代表主機的IP地址,port代表端口號
如果對該主機的特定端口號能建立一個socket,則說明該主機的該端口在使用。

實現思路:
通過在項目啓動前,獲取有效的隨機端口並通過System.setProperty將變量設置到系統的全局變量中,這樣項目啓動時就可以從全局變量中獲取到server.port變量的值。
這裏的system,系統指的是 JRE (runtime)system,即設置jvm運行時的全局變量。

工具類:

@Slf4j
public class NetUtils {
    
    /**
     * 測試本機端口是否被使用
     * @param port
     * @return
     */
    public static boolean isLocalPortUsing(int port){
        boolean flag = true;
        try {
            //如果該端口還在使用則返回true,否則返回false,127.0.0.1代表本機
            flag = isPortUsing("127.0.0.1", port);
        } catch (Exception e) {
        }
        return flag;
    }
    /***
     * 測試主機Host的port端口是否被使用
     * @param host
     * @param port
     * @throws UnknownHostException
     */
    public static boolean isPortUsing(String host,int port)  {
        boolean flag = false;
        try {
            InetAddress Address = InetAddress.getByName(host);
            Socket socket = new Socket(Address,port);  //建立一個Socket連接
            flag = true;
        } catch (IOException e) {
           //log.info(e.getMessage(),e);
        }
        return flag;
    }

    //start--end是所要檢測的端口範圍
    static int start=0;
    static int end=1024;
    
    /**
     * 由於本機上安裝了mysql,採用3306端口去驗證
     * @param args
     */
    public static void main(String args[]){
            int testPost =3306;
            if(isLocalPortUsing(testPost)){
                System.out.println("端口 "+testPost+" 已被使用");
            }else{
                System.out.println("端口 "+testPost+"未使用");
            }
    }
}

 public class ServerPortUtils {

    /**
     * 獲取可用端口
     * @return
     */
    public static int getAvailablePort(){
         int max = 65535;
         int min = 2000;

         Random random = new Random();
         int port = random.nextInt(max)%(max-min +1) + min;
         boolean using = NetUtils.isLocalPortUsing(port);
         if(using){
             return  getAvailablePort();
         }else{
             return  port;
         }
    }

}

項目啓動前設置server.port環境變量

/**
 * 開始命令
 */
@Slf4j
public class StartCommand {

    public StartCommand(String[] args){
         Boolean isServerPort = false;
         String serverPort = "";
         if(args != null){
              for (String arg:args){
                    if(StringUtils.hasText(arg) &&
                            arg.startsWith("--server.port")
                    ){
                        isServerPort = true;
                        serverPort = arg;
                        break;
                    }
              }
         }

         //沒有指定端口,則隨機生成一個可用的端口
        if(!isServerPort){
              int port = ServerPortUtils.getAvailablePort();
              log.info("current server.port=" + port);
              System.setProperty("server.port",String.valueOf(port));
        }else{//指定了端口,則以指定的端口爲準
            log.info("current server.port=" + serverPort.split("=")[1]);
            System.setProperty("server.port",serverPort.split("=")[1]);
        }
    }

}

啓動類調用方法:

@SpringBootApplication
@EnableUserClient
@RestController
public class DemoApplication {
    @Autowired
    Environment environment;

    public static void main(String[] args) {
        new StartCommand(args);
        SpringApplication.run(DemoApplication.class, args);
    }
}

通過自定義PropertiesPropertySource屬性源實現

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        //MapPropertySource
        Properties properties = new Properties();
        properties.put("server.port", ServerPortUtils.getAvailablePort());
        System.out.println(properties.get("server.port"));
        PropertiesPropertySource source = new PropertiesPropertySource("myCustom", properties);
        environment.getPropertySources().addLast(source);
        //environment.getPropertySources().addAfter();
    }
}

通過配置在resources/META-INF/spring.factories文件中使用全名註冊

org.springframework.boot.env.EnvironmentPostProcessor=com.laowan.demo.command.MyEnvironmentPostProcessor

這樣在項目啓動後,就會將該屬性源加載到Environment中。

在這裏插入圖片描述

總結

1、爲什麼要設置隨機端?主要是爲了解決動態擴容時出現端口衝突的問題。
2、怎麼獲取一個有效的隨機端口號
3、spring boot下實現隨機端口的三種方式。關於方式三的自定義屬性源的實現方式可以多多品味,實踐一下,更好的體會屬性文件的加載順序。

覺得有用,記得點贊加關注。
跟着老萬學java

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章