SSH-2協議工具包:
CacheCloud平臺提供了對redis集羣的集中化管理、運維、監控等功能,但是CacheCloud是如何對機器進行監控和維護的呢?是
如何獲得redis所在服務器的信息呢?
通過查看源代碼,發現平臺引入了SSH-2協議的工具包:ganymed-ssh2,這個工具包提供了java連接linux服務器的功能,通過這個工具包就可以在服務器上面執行應用邏輯
<dependency>
<groupId>ch.ethz.ganymed</groupId>
<artifactId>ganymed-ssh2</artifactId>
<version>build210</version>
</dependency>
CacheCloud中用於SSH-2連接的類主要包含了以下幾個:
- SSHTemplate:提供了SSH操作的模板,包含了創建SSH連接、執行SSHCallback回調、獲取結果、解析結果和關閉連接的功能
- SSHSession:根據SSH連接獲取session會話;執行linux命令,獲得執行結果;上傳文件到遠程服務器
- Result:SSH結果包裝類
- SSHCallback:SSH執行命令回調
- LineProcessor:結果解析器
- DefaultLineProcessor:默認的結果解析器
- SSHUtil:工具類,包含了收集機器信息、傳輸文件到遠程服務器、查看端口是否被佔用、獲得ssh端口和獲取cpu狀況
SSHSession:
獲取SSH連接:
/**
* 通過回調執行命令
* @param ip
* @param port
* @param username
* @param password
* @param callback 可以使用Session執行多個命令
* @throws SSHException
*/
public Result execute(String ip, int port, String username, String password,
SSHCallback callback) throws SSHException{
Connection conn = null;
try {
conn = getConnection(ip, port, username, password);
return callback.call(new SSHSession(conn, ip+":"+port));
} catch (Exception e) {
throw new SSHException("SSH err: " + e.getMessage(), e);
} finally {
close(conn);
}
}
/**
* 執行命令並返回結果,可以執行多次
* @param cmd 執行命令
* @param lineProcessor 回調消息解析器
* @return 如果lineProcessor不爲null,那麼永遠返回Result.true
*/
public Result executeCommand(String cmd, LineProcessor lineProcessor, int timoutMillis) {
Session session = null;
try {
//通過SSH連接打開session會話
session = conn.openSession();
return executeCommand(session, cmd, timoutMillis, lineProcessor);
} catch (Exception e) {
logger.error("execute ip:"+conn.getHostname()+" cmd:"+cmd, e);
return new Result(e);
} finally {
close(session);
}
}
執行命令:
public Result executeCommand(final Session session, final String cmd,
final int timoutMillis, final LineProcessor lineProcessor) throws Exception{
//通過線程池的方式異步執行linux命令
Future<Result> future = taskPool.submit(new Callable<Result>() {
public Result call() throws Exception {
//具體執行linux命令的代碼
session.execCommand(cmd);
//如果客戶端需要進行行處理,則直接進行回調
if(lineProcessor != null) {
processStream(session.getStdout(), lineProcessor);
} else {
//獲取標準輸出
String rst = getResult(session.getStdout());
if(rst != null) {
return new Result(true, rst);
}
//返回爲null代表可能有異常,需要檢測標準錯誤輸出,以便記錄日誌
Result errResult = tryLogError(session.getStderr(), cmd);
if(errResult != null) {
return errResult;
}
}
return new Result(true, null);
}
});
Result rst = null;
try {
//當timeoutMillis毫秒還沒有返回結果,則取消執行,否則返回結果,不過Future只能確定線程執行完,
//不能確定執行是否成功,因爲Future只提供了isDone()方法確定線程執行完畢
rst = future.get(timoutMillis, TimeUnit.MILLISECONDS);
future.cancel(true);
} catch (TimeoutException e) {
logger.error("exec ip:{} {} timeout:{}", conn.getHostname(), cmd, timoutMillis);
throw new SSHException(e);
}
return rst;
}
上傳文件:
/**
* Copy a set of local files to a remote directory, uses the specified mode when
* creating the file on the remote side.
* @param localFiles
* Path and name of local file.
* @param remoteFiles
* name of remote file.
* @param remoteTargetDirectory
* Remote target directory. Use an empty string to specify the default directory.
* @param mode
* a four digit string (e.g., 0644, see "man chmod", "man open")
* @throws IOException
*/
public Result scp(String[] localFiles, String[] remoteFiles, String remoteTargetDirectory, String mode) {
try {
//通過連接創建SCP客戶端,使用put方法將文件上傳到遠程服務器
SCPClient client = conn.createSCPClient();
client.put(localFiles, remoteFiles, remoteTargetDirectory, mode);
return new Result(true);
} catch (Exception e) {
logger.error("scp local="+Arrays.toString(localFiles)+" to "+
remoteTargetDirectory+" remote="+Arrays.toString(remoteFiles)+" err", e);
return new Result(e);
}
}
SSHCallback:
/**
* 執行命令回調
*/
public interface SSHCallback{
/**
* 執行回調
* @param session
*/
Result call(SSHSession session);
}
/**
* 從流中直接解析數據
*/
public static interface LineProcessor{
/**
* 處理行
* @param line 內容
* @param lineNum 行號,從1開始
* @throws Exception
*/
void process(String line, int lineNum) throws Exception;
/**
* 所有的行處理完畢回調該方法
*/
void finish();
}
//默認的數據解析器
public static abstract class DefaultLineProcessor implements LineProcessor{
public void finish() {}
}
大致整理一下調用邏輯:
邏輯有點複雜,需要大家耐心去看,多看幾遍代碼就懂了,哈哈!
如果有不懂之處,歡迎關注微信公衆號:代碼小棧,期待爲您解決更多難題