HDFS实战之保存数据
Hadoop分布式文件系统可以帮助我们存储很多的文件,今天给大家带来的是使用hdfs来定时保存本地的文件到hdfs(虚拟机)中。
需求:
- 启动定时任务。
- 定时探测日志源目录,获取需要采集的文件。
- 移动这些文件到待上传零时目录
- 遍历待上传目录中个文件,逐一传输到HDFS的目标路径。
- 同时将传输完成的文件移动到备份目录。
- 启动一个定时任务:探测备份目录中的备份数据,检查是否已经超出最长备份时长,如果超出则删除。
代码编写
编写主函数类
主函数主要负责运行并调用其他类和函数。
import java.util.Timer;
public class DataCollectMain {
public static void main(String[] args) {
Timer timer = new Timer();
// 每隔60*60*1000L(单位是毫秒现在是一小时)则执行CollectTack里面的run()方法
// 运行CollectTack类中的run方法,方法主要完成,采集文件,将文件移动到待上传区域,上传文件,上传完成后将文件移动到备用目录。
timer.schedule(new CollectTack(),0,60*60*1000L);
// 运行BackupCleanTask类中的run方法,方法主要完成,查看备用目录中是否有超过规定时间的文件夹,如果有则将文件夹删除。
timer.schedule(new BackupCleanTask(),0,60*60*1000L);
}
}
编写和引用配置文件collect.properties
因为我们在编写代码的时候会有用到很多的链接、地址等信息,所以干脆我们就将其编写成一个配置文件,在其他类想用配置文件的时候就不用了编写很长的链接信息了。
#这个是小编的日志源的根目录
LOG_SOURCE_DIR=D:/Linux/DAY02/logs/accesslog/
#这个是小编的日志的待上传目录
LOG_TOUPLOAD_DIR=D:/Linux/DAY02/logs/toupload/
#这个是小编的日志最后的备份目录
LOG_BACKUP_BASE_DIR=D:/Linux/DAY02/logs/backup/
#这个是小编设置的删除多少个小时前的日志文件
LOG_BACKUP_TIMEOUT=24
#因为在代码里面会有很多的用到这个字符串拼接,所以一起写上了
LOG_LEGAL_PREFIX=access.log.
#这个是这个是小编自己的hdfs的链接
HDFS_URI=hdfs://192.168.200.137:9000/
#这个是小编的hdfs里面的存放日志的路径
HDFS_DEST_BASE_DIR=/loags/
HDFS_FILE_PREFIX=access_log_
HDFS_FILE_SUFFIX=.log
编写一个封装类Constants
大家可以看到就算编写完上面的信息,但是在导入到我们需要的类的时候还是要编写配置文件中的字符串的名字,这样还是容易写错。所以我们再编写一个类,将这个配置文件中的属性的名称将其用字符串的形式赋值给定义的字符串,这样我们调用的时候就能有智能提示,提高了下面的开发的效率。
public class Constants {
// 日志源参数
public static final String LOG_SOURCE_DIR="LOG_SOURCE_DIR";
// 日志待上传目录参数
public static final String LOG_TOUPLOAD_DIR="LOG_TOUPLOAD_DIR";
public static final String LOG_BACKUP_BASE_DIR="LOG_BACKUP_BASE_DIR";
public static final String LOG_BACKUP_TIMEOUT="LOG_BACKUP_TIMEOUT";
public static final String LOG_LEGAL_PREFIX="LOG_LEGAL_PREFIX";
public static final String HDFS_URI="HDFS_URI";
public static final String HDFS_DEST_BASE_DIR="HDFS_DEST_BASE_DIR";
public static final String HDFS_FILE_PREFIX="HDFS_FILE_PREFIX";
public static final String HDFS_FILE_SUFFIX="HDFS_FILE_SUFFIX";
}
这里还有一个危险的地方,因为我们使用的是timer的方法定时的去执行任务,这样就造成了可能上一个任务还没有结束但是下一个任务就会直接进来的这种情况,这样就会一直的创建配置文件的信息给我们带来不便,这并不是我们想要的,所以我们需要一个锁来将我们的配置文件进行上锁,当配置文件的类还没有被生成的时候,就生成一个配置文件的类,但是配置文件生成了第一个任务还没有结束,紧接着第二个任务来创建配置文件类的时候则上锁,直到第一个任务创建配置文件信息完成第二个以后的任务就直接拿到配置文件信息无需创建。
上锁有两种方法供大家选择:以下方法选择一种即可。
懒汉式:PropertyHolderLazy
import java.io.IOException;
import java.util.Properties;
public class PropertyHolderLazy {
private static Properties prop=null;
public static Properties getProps() throws Exception {
if (prop == null) {
synchronized (PropertyHolderLazy.class) {
if (prop == null) {
prop = new Properties();
prop.load(PropertyHolderHunery.class.getClassLoader().getResourceAsStream("collect.properties"));
}
}
}
return prop;
}
}
饿汉式:PropertyHolderHunery
import java.util.Properties;
public class PropertyHolderHunery {
private static Properties prop=new Properties();
static {
try {
prop.load(PropertyHolderHunery.class.getClassLoader().getResourceAsStream("collect.properties"));
}catch (Exception e){
}
}
public static Properties getProps(){
return prop;
}
}
编写上传备份的CollectTack类
该类继承TimerTask,因为我们是在timer方法中调用这个类的,所以要想达到有定时的作用效果就要继承这个父类。
主要的方法都写在run()方法中,因为上面的timer是直接调用该类的run()方法。
当然在运行的时候我们要将我们之前搭好的hadoop打开。
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.fs.Path;
import sun.java2d.pipe.SpanShapeRenderer;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.*;
public class CollectTack extends TimerTask {
public void run() {
第一步:检查日志源目录里面的文件,并获取需要采集到的文件信息。
try{
// 获取配置参数这里使用的是懒汉式
final Properties props = PropertyHolderLazy.getProps();
// 获取本次采集时的日期
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
String day = sdf.format(new Date());
File srcDir = new File(props.getProperty(Constants.LOG_SOURCE_DIR));
// 列出日志源目录中需要采集的文件,只选出在配置文件中编写的如:access.log.3这种格式的日志文件。
File[] listfiles = srcDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
if(name.startsWith(props.getProperty(Constants.LOG_LEGAL_PREFIX))){
return true;
}
return false;
}
});
// 日志记录
System.out.println("探测到如下文件需要采集"+ Arrays.toString(listfiles));
第二步: 将日志源中的信息移动到待上传的本地文件夹下
// 将要采集的文件移动到待上传临时目录
File toUploadDir = new File(props.getProperty(Constants.LOG_TOUPLOAD_DIR));
for (File file : listfiles) {
FileUtils.moveFileToDirectory(file,toUploadDir,true);
}
// 记录日志
System.out.println("上述文件移动到待上上传目录"+toUploadDir.getAbsolutePath());
第三步:将日志文件上传到hdfs上并备份到本地的备份文件夹下
// 构造一个hdfs的客户端对象
FileSystem fs = FileSystem.get(new URI(props.getProperty(Constants.HDFS_URI)), new Configuration(), "kjxy");
File[] toUploadFiles = toUploadDir.listFiles();
// 检查HDFS中的日期目录是否存在,如果不存在,则创建
Path hdfsDestPath = new Path(props.getProperty(Constants.HDFS_DEST_BASE_DIR) + day+"/");
if(!fs.isDirectory(hdfsDestPath)){
fs.mkdirs(hdfsDestPath);
}
// 检查本地的备份目录的是否存在,如果不存在,则创建
File backDir = new File(props.getProperty(Constants.LOG_BACKUP_BASE_DIR) + day+"/");
if(!backDir.exists()){
backDir.mkdirs();
}
for (File file : toUploadFiles) {
// 传输文件到HDFS并改名
Path destPath = new Path(hdfsDestPath+"/" + UUID.randomUUID() +props.getProperty(Constants.HDFS_FILE_SUFFIX) );
fs.copyFromLocalFile(new Path(file.getAbsolutePath()),destPath);
System.out.println("文件传输到HDFS完成"+file.getAbsolutePath()+"-->"+destPath);
FileUtils.moveFileToDirectory(file,backDir,true);
System.out.println("文件备份完成"+file.getAbsolutePath()+"-->"+backDir);
}
fs.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
编写删除过去很久的日志文件的BackupCleanTask类
这个类的主要的目的就是将指定目录下的日志文件不符合我们要求的筛选出来,将其删除。
因为我们之前上传的文件文件夹命名的格式都是按照:年-月-日-时 的格式,所以在这里我们只要将其的文件夹名称拿到,就可以筛选出多少个小时之前的日志文件夹,将其删除。
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;
public class BackupCleanTask extends TimerTask {
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
long now = new Date().getTime();
// 探测本地备份目录
try {
File backupBaseDir = new File("D:\\Linux\\DAY02\\logs\\backup");
File[] dayBackDir = backupBaseDir.listFiles();
// 判断备份日期子目录是否已超24小时
for (File dir : dayBackDir) {
long time = sdf.parse(dir.getName()).getTime();
if(now-time>24*60*60*1000L){
FileUtils.deleteDirectory(dir);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
---------------------------------------------------------我是一个漂亮的分割线-------------------------------------------------------------------
写到这里我们这次的分享就结束了,其实代码的内容并不是很难,小编在学习的过程中边学边敲的,如果有什么不合理的地方还行各位大佬指出,我会很快的去纠正的。
代码链接
提取码: jysj