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;
	}
}

 

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