fltp備份檔案後統計驗證

2022-11-10 18:00:23
上一篇(https://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功能,網上的垃圾文章直接略過,直接用官方的範例測試:

org.apache.commons.net測試範例

其中main函數裡的測試方式可以是:

    public static void test() throws UnknownHostException {
        String cmd = "-n ftp伺服器ip 賬號 密碼 /";
        String[] cmds = cmd.split(" ");
        main(cmds);
    }

不記得有沒有參考這倆文章了:https://www.cnblogs.com/chen1281024/p/15625278.html 、https://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 : 檢視當前目錄下一級子檔案和子目錄佔用的磁碟容量。

關於以上常用選項的範例可以參考:https://blog.csdn.net/pichcar1982/article/details/121531546

因為lftp也支援du,所以貌似可以直接通過du統計伺服器本地原始檔目錄大小和ftp伺服器檔案目錄大小對比就可以了,但實際執行過程發現本地檔案要比同步到ftp伺服器上的檔案大,於是使用ls -l檢視發現單個檔案大小也不一致,個人猜測是伺服器本地檔案上傳儲存的位元組流和ftp備份的位元組流長度不一致導致的。還有一種解釋(https://blog.csdn.net/mtawaken/article/details/8491413 或https://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 表示統計輸出資訊的行數,因為經過前面的過濾已經只剩下普通檔案,一個目錄或檔案對應一行,所以統計的資訊的行數也就是目錄或檔案的個數。參考:https://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' 'http://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;
        }
    }
}
傳送郵件