fltp備份文件後統計驗證
上一篇(//www.cnblogs.com/jying/p/16805821.html)記錄了自己在centos使用lftp備份文件的過程,本篇記錄自己對備份後的文件與源文件目錄的對比統計。
三種思路:
1、代碼(如java等)循環遍歷所有備份的源和ftp目標文件夾,統計個數對比。
2、執行linux命令行統計ftp備份文件夾和本地源文件夾文件總大小寫入統計文件,再利用java代碼讀取統計值後對比。
3、執行linux命令行統計ftp備份文件夾和本地源文件夾文件總個數寫入統計文件,再利用java代碼讀取統計值後對比。
其中網上的介紹多是用的第一種方式,而且幾乎全都是複製粘貼的重複垃圾文章,個人嘗試可以執行,但文件夾較多和層級較多時會非常耗時(因為ftp的連接原理導致),最終放棄方式一。
用到的方式為commons.net(org.apache.commons.net)包的ftp功能,網上的垃圾文章直接略過,直接用官方的實例測試:

其中main函數里的測試方式可以是:
public static void test() throws UnknownHostException {
String cmd = "-n ftp服務器ip 賬號 密碼 /";
String[] cmds = cmd.split(" ");
main(cmds);
}
不記得有沒有參考這倆文章了://www.cnblogs.com/chen1281024/p/15625278.html 、//www.cnblogs.com/leonlipfsj/p/15972372.html
然後考慮方式2,關於linux統計目錄下文件大小的命令是du,
du常用的選項: -h:--human-readable 以人類可讀的方式顯示,即自動轉為K,M,G等單位。 -a:-all 顯示目錄佔用的磁盤空間大小,含子目錄和文件佔用磁盤空間的大小詳細列表 -s:--summarize 顯示目錄佔用的磁盤空間大小,不含子目錄和文件佔用的磁盤空間大小詳細列表
-b:-bytes 顯示目錄或文件大小時,以byte為單位。
-k:--kilobytes 以KB(1024bytes)為單位輸出。
-m:--megabytes 以MB為單位輸出。 -c:--total 顯示目錄或文件佔用的磁盤空間大小,統計它們的總和。 --apparent-size:顯示目錄或文件自身的大小 -l :統計硬鏈接佔用磁盤空間的大小 -L:統計符號鏈接所指向的文件佔用的磁盤空間大小 du -sh : 查看當前目錄總共占的容量。而不單獨列出各子項佔用的容量。 du -sh * | sort -n 統計當前文件夾(目錄)大小,並按文件大小排序 du -lh --max-depth=1 : 查看當前目錄下一級子文件和子目錄佔用的磁盤容量。
關於以上常用選項的實例可以參考://blog.csdn.net/pichcar1982/article/details/121531546
因為lftp也支持du,所以貌似可以直接通過du統計服務器本地源文件目錄大小和ftp服務器文件目錄大小對比就可以了,但實際執行過程發現本地文件要比同步到ftp服務器上的文件大,於是使用ls -l查看發現單個文件大小也不一致,個人猜測是服務器本地文件上傳保存的位元組流和ftp備份的位元組流長度不一致導致的。還有一種解釋(//blog.csdn.net/mtawaken/article/details/8491413 或//blog.csdn.net/weixin_42803243/article/details/123724755)說是服務器本身的存儲塊大小不一致導致的,所以du加參數–apparent-size即可,而我加上此參數發現還是不一致,即使單個文件顯示一致了,整個目錄的大小仍然有差異。
方式3,對比文件個數, linux命令ls -l 可以按行列出目錄下所有文件,可以直接根據行數統計出文件個數。
# 查看當前目錄下的文件數量(不包含子目錄中的文件) ls -l|grep "^-"| wc -l # 查看當前目錄下的文件數量(包含子目錄中的文件) 注意:R,代表遍歷子目錄 ls -lR|grep "^-"| wc -l # 查看當前目錄下的文件夾目錄個數(不包含子目錄中的目錄),同上,如果需要查看子目錄的,加上R ls -l|grep "^d"| wc -l
wc -l 表示統計輸出信息的行數,因為經過前面的過濾已經只剩下普通文件,一個目錄或文件對應一行,所以統計的信息的行數也就是目錄或文件的個數。參考://www.cnblogs.com/wangyuxing/p/15818042.html
在lftp中也可以使用該方式來統計文件個數,但有一些限制,比如lftp中的ls命令默認就是顯示按行的文件詳情,等同於普通命令ls -l,而且lftp中使用R參數無效,這意味着無法循環遍歷子文件夾目錄(了解過ftp的連接過程則知道在ftp里切換目錄需要重新連接),所以在lftp中的統計文件個數命令寫為:
ls 文件夾目錄 | grep "^-" | wc -l
這也就意味着只能統計單層的文件夾里的文件個數。但這也是能準確統計是否備份成功的最準確方式了。
所以我們需要對文件存儲路徑進行優化,優化後的存儲路徑應該滿足最終的統計路徑只有一層:
1、存儲根目錄/模塊/直接存儲文件
2、存儲根目錄/模塊/年月/日/存儲文件
3、存儲根目錄/模塊/年月日/存儲文件
4、存儲根目錄/年月/日/存儲文件
5、存儲根目錄/年月/日/模塊/存儲文件
推薦使用方式2或方式5,而且所有存儲格式應該統一,這樣便於備份腳本只寫一種遍歷即可。我這邊目前因為採用了多種存儲方式,導致編寫備份腳本時要分開寫多個(參考上一篇文章)。
一個備份和統計的實例如下:
if [ -d 存儲根目錄/模塊/年月/日 ]; then echo "目錄存在" ls 存儲根目錄/模塊/年月/日/存儲文件 -l|grep "^-"| wc -l >> /本地腳本目錄/bak_logs/年月/日/模塊.bak.count lftp -u 賬號,密碼 ftp服務器ip << EOF mirror --reverse --only-missing --only-newer 存儲根目錄/模塊/年月/日 --parallel=3 --log=/本地腳本目錄/logs/年月/日/模塊_小時.log ls 存儲根目錄/模塊/年月/日/存儲文件 |grep "^-"| wc -l >> /本地腳本目錄/bak_logs/年月/日/模塊.bak.count bye EOF else echo "不存在" fi
有了統計個數,就可以通過代碼來讀取並推送郵件給管理員了。


package test; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Properties; public class BakFile { private Properties props; private WarningEmail warningEmail; public BakFile(Properties _props) { props = _props; } public void run() { try { // 初始化 this.InitSetup(); // 獲取統計信息 this.readResultCount(); } catch (Exception exIO) { warningEmail.send_report_mail("核驗備份文件出錯啦!!!", exIO.toString()); exIO.printStackTrace(); } } private void InitSetup() throws IOException, SQLException { // 提醒郵件 warningEmail = new WarningEmail(props); } public void readResultCount() { // 獲取昨天的日期, Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE,-1);//昨天 String yesterday_ym = new SimpleDateFormat("yyyy-MM").format(cal.getTime()); String yesterday_d = new SimpleDateFormat("dd").format(cal.getTime()); String yesterday = yesterday_ym+"-"+yesterday_d; String path = "/var/jenkins_home/bak_logs/" + yesterday_ym + "/" + yesterday_d; File dirFile = new File(path); //如果dir對應的文件不存在,或者不是一個目錄,則退出 if (!dirFile.isDirectory()) { warningEmail.send_report_mail(yesterday+"核驗備份文件異常", "未獲取到昨天備份目錄"); return; } //獲取文件夾下所有文件 File[] files = dirFile.listFiles(); File file; List<String> list; String content = ""; String bodyTrContent = ""; if(files!=null && files.length>0) { for (int i = 0; i < files.length; i++) { file = files[i]; if(file.getName().endsWith("count")) { list = readFileContent(file); content = setContent(file, list); bodyTrContent = String.format("%s%s", bodyTrContent, content); } } content = setMailHtml(bodyTrContent, yesterday); warningEmail.send_report_mail(yesterday+"文件備份情況", content); } else { warningEmail.send_report_mail(yesterday+"文件備份為空", "未獲取到昨天備份目錄"); } } public List<String> readFileContent(File file) { BufferedReader reader = null; FileReader fileReader = null; List<String> list = new ArrayList<String>(); try { fileReader = new FileReader(file); reader = new BufferedReader(fileReader); String tempString = null; // 一次讀入一行,直到讀入null為文件結束 while ((tempString = reader.readLine()) != null) { list.add(tempString); // System.out.println(tempString); } } catch (IOException e) { } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { } } if(fileReader != null) { try { fileReader.close(); } catch (IOException e1) { } } } return list; } public String setContent(File file, List<String> list) { if(list.size()>=2) { String line1 = list.get(list.size()-2); String line2 = list.get(list.size()-1); return setTableBodyTr(file.getName().replace(".bak.count", ""), line1, line2); } else if(list.size()==1){ // String line1 = list.get(list.size()-1); return setTableBodyTr(file.getName().replace(".bak.count", ""), line1, "未同步"); } else { return setTableBodyTr(file.getName().replace(".bak.count", ""), "未獲取到文件夾", "未同步"); } } public String setMailHtml(String content, String yesterday) { String html = String.format("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' '//www.w3.org/TR/html4/strict.dtd'>" + " <html lang='en'> " + " <head> " + " <meta http-equiv='Content-Type' content='text/html;charset=UTF-8'>" + " </head> " + " <body>" + "<div style='font-size:12px;'>%s文件備份情況:<br/><br/>" + "<table width='584' style='border-collapse: collapse;font-size:9pt;width:438pt'><tr style='background-color:#f7f7f7;'>" + "<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px;width:38pt;'>目錄</td>" + "<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px;width:60pt;'>本地</td>" + "<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px;width:60pt;'>ftp</td>" + "</tr>", yesterday); html = String.format("%s%s", html, content); html = String.format("%s</table></body></html>", html); return html; } public String setTableBodyTr(String mName, String local, String ftp) { String content = ""; if(!local.equals(ftp)) { ftp = String.format("<span style='color:red;' color='red'>%s</span>", ftp); } content = String.format("%s<tr><td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px 5px 0 5px;width:38pt;'>%s</td>", content, mName); content = String.format("%s<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px 5px 0 5px;width:60pt;'>%s</td>", content, local); content = String.format("%s<td style='border:solid 1px #ccc;text-align:center;vertical-align:top;padding:5px 5px 0 5px;width:60pt;'>%s</td>", content, ftp); content = String.format("%s</tr>", content); return content; } }
java獲取統計信息並郵件推送給管理員


package test; import java.util.Date; import java.util.Properties; import javax.mail.BodyPart; import javax.mail.Message; import javax.mail.Multipart; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; public class WarningEmail { private Properties props; public WarningEmail(Properties _props) { this.props = _props; } public void send_report_mail(String subject, String content) { System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // System.out.println(subject); // System.out.println(content); try { String smtp = props.getProperty("mail.smtp"); String user = props.getProperty("mail.user"); String password = props.getProperty("mail.pwd"); String from = props.getProperty("mail.from"); String to = props.getProperty("mail.admin"); Properties p = new Properties(); p.put("mail.smtp.host", smtp); p.put("mail.smtp.port", "587"); p.put("mail.smtp.auth", "true"); Session ssn = Session.getDefaultInstance(p); Transport transport = ssn.getTransport("smtp"); transport.connect(smtp, user, password); send_email(ssn, transport, subject, content, from, to); } catch (Exception e) { e.printStackTrace(); } } private boolean send_email(Session ssn, Transport transport, String subject, String content, String from, String to) { try { BodyPart html = new MimeBodyPart(); html.setContent(content, "text/html; charset=utf-8"); Multipart mainPart = new MimeMultipart(); mainPart.addBodyPart(html); MimeMessage message = new MimeMessage(ssn); message.setContent(mainPart); message.setFrom(new InternetAddress(from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); // message.setRecipients(Message.RecipientType.BCC, InternetAddress.parse("抄送人郵箱")); // 密送人 message.setSubject(subject); message.setContent(mainPart); message.setSentDate(new Date()); transport.sendMessage(message, message.getAllRecipients()); System.out.println(String.format("%s : %s", to, subject)); return true; } catch (Exception e) { e.printStackTrace(); return false; } } }
發送郵件