随机端口

场景

代码部署总是需要一个端口,虽然我们可以在配置文件中定义与修改,也可以在部署的时候手动指定。为避免端口冲突何不用代码来为我们自动随机一个端口呢?

方法一

设置server.port=0,当应用启动的时候会自动的分配一个随机端口,但是该方式在注册到Eureka的时候会一个问题:所有实例都使用了同样的实例名(如:Lenovo-test:hello-service:0),这导致只出现了一个实例。所以,我们还需要修改实例ID的定义,让每个实例的ID不同,比如使用随机数来配置实

1
2
server.port=0
eureka.instance.instance-id=${spring.application.name}:${random.int}

方法二

除了上面的方法,实际上我们还可以直接使用random函数来配置server.port。这样就可以指定端口的取值范围,比如:

1
server.port=${random.int[10000,19999]}

由于默认的实例ID会由server.port拼接,而此时server.port设置的随机值会重新取一次随机数,所以使用这种方法的时候不需要重新定义实例ID的规则就能产生不同的实例ID了。

方法三

在《spring cloud 微服务 入门、进阶与实战》中说方法二的配置如果随机的端口刚好已经被使用了,那么启动就会报错。于是通过代码的方式来随机生成一个端口

书中源代码

StartCommand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class StartCommand {
private Logger logger = LoggerFactory.getLogger(StartCommand.class);

public StartCommand(String[] args) {
Boolean isServerPort = false;
String serverPort = "";
if (args != null) {
for (String arg : args) {
System.out.println("args out ==> "+arg);
if (StringUtils.hasText(arg) && arg.startsWith("--server.port")) {
isServerPort = true;
serverPort = arg;
break;
}
}
}
// 没有指定端口,则随机生成一个可用的端口
if (!isServerPort) {
int port = ServerPortUtils.getAvailablePort();
logger.info("no ==== current server.port=" + port);
System.setProperty("server.port", String.valueOf(port));
} else {
logger.info("yes ===== current server.port=" + serverPort.split("=")[1]);
System.setProperty("server.port", serverPort.split("=")[1]);
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ServerPortUtils {
/**
* 获取可用端口,范围2000-65535
* @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.isLoclePortUsing(port);
if (using) {
return getAvailablePort();
} else {
return port;
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class NetUtils {

/***
* true:already in using false:not using
* @param port
*/
public static boolean isLoclePortUsing(int port){
boolean flag = true;
try {
flag = isPortUsing("127.0.0.1", port);
} catch (Exception e) {
}
return flag;
}

/***
* true:already in using false:not using
* @param host
* @param port
* @throws
*/
public static boolean isPortUsing(String host,int port) {
boolean flag = false;
try {
InetAddress theAddress = InetAddress.getByName(host);
Socket socket = new Socket(theAddress,port);
socket.close();
flag = true;
} catch (Exception e) {

}
return flag;
}

}

在启动类里:

1
2
3
4
5
6
7
8
@SpringBootApplication
public class SpringbootBaseApplication {
public static void main(String[] args) {
new StartCommand(args);//加上这一行
SpringApplication.run(SpringbootBaseApplication.class, args);
}

}

通过对启动参数进行遍历判断,如果有指定启动端口,后续就不自动生成了;如果没有指定,就通过ServerPortUtils获取一个可以使用的端口,然后设置道环境变量中。在application.properties中通过下面的方式获取端口:

——《spring cloud 微服务 入门、进阶与实战》23页

server.port=${server.port}

项目启动后发现的确重新生成了端口:(但生成了两次目前还不知道为什么)

1
2
22:26:19.423 [main] INFO com.example.springbootbase.common.StartCommand -     current server.port=39401
22:26:21.550 [restartedMain] INFO com.example.springbootbase.common.StartCommand - current server.port=2204

参考博客:

springboot:随机端口

部分代码源自《spring cloud 微服务 入门、进阶与实战》