jsch教程(用java ssh 提交spark yarn任务)

jsch 是ssh2的一个纯Java实现。它允许你连接到一个sshd 服务器,使用端口转发,X11转发,文件传输等等。你可以将它的功能集成到你自己的 程序中。同时该项目也提供一个J2ME版本用来在手机上直连SSHD服务器。


一般连接到服务器有两种方式:

1、通过用户名和密码连接,缺点(出于安全需要,一般服务器的密码会定期修改,程序部署后将不得不经常更新配置文件中的密    码。)

2、通过用户名和ssh private key file连接,缺点(因为Java程序必须和private key file在同一台机器上,将服务器的private key file复制到本地后,本地机器的安全措施可能会使private key file被窃取,威胁服务器安全。)


jsch官网地址为http://www.jcraft.com/jsch/,实现jsch功能需要添加一个jsch-0.1.51.jar包,官网有一些例子可直接下载参考。

maven的配置为:

 <dependency>
                <groupId>com.jcraft</groupId>
                <artifactId>jsch</artifactId>
                <version>0.1.51</version>
    </dependency>

jsch中ChannelShell与ChannelExec区别 

ChannelShell

对于ChannelShell,以输入流的形式,提供命令和输入这些命令,这就像在本地计算机上使用交互式shell
(它通常用于:交互式使用)

缺点:

建立的是shell管道,并且我们又使用readLine方法,当命令全部执行完毕后,远程端并不知道执行完毕,还在等待接受数据,所以呢reandLine就一直阻塞在那里。
即便你换成read方法还是一样的,因为shell管道本身就是交互模式的。要想停止,有两种方式:
①人为的发送一个exit命令,告诉程序本次交互结束啦
②使用字节流中的available方法,来获取数据的总大小,然后循环去读。

  InputStream inputStream = channel.getInputStream();//从远程端到达的所有数据都能从这个流中读取到
        OutputStream outputStream = channel.getOutputStream();//写入该流的所有数据都将发送到远程端。
        //使用PrintWriter流的目的就是为了使用println这个方法
        //好处就是不需要每次手动给字符串加\n
        PrintWriter printWriter = new PrintWriter(outputStream);

        String cmd = "ls";
        printWriter.println(cmd);
        String cmd2 = "cd /home/jenkins/workspace/ggservice";
        printWriter.println(cmd2);
        String cmd3 = "ls";
        printWriter.println(cmd3);
        printWriter.println("exit");//加上个就是为了,结束本次交互
        printWriter.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String msg = null;
        while((msg = in.readLine())!=null){
            System.out.println(msg);
        }
        in.close();
ChannelShell channel = (ChannelShell) session.openChannel("shell");
channel.connect();
//从远程端到达的所有数据都能从这个流中读取到
InputStream in = channel.getInputStream();
//写入该流的所有数据都将发送到远程端。
OutputStream outputStream = channel.getOutputStream();
byte[] tmp=new byte[1024];
while(true){
  while(in.available()>0){
    int i=in.read(tmp, 0, 1024);
    if(i<0)break;
    System.out.print(new String(tmp, 0, i));
  }
  if(channel.isClosed()){
    if(in.available()>0) continue;
    System.out.println("exit-status: "+channel.getExitStatus());
    break;
  }
}

 

ChannelExec

对于ChannelExec,在调用connect()方法之前这个命令提供了setCommand()方法,
并且这些命令作为输入将以输入流的形式被发送出去。
(通常,你只能有调用setCommand()方法一次,多次调用只有最后一次生效),
但是你可以使用普通shell的分隔符(&,&&,|,||,; , \n, 复合命令)来提供多个命令。
这就像在你本机上执行一个shell脚本一样(当然,如果一个命令本身就是个交互式shell,这样就像ChannelShell)

明显:使用命令通道更容易,因为您不需要处理命令提示符。

问题:只能执行一条语句,有些在shell中的语句还不能执行,比如配置了环境变量:JAVA_HOME,

在shell模式下面执行:java -version,是可以的

在ChannelExec模式下面执行:java -version,是会报错  bash: java: command not found

ChannelExec是不会去获取环境变量里面的(刚刚学习,也不知道为什么),只能进入到目录下面执行才可以:

/usr/jdk1.8.0_121/bin/java -version

下面是测试的全量代码(提交spark的时候一定要在spark-env.sh里面把环境变量配置全,java、hadoop、spark、sacla的):

package com.spark.demo.sub;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

public class Exec {

	private static Logger logger = Logger.getLogger(Exec.class);

	private static final String USER = "root";
	private static final String PASSWORD = "******";
	private static final String HOST = "192.168.03.13";
	private static final int DEFAULT_SSH_PORT = 22;

	public static void main(final String[] arg) {
		try {
			JSch jsch = new JSch();

			Session session = jsch.getSession(USER, HOST, DEFAULT_SSH_PORT);
			session.setPassword(PASSWORD);
			session.setConfig("StrictHostKeyChecking", "no");// 第一次访问服务器不用输入yes
			session.connect();

			String command = "/usr/dh/spark/spark-2.3.4/bin/spark-submit --class wordcount.JavaWordCount --master yarn --deploy-mode cluster --driver-memory 1G --num-executors 3 --executor-memory 1G --executor-cores 1  hdfs://10.121.55.67:9000/dh/wordcount-two-1.0.0.jar";
			//
			command = "/usr/jdk1.8.0_121/bin/java -version";

			Channel channel = session.openChannel("exec");
			((ChannelExec) channel).setCommand(command);
			channel.setInputStream(null);
			((ChannelExec) channel).setErrStream(System.err);

			InputStream in = channel.getInputStream();
			channel.connect();

			getIn(in, channel);
			//getIn2(in, channel);

			channel.disconnect();
			session.disconnect();
		} catch (Exception e) {
			logger.info(e);
		}
	}

	private static void getIn(final InputStream in, final Channel channel) throws IOException {
		byte[] tmp = new byte[1024];
		while (true) {
			while (in.available() > 0) {
				int i = in.read(tmp, 0, 1024);
				if (i < 0) {
					break;
				}
				logger.info(new String(tmp, 0, i));
			}
			if (channel.isClosed()) {
				if (in.available() > 0) {
					continue;
				}
				logger.info("exit-status: " + channel.getExitStatus());
				break;
			}
			try {
				Thread.sleep(1000);
			} catch (Exception ee) {
			}
		}
	}

        /**
         *获取sub提交时候的appId
         */
	private static String getIn2(final InputStream in, final Channel channel) throws IOException {
		// 返回结果存在ExtInputStream中,所以需要合并两个返回结果
		BufferedReader input2 = new BufferedReader(new InputStreamReader(in));
		// 接收远程服务器执行命令的结果
		String line;
		String pattern = "Submitted application application_(.*)";
		String appid = null;
		Pattern p = Pattern.compile(pattern);
		boolean isDealed = false;
		while ((line = input2.readLine()) != null) {
			Matcher m = p.matcher(line);
			if (!isDealed && m.find()) {
				// 这里虽然拿到了Yarn Id,但是不能立即返回,因为该通道还是处于暂用状态,必须阻塞线程
				appid = "application_" + m.group(1);
				isDealed = true;
			}
		}
		
		System.out.println("appid:" + appid);
		return appid;
	}
}

 

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