hadoop与java客户端编程

开发前准备工作

在windows开发环境中做一些准备工作,因为Hadoop是适用于Linux操作系统上的,所以下载的开发包也是按照Linux系统编译的,因此,要在window下开发,需要下载源码包,使用windows进行编译。否则在使用某些功能时会提示找不到HADOOP_HOME,winutils.exe等。这里有一份已经编译的精简的windows版本(约3M左右,只保留了一些必须的功能)。链接:https://pan.baidu.com/s/1E5fsKpCigPxi1h5fMLJKRg
提取码:6al7

  1. 在windows的某个路径中解压一份windows版本的hadoop安装包
  2. 将解压出的hadoop目录配置到windows的环境变量中:HADOOP_HOME

 

开发代码

1、配置开发环境

如果你习惯于导入jar包来进行开发,那么请将hdfs客户端开发所需的jar导入工程(jar包可在hadoop安装包中找到common/hdfs),这里我使用的是maven工程,pom.xml中导入的jar包最好和你安装的hadoop版本一致,如需其他配置,请访问仓库:https://mvnrepository.com/

pom.xml 配置如下:


        3.0.2
        1.2.17
    
    
        
            org.apache.hadoop
            hadoop-client
            ${hadoop-version}
        
        
            org.apache.hadoop
            hadoop-common
            ${hadoop-version}
        
        
            org.apache.hadoop
            hadoop-hdfs
            ${hadoop-version}
        
        
            junit
            junit
            3.8.1
            test
        
        
            junit
            junit
            4.12
            compile
        
        
            log4j
            log4j
            ${log-version}
        
    

 

2、编写代码,上传一个本地文件到hdfs

要点:要对hdfs中的文件进行操作,代码中首先需要获得一个hdfs的客户端对象

public static void main(String[] args) throws Exception {
        // 加载配置,其实设置hdfs-site.xml里面的配置,若不设置,默认为hdfs-site.xml里面的配置
        Configuration conf=new Configuration();
        // 设置副本数量
        conf.set("dfs.replication","2");
        // 设置切块大小
        conf.set("def.blocksize","20m");
        // 获得一个hdfs的客户端对象,uri就是访问的结点,conf就是上面的配置,user就是用户身份,                      不写默认为windows的用户
        FileSystem fs=FileSystem.get(new URI("hdfs://node1:9000"),conf,"ws");
        // 上传本地文件到hdfs
        fs.copyFromLocalFile(new Path("F:\\spark-2.3.3-bin-hadoop2.7.tgz")
                ,new Path("/"));
        // 关流
        fs.close();
 }

 

 

demo1---日志上传

1、需求描述:

在业务系统的服务器上,业务程序会不断生成业务日志(比如网站的页面访问日志),业务日志是用log4j生成的,会不断地切出日志文件,需要定期(比如每小时)从业务服务器上的日志目录中,探测需要采集的日志文件,发往HDFS

注意点:业务服务器可能有多台(hdfs上的文件名不能直接用日志服务器上的文件名)

当天采集到的日志要放在hdfs的当天目录中

采集完成的日志文件,需要移动到到日志服务器的一个备份目录中

定期检查(一小时检查一次)备份目录,将备份时长超出24小时的日志文件清除

2、需求设计

1、流程

    生成日志

    启动一个定时任务:
	    ——定时探测日志源目录
	    ——获取需要采集的文件
	    ——移动这些文件到一个待上传临时目录
	    ——遍历待上传目录中各文件,逐一传输到HDFS的目标路径,同时将传输完成的文件移动到备份目录

    启动一个定时任务:
	——探测备份目录中的备份数据,检查是否已超出最长备份时长,如果超出,则删除
	
	
2、规划各种路径
    日志源路径: d:/logs/accesslog/
    待上传临时目录: d:/logs/toupload/
    备份目录: d:/logs/backup/日期/

    HDFS存储路径: /logs/日期
    HDFS中的文件的前缀:access_log_
    HDFS中的文件的后缀:.log

3、代码设计

3.1、日志生成模块

使用log4j生成日志文件

LoggerWriter.java 

public class LoggerWriter {
	public static void main(String[] args) throws Exception {
		while (true) {
			Logger logger = Logger.getLogger("logRollingFile");
			logger.info("111111111111111111111111111110----"+ System.currentTimeMillis());
			Thread.sleep(10);
		}
	}
}

log4j.properties

#log4j.rootLogger=debug,stdout,genlog
log4j.rootLogger=logRollingFile,stdout


log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
        
