目录
背景
客户需要对服务器上存量的文件有一个直观的了解,通过图形界面手动操作文件的定时定量备份
存储方案对比
网上一些现有的存储方案对比
备份一体机 | 传统 | |
备份原理 | 在设计部文件服务器上安装备份代理,定期通过网络备份到一体机 | 通过DFS复制,实时或定期将文件副本写入新服务器,定期对新服务器存储的文件备份到本地 |
安全性 | 因为其使用linux做为底层,不易受到勒索病毒***,文件不易被删除,安全性较高 | 使用windows,在windows上使用备份工具备份,易受到勒索病毒***,存在安全风险 |
可靠性 | 使用单独的一体机,只用于备份设计部文件服务器时,可靠性较高 | 使用wsb,文件易被误删除,导致无法还原,可靠性比一体机低 |
如果DFS出错,未能同步,可能导致数据丢失 | ||
底层OS为windows,备份做为一种工具或程序存在,OS故障,则导致备份可能无法还原 | ||
备份时间 | 按备份速度为50MB/s,即网络流量为400Mbps,单次全备时间预计60个小时以上 | 除首次同步外,不需长时间占用网络流量,在同一台机器上备份 |
价格 | 160000 | 55000 |
优势 | 可靠性高,操作简单,易于验证,不易出错 | 价格较低,只购买一台服务器,且服务器可以用于提供文件服务(备用文件服务器) |
劣势 | 价格较高 | 可靠性和安全性比备份一体机低,无售后技术支持 |
逻辑思路
根据现实情况,考虑到经费问题,采用传统方案
在原有系统中开发
- 系统原本文件地址信息可能分布于不同的模块内容(表)中,//也有可能集中存储
- 单独一张表,用于存储文件相关信息,其中包含上传时间、是否已备份
- 文件服务器上部署相关代码,提供接口,负责文件的增删改查功能
- 业务系统增加参数设置,控制文件是否实时同步、定时任务(对备份时间、数量、文件类型等情况进行管理)
- 定时任务执行文件信息表中最近上传且未备份的记录
- 异常情况预警处理,如:备份失败自动跳过并汇总失败信息、磁盘空间不足预警提示
- 操作层面删除的文件在服务器系统内是否保存根据实际情况决定
在原有系统外运行客户端代码
待续
MD5文件校验
MD5校验(checksum)是通过对接收的传输数据执行散列运算来检查数据的正确性。
一个散列函数,比如 MD5,是一个将任意长度的数据字符串转化成短的固定长度的值的单向操作。任意两个字符串不应有相同的散列值(即,有“很大可能”是不一样的,并且要人为地创造出来两个散列值相同的字符串应该是困难的)。
一个 MD5 校验和(checksum)通过对接收的传输数据执行散列运算来检查数据的正确性。计算出的散列值拿来和随数据传输的散列值比较。如果两个值相同,说明传输的数据完整无误、没有被窜改过(前提是散列值没有被窜改),从而可以放心使用。
MD5校验可以应用多个领域,比如说机密资料的检验,下载文件的检验,明文密码的加密等。
如客户往我们数据中心同步一个文件,该文件使用MD5校验,那么客户在发送文件的同时会再发一个存有校验码的文件,我们拿到该文件后做MD5运算,得到的计算结果与客户发送的校验码相比较,如果一致则认为客户发送的文件没有出错,否则认为文件出错需要重新发送。
在使用网盘存储文件时,会对本地文件进行MD5校验,服务端数据会对存储的文件MD5校验值做存储,拿本地MD5值在数据库中做匹配,如果存在,直接在服务器内赋值,就能实现快速上传。
文件是否改变
用比较文件hash值的方法来对比文件是否变更,文件hash又叫文件签名,文件中哪怕一个bit位被改变了,文件hash就会不同。
比较常用的文件hash算法有MD5和SHA-1,这里使用MD5。
package com.test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
public class MD5Util {
public static void main(String[] args) {
try {
//此处我测试的是我本机jdk源码文件的MD5值
String filePath = "C:\\Program Files\\Java\\jdk1.7.0_45\\src.zip";
String md5Hashcode = md5HashCode(filePath);
String md5Hashcode32 = md5HashCode32(filePath);
System.out.println(md5Hashcode + ":文件的md5值");
System.out.println(md5Hashcode32+":文件32位的md5值");
//System.out.println(-100 & 0xff);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取文件的md5值 ,有可能不是32位
* @param filePath 文件路径
* @return
* @throws FileNotFoundException
*/
public static String md5HashCode(String filePath) throws FileNotFoundException{
FileInputStream fis = new FileInputStream(filePath);
return md5HashCode(fis);
}
/**
* 保证文件的MD5值为32位
* @param filePath 文件路径
* @return
* @throws FileNotFoundException
*/
public static String md5HashCode32(String filePath) throws FileNotFoundException{
FileInputStream fis = new FileInputStream(filePath);
return md5HashCode32(fis);
}
/**
* java获取文件的md5值
* @param fis 输入流
* @return
*/
public static String md5HashCode(InputStream fis) {
try {
//拿到一个MD5转换器,如果想使用SHA-1或SHA-256,则传入SHA-1,SHA-256
MessageDigest md = MessageDigest.getInstance("MD5");
//分多次将一个文件读入,对于大型文件而言,比较推荐这种方式,占用内存比较少。
byte[] buffer = new byte[1024];
int length = -1;
while ((length = fis.read(buffer, 0, 1024)) != -1) {
md.update(buffer, 0, length);
}
fis.close();
//转换并返回包含16个元素字节数组,返回数值范围为-128到127
byte[] md5Bytes = md.digest();
BigInteger bigInt = new BigInteger(1, md5Bytes);//1代表绝对值
return bigInt.toString(16);//转换为16进制
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* java计算文件32位md5值
* @param fis 输入流
* @return
*/
public static String md5HashCode32(InputStream fis) {
try {
//拿到一个MD5转换器,如果想使用SHA-1或SHA-256,则传入SHA-1,SHA-256
MessageDigest md = MessageDigest.getInstance("MD5");
//分多次将一个文件读入,对于大型文件而言,比较推荐这种方式,占用内存比较少。
byte[] buffer = new byte[1024];
int length = -1;
while ((length = fis.read(buffer, 0, 1024)) != -1) {
md.update(buffer, 0, length);
}
fis.close();
//转换并返回包含16个元素字节数组,返回数值范围为-128到127
byte[] md5Bytes = md.digest();
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;//解释参见最下方
if (val < 16) {
/**
* 如果小于16,那么val值的16进制形式必然为一位,
* 因为十进制0,1...9,10,11,12,13,14,15 对应的 16进制为 0,1...9,a,b,c,d,e,f;
* 此处高位补0。
*/
hexValue.append("0");
}
//这里借助了Integer类的方法实现16进制的转换
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* 方法md5HashCode32 中 ((int) md5Bytes[i]) & 0xff 操作的解释:
* 在Java语言中涉及到字节byte数组数据的一些操作时,经常看到 byte[i] & 0xff这样的操作,这里就记录总结一下这里包含的意义:
* 1、0xff是16进制(十进制是255),它默认为整形,二进制位为32位,最低八位是“1111 1111”,其余24位都是0。
* 2、&运算: 如果2个bit都是1,则得1,否则得0;
* 3、byte[i] & 0xff:首先,这个操作一般都是在将byte数据转成int或者其他整形数据的过程中;使用了这个操作,最终的整形数据只有低8位有数据,其他位数都为0。
* 4、这个操作得出的整形数据都是大于等于0并且小于等于255的
*/
}