背景
现在很多软件都支持集群部署,但是测试环境通常资源有限,所以一般通过单台机器模拟集群部署(使用不同端口,运行相同jar包),本文的目的就是通过多种方式实现此需求。
两个程序
1、jar程序
① springboot程序
② 只包含一个main方法,用于启动程序,输出进程ID
③ 路径:C:/demo.jar(windows) /demo.jar(Linux)
2、启动程序
① 包含main方法的程序
多种方式
1、通过URLClassLoader加载jar程序(windows平台)
2、通过java -jar命令启动jar程序(windows平台)
3、通过复制原始jar文件,启动不同的jar程序(windows平台)
4、通过Linux Shell脚本启动(Linux平台)
方式一
1、通过URLClassLoader加载jar程序(windows平台)
① 说明
1) 启动程序多次加载jar程序
2) jar程序和启动程序使用相同进程,非独立进程,无实际意义,仅介绍
② 启动jar程序:运行启动程序main方法
③ 终止jar程序:停止启动程序(因为共用同一个进程,终止主程序,jar程序会同时终止)
2、代码
① jar程序
@SpringBootApplication
public class DemoStarter {
public static void main(String[] args) {
// 获取进程Id
String name = ManagementFactory.getRuntimeMXBean().getName();
String processId = name.split("@")[0];
System.out.println(processId);
SpringApplication.run(DemoStarter.class, args);
}
}
② 启动程序
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class Starter1 {
public static void main(String[] args) throws Exception {
start("7001");
start("7002");
start("7003");
}
private static void start(String port) throws Exception {
String path = "file:" + "C:/demo.jar";
URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL(path)});
// jar程序的启动类完整路径
Class demo = classLoader.loadClass("DemoStarter");
Method method = demo.getMethod("main", String[].class);
method.invoke(null, (Object) new String[]{port});
}
}
方式二
1、通过java -jar命令启动jar包(windows平台)
① 说明
1) 启动程序使用命令多次启动jar包
2) 动态构建cmd命令(不同参数),启动相同jar程序,各个jar程序使用不同进程
② 启动jar程序
1) 运行启动程序main方法
2) 保存各个进程ID到文件
③ 终止jar程序
1) 根据保存的进程ID停止各个jar程序
2、代码
① jar程序(同方式一)
② 启动程序
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Starter2 {
public static void main(String[] args) throws Exception {
cmd("7001");
cmd("7002");
cmd("7003");
// 根据文件中的进程Id终止程序
killByProcessId("PID1");
killByProcessId("PID2");
killByProcessId("PID3");
}
private static void cmd(String port) throws Exception {
String cmd = "java -jar -Dserver.port=" + port + " " + "C:/demo.jar";
Process p = Runtime.getRuntime().exec(cmd);
InputStream is = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
// 获取进程Id(DemoStarter-->main方法
// reader.readLine()第一行为System.out.println(processId)输出内容
String processId;
while ((processId = reader.readLine()) != null) {
break;
}
is.close();
reader.close();
// 这里可以将进程ID保存到文件中
System.out.println("processId:" + processId);
}
private static void killByProcessId(String processId) throws Exception {
String cmd = "taskkill /F /PID \"" + processId + "\"";
Runtime.getRuntime().exec(cmd);
}
}
方式三
1、通过复制原始jar文件,启动不同的jar程序(windows平台)
① 说明
1) 复制原始jar包,生成新的jar包
2) 动态构建cmd命令(不同参数、不同jar包名称),启动不同jar包,各个jar包使用不同进程
② 启动jar程序
1) 运行启动程序的main方法
2) 保存各个进程ID到文件
③ 终止程序
1) 根据保存的进程ID停止各个jar程序
④ 复制jar文件需要时间,但可以解决启动相同jar包可能存在的问题
2、代码
③ jar程序(同方式一)
④ 启动程序
import org.apache.commons.io.FileUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Starter3 {
public static void main(String[] args) throws Exception {
copyCmd();
// 根据文件中的进程Id终止程序
killByProcessId("PID1");
killByProcessId("PID2");
killByProcessId("PID3");
}
private static void copyCmd() throws Exception {
File srcFile = new File("C:/demo.jar");
File destiFile2 = new File("C:/demo2.jar");
File destiFile3 = new File("C:/demo3.jar");
// 删除之前复制的jar包
FileUtils.forceDelete(destiFile2);
FileUtils.forceDelete(destiFile3);
FileUtils.copyFile(srcFile, destiFile2);
FileUtils.copyFile(srcFile, destiFile3);
copy("java -jar -Dserver.port=7001 C:/demo.jar");
copy("java -jar -Dserver.port=7002 C:/demo2.jar");
copy("java -jar -Dserver.port=7003 C:/demo3.jar");
}
private static void copy(String cmd) throws Exception {
Process p;
p = Runtime.getRuntime().exec(cmd);
InputStream is = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
// 获取进程Id(DemoStarter-->main方法
// reader.readLine()第一行为System.out.println(processId)输出内容
String processId;
while ((processId = reader.readLine()) != null) {
break;
}
is.close();
reader.close();
// 这里可以将进程ID保存到文件中
System.out.println("ProcessId:" + processId);
}
private static void killByProcessId(String processId) throws Exception {
String cmd = "taskkill /F /PID \"" + processId + "\"";
Runtime.getRuntime().exec(cmd);
}
}
方式四
1、通过Linux Shell脚本启动(Linux平台)
① 执行java -jar命令
② 根据端口获取进程ID
③ 根据进程ID终止程序
2、代码
① jar程序(同方式一)
② Shell命令
1) 启动程序
#!/bin/bash
java -jar -Dserver.port=7001 /demo.jar
java -jar -Dserver.port=7002 /demo.jar
java -jar -Dserver.port=7003 /demo.jar
2) 终止程序
#!/bin/bash
pid1=`netstat -anp | grep 7001 | awk '{printf $7}' | cut -d/ -f1`
pid2=`netstat -anp | grep 7002 | awk '{printf $7}' | cut -d/ -f1`
pid3=`netstat -anp | grep 7003 | awk '{printf $7}' | cut -d/ -f1`
kill ${pid1}
kill ${pid2}
kill ${pid3}
问题&总结
1、方式一可以通过调用method.invoke传递参数
2、其它方式可通过jar命令传递参数
3、启动程序通过Process启动jar程序并获取jar程序进程ID
4、多次启动jar程序时报错:”unable to register MBean”
设置参数spring.jmx.enabled=false
5、根据端口号获取进程ID(windows)
#!/bin/bash
netstat -ano|findstr "7001 7002 7003"
netstat -anp | grep 7003 | awk '{printf $7}' | cut -d/ -f1
6、根据进程ID停止进程(windows)
taskkill /F /PID “1”
参考资料
- https://my.oschina.net/u/2971292/blog/2960777
- https://www.jianshu.com/p/3eea5e7e1e6f
- https://www.cnblogs.com/sxdcgaq8080/p/10579073.html