###
log4j.logger.logRollingFile= DEBUG,test1
log4j.appender.test1 = org.apache.log4j.RollingFileAppender
log4j.appender.test1.layout = org.apache.log4j.PatternLayout
log4j.appender.test1.layout.ConversionPattern =%d{yyyy-MMM-dd HH:mm:ss}-[TS] %p %t %c - %m%n
log4j.appender.test1.Threshold = DEBUG
log4j.appender.test1.ImmediateFlush = TRUE
log4j.appender.test1.Append = TRUE
log4j.appender.test1.File = d:/logs/accesslog/access.log
# 设置日志文件大小
log4j.appender.test1.MaxFileSize = 64KB
# 设置最大文件索引
log4j.appender.test1.MaxBackupIndex = 200

 3.2、demo配置文件模块

从我们需求设计可以看出来,我们demo中有大量的文件路径,此时我们可以写一个配置文件,避免代码中出现大量的字符串。与此同时,为了避免我们编写代码获取配置文件时拼错,我们还应该将配置文件中的key放置到一个常量池。再深入思考,我们加载配置文件的程序应该只需要执行一次,便可以在各处得到它的实例,为了避免重复加载,我们应该使用单例模式

collect.properties

LOG_SOURCE_DIR=d:/logs/accesslog/
LOG_TOUPLOAD_DIR=d:/logs/toupload/
LOG_BACKUP_BASE_DIR=d:/logs/backup/
LOG_BACKUP_TIMEOUT=24
LOG_LEGAL_PREFIX=access.log.

HDFS_URI=hdfs://node1:9000/
HDFS_DEST_BASE_DIR=/logs/
HDFS_FILE_PREFIX=access_log_
HDFS_FILE_SUFFIX=.log

 Constant.java

public class Constant {
       static final String LOG_SOURCE_DIR="LOG_SOURCE_DIR";
       static final String LOG_TOUPLOAD_DIR="LOG_TOUPLOAD_DIR";
       static final String LOG_BACKUP_BASE_DIR="LOG_BACKUP_BASE_DIR";
       static final String LOG_BACKUP_TIMEOUT="LOG_BACKUP_TIMEOUT";
       static final String LOG_LEGAL_PREFIX="LOG_LEGAL_PREFIX";
       static final String HDFS_URI="HDFS_URI";
       static final String HDFS_DEST_BASE_DIR="HDFS_DEST_BASE_DIR";
       static final String HDFS_FILE_PREFIX="HDFS_FILE_PREFIX";
       static final String HDFS_FILE_SUFFIX="HDFS_FILE_SUFFIX";
}

 PropertyHolderLazy.java(懒汉式)

