前言
项目中用到了cmd命令去执行,但是发现一个问题就是,当需要切换用户和执行命令的时候特别的麻烦,所以后面又该用了jsch的连接方式,测试一下性能理想不理想,看看有劣势。
相关配置
ssh的连接数
# cat /etc/ssh/sshd_config | grep MaxSessions
#MaxSessions 1000
测试代码
@GetMapping("/cmd")
public String testCmd(@RequestParam Integer count) {
log.info(" id = {}", count);
ExecutorService executorService = Executors.newFixedThreadPool(count);
long start = System.nanoTime();
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
executorService.execute(()->{
try {
String command = "sleep 3 && echo 'Done waiting!' "; // 替换成您要运行的CMD命令
// Process process = Runtime.getRuntime().exec(command);
// 创建ProcessBuilder对象,设置要执行的命令
ProcessBuilder pb = new ProcessBuilder("sleep", "10");
// 启动进程
Process process = pb.start();
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
log.info("data:{}", line);
}
reader.close();
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
});
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.nanoTime();
long offset = TimeUnit.NANOSECONDS.toMillis(end - start);
log.info("cost time = {} ms", offset);
return "hello: " + offset;
}
@GetMapping("/ssh")
public String testSsh(@RequestParam Integer count) {
log.info(" id = {}", count);
ExecutorService executorService = Executors.newFixedThreadPool(count);
long start = System.nanoTime();
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
executorService.execute(()->{
try {
cmdCreateFileDirectory(" sleep 10 && echo 'Done waiting!'", "steven",
"admin");
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
});
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.nanoTime();
long offset = TimeUnit.NANOSECONDS.toMillis(end - start);
log.info("cost time = {} ms", offset);
return "hello: " + offset;
}
// write a create file directory function
public void cmdCreateFileDirectory(final String command, final String serverUser,
final String serverPassword) {
int exitValue = 0;
long startTime = System.currentTimeMillis();
final StringBuilder result = new StringBuilder();
try {
JSch jsch = new JSch();
Session session = jsch.getSession(serverUser, "127.0.0.1", 22);
session.setPassword(serverPassword);
session.setConfig("StrictHostKeyChecking", "no");
session.connect(10000);
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
InputStream in = channel.getInputStream();
channel.connect();
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0) break;
result.append(new String(tmp, 0, i));
}
if (channel.isClosed()) {
if (in.available() > 0) continue;
exitValue = channel.getExitStatus();
log.debug("exit-status: {}", exitValue);
break;
}
try {
Thread.sleep(10);
} catch (Exception ee) {
}
}
channel.disconnect();
session.disconnect();
if (exitValue != 0) {
log.error("create file directory error: " + exitValue + " " + command);
}
log.info(" create file directory success, command: {}, content:{} total cost time:{}", command,
result.toString(), System.currentTimeMillis() - startTime);
} catch (JSchException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
测试cmd
CPU还算稳定,刚开始120%后面50%,然后30%,然后50%这样波动
程序日志
2023-07-12 17:12:18.643 INFO 6466 --- [nio-8082-exec-1] com.example.demotest.api.TestController : id = 1000
2023-07-12 17:12:32.385 INFO 6466 --- [nio-8082-exec-1] com.example.demotest.api.TestController : cost time = 13739 ms
2023-07-12 17:13:04.250 INFO 6466 --- [nio-8082-exec-2] com.example.demotest.api.TestController : id = 1000
2023-07-12 17:13:17.461 INFO 6466 --- [nio-8082-exec-2] com.example.demotest.api.TestController : cost time = 13210 ms
2023-07-12 17:13:42.428 INFO 6466 --- [nio-8082-exec-3] com.example.demotest.api.TestController : id = 1000
2023-07-12 17:13:55.645 INFO 6466 --- [nio-8082-exec-3] com.example.demotest.api.TestController : cost time = 13216 ms
2023-07-12 17:14:04.956 INFO 6466 --- [nio-8082-exec-4] com.example.demotest.api.TestController : id = 1000
2023-07-12 17:14:17.633 INFO 6466 --- [nio-8082-exec-4] com.example.demotest.api.TestController : cost time = 12676 ms
平均执行时间在13s左右
测试ssh
cpu在240~100之间徘徊
2023-07-12 17:18:10.288 INFO 6466 --- [nio-8082-exec-5] com.example.demotest.api.TestController : cost time = 30565 ms
2023-07-12 17:19:59.174 INFO 6466 --- [nio-8082-exec-9] com.example.demotest.api.TestController : cost time = 35478 ms
介于ssh连接的原因,还是会有很多连接不上ssh,当IO操作不频繁有一部分可能会导致CPU变高,因为线程数很多。
com.jcraft.jsch.JSchException: Session.connect: java.net.SocketException: Connection reset
at com.jcraft.jsch.Session.connect(Session.java:565) ~[jsch-0.1.55.jar!/:na]
at com.example.demotest.api.TestController.cmdCreateFileDirectory(TestController.java:113) ~[classes!/:0.0.1-SNAPSHOT]
at com.example.demotest.api.TestController.lambda$testSsh$1(TestController.java:82) ~[classes!/:0.0.1-SNAPSHOT]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
2023-07-12 18:08:47.839 ERROR 6466 --- [l-50-thread-136] com.example.demotest.api.TestController : Session.connect: java.net.SocketException: Connection reset
com.jcraft.jsch.JSchException: Session.connect: java.net.SocketException: Connection reset
at com.jcraft.jsch.Session.connect(Session.java:565) ~[jsch-0.1.55.jar!/:na]
at com.example.demotest.api.TestController.cmdCreateFileDirectory(TestController.java:113) ~[classes!/:0.0.1-SNAPSHOT]
at com.example.demotest.api.TestController.lambda$testSsh$1(TestController.java:82) ~[classes!/:0.0.1-SNAPSHOT]
100条以内执行统计
# cat /etc/ssh/sshd_config | grep MaxSessions
#MaxSessions 10
改成请求100
cpu
ssh30~4%. cmd无影响
执行耗时单位ms
[root@dev-base-01 ~]# curl http://localhost:8082/api/test/ssh?count=10
hello: 10430
[root@dev-base-01 ~]# curl http://localhost:8082/api/test/ssh?count=10
hello: 10356
[root@dev-base-01 ~]# curl http://localhost:8082/api/test/ssh?count=10
hello: 10502
[root@dev-base-01 ~]# curl http://localhost:8082/api/test/cmd?count=10
hello: 10017
[root@dev-base-01 ~]# curl http://localhost:8082/api/test/cmd?count=10
hello: 10023
[root@dev-base-01 ~]# curl http://localhost:8082/api/test/cmd?count=10
hello: 10018
改成500
cpu消耗
cmd: 50~10% ssh: 200~30%
耗时消耗ms
[root@dev-base-01 ~]# curl http://localhost:8082/api/test/cmd?count=1000
hello: 14946[root@dev-base-01 ~]# curl http://localhost:8082/api/test/cmd?count=500
hello: 12318[root@dev-base-01 ~]# curl http://localhost:8082/api/test/cmd?count=500
hello: 11037[root@dev-curl http://localhost:8082/api/test/ssh?count=500
^[[Ahello: 20826[root@dev-base-01 ~]# curl http://localhost:8082/api/test/ssh?count=500
hello: 22675[root@dev-base-01 ~]# curl http://localhost:8082/api/test/ssh?count=500
hello: 22629[root@dev-base-01 ~]#
总结
从测试结果来看cmd的执行耗时cpu明显要低很多而且耗时也低很多,但是明明cmd创建了一个进程去执行命令,所以但从表面数据来看是好像明显cmd更加的明显,但是从代码维护和后续更多功能开发又觉得cmd有点像一个附加功能,不是主要的做命令执行的,SSH之类的脚本还是需要连接工具去执行,也搜索到类似的文章,也看过类似的书籍,都讨论JAVA命令行的缺点,之前一个是内存溢出,后面一个案例是重构很难适应新业务。
一般来说,使用第三方库连接SSH执行脚本的性能更好。因为第三方库连接SSH可以直接在Java代码中执行远程命令,而不需要像底层调用exec一样需要创建进程、执行命令、等待命令执行完成等操作,从而可以更加高效地执行远程命令。但具体的性能情况还需要根据具体的实现方式和场景来进行评估。
引用
SSH Connection With Java | Baeldung
JSch- Java Secure Channel - Examples
JavaJSchExample to run Shell Commands on SSH Unix Server | DigitalOcean