Hadoop - 国内各站点最高温度、气压和风速统计

版权说明:  本文章版权归本人及博客园共同所有,转载请标明原文出处(http://www.cnblogs.com/mikevictor07/),以下内容为个人理解,仅供参考。

 

一、简介

    该实例统计国内各个站点的最高温度(为节省篇幅只以温度为例,可稍作修改即可统计气压与风速),数据来源于汇总在NCDC的天气气球数据集中(包含世界大量数据集,该实例只分析国内站点,数据对外公开,可下载)。

二、数据准备与预处理

    从NCDC下载的天气气球数据集(ftp://ftp.ncdc.noaa.gov/pub/data/igra或http://www1.ncdc.noaa.gov/pub/data/igra/ , 压缩为gz包)如下,可见并不适合Hadoop的MR模块处理,需要进行预处理(本例下载数据gz包总大小为293MB,解压缩后为1.43GB):

#5928719630901009999   5
10 85000  1481B  202B-9999  190   20
10 70000  3139B  142B-9999  180   20
10 50000  5880B  -55B-9999   60   30
10 40000  7605B -142B-9999  100   40
10 30000  9750B -255B-9999  100   70
#5928719630901129999   7
10 85000  1481B  215B-9999  320   20
10 70000  3142B  132B-9999  300   10
10 50000  5889B  -35B-9999   50   30
10 40000  7620B -125B-9999  100   40
10 30000  9759B -275B-9999   70   60
10 20000 12561B -482B-9999   90  110
10 10000 16788B -785B-9999   90  100

首先需要阅读下载相关目录的readme.txt,才能站点相关字段的含义,温度数据已经*10(为了保留一位小数)

以该数据为例(其中的 9999一般代表数据缺失):

#5052719630901009999 5
10 85000 1314B 68B-9999-9999-9999

数据头部
标识 站点编号 日期 观察起始时间 观察结束时间 记录数
# 50527 19630901 00 9999 5

 

数据记录          
Major Level Minor Level 气压(Pa)3-8 气压标识 位势高度(米)10-14 位势高度标识 温度*10(16- 20位) 温度标识 露点下降 风力方向 风速(m/s)
1 0 85000 空格 1314 B 68 B -9999 -9999 -9999

 由于MapReduce一行行处理数据,而该数据记录部分依赖于数据头部,MR对数据进行分区时对它们分开的可能性非常大,所以每条数据记录部分必须加上头部的部分信息(根据需要确定),即预处理,对数据预处理的结果可输出到本地,然后再拷贝至HDFS。

    在本例中,数据头部只关注<站点编号>、<日期>,数据头部与数据记录形成的新格式如下:

预处理后的数据格式              
505271963090110 85000  1314B   68B-9999-9999-9999                
站点编号 日期 Major Level Minor Level 气压(Pa) 气压标识 位势高度(米)10-14位 位势高度标识 温度*10 温度标识 露点下降 风力方向 风速(m/s)
50527 19630901 1 0 85000 空格 1314 B 68 B -9999 -9999 -9999

即如下面格式的新格式:

592871963090110 85000  1481B  202B-9999  190   20
592871963090110 70000  3139B  142B-9999  180   20
592871963090110 50000  5880B  -55B-9999   60   30

 

三、数据拷贝至HDFS

    数据从本地拷贝至HDFS可以通过编码,也可使用eclipse的hadoop插件进行(该插件目前一般需要根据自己的环境编译得到jar放入eclipse的plugins文件夹下,过程稍微繁琐),

当然也可以使用bin/hadoop工具copyFromLocal进行(不过需要先复制到集群中的任意一台机器),本例中把数据存放在HDFS的 /weatherballoon 目录下,以下代码可供参考:

 

core-site.xml:不同的配置文件方便本地测试和集群切换,在MR程序调试的时候很有用

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
     <property>
         <name>fs.default.name</name>
         <value>hdfs://hbase-01:9000</value>
     </property>
</configuration>
public class CopyFromLocalMain {
    
    private static Configuration config = new Configuration();
    
    public static void main(String[] args) throws Exception {
        config.addResource("core-site.xml");
        File inputDir = new File("d:/weatherballoon/input/");
        String hdfsDir = "/weatherballoon/input/";
        
        if (!inputDir.exists()) {
            System.err.println(inputDir.getAbsolutePath() + " directory not exist .");
            System.exit(-1);
        }
        File[] files = inputDir.listFiles();
        if (files == null) {
            System.err.println(inputDir.getAbsolutePath() + " directory is empty .");
            System.exit(-1);
        }
        
        for (File file : files) {
            copyFileToHdfs(file, hdfsDir + file.getName());
        }
        System.out.println("Copy finished, total: " + files.length);
    }
public static void copyFileToHdfs(File local,String dest) throws IOException{ InputStream in = new BufferedInputStream(new FileInputStream(local)); FileSystem fs = FileSystem.get(config); FSDataOutputStream out = fs.create(new Path(dest)); IOUtils.copyBytes(in, out, 4096, true); out.close(); } }

数据拷贝完毕后可访问HDFS的namenode查看状态(默认50070端口),本例状态如下图:

 

 

四、编写MapReduce程序

    目前的数据格式已经每行之间无依赖性,首先编辑Mapper部分,该部分用于把站点ID作为key的数据集存入OutputCollector中:

public class MaxTemperatureMapper 
    extends MapReduceBase implements Mapper<LongWritable, Text, Text, RecordValue>{
    
    public static final int DATA_LENGTH = 49; //预处理后的数据行长度
    
    @Override
    public void map(LongWritable key, Text value,
            OutputCollector<Text, RecordValue> output, Reporter reporter) throws IOException {
        
        //505271963090110 85000  1314B   68B-9999-9999-9999
        String line = value.toString();
        if (line.length() != DATA_LENGTH) {
            System.out.println("------------->Error record : " + line);
            return;
        }
        
        String stationId = line.substring(0, 5);
        String date = line.substring(5, 13);
        String temp = line.substring(28, 33);
        if (!missing(temp)) {
            int temperature = Integer.parseInt(temp.trim());
            output.collect(new Text(stationId), new RecordValue(date, temperature));
        }
    }
    
    private boolean missing(String temp) {
        return temp.equals("-9999");
    }
    
}

    Mapper中输出的Value值为自定义的类型(即RecordValue),因为需要同时记录日期和温度,如果要自定义类型,则必须实现Writable(如Hadoop自带的LongWritable,IntWritable,Text等)。

    实现该接口使得对象能够序列化在不同机器间传输(进程间采用RPC通信,Hadoop采用Avro序列化,其他比较流行的序列化框架有Apache Thrift和Google protocol buffers),

一般建议实现WritableComparable接口,该接口中有个compareTo 方法的实现对于MapReduce来说是比较重要的,用于基于键的中间结果排序。

也可以实现RawComparator ,即可在字节流中排序,而不需要反序列化,减小额外开销。

RecordValue的实现如下:

public class RecordValue implements WritableComparable<RecordValue>{
    
    private String date;
    private int temperature;
    
    public RecordValue(){}
    public RecordValue(String date, int temperature) {
        this.date = date;
        this.temperature = temperature;
    }
    @Override
    public void write(DataOutput out) throws IOException {
        out.write(date.getBytes());
        out.writeInt(temperature);
    }
    @Override
    public void readFields(DataInput in) throws IOException {
        byte[] buff = new byte[8];
        in.readFully(buff);
        date = new String(buff);
        temperature = in.readInt();
    }
    @Override
    public int compareTo(RecordValue o) {
        if (date.compareTo(o.getDate()) > 0 || temperature > o.getTemperature()) {
            return 1;
        }
        return -1;
    }
    @Override
    public String toString() {
        return " -- " + date + "," + temperature;
    }
    
    //省略setter getter
}

Mapper部分需要做单元测试,成功后接下面编写Reducer部分:

public class MaxTemperatureReducer extends MapReduceBase implements
        Reducer<Text, RecordValue, Text, RecordValue> {

    @Override
    public void reduce(Text key, Iterator<RecordValue> values,
            OutputCollector<Text, RecordValue> output, Reporter reporter)
            throws IOException {
        
        int maxValue = Integer.MIN_VALUE;
        String date = "00000000";
        while (values.hasNext()) {
            RecordValue record = values.next();
            int temp = record.getTemperature();
            if (temp > maxValue) {
                maxValue = temp;
                date = record.getDate();
            }
        }
        output.collect(key, new RecordValue(date, maxValue));
    }
    
}

当Reduce部分单元测试成功后即可编写驱动程序MaxTemperatureDriver,先用本地小数据集进行测试,配置文件切换为本地配置,如:

public static void main(String[] args) throws IOException {
        
        String inputPath = "file:///d:/weatherballoon/input/*";
        String outputPath = "file:///d:/weatherballoon/output";
        
        File out = new File("d:/weatherballoon/output");
        if (out.exists()) {
            FileUtils.forceDelete(out); //采用apache common io包
        }
        
        Configuration conf = new Configuration();
        conf.addResource("core-site-local.xml");
        
        JobConf job = new JobConf(conf);
        job.setJobName("Max Temperature(NCDC)");
        job.setMapperClass(MaxTemperatureMapper.class);
        job.setReducerClass(MaxTemperatureReducer.class);
        
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(RecordValue.class);
        
        FileInputFormat.addInputPath(job, new Path(inputPath));
        FileOutputFormat.setOutputPath(job, new Path(outputPath));
        JobClient.runJob(job);
    }

运行程序,如果出错则在本地容易查出错误地方,查看输出结构是否如何预期,下面是本例小部分数据集的输出结果:

50527 -- 20040721,358
50557 -- 20100627,342
50603 -- 19730409,440

温度已经乘以10,故对应的结果如下表格:

站点ID 日期 温度(摄氏度)
50527 20040721 35.8
50557 20100627 34.2
50603 19730409 44.0

五、集群运行

测试成功后可切换至集群运行,更改MaxTemperatureDriver的main,如下:

public static void main(String[] args) throws IOException {
        
        String inputPath = "/weatherballoon/input/";
        String outputPath = "/weatherballoon/output";
        
        Configuration conf = new Configuration();
        
        JobConf job = new JobConf(conf);
        job.setJobName("Max Temperature(NCDC)");
        job.setMapperClass(MaxTemperatureMapper.class);
        job.setReducerClass(MaxTemperatureReducer.class);
job.setJarByClass(MaxTemperatureDriver.class); //!important job.setOutputKeyClass(Text.
class); job.setOutputValueClass(RecordValue.class); FileInputFormat.addInputPath(job, new Path(inputPath)); FileOutputFormat.setOutputPath(job, new Path(outputPath)); JobClient.runJob(job); }

 

然后程序打包(编写MANIFEST.MF):

Manifest-Version: 1.0
Class-Path: .
Main-Class: org.mike.hadoop.weatherballoon.MaxTemperatureDriver

eclipse->export->jar并选择MANIFEST.MF文件,把jar上传到集群任一节点,执行如下命令:

hadoop jar MaxTemperature.jar

 

运行如下图:

 

成功后即可从HDFS拷贝结果文件至本地查看(或者直接hadoop dfs -cat也可以),本例得到的结果如下(列出小部分):

 

50527    -- 20040721,358
50557    -- 20100627,342
50603    -- 19730409,440
50745    -- 19990413,386
50774    -- 20100624,316
50834    -- 19920428,426
50953    -- 20010604,346
51076    -- 19931031,506
51133    -- 19870716,600
51156    -- 19860309,552
51232    -- 19800802,220

根据NCDN中igra-stations.txt文件得到对应的站点整理后如下:

站点ID 站点名称 日期 最高温度
50527 HAILAR 20040721 35.8
50557 NENJIANG 20100627 34.2
50603 CHIN-BARAG 19730409 44
50745 CHICHIHAR 19990413 38.6
50774 YICHUN 20100624 31.6
50834 TA KO TAI 19920428 42.6
50953 HARBIN 20010604 34.6
51076 ALTAY 19931031 50.6
51133 TA CHENG 19870716 60
51156 HOBOG SAIR 19860309 55.2
51232 BORDER STATION 19800802 22

 

Finished ..

 

posted @ 2015-01-18 12:18  mikevictor  阅读(2582)  评论(3编辑  收藏  举报