‘壹’ 怎么用java代码调用远程Linux上的shell脚本
package org.shirdrn.shell;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
/**
* 远程Shell脚本执行工具
*
* @author Administrator
*/
public class RemoteShellTool {
private Connection conn;
private String ipAddr;
private String charset = Charset.defaultCharset().toString();
private String userName;
private String password;
public RemoteShellTool(String ipAddr, String userName, String password, String charset) {
this.ipAddr = ipAddr;
this.userName = userName;
this.password = password;
if(charset != null) {
this.charset = charset;
}
}
/**
* 登录远程Linux主机
*
* @return
* @throws IOException
*/
public boolean login() throws IOException {
conn = new Connection(ipAddr);
conn.connect(); // 连接
return conn.authenticateWithPassword(userName, password); // 认证
}
/**
* 执行Shell脚本或命令
*
* @param cmds 命令行序列
* @return
*/
public String exec(String cmds) {
InputStream in = null;
String result = "";
try {
if (this.login()) {
Session session = conn.openSession(); // 打开一个会话
session.execCommand(cmds);
in = session.getStdout();
result = this.processStdout(in, this.charset);
conn.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
return result;
}
/**
* 解析流获取字符串信息
*
* @param in 输入流对象
* @param charset 字符集
* @return
*/
public String processStdout(InputStream in, String charset) {
byte[] buf = new byte[1024];
StringBuffer sb = new StringBuffer();
try {
while (in.read(buf) != -1) {
sb.append(new String(buf, charset));
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
}
‘贰’ linux上的rsync命令详解
rsync 就是远程同步的意思remote sync.
rsync 被用在UNIX / Linux执行备份操作操作.
rsync 工具包被用来从一个位置到另一个位置高效地同步文件和文件夹. rsync可以实现在同一台机器的不同文件直接备份,也可以跨服务器备份.
rsync 语法如下
从语法结构我们可以看出, 源和目标即可以在本地也可以在远端. 如果是远端的话,需要指明登录用户名, 远端服务器名, 和远端文件或目录. 同时源可以是多个, 目标位置只能是一个.
说明:
-z: --compress 使用压缩机制
-v: --verbose 打印详细信息
-r: --recursive 以递归模式同步子目录
注意: 同步完成后, 我们会发现文件的时间戳timestamps发生了改变.
有时我们希望拷贝或同步时, 时间戳不要发生变化, 源文件是什么时间戳,目标文件就是什么时间戳, 这时我们需要使用 -a --archive 归档模式选项. -a 选项相当于7个选项的组合 -rlptgoD
-r, --recursive: 递归模式Recursive mode
-l, --links: 将符号链接当作符号链接文件拷贝, 不拷贝符合链接指向的文件内容.
-p, --perms: 保留文件权限
-t, --times: 保留修改时间戳
-g, --group: 保留用户组信息
-o, --owner: 保留用户信息(需要超级用户权限)
-D, 相当于 --devices --specials 的组合, 保留设备文件, 保留特殊文件.
同步完成后, 我们再来看文件属性, 时间戳信息得到了保留, 不仅如此文件的所有者 和所在组也得到保留.
说明: Pubkeys 是一个普通文件
使用rsync, 也可以从本地拷贝多个文件或目录到远端, 以下即为示例:
注意:
与本地文件拷贝不同的地方在于, 当拷贝文件到远程服务器时, 我们指定远程主机上的用户名, 服务器地址, 路径等信息, 类是于使用scp命令拷贝, 如果没有设置ssh免密码登录我们还需要提供远程用户的密码等信息.有时你不想频繁输入密码, 或者rsync运行在一个无人执守的脚本里面, 这是需要预先设置ssh免密登录, 或者使用结合expect命令,自动输入密码, 但是出于安全考虑, 密码需要加密. 所以在条件许可的情况下, 还是推荐设置ssh免密登录.
与示例 4 稍有不同, 这时远端目录或文件作为源位置, 本地目录或文件作为目标位置, 示例如下:
rsync 允许指定远程主机上运行shell命令.
这时需要使用 -e 选项:
-e, --rsh=COMMAND 指定远端使用的shell命令
Use rsync -e ssh to specify which remote shell to use. In this case, rsync will use ssh.
在一下特殊的使用场景中, 我们不希望拷贝文件时, 我们不希望拷贝过程覆盖掉目标位置中用户做出的修改. 这时我们需要使用 -u 选项明确的告诉rsync命令保留用户在目标文件中作出的修改. 在下面的例子中, 文件Basenames是用户基于上次的拷贝, 修改过的文件, 当我们使用了-u 选项后, 该文件中的修改将不会被覆盖掉.
在某些特殊场景中, 我们只需要远程服务器上的目录结构, 而不希望花大量时间, 带宽拷贝文件内容, 这时我们可以使用 -d, --dirs选项来达到目的.
有时我们希望拷贝文件时, 能实时的显示拷贝进度, 以及传输速率等信息. 尤其是拷贝大文件时, 程序不输出信息, 用户往往无法区分程序是在响应中, 还是已经挂起, 在这种情况下如果使用 –progress 就会非常有帮助.
rsync –progress option displays detailed progress of rsync execution as shown below.
你也可以使用rsnapshot工具 (rsnapshot会调用rsync)来备份本地linux服务器, 或者备份远程linux服务器.
有时我们希望目标文件和源文件保持严格一致, 不要多文件也不要少文件, 这是我们可能需要使用 -delete 选项来达到目的. 如果使用 -delete 选项, rsync将删除目标位置多余的文件或文件夹. 此选项还可以结合--delete-excluded 选项一起使用, 添加一些例外的文件.
上述示例中, new-file.txt 是源文件中没有的文件, 其将会在拷贝时被删除掉.
在某些特殊的场景下, 我们只想更新, 目标位置已经存在的文件或目录, 而不关心源位置的新文件, 这时我们可以使用-existing 选项仅仅更新已经存在的文件.
让我们来验证一下这个选项的功能, 首先在源端添加一个新文件 new-file.txt.
从上面的例子可以看到, 由于加了--existing选项新文件new-file.txt没有被拷贝到目标位置
选项-i, --itemize-changes 非常有用, 当我们想了解目标位置和源位置的文件差异时.
在源端:
在目标端:
注意: 在上面的例子中, 源位置和目标位置有两处差异. 第一, 源文件Basenames的所有者和组 与 目标文件不同, 第一Dirnames文件大小也不一样.
现在让我们来看看rsync会怎样显示这些差异
输出信息只在相应文件前面显示了9个字母来标识改变, 这些字母具体是什么意思呢? 请参考以下详细说明
rsync 可以使用--include 和 --exclude 选项结合通配符进行文件或文件夹过滤
在上面的示例中, 仅仅以P打头的文件和文件夹被包含了进来, 其他的文件都被过滤在拷贝的过程中被排除在外了.
可以使用--max-size 告诉rsync 不要拷贝大小超过某个值的文件, 可以使用K, M, G指定文件大小, M for megabytes and G for gigabytes.
rsync 有个重要优点就是, 可以做到在拷贝的过程中, 只拷贝发生变化了的部分, 而不是发送整个文件.
但是在某些场景中, 比如文件较少, 文件size较小时, 我们的带宽又足够大, cpu资源相对又贫乏, 我们不希望它这样做, 因为毕竟计算源端和目标端的checksum, 并做对比, 也需要额外cpu开销. 这时我们可以使用 -W, --whole-file 选项, 让rsync不用计算那么多, 一上来就直接开始传送文件. 我们可以像下面这么做.
15个Rsync命令实例
‘叁’ 如何在java程序中调用linux命令或者shell脚本
//实现远程连接Connectionconn=newConnection(scpAddr,Integer.parseInt(scpPort));conn.connect();//连接logger.debug("登陆远程服务器:"+scpUserName+","+scpPassword);//登陆远程服务器的用户名密码booleanisAuthed=conn.authenticateWithPassword(scpUserName,scpPassword);//登陆失败if(!isAuthed){logger.debug("登陆远程服务器失败");returnfalse;}SessionsshSession=conn.openSession();logger.debug("cd/&&rm-f"+file+"&&pwd");//linux命令sshSession.execCommand("cd/&&rm-f"+file+"&&pwd");//执行sshSession.close();conn.close();
‘肆’ 如何使用Windows下cwRsync搭建步骤文件详细同步教程
详细配置步骤如下:
一、服务器端安装配置
1、下载服务端安装文件/softs/55933.html
服务端是安装在“需要被同步的数据所在的服务器”
2、安装服务端
默认一直下一步;到下图所示
这一步很重要,可以说是cwRsync安装成功与否的关键。这一步会在当前系统下生成SvcCWRSYNC一个账户,用来启动RsyncServer服务。建议这个对此账户的密码进行重新设置,从新设置密码后,需要在服务中更改,否则RsyncServer服务不能启动
程序自动设置程序目录可修改的权限,因为需要写日志文件。
配置文件默认在程序的安装目录下,默认是 C:Program FilesICW
3、修改配置文件rsyncd.conf
use chroot = false
strict modes = false
hosts allow = *
log file = rsyncd.log
# Mole definitions
# Remember cygwin naming conventions : c:work becomes /cygwin/c/work
#
[test]
path = /cygdrive/c/work
read only = false
transfer logging = yes
修改配置内容如下,参数解释:
use chroot = false # 不使用chroot
strict modes = false #使用静态模式
pid file = rsyncd.pid # 日志记录文件
log file = rsyncd.log # 日志记录文件
[test] # 这里是认证的模块名,在client端需要指定
path = /cygdrive/e/bak # 需要做镜像的目录,如e/bak(此目录需要给SvcCWRSYNC 账户的读取权限)
transfer logging = yes # 记录传输日志
read only = yes # 只读
list = no # 不允许列文件
hosts allow = 192.168.0.0/255.255.252.0
hosts deny = * # 禁止除192.168.1.0/255.255.255.0段外的连接IP
uid = 0 #解决运行后的@ERROR: invalid uid 错误
gid = 0 #解决运行后的@ERROR: invalid gid错误
lock file = rsyncd.lock #解决文件被锁定无法复制问题
#auth users = SvcCWRSYNC # 认证的用户名,这里没有这行,则表明是匿名 #secrets file = /cygdrive/c/pass/rsync.passwd # 认证文件名
创建认证文件 c/pass/rsync.passwd 内容格式:用户:密码,如SvcCWRSYNC:star888 启动RsyncServer服务并保证开机自动重启,服务器端配置完毕。
二、客户端安装配置
1、下载客户端安装文件/softs/55934.html
2、安装
一直下一步,安装完成。
3、cwRsync客户端不需要很复杂的.配置,需要设置环境变量path:c:Program FilescwRsyncbin
4、通过命令建一个批处理
@echo off #没有密码
set RSYNC_HOME=c:Program FilescwRsyncbin
cd %RSYNC_HOME%
d:
rsync -av rsync://192.168.2.242/test /cygdrive/e/test
@echo off #需要密码
set RSYNC_HOME=c:Program FilescwRsyncbin
cd %RSYNC_HOME%
d:
rsync -avzP --progress -- --password-file=/cygdrive/c/pass/rsync.passwd [email protected]::test /cygdrive/e/test
意思是将192.168.2.242的服务端下的test模块下的e:bak目录同步到客户端的e:test下
命令参数解释:
-avzP
-a 参数,相当于-rlptgoD,-r 是递归 -l 是链接文件,意思是拷贝链接文件;-p 表示保持文件原有权限;-t 保持文件原有时间;-g 保持文件原有用户组;-o 保持文件原有属主;-D 相当于块设备文件;
-z 传输时压缩;
-P 传输进度;
-v verbose,详细模式输出
-u是只同步已经更新的文件,避免没有更新的文件被重复更新一次,不过要注意两者机器时钟的同步
--progress
显示备份过程
--
删除那些DST中SRC没有的文件
--password-file
指定密码文件,只需包含密码而不用包含用户名
[email protected]::test
SvcCWRSYNC 登陆服务器端的用户名
192.168.2.242 服务器端的ip地址
test 服务器端指定的认证模块名,多个同步作业靠此模块名区分
/cygdrive/e/test
存放服务器端同步文件的本地目录
4、配置系统作业,运行脚本选择rsync.bat,客户端配置完毕。
‘伍’ java 调用 shell 脚本
在写程序时,有时需要在java程序中调用shell脚本,可以通过Runtime的exec方法来调用shell程序,运行脚本。每个Java 应用程序都有一个Runtime 类实例,使应用程序能够与其运行的环境相连接。通过Runtime对象可以返回运行环境的情况,包括CPU数,虚拟机内存大小等,并能够通过exec方法调用执行命令。可以通过getRuntime 方法获取当前Runtime实例。 public boolean ExeShell(){ Runtime rt = Runtime.getRuntime(); try { Process p = rt.exec(checkShellName); if(p.waitFor() != 0) return false; } catch (IOException e) { SysLog.error("没有找到检测脚本"); return false; } catch (InterruptedException e) { e.printStackTrace(); return false; } return true; } 其中p.waitFor()语句用来等待子进程结束,其返回值为进程结束退出码。
‘陆’ java 远程调用,具体实现
1、使用java代码通过ssh登陆linux并执行命令,正常情况下sshd服务都会有,知道用户密码就可以使用这种方式了。
2、要在远程linux服务器上执行命令,可以在linux服务器上启动一个服务做执行脚本的代理,不断接受来自你这个项目发来的脚本信息,然后执行,再把结果返回给你项目中去。
第一种:以消息队列的方式发送这些脚本信息,linux代理不断接受,并返回结果。项目同时要不断接受结果。
第二种:项目中产生的脚本,直接记录到数据库,linux上的代理访问这个数据库,获取脚本信息,并将执行结果写入数据库。项目中轮询执行结果。
上面我说的消息队列可以采用activemq,或者rabbitmq这些。
命令执行,java中直接Runtime.getRuntime().exec(command);就行了。
‘柒’ 如何在java程序中调用linux命令或者shell脚本
做到这,主要依赖2个类:Process和Runtime。
首先看一下Process类:
ProcessBuilder.start() 和 Runtime.exec 方法创建一个本机进程,并返回 Process 子类的一个实例,
该实例可用来控制进程并获得相关信息。Process 类提供了执行从进程输入、执行输出到进程、等待进程完成、
检查进程的退出状态以及销毁(杀掉)进程的方法。
创建进程的方法可能无法针对某些本机平台上的特定进程很好地工作,比如,本机窗口进程,守护进程,Microsoft Windows
上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)
操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程。
父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,
如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁。
当没有 Process 对象的更多引用时,不是删掉子进程,而是继续异步执行子进程。
对于带有 Process 对象的 Java 进程,没有必要异步或并发执行由 Process 对象表示的进程。
特别需要注意的是:
1,创建的子进程没有自己的终端控制台,所有标注操作都会通过三个流
(getOutputStream()、getInputStream() 和 getErrorStream()) 重定向到父进程(父进程可通过这些流判断子进程的执行情况)
2,因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,如果读写子进程的输出流或输入流迅速出现失败,
则可能导致子进程阻塞,甚至产生死锁
abstract void destroy()
杀掉子进程。
abstract int exitValue()
返回子进程的出口值。根据惯例,值0表示正常终止。
abstract InputStream getErrorStream()
获取子进程的错误流。
abstract InputStream getInputStream()
获取子进程的输入流。
abstract OutputStream getOutputStream()
获取子进程的输出流。
abstract int waitFor()
导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。
如果已终止该子进程,此方法立即返回。如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程。
特别需要注意:如果子进程中的输入流,输出流或错误流中的内容比较多,最好使用缓存(注意上面的情况2)
再来看一下Runtime类:
每个Java应用程序都有一个Runtime类实例,使应用程序能够与其运行的环境相连接。可以通过getRuntime方法获取当前运行时环境。
应用程序不能创建自己的Runtime类实例。
介绍几个主要方法:
Process exec(String command)
在单独的进程中执行指定的字符串命令。
Process exec(String command, String[] envp)
在指定环境的单独进程中执行指定的字符串命令。
Process exec(String command, String[] envp, File dir)
在有指定环境和工作目录的独立进程中执行指定的字符串命令。
Process exec(String[] cmdarray)
在单独的进程中执行指定命令和变量。
Process exec(String[] cmdarray, String[] envp)
在指定环境的独立进程中执行指定命令和变量。
Process exec(String[] cmdarray, String[] envp, File dir)
在指定环境和工作目录的独立进程中执行指定的命令和变量。
command:一条指定的系统命令。
envp:环境变量字符串数组,其中每个环境变量的设置格式为name=value;如果子进程应该继承当前进程的环境,则该参数为null。
dir:子进程的工作目录;如果子进程应该继承当前进程的工作目录,则该参数为null。
cmdarray:包含所调用命令及其参数的数组。
以下为示例(要打成可执行jar包扔到linux下执行):
public class test {
public static void main(String[] args){
InputStream in = null;
try {
Process pro = Runtime.getRuntime().exec(new String[]{"sh",
"/home/test/test.sh","select admin from M_ADMIN",
"/home/test/result.txt"});
pro.waitFor();
in = pro.getInputStream();
BufferedReader read = new BufferedReader(new InputStreamReader(in));
String result = read.readLine();
System.out.println("INFO:"+result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在这用的是Process exec(String[] cmdarray)这个方法
/home/test/test.sh脚本如下:
#!/bin/sh
#查询sql
SQL=$1
#查询结果保存文件
RESULT_FILE=$2
#数据库连接
DB_NAME=scott
DB_PWD=tiger
DB_SERVER=DB_TEST
RESULT=`sqlplus -S ${DB_NAME}/${DB_PWD}@${DB_SERVER}<< !
set heading off
set echo off
set pages 0
set feed off
set linesize 3000
${SQL}
/
commit
/
!`
echo "${RESULT}" >> ${RESULT_FILE}
echo 0;
特别需要注意的是,当需要执行的linux命令带有管道符时(例如:ps -ef|grep java),用上面的方法是不行的,解决方式是将需要执行的命令作为参数传给shell
public class Test {
public static void main(String[] args) throws Exception{
String[] cmds = {"/bin/sh","-c","ps -ef|grep java"};
Process pro = Runtime.getRuntime().exec(cmds);
pro.waitFor();
InputStream in = pro.getInputStream();
BufferedReader read = new BufferedReader(new InputStreamReader(in));
String line = null;
while((line = read.readLine())!=null){
System.out.println(line);
}
}
}
PS:
Runtime.getRuntime().exec()这种调用方式在java虚拟机中是十分消耗资源的,即使命令可以很快的执行完毕,频繁的调用时创建进程消耗十分客观。
java虚拟机执行这个命令的过程是,首先克隆一条和当前虚拟机拥有一样环境变量的进程,再用这个新的进程执行外部命令,最后退出这个进程。频繁的创建对CPU和内存的消耗很大。
‘捌’ 2020-02-29 Rsync企业级全网备份项目案例实践
某公司里有一台Web服务器,里面的数据很重要,但是如果硬盘坏了数据就会丢失,现在领导要求把数据做备份,这样Web服务器数据丢失在可以进行恢复,要求如下:
每天晚上00点整在Web服务器A上打包备份系统配置文件、网站程序目录及访问日志并通过rsync命令推送到服务器B上备份保留(备份思路可以是先在本地按日期打包,然后再推到备份服务器B上)。
已知3台服务器主机名分别为Web01、BACKUP、NFS01,主机信息建下表。
(1)备份要求
每天晚上00点整在Web服务器上打包备份系统配置文件、网站程序目录及访问日志并通过rsync命令推送到备份服务器BACKUP上备份保留。
(2)备份思路
可以是先在本地按日期打包,然后再推送到备份服务器BACKUP上;NFS存储服务器和Web服务器在实际工作中就是运维主要管理的全部服务器。
1)所有服务器的备份目录必须都为/backup。
2)要备份的系统配置文件包括但不限于:
3)Web服务器站点目录假定为/var/html/www,如果没有,可以先模拟创建。
4)Web服务器访问日志路径假定为/app/logs,如果没有,可以先模拟创建。
5)Web服务器本地保留打包后的7天备份数据即可(本地留存不能多于7天,因为数据太多硬盘会满)。
6)备份服务器BACKUP上,保留最近7天所有备份数据,保留6个月内每周一的所有数据副本。
7)备份服务器上,要按照备份数据服务器内网IP为目录保存备份,备份的文件按照时间、名称保存。
8)需要确保备份的数据尽量完整准确,在备份服务器上对备份的数据是否完整进行检查。
9)备份服务器BACKUP上,每天早晨8:00把备份成功或失败结果信息发送到系统管理员邮箱里。
初始化3台服务器并将其作为全网备份方案测试服务器,3台服务器基本规划信息如下:
(1)搭建备份服务器
(2)Web服务器搭建部署
(3)NFS服务器搭建部署
第一个里程碑:在BACKUP上部署Rsync服务
在BACKUP Server上部署Rsync服务(前面有讲,这里忽略),并且在Web01和NFS01客户端服务器测试推送成功。
第二个里程碑:客户端本地开发打脚本
Nginx webserver,NFS dataserver本地打包备份脚本实现。
1)Web01服务器待备份信息。
Web站点目录与日志目录创建完毕后,开始编写脚本信息(脚本编写信息需要在命令行测试成功后再放入脚本文件中)。
第三个里程碑:配置定时任务
1)编辑定时任务,实现每天00:00定时备份本地数据,并推送到备份数据Rsync服务器上。
2)编辑服务端删除文件脚本文件,删除180天前的所有备份数据,但保存每周一的。
3)配置定时任务,实现服务端180天前数据自动删除。
第四个里程碑:数据传输完整性验证与监控告警
服务端针对客户端备份时的md5指纹数据,利用MD5命令进行验证,完成数据传输过程完整性验证。
第五个里程碑:配置mail使用外部SMTP发邮件
安装mailx
编辑/etc/mail.rc配置文件
各命令参数的含义说明如下:
配置成功后,测试一下。
至此,全网备份方案完成。
‘玖’ Linux下rsync怎么用
rsync的命令格式可以为:
1. rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
2. rsync [OPTION]... [USER@]HOST:SRC DEST
3. rsync [OPTION]... SRC [SRC]... DEST
4. rsync [OPTION]... [USER@]HOST::SRC [DEST]
5. rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
6. rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]
rsync有六种不同的工作模式:
1. 拷贝本地文件;当SRC和DES路径信息都不包含有单个冒号":"分隔符时就启动这种工作模式。
2.使用一个远程shell程序(如rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当DST路径地址包含单个冒号":"分隔符时启动该模式。
3.使用一个远程shell程序(如rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当SRC地址路径包含单个冒号":"分隔符时启动该模式。
4. 从远程rsync服务器中拷贝文件到本地机。当SRC路径信息包含"::"分隔符时启动该模式。
5. 从本地机器拷贝文件到远程rsync服务器中。当DST路径信息包含"::"分隔符时启动该模式。
6. 列远程机的文件列表。这类似于rsync传输,不过只要在命令中省略掉本地机信息即可。
-a 以archive模式操作、复制目录、符号连接 相当于-rlptgoD
rsync中的参数
-r 是递归
-l 是链接文件,意思是拷贝链接文件;-p 表示保持文件原有权限;-t 保持文件原有时间;-g 保持文件原有用户组;-o 保持文件原有属主;-D 相当于块设备文件;
-z 传输时压缩;
-P 传输进度;
-v 传输时的进度等信息,和-P有点关系,自己试试。可以看文档;
-e ssh的参数建立起加密的连接。
-u只进行更新,防止本地新文件被重写,注意两者机器的时钟的同时
--progress是指显示出详细的进度情况
--delete是指如果服务器端删除了这一文件,那么客户端也相应把文件删除,保持真正的一致
--password-file=/password/path/file来指定密码文件,这样就可以在脚本中使用而无需交互式地输入验证密码了,这里需要注意的是这份密码文件权限属性要设得只有属主可读。
B、一些实例
B1、列出rsync 服务器上的所提供的同步内容;
首先:我们看看rsync服务器上提供了哪些可用的数据源
# rsync --list-only [email protected]::
++++++++++++++++++++++++++++++++++++++++++++++
Welcome to use the mike.org.cn rsync services!
2002------2009
++++++++++++++++++++++++++++++++++++++++++++++
rhel4home This is RHEL 4 data
注:前面是rsync所提供的数据源,也就是我们在rsyncd.conf中所写的[rhel4home]模块。而“This is RHEL 4 data”是由[rhel4home]模块中的 comment = This is RHEL 4 data 提供的;为什么没有把rhel4opt数据源列出来呢?因为我们在[rhel4opt]中已经把list=no了。
$ rsync --list-only [email protected]::::rhel4home
++++++++++++++++++++++++++++++++++++++++++++++
Welcome to use the mike.org.cn rsync services!
2002------2009
++++++++++++++++++++++++++++++++++++++++++++++
Password:
drwxr-xr-x 4096 2009/03/15 21:33:13 .
-rw-r--r-- 1018 2009/03/02 02:33:41 ks.cfg
-rwxr-xr-x 21288 2009/03/15 21:33:13 wgetpaste
drwxrwxr-x 4096 2008/10/28 21:04:05 cvsroot
drwx------ 4096 2008/11/30 16:30:58 easylife
drwsr-sr-x 4096 2008/09/20 22:18:05 giddir
drwx------ 4096 2008/09/29 14:18:46 quser1
drwx------ 4096 2008/09/27 14:38:12 quser2
drwx------ 4096 2008/11/14 06:10:19 test
drwx------ 4096 2008/09/22 16:50:37 vbird1
drwx------ 4096 2008/09/19 15:28:45 vbird2
后面的root@ip中,root是指定密码文件中的用户名,之后的::rhel4home这是rhel4home模块名
B2、rsync客户端同步数据;
#rsync -avzP [email protected]::rhel4home rhel4home
Password: 这里要输入root的密码,是服务器端rsyncd.secrets提供的。在前面的例子中我们用的是mike,输入的密码并不回显,输好就回车。
注: 这个命令的意思就是说,用root用户登录到服务器上,把rhel4home数据,同步到本地当前目录rhel4home上。当然本地的目录是可以你自己 定义的。如果当你在客户端上当前操作的目录下没有rhel4home这个目录时,系统会自动为你创建一个;当存在rhel4home这个目录中,你要注意 它的写权限。
#rsync -avzP --delete [email protected]::rhel4home rhel4home
这回我们引入一个--delete 选项,表示客户端上的数据要与服务器端完全一致,如果 linuxsirhome目录中有服务器上不存在的文件,则删除。最终目的是让linuxsirhome目录上的数据完全与服务器上保持一致;用的时候要 小心点,最好不要把已经有重要数所据的目录,当做本地更新目录,否则会把你的数据全部删除;
设定 rsync client
设定密码文件
#rsync -avzP --delete --password-file=rsyncd.secrets [email protected]::rhel4home rhel4home
这次我们加了一个选项 --password-file=rsyncd.secrets,这是当我们以root用户登录rsync服务器同步数据时,密码将读取rsyncd.secrets这个文件。这个文件内容只是root用户的密码。我们要如下做;
# touch rsyncd.secrets
# chmod 600 rsyncd.secrets
# echo "mike"> rsyncd.secrets
# rsync -avzP --delete --password-file=rsyncd.secrets [email protected]::rhel4home rhel4home
注:这里需要注意的是这份密码文件权限属性要设得只有属主可读。
这样就不需要密码了;其实这是比较重要的,因为服务器通过crond 计划任务还是有必要的;
B3、让rsync客户端自动与服务器同步数据
服务器是重量级应用,所以数据的网络备份还是极为重要的。我们可以在生产型服务器上配置好rsync 服务器。我们可以把一台装有rysnc机器当做是备份服务器。让这台备份服务器,每天在早上4点开始同步服务器上的数据;并且每个备份都是完整备份。有时 硬盘坏掉,或者服务器数据被删除,完整备份还是相当重要的。这种备份相当于每天为服务器的数据做一个镜像,当生产型服务器发生事故时,我们可以轻松恢复数 据,能把数据损失降到最低;是不是这么回事??
step1:创建同步脚本和密码文件
#mkdir /etc/cron.daily.rsync
#cd /etc/cron.daily.rsync
#touch rhel4home.sh rhel4opt.sh
#chmod 755 /etc/cron.daily.rsync/*.sh
#mkdir /etc/rsyncd/
#touch /etc/rsyncd/rsyncrhel4root.secrets
#touch /etc/rsyncd/rsyncrhel4easylife.secrets
#chmod 600 /etc/rsyncd/rsync.*
注: 我们在 /etc/cron.daily/中创建了两个文件rhel4home.sh和rhel4opt.sh ,并且是权限是755的。创建了两个密码文件root用户用的是rsyncrhel4root.secrets ,easylife用户用的是 rsyncrhel4easylife.secrets,权限是600;
我们编辑rhel4home.sh,内容是如下的:
#!/bin/sh
#backup 192.168.145.5:/home
/usr/bin/rsync -avzP --password-file=/etc/rsyncd/rsyncrhel4root.password [email protected]::rhel4home /home/rhel4homebak/$(date +'%m-%d-%y')
我们编辑 rhel4opt.sh ,内容是:
#!/bin/sh
#backup 192.168.145.5:/opt
/usr/bin/rsync -avzP --password-file=/etc/rsyncd/rsyncrhel4easylife.secrets [email protected]::rhel4opt /home/rhel4hoptbak/$(date +'%m-%d-%y')
注:你可以把rhel4home.sh和rhel4opt.sh的内容合并到一个文件中,比如都写到rhel4bak.sh中;
接着我们修改 /etc/rsyncd/rsyncrhel4root.secrets和rsyncrhel4easylife.secrets的内容;
# echo "mike" > /etc/rsyncd/rsyncrhel4root.secrets
# echo "keer"> /etc/rsyncd/rsyncrhel4easylife.secrets
然后我们再/home目录下创建rhel4homebak 和rhel4optbak两个目录,意思是服务器端的rhel4home数据同步到备份服务器上的/home/rhel4homebak 下,rhel4opt数据同步到 /home/rhel4optbak/目录下。并按年月日归档创建目录;每天备份都存档;
#mkdir /home/rhel4homebak
#mkdir /home/rhel4optbak
step2:修改crond服务器的配置文件 加入到计划任务
#crontab -e
加入下面的内容:
# Run daily cron jobs at 4:10 every day backup rhel4 data:
10 4 * * * /usr/bin/run-parts /etc/cron.daily.rsync 1> /dev/null
注:第一行是注释,是说明内容,这样能自己记住。
第二行表示在每天早上4点10分的时候,运行 /etc/cron.daily.rsync 下的可执行脚本任务;
配置好后,要重启crond 服务器;
# killall crond 注:杀死crond 服务器的进程;
# ps aux |grep crond 注:查看一下是否被杀死;
# /usr/sbin/crond 注:启动 crond 服务器;
# ps aux |grep crond 注:查看一下是否启动了?
root 3815 0.0 0.0 1860 664 ? S 14:44 0:00 /usr/sbin/crond
root 3819 0.0 0.0 2188 808 pts/1 S+ 14:45 0:00 grep crond
‘拾’ 怎么通过java去调用并执行shell脚本以及问题总结
对于第一个问题:java抓取,并且把结果打包。那么比较直接的做法就是,java接收各种消息(db,metaq等等),然后借助于jstorm集群进行调度和抓取。
最后把抓取的结果保存到一个文件中,并且通过调用shell打包, 回传。 也许有同学会问,
为什么不直接把java调用odps直接保存文件,答案是,我们的集群不是hz集群,直接上传odps速度很有问题,因此先打包比较合适。(这里不纠结设计了,我们回到正题)
java调用shell的方法
通过ProcessBuilder进行调度
这种方法比较直观,而且参数的设置也比较方便, 比如我在实践中的代码(我隐藏了部分业务代码):
ProcessBuilderpb = new ProcessBuilder("./" + RUNNING_SHELL_FILE, param1,
param2, param3);
pb.directory(new File(SHELL_FILE_DIR));
int runningStatus = 0;
String s = null;
try {
Process p = pb.start();
try {
runningStatus = p.waitFor();
} catch (InterruptedException e) {
}
} catch (IOException e) {
}
if (runningStatus != 0) {
}
return;
这里有必要解释一下几个参数:
RUNNING_SHELL_FILE:要运行的脚本
SHELL_FILE_DIR:要运行的脚本所在的目录; 当然你也可以把要运行的脚本写成全路径。
runningStatus:运行状态,0标识正常。 详细可以看java文档。
param1, param2, param3:可以在RUNNING_SHELL_FILE脚本中直接通过1,2,$3分别拿到的参数。
直接通过系统Runtime执行shell
这个方法比较暴力,也比较常用, 代码如下:
p = Runtime.getRuntime().exec(SHELL_FILE_DIR + RUNNING_SHELL_FILE + " "+param1+" "+param2+" "+param3);
p.waitFor();
我们发现,通过Runtime的方式并没有builder那么方便,特别是参数方面,必须自己加空格分开,因为exec会把整个字符串作为shell运行。
可能存在的问题以及解决方法
如果你觉得通过上面就能满足你的需求,那么可能是要碰壁了。你会遇到以下情况。
没权限运行
这个情况我们团队的朱东方就遇到了, 在做DTS迁移的过程中,要执行包里面的shell脚本, 解压出来了之后,发现执行不了。 那么就按照上面的方法授权吧
java进行一直等待shell返回
这个问题估计更加经常遇到。 原因是, shell脚本中有echo或者print输出, 导致缓冲区被用完了! 为了避免这种情况, 一定要把缓冲区读一下, 好处就是,可以对shell的具体运行状态进行log出来。 比如上面我的例子中我会变成:
ProcessBuilderpb = new ProcessBuilder("./" + RUNNING_SHELL_FILE, keyword.trim(),
taskId.toString(), fileName);
pb.directory(new File(CASPERJS_FILE_DIR));
int runningStatus = 0;
String s = null;
try {
Process p = pb.start();
BufferedReaderstdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
BufferedReaderstdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));
while ((s = stdInput.readLine()) != null) {
LOG.error(s);
}
while ((s = stdError.readLine()) != null) {
LOG.error(s);
}
try {
runningStatus = p.waitFor();
} catch (InterruptedException e) {
}
记得在start()之后, waitFor()之前把缓冲区读出来打log, 就可以看到你的shell为什么会没有按照预期运行。 这个还有一个好处是,可以读shell里面输出的结果, 方便java代码进一步操作。
也许你还会遇到这个问题,明明手工可以运行的命令,java调用的shell中某一些命令居然不能执行,报错:命令不存在!
比如我在使用casperjs的时候,手工去执行shell明明是可以执行的,但是java调用的时候,发现总是出错。
通过读取缓冲区就能发现错误日志了。 我发现即便自己把安装的casperjs的bin已经加入了path中(/etc/profile,
各种bashrc中)还不够。 比如:
exportNODE_HOME="/home/admin/node"
exportCASPERJS_HOME="/home/admin/casperjs"
exportPHANTOMJS_HOME="/home/admin/phantomjs"
exportPATH=$PATH:$JAVA_HOME/bin:/root/bin:$NODE_HOME/bin:$CASPERJS_HOME/bin:$PHANTOMJS_HOME/bin
原来是因为java在调用shell的时候,默认用的是系统的/bin/下的指令。特别是你用root权限运行的时候。 这时候,你要在/bin下加软链了。针对我上面的例子,就要在/bin下加软链:
ln -s /home/admin/casperjs/bin/casperjscasperjs;
ln -s /home/admin/node/bin/nodenode;
ln -s /home/admin/phantomjs/bin/phantomjsphantomjs;
这样,问题就可以解决了。
如果是通过java调用shell进行打包,那么要注意路径的问题了
因为shell里面tar的压缩和解压可不能直接写:
tar -zcf /home/admin/data/result.tar.gz /home/admin/data/result
直接给你报错,因为tar的压缩源必须到路径下面, 因此可以写成
tar -zcf /home/admin/data/result.tar.gz -C /home/admin/data/ result
如果我的shell是在jar包中怎么办?
答案是:解压出来。再按照上面指示进行操作。(1)找到路径
String jarPath = findClassJarPath(ClassLoaderUtil.class);
JarFiletopLevelJarFile = null;
try {
topLevelJarFile = new JarFile(jarPath);
Enumeration<JarEntry> entries = topLevelJarFile.entries();
while (entries.hasMoreElements()) {
JarEntryentry = entries.nextElement();
if (!entry.isDirectory() && entry.getName().endsWith(".sh")) {
对你的shell文件进行处理
}
}
对文件处理的方法就简单了,直接touch一个临时文件,然后把数据流写入,代码:
FileUtils.touch(tempjline);
tempjline.deleteOnExit();
FileOutputStreamfos = new FileOutputStream(tempjline);
IOUtils.(ClassLoaderUtil.class.getResourceAsStream(r), fos);
fos.close();