public class PropertyHolderLazy {
    private static Properties prop=null;
    public static Properties getProps(){
        if(prop==null){
            synchronized (PropertyHolderLazy.class){
                if (prop==null){
                    prop=new Properties();
                    try {
                        prop.load(PropertyHolderLazy.class.getClassLoader().getResourceAsStream("collect.properties"));
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }
        return prop;
    }
}

 3.3、日志收集模块

 CollectTask.java

public class CollectTask extends TimerTask {
    @Override
    public void run() {
        //探测日志源目录
        SimpleDateFormat sdf=new SimpleDateFormat("yy-MM-dd-HH");
        String data = sdf.format(new Date());
        Properties props = PropertyHolderLazy.getProps();
        File srcDir = new File(props.getProperty(Constant.LOG_SOURCE_DIR));
        File[] listFiles = srcDir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                if (name.startsWith(props.getProperty(Constant.LOG_LEGAL_PREFIX))) {
                    return true;
                } else {
                    return false;
                }
            }
        });
        System.out.println(listFiles);
        //移动这些文件到一个待上传临时目录
        try {
            File toupload = new File(props.getProperty(Constant.LOG_TOUPLOAD_DIR));
            for (File files:listFiles
            ) {
                FileUtils.moveFileToDirectory(files, toupload, true);
            }
         //遍历待上传目录中各文件,逐一传输到HDFS的目标路径,同时将传输完成的文件移动到备份目录
            FileSystem fs = FileSystem.get(new URI(props.getProperty(Constant.HDFS_URI)), new Configuration(), "ws");
            File[] touploadFiles = toupload.listFiles();
            Path hdfsDestPath = new Path(props.getProperty(Constant.HDFS_DEST_BASE_DIR) + data);
            if(fs.exists(hdfsDestPath)){
                fs.create(hdfsDestPath);
            }
            for (File file:touploadFiles
                 ) {
                fs.copyFromLocalFile(new Path(file.getAbsolutePath()),
                        new Path(hdfsDestPath+props.getProperty(Constant.HDFS_FILE_PREFIX)+ UUID.randomUUID()+props.getProperty(Constant.HDFS_FILE_SUFFIX)));
                FileUtils.moveFileToDirectory(file,new File(props.getProperty(Constant.LOG_BACKUP_BASE_DIR)+data+"/"),true);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

 3.4、超时日志删除模块

 DeleOvertimeFile.java

public class DeleOvertimeFile extends TimerTask{
    @Override
    public void run() {
        SimpleDateFormat sdf=new SimpleDateFormat();
        long now = new Date().getTime();
        Properties props = PropertyHolderLazy.getProps();
        File backupBaseDir = new File(props.getProperty(Constant.LOG_BACKUP_BASE_DIR));
        File[] backupDir =backupBaseDir.listFiles();
        try{
            for (File dir:backupDir
            ) {
                long time = sdf.parse(dir.getName()).getTime();
                if (now-time>24*60*60*1000L){
                    FileUtils.deleteDirectory(dir);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

 3.5、主函数

public class DataConllectMain {
    public static void main(String[] args) {
        Properties props = PropertyHolderLazy.getProps();
        Timer timer = new Timer();
        // 收集任务
        timer.schedule( new CollectTask(),0,24*60*60*1000L);
        // 删除过期文件任务
        timer.schedule(new DeleOvertimeFile(),0,24*60*60*1000L);
    }
}

demo2--简单词频统计

1、需求描述:

现在我们需要实现一个简单的单词统计功能,现在有多个文件,我们需要统计其中的单词,以及单词出现的次数,并将统计结果保存到一个文件中,方便查阅

2、需求设计

1、流程

    得到待统计文件:
	    
    将统计结果保存到结果文件:
	
	
2、规划路径
    待统计文件路径: /wordcount/input
    统计结果保存路径: /wordcount/output/result.data
    

3、代码设计

为了使我们的程序鲁棒性更强,我们在设想一些情况,假设我们不仅仅只做单词统计,而且在后期或许我们还会需要其它功能来对这些文件进行处理,因此我们需要编写一个接口,并且可以通过类反射加载配置来为这个接口更换不同的实现类。再深层次思考,如果我还需要进行缓存,那么我们编写的接口应该怎么设计?我们可以在接口中传入一个上下文参数,让上下文参数去执行缓存任务。

Mapper.java-----其中传入一个上下文参数

public interface Mapper {
    void map(String line,Context context);
}

Context.java-----使用hashmap简单实现缓存

public class Context {
    HashMap contextMap=new HashMap<>();
    public void write(Object key,Object value){
        contextMap.put(key,value);
    }
    public Object getValue(Object key){
        return contextMap.get(key);
    }
    public HashMap getContextMap(){
        return contextMap;
    }
}

 WordCountMapper.java---实现mapper接口,处理具体的业务逻辑

public class WordCountMapper implements Mapper {
    @Override
    public void map(String line, Context context) {
        String[] words = line.split(" ");
        for (String word :words
                ) {
            Object value = context.getValue(word);
            if (null==value){
                context.write(word,1);
            }else {
                int v=(int)value;
                context.write(word,v+1);
            }
        }
    }
}

 HdfsWordCount.java---main函数

public class HdfsWordCount {
    public static void main(String[] args) throws Exception{
        /*
        * @Author shun
        * @Description 初始化
        **/
        Properties properties = new Properties();
        properties.load(HdfsWordCount.class.getClassLoader().getResourceAsStream("job.properties"));
        String mapper_class = properties.getProperty("MAPPER_CLASS");
        Mapper mapper = (Mapper) Class.forName(mapper_class).newInstance();
        Context context=new Context();
        /*
        * @Author shun
        * @Description 数据处理
        **/
        FileSystem fs = FileSystem.get(new URI("hdfs://node1:9000"), new Configuration(), "ws");
        RemoteIterator iter = fs.listFiles(new Path(properties.getProperty("INPUT_PATH")), false);
        while (iter.hasNext()){
            LocatedFileStatus file = iter.next();
            FSDataInputStream in = fs.open(file.getPath());
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
            String line=null;
            while ((line=bufferedReader.readLine())!=null){
                // 调用业务逻辑
                mapper.map(line,context);
            }
            bufferedReader.close();
        }
        /*
        * @Author shun
        * @Description 输出结果
        **/
        HashMap contextMap = context.getContextMap();
        Path outpath = new Path("/wordcount/output/");
        if (!fs.exists(outpath)){
            fs.mkdirs(outpath);
        }
        FSDataOutputStream out = fs.create(new Path(properties.getProperty("OUTPUT_PATH")));
        Set> entries = contextMap.entrySet();
        for (Map.Entry entry:entries
             ) {
            out.write((entry.getKey().toString()+"\t"+entry.getValue()+"\n").getBytes());
        }
        fs.close();
    }
}

最后总结

上诉两个demo其实都有更简单的实现,但是为了练习一些基础知识,和使用设计模式。编写的较为复杂,不过作为简单练手demo还是不错的选择。

你可能感兴趣的:(hadoop,hdfs)