浅谈Hadoop

HDFS
分布式存储系统,提供高可靠性,高扩展性,高吞吐率的存储服务
优点:1)高容错性具有数据自动保存多个副本,副本丢失后自动恢复
      2)批处理具有移动计算而非数据,只需要将存储数据的位置暴露给计算框架
      3)大数据处理具有可以处理百万规模以上的文件
缺点:1)数据访问时间比较长,寻址时间比较长(小文件造成)
      2)小文件存取,元数据存放在namenode内存中,如果小文件过多元数据也会很大占用namenode内存也会很大
      3)并发写入和文件随机修改,不能进行文件内容修改。2.x后提供各apppend功能,但是一般不使用,因为这样操作会连同副本一起修改,会大量占用cpu和磁盘资源
架构
NameNode:有且只有一个NN处于active状态,保存文件的元数据(权限、文件由哪些block构成、每个block存放在哪些DN上、名称、路径、创建时间等),元数据信息除了在内存中(完整的)有,还会持久化到磁盘中的fsimage文件(block位置不会存放),对元数据的操作日志会存储在edits文件中。NN来接收client端的读写请求,会监控DN的健康状态。
SecondaryNameNode:辅助NN,可以做到一定程度上备份。它主要帮助NN合并edits和fsimage,保证edits文件比较小。还可以减少NN的启动时间,否则开机启动的时间会很长。合并触发条件fs.checkpoint.period默认3600s,或者根据edits文件大小fs.checkpoint.size默认64M
    合并流程:
        触发了edits和fsimage合并条件后,SN会将NN中的edits和fsimage文件拉取到内存中,然后SN会一条一条读edits文件,根据操作日志对fsimage文件进行操作,操作完毕后会存到fsimage.ckpt文件,然后提交给NN,NN会将fsimage.ckpt文件修改为fsimage文件。在SN合并的时候,NN会同时创建一个edits.new文件用来记录SN合并时这段时间client对HDFS的操作,NN修改fsimage.ckpt文件的同时也会将edits.news文件修改为edits文件
    
DataNode:存放数据的节点,启动是会向NN上报block保存在哪个DN上,DN的总容量除以3就是HDFS的容量,里面由block数据块组成,但是不会出现block及其副本在一个DN上出现,默认每3秒向NN发送心跳包,如果NN10分钟没有收到DN的心跳则认为DN宕机了,那么会拷贝它的副本到别的DN上保证副本为3个
    block副本存放策略:
        第一个副本:随机选一台磁盘和cpu的比较空闲的节点
        第二个副本:放在与第一个节点不同的机架上
        第三个副本:与第二个节点相同机架上面
        更多副本:随机节点
存储
将文件切分成block块(默认1.x里64M,2.x里128M,不到指定大小单独存储一个block,但真正占用磁盘空间就是文件大小),存储到不同DN节点上,每个block默认有3各副本,client端不能修改block大小,副本数可以变更,副本数只要不小于默认的就可以。如果某些DN上的block的个数非常多,或者请求压力非常大,叫做数据倾斜,可通过shell命令来达到数据平衡
启动流程
NN会读fsimage和edits文件,将这两部分信息加载到内存中,DN会把自己有哪些block上传给NN,NN会根据这些信息拼接成一份完整的元数据信息并加载到内存中
HDFS读写流程
    写:client调用api中的open方法创建distributedFileSystem对象,并通过该对象将请求提交给NN,请求中还带有文件名称,写到哪个目录,当前的操作用户(HDFS在做权限认证的时候是根据用户名来匹配,如果匹配不上,那么NN会返回给刚才的对象,通知客户端没有对应的操作权限),权限匹配上后,NN会将多个负载比较低的DN节点返回给刚才的对象,之后client对文件进行切分成多个block,然后client创建FSDataOutputStream流将block文件有序的写入,第一个所选的DN会随机选择,将第一个block写入到DN中,写完第一个之后DN会再开一个线程将这个block并发的复制到另外两台DN,同时FSDataOutputStream流会将第二个block写入到DN(也可能是另外一个DN)中,直到全部写入DN中,并且副本赋值完成后,这时DN会反馈给client可以关闭FSDataOutputStream流了,client关闭之后会通知NN这次写请求完成,这时NN会更新这一条元数据信息写道edits文件中    
    读:client调用api中的open方法创建distributedFileSystem对象,这个对象将请求提交给NN,请求中带有文件名称,文件位置,用户名,NN会根据用户名做权限认证,判断该用户有没有权限操作,如果没有NN直接返回给该对象并通知client没有权限访问,如果有权限,NN会将client所访问的文件由哪些block构成,这些block存放在哪些DN上这些信息。client通过FSDataInputStream流来读,它会通过网络拓扑图找到离client最近的DN来读block,当所有的block全读完后关闭SDataInputStream流就完了
HDFS权限
    和Linux类似,r可读w可写,x不同这里是对文件忽略,对于文件夹表示是否允许访问其内容,默认的权限认证是通过用户名来匹配,如果用root用户创建了一个目录,那么HDFS对这个目录的权限就是root,其他用户读取不到
安全模式
    NN启动的时候有个阶段为安全模式,做的事情:首先将fsimage文件加载到内存中,并执行edits文件,这时内存中的元数据信息就比较完整了,然后会创建一个新的fsimage文件(包含了刚才旧的fsimage和edits所有信息),还会创建一个空的edits文件,DN会将block的信息上传给NN,NN会收集DN上报的数据,NN会判断集群中block个数有没有达到配置的最小副本数,如果大于等于则正常,如果小于,那么NN还会复制block来满足配置中副本数,最后会将NN中的block信息和内存里面的元数据信息进行拼接成一份真正完整的元数据信息。
    安全模式中不能做读写请求。
HDFS2.x    
基于HDFS1.x中NN单点故障、内存受限问题,提出了2.x。2.x中执行edits和fsimage合并是交给处于standby状态的NN来做的,合并流程和1.x是一样的,只不过是从JN集群上取edits文件。在格式化NN的时候是将一台NN进行格式化,然后将元数据拷贝给另一台,从而保证了元数据一致。
架构
JournalNode:JN集群用来管理edits文件,它的个数是2n+1个
ZKFC:监控处于active状态下NN的健康状态并汇报给zookeeper,如果发现active下的NN宕机了,会将处于standby状态的NN转成active状态。默认会和NN在同一个节点上
HDFS搭建(笔者这里采用的2.7.5版)
1.伪分布式搭建(依赖jdk)
    1)配置免密钥(rsa/dsa是加密方式,id_rsa文件是私钥文件,id_rsa.pub是公钥文件)
        ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
        cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
    2)修改etc/hadoop/hadoop-env.sh
        export JAVA_HOME=/usr/java/default
    3)配置etc/hadoop/core-site.xml
        <configuration>
            <property>
                <!--设置服务入口-->
                <name>fs.defaultFS</name>
                <value>hdfs://localhost:9000</value>
            </property>
        </configuration>
    4)配置etc/hadoop/hdfs-site.xml
        <configuration>
            <property>
                <!--设置副本数-->
                <name>dfs.replication</name>
                <value>1</value>
            </property>
        </configuration>
    5)格式化NN
        bin/hdfs namenode -format
    6)启动HDFS
        sbin/start-dfs.sh
    7)浏览器访问
        http://localhost:50070
2.完全分布式搭建(依赖jdk)
    1)配置免密钥,将NN节点的公钥文件追加到其他节点中
        ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
        cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
    2)修改etc/hadoop/hadoop-env.sh
        export JAVA_HOME=/usr/java/default
    3)配置etc/hadoop/core-site.xml
        <configuration>
            <property>
                <!--设置NN入口,9000是rpc端口,client可通过该端口访问HDFS-->
                <name>fs.defaultFS</name>
                <value>hdfs://localhost:9000</value>
            </property>
            <property>
                <!--该目录是一些配置的依赖目录,必须为空或者不存在,其下/dfs/name目录用来存放fsimage和edits-->
                <name>haodoop.tmp.dir</name>
                <value>/opt/hadoop</value>
            </property>
        </configuration>
    4)配置etc/hadoop/hdfs-site.xml
        <configuration>
            <property>
                <!--设置副本数,默认也为3-->
                <name>dfs.replication</name>
                <value>3</value>
            </property>
            <property>
                <!--设置SNN请求http的端口-->
                <name>dfs.namenode.secondary.http-address</name>
                <value>SNN_IP:50090 </value>
            </property>
            <property>
                <!--设置SNN请求https的端口-->
                <name>dfs.namenode.secondary.https-address</name>
                <value>SNN_IP:50091</value>
            </property>
        </configuration>
    5)创建etc/hadoop/masters文件来存放SNN节点
    6)添加etc/hadoop/slaves文件内容来存放DN节点
    7)同步配置文件
    8)格式化NN,之后就会产生fsimage,如果有edits也会有edits
        bin/hdfs namenode -format
    9)启动HDFS
        sbin/start-dfs.sh
    10)浏览器访问
        NN_IP:50070
        SN_IP:50090
3.高可用搭建(依赖zookeeper和jdk)
    1)配置免密钥,将NN节点的公钥文件追加到其他节点中
        ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
        cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
    2)修改etc/hadoop/hadoop-env.sh
        export JAVA_HOME=/usr/java/default
    3)配置etc/hadoop/core-site.xml
        <configuration>
            <property>
                <!--设置NN入口,9000是rpc端口,client可通过该端口访问HDFS-->
                <name>fs.defaultFS</name>
                <value>hdfs://mycluster</value>
            </property>
            <property>
                <!--该目录是一些配置的依赖目录,必须为空或者不存在,其下/dfs/name目录用来存放fsimage-->
                <name>haodoop.tmp.dir</name>
                <value>/opt/hadoop</value>
            </property>
            <property>
                <!--设置zookeeper集群-->
                <name>ha.zookeeper.quorum</name>
                <value>ZK1_IP:2181,ZK2_IP:2181,ZK3_IP:2181</value>
            </property>
        </configuration>
    4)配置etc/hadoop/hdfs-site.xml
        <configuration>
            <property>
                <!--设置副本数,默认也为3-->
                <name>dfs.replication</name>
                <value>3</value>
            </property>
            <property>
                <!--设置匿名服务-->
                <name>dfs.nameservices</name>
                <value>mycluster</value>
            </property>
            <property>
                <!--设置集群中NN的名字-->
                <name>dfs.ha.namenodes.mycluster</name>
                <value>nn1,nn2</value>
            </property>
            <property>
                <!--设置集群中真实的NN1地址和rpc端口-->
                <name>dfs.namenode.rpc-address.mycluster.nn1</name>
                <value>NN1_IP:8020</value>
            </property>
            <property>
                <!--设置集群中真实的NN2地址和rpc端口-->
                <name>dfs.namenode.rpc-address.mycluster.nn2</name>
                <value>NN2_IP:8020</value>
            </property>
            <property>
                <!--设置集群中真实的NN1地址和http端口-->
                <name>dfs.namenode.http-address.mycluster.nn1</name>
                <value>NN1_IP:50070</value>
            </property>
            <property>
                <!--设置集群中真实的NN2地址和http端口-->
                <name>dfs.namenode.http-address.mycluster.nn2</name>
                <value>NN2_IP:50070</value>
            </property>
            <property>
                <!--设置JN集群-->
                <name>dfs.namenode.shared.edits.dir</name>
                <value>qjournal://JN1_IP:8485;JN2_IP:8485;JN3_IP:8485/mycluster</value>
            </property>
            <property>
                <!--设置高可用的故障迁移方法-->
                <name>dfs.client.failover.proxy.provider.mycluster</name>
                <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
            </property>
            <property>
                <!--设置登陆主机的方式是ssh方式-->
                <name>dfs.ha.fencing.methods</name>
                <value>sshfence</value>
            </property>
            <property>
                <!--设置私钥文件存放的地址-->
                <name>dfs.ha.fencing.ssh.private-key-files</name>
                <value>/root/.ssh/id_rsa</value>
            </property>
            <property>
                <!--设置JN集群中edits文件存放的位置-->
                <name>dfs.journalnode.edits.dir</name>
                <value>/path/to/journal/node/local/data</value>
            </property>
            <property>
                <!--设置自动启动故障迁移-->
                <name>dfs.ha.automatic-failover.enabled</name>
                <value>true</value>
            </property>
        </configuration>
    5)添加etc/hadoop/slaves文件内容来存放DN节点
    6)同步配置文件
    7)启动JN集群(每个JN都需要启动)
        sbin/hadoop-daemon.sh start journalnode
    8)格式化NN,之后就会产生fsimage,如果有edits也会有edits
        bin/hdfs namenode -format(其中一台)
        sbin/hadoop-daemon.sh start namenode(启动刚才的NN)
        bin/hdfs namenode -bootstrapStandby(另一台没有进行格式化的NN)
    9)启动zookeeper进行格式化zookeeper,将nameservices的这些信息保存到zookeeper的内存数据库中
        bin/hdfs zkfc -formatZK
    10)启动hdfs/全启动
        sbin/start-dfs.sh  /  sbin/start-all.sh

常用命令行操作
hdfs dfs -mkdir /user # 创建文件目录
hdfs dfs -ls /        # 显示目录中的文件
hdfs dfs -put 本地文件 HDFS目录/     # 上传
hdfs dfs -get HDFS文件 本地路径/     # 下载,本地路径必须存在
hdfs dfs -rmr HDFS文件或目录        # 删除指定文件或目录
hdfs dfs -cat HDFS文件             # 打印HDFS文件内容
hdfs balancer                 # 数据平衡操作
hdfs dfsadmin -safemode leave         # 强制离开安全模式
hdfs dfsadmin -safemode enter     # 进入安全模式
hdfs dfsadmin -report         # 查看HDFS状态,比如有哪些datanode,每个datanode的情况

Java操作HDFS
public class hdfsTest {
    Configuration conf;
    FileSystem fs;
    @Before
    public void setup() throws IOException {
        conf = new Configuration();//这种方式如果在本地操作会加载src目录中的core-site和hdfs-site文件
  //如果在集群中会加载Hadoop的配置
  //conf.set("fs.defaultFS","hdfs://node2:8020");//这种不需要HDFS配置文件
  //不管哪种只要在内地上运行都需要在本地配置Hadoop环境变量
   fs = FileSystem.get(conf);
    }
    @After
    public void end() throws IOException {
        fs.close();
    }
    @Test//创建目录
    public void mkdir() throws IOException {
        boolean bool = fs.mkdirs(new Path("/hdfs"));
        System.out.println("创建结果:"+bool);
    }
    @Test//删除目录或文件,参数二是否禁止递归删除
    public void delete() throws IOException {
        boolean bool = fs.delete(new Path("/hdfs"),true);
        System.out.println("删除结果:"+bool);
    }
    @Test//本地文件上传到HDFS指定目录中
    public void upload() throws IOException {
        FSDataOutputStream outputStream = fs.create(new Path("/user/test"));
        FileInputStream inputStream = new FileInputStream("E://a.txt");
        IOUtils.copyBytes(inputStream, outputStream, conf);
    }
    @Test//将HDFS中文件拷贝到本地文件中(下载)
    public void download() throws IOException {
        FSDataInputStream inputStream = fs.open(new Path("/user/test.txt"));
        FileOutputStream outputStream = new FileOutputStream("E://a.txt");
        IOUtils.copyBytes(inputStream, outputStream, conf);
    }
    @Test//显示HDFS目录中文件
    public void ls() throws IOException {
        FileStatus[] fss = fs.listStatus(new Path("/"));
        for (FileStatus s : fss) {
            System.out.println(s.getPath() + " - " + s.getLen() + " - " + s.getModificationTime());
        }
    }
    @Test//将本地目录中所有的文件上传到HDFS中合并在一起放在HDFS文件中
    public void uploadSeq() throws IOException {
        SequenceFile.Writer seq = SequenceFile.createWriter(fs, conf, new Path("/user/seq"), Text.class, Text.class);
        File file = new File("E://seq");
        for(File f : file.listFiles()) {
            seq.append(new Text(f.getName()), new Text(FileUtils.readFileToString(f)));
        }
    }
    @Test//将刚刚合并文件显示出来
    public void getSeq() throws IOException {
        SequenceFile.Reader reader = new SequenceFile.Reader(fs, new Path("/user/seq"), conf);
        Text key = new Text();
        Text value = new Text();
        while(reader.next(key, value)) {
            System.out.println(key.toString());
            System.out.println(value.toString());
        }
    }
}

Yarn
Hadoop2.x中的资源管理采用yarn来资源,它的好处就是可以使用除了mr之外的计算框架
Yarn
对资源的管理和分配
1.x中叫JobTracker,2.x中叫做resourcemanager,它来判断mapTask或reduceTask应该交给哪个nodemanager来计算,并监控运行状态如果发现某个task失败了,会重新分给其他节点。
每一个DN上都有一个TaskTracker(2.x中nodemanager)可以减少网络带宽,主动和nodemanager进行通信,接收作业并执行。
架构
ResourceManager:主节点,用来接收applicationMaster提交的请求
NodeManager:从节点,执行程序的节点
applicationMaster:每个MR程序都会创建一个AM,用来和RM进行交互
continer:NM中真正执行程序的进程,分配资源的最小单位
原理
RM启动后会和NM进行心跳包传输,从而RM知道NM的资源情况。当MR提交一个程序的时候,由于MR不能直接和RM进行直接交互,
所以RM会创建一个AM,通过AM会向RM申请资源,RM会将资源使用较少的NM反向传递给AM,这时NM启动continer(线程),MR就可以将task任务发动到continer上执行,
每执行完一个task,continer就会销毁continer,并将结果落地写到HDFS中,下个task,AM会需要向RM申请资源来运行,知道整个程序都执行玩,AM才会销毁。
如果是spark,那么不会每次task都会向RM申请资源,spark会在程序开始的时候将资源全部申请好,然后才会执行程序,程序结束全部销毁。
yarn中同时执行任务的单元是槽并非task,相当于cpu的线程数,MR如果有100task,yarn中的槽只有10个,那么会分10批来执行
配置(yarn-default.xml)
yarn.nodemanager.resource.memory-mb        表示每个NM进程最多为多大,默认8192M,根据业务可以调大或者多启动几个NM(多个NM有多个continer)
yarn.nodemanager.resource.cpu-vcores    每个NM最多分配的CPU的虚拟核数(CPU线程数可以并行跑任务),根据CPU来配置,默认8个
yarn.scheduler.minimum-allocation-mb     yarn调度资源的时候每个contarner进程的内存最小值,默认值1024M
yarn.scheduler.maximum-allocation-mb     yarn调度资源时每个contarner进程的内存最大值,默认8192M

MapReduce
分布式计算框架,移动计算而非移动数据,数据在哪个DN上,那么计算就在哪个DN上执行,mr就相当于HDFS的客户端
采用"分而治之"的思想,这样计算规模和数据都会大大缩减,小任务并行计算,彼此之间没有依赖关系,就近计算,数据在哪个节点,就会到哪个节点计算
架构
split(必须的):将大文件拆分成小文件,数据经过split切分后有多少个数据片段就有多少个mapTask。
    自动框架自己做的,切分规则,配置文件中切分上限100M下限10M,如果block是64M,它的规则就是将上限和block取最小,再和下限取最大。如果定死为70M为一个数据片段而且它是移动计算的,那么一个64M填不满剩下的6M就会通过网络传输到其他节点上获取这样速度会变慢
map(必须的):简单的业务处理和分析,map端可以做聚合操作,只是有时比较消耗内存(比如map所计算的数据片段中只有第一行和最后一行的数据一样,那么整个片段就会长期驻留在内存中),需要写代码
shuffle:分区(作用就是决定一组数据最后交给哪个reduce来计算),排序(默认的根据key做字典排序),分组(key的hash值相同的分为一组)。分为mapShuffle和reduceShuffle,
    map分析的结果在mapShuffle阶段先加载到内存缓冲区,在加载到内存之前会先分区,默认的分区规则是key的hash值对reduce个数取模,比如模的结果是0、1、2,那么分别对应reduce0、reduce1、reduce2。内存缓冲区达到0.8(spill.percent默认0.8,剩下的0.2接着读)就会启动一个线程溢写磁盘生成一个小文件,最后合并成一个大文件。在溢写启动后会先排序和combiner(默认没有combiner,根据业务需要自己加)然后再溢写,sort排序(默认key按照字典排序),只对一个数据片段中的数据进行排序。combiner作用将一个片段中排好序后相同key的value做聚合,这样可以减少网络的消耗
    reduceShuffle会将不同mapShuffle合并的大文件按照分区规则抓取自己的数据到内存缓冲区中(默认也是0.8)写满后也会溢写成小文件,这些小文件最终也会自动合并成一个大文件,然后在reduceShuffle还会再做一次sort排序(这里也叫分组),按照key的hash相同归为一组同时将组内数据进行字典排序,然后将这一组数据通过迭代器交给reduce。
    在reduceShuffle主动从mapShuffle抓取文件的时候会出现文件跨网络传输。
reduce:需要写代码,reduce的个数需要自己设置,可以通过配置文件mapred-site.xml(mapred.reduce.tasks缺省值默认1一般不修改),一般会通过代码来设置。reduce通过迭代器的方式拿到一组key-value的数据,不会将一组内所有数据都加载到内存中,如果那样数据很大,会报OOM。
    主要是对map阶段之后经过shuffle分好组的结果进行汇总。

MapReduce搭建(在HDFS的基础上进行修改,其中slave节点除了是DN配置也是NM配置)
1)修改etc/hadoop/mapred-site.xml
    <configuration>
        <property>
            <name>mapreduce.framework.name</name>
            <value>yarn</value>
        </property>
    </configuration>
2)修改etc/hadoop/yarn-site.xml
    <configuration>
        <property>
            <name>yarn.nodemanager.aux-services</name>
            <value>mapreduce_shuffle</value>
        </property>
        <!--下面是对resourcemanager做高可用配置-->
        <property>
        <!--设置是否启动resourcemanager的高可用-->
          <name>yarn.resourcemanager.ha.enabled</name>
          <value>true</value>
        </property>
        <property>
        <!--设置resourcemanager的标识-->
          <name>yarn.resourcemanager.cluster-id</name>
          <value>cluster1</value>
        </property>
        <property>
          <name>yarn.resourcemanager.ha.rm-ids</name>
          <value>rm1,rm2</value>
        </property>
        <property>
        <!--设置resourcemanager1的IP-->
          <name>yarn.resourcemanager.hostname.rm1</name>
          <value>master1</value>
        </property>
        <property>
        <!--设置resourcemanager2的IP-->
          <name>yarn.resourcemanager.hostname.rm2</name>
          <value>master2</value>
        </property>
        <property>
        <!--设置resourcemanager1的http端口-->
          <name>yarn.resourcemanager.webapp.address.rm1</name>
          <value>master1:8088</value>
        </property>
        <property>
        <!--设置resourcemanager2的http端口-->
          <name>yarn.resourcemanager.webapp.address.rm2</name>
          <value>master2:8088</value>
        </property>
        <property>
        <!--设置resourcemanager的zookeeper集群-->
          <name>yarn.resourcemanager.zk-address</name>
          <value>zk1:2181,zk2:2181,zk3:2181</value>
        </property>
    </configuration>
3)启动mapreduce和yarn 或者全部启动
    sbin/start-yarn.sh  /  sbin/start-all.sh
4)手动启动resourcemanager节点上的resourcemanager,
    sbin/yarn-daemon.sh start resourcemanager
5)浏览器访问
    resourcemanagerIP:8088

java操作Mapreduce
示例一:wordcount
    public class WordCountJob {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            Configuration conf = new Configuration();//先加载配置文件
            Job job = Job.getInstance(conf);
            FileSystem fs = FileSystem.get(conf);//文件系统
            //====================================================
            job.setJobName("word count");//设置job的名称
            job.setJarByClass(WordCountJob.class);//指定程序的入口
            job.setMapperClass(WordCountMap.class);//指定map
            job.setReducerClass(WordCountReduce.class);//指定reduce
            //job.setCombinerClass(WordCountReduce.class);//设置combiner
            job.setMapOutputKeyClass(Text.class);//指定map输出时key类型
            job.setMapOutputValueClass(IntWritable.class);//指定map输出时value类型
            FileInputFormat.addInputPath(job, new Path("/wc.txt"));//指定输入数据,默认HDFS路径
            //如果目录存在那么先删除掉,不然会报错
            Path outpath = new Path("/wc_output/");
            if(fs.exists(outpath)) {
                fs.delete(outpath, true);
            }
            FileOutputFormat.setOutputPath(job, outpath);//指定输出路径,默认HDFS路径
            if(job.waitForCompletion(true)) {//最后提交,返回Job提交是否成功
                System.out.println("job success!");
            }
        }
    }
    class WordCountMap extends org.apache.hadoop.mapreduce.Mapper<LongWritable, Text, Text, IntWritable>{
        @Override//泛型第一个相当于数据下表,第二个是输入value类型,后面两个是输出key和value类型
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] strs = StringUtils.split(value.toString(), ' ');
            for (String s : strs) {
                context.write(new Text(s), new IntWritable(1));//交给下游
            }
        }
    }
    class WordCountReduce extends org.apache.hadoop.mapreduce.Reducer<Text, IntWritable, Text, IntWritable>{
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int sum = 0;
            for(IntWritable i:values){
                sum += i.get();
            }
            context.write(key,new IntWritable(sum));
        }
    }
    数据
    hadoop hello world
    hello hadoop
    hbase zookeeper
    name name name
    结果
    hadoop    2
    hbase    1
    hello    2
    name    3
    world    1
    zookeeper    1
示例二:每个年月中,温度最高的前两天(时间升序,温度降序)实现了二次排序(需要对象类实现WritableComparable接口)
WritableComparable接口实现了序列化、反序列化和比较器
Hadoop中自定义的对象必须可以比较(数据要分组排序)而且可序列化和反序列化(数据在HDFS上)
    TQJob.java
    public class TQJob {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
            Configuration conf = new Configuration();//先加载配置文件
            Job job = Job.getInstance(conf);
            FileSystem fs = FileSystem.get(conf);//文件系统
            //====================================================
            job.setJobName("tq");//设置job的名称
            job.setJarByClass(TQJob.class);//指定程序的入口
            job.setMapperClass(TQMap.class);//指定map
            job.setReducerClass(TQReduer.class);//指定reduce
            job.setMapOutputKeyClass(TQ.class);//指定map输出时key类型
            job.setMapOutputValueClass(IntWritable.class);//指定map输出时value类型
            job.setPartitionerClass(TQPartition.class);//指定如何分区
            job.setSortComparatorClass(TQSort.class);//指定自定义的sort
            job.setGroupingComparatorClass(TQGroup.class);//指定自定义的分组
            job.setNumReduceTasks(3);//设置reduce个数
            FileInputFormat.addInputPath(job, new Path("/tq.txt"));//指定输入数据,默认HDFS路径
            //如果目录存在那么先删除掉,不然会报错
            Path outpath = new Path("/tq_output/");
            if(fs.exists(outpath)) {
                fs.delete(outpath, true);
            }
            FileOutputFormat.setOutputPath(job, outpath);//指定输出路径,默认HDFS路径
            if(job.waitForCompletion(true)) {//最后提交,返回Job提交是否成功
                System.out.println("job success!");
            }
        }
    }
    class TQMap extends org.apache.hadoop.mapreduce.Mapper<LongWritable, Text, TQ, IntWritable>{
        @Override//泛型第一个相当于数据下表,第二个是输入value类型,后面两个是输出key和value类型
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] strs = StringUtils.split(value.toString(), '\t');
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Calendar cal = Calendar.getInstance();
            Date date = new Date();
            try {
                date = sdf.parse(strs[0]);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            cal.setTime(date);
            TQ tq = new TQ();
            tq.setYear(cal.get(Calendar.YEAR));
            tq.setMonth(cal.get(Calendar.MONTH) + 1);
            tq.setDay(cal.get(Calendar.DAY_OF_MONTH));
            int wd = Integer.parseInt(strs[1].substring(0, strs[1].lastIndexOf("c")));
            tq.setWd(wd);
            context.write(tq, new IntWritable(wd));
        }
    }
    //设置分区,越简单越好,根据业务场景来定
    class TQPartition extends HashPartitioner<TQ, IntWritable> {
        @Override
        public int getPartition(TQ key, IntWritable value, int numReduceTasks) {
            return (key.getYear() - 1949) % numReduceTasks;
        }
    }
    //一定要重写构造,构造里面指定需要比较的对象是哪个类
    class TQSort extends WritableComparator {
        public TQSort() {
            super(TQ.class, true);
        }
        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            TQ t1 = (TQ) a;
            TQ t2 = (TQ) b;
            int c1 = Integer.compare(t1.getYear(), t2.getYear());
            if (c1 == 0) {
                int c2 = Integer.compare(t1.getMonth(), t2.getMonth());
                if (c2 == 0) {
                    // 温度降序
                    return -Integer.compare(t1.getWd(), t2.getWd());
                }
                return c2;
            }
            return c1;
        }
    }
    class TQGroup extends WritableComparator {
        public TQGroup() {
            super(TQ.class, true);
        }
        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            TQ t1 = (TQ)a;
            TQ t2 = (TQ)b;
            int c1 = Integer.compare(t1.getYear(), t2.getYear());
            if(c1 == 0) {
                return Integer.compare(t1.getMonth(), t2.getMonth());
            }
            return c1;
        }
    }
    class TQReduer extends org.apache.hadoop.mapreduce.Reducer<TQ, IntWritable, Text, NullWritable>{
        @Override
        protected void reduce(TQ tq, Iterable<IntWritable> iterable, Context context) throws IOException, InterruptedException {
            int i = 0;
            for (IntWritable iw : iterable) {
                if(i >= 2) {
                    break;
                }
                String msg = tq.getYear() + "-" + tq.getMonth() + "-" + tq.getDay() + "-" + iw.get();
                context.write(new Text(msg), NullWritable.get());
                i++;
            }
        }
    }
    TQ.java
    public class TQ implements WritableComparable<TQ>{
        private int year;
        private int month;
        private int day;
        private int wd;
        public int getYear() {
            return year;
        }
        public void setYear(int year) {
            this.year = year;
        }
        public int getMonth() {
            return month;
        }
        public void setMonth(int month) {
            this.month = month;
        }
        public int getDay() {
            return day;
        }
        public void setDay(int day) {
            this.day = day;
        }
        public int getWd() {
            return wd;
        }
        public void setWd(int wd) {
            this.wd = wd;
        }
        @Override//比较对象的方法,如果没有重写resource和group的时候,那么就会调用compareTo方法,读数据的时候也会调用这个方法
        public int compareTo(TQ tq) {
            int c = Integer.compare(this.year, tq.getYear());
            if(c == 0) {
                int c2 = Integer.compare(this.month , tq.getMonth());
                if(c2 == 0) {
                    return Integer.compare(this.wd, tq.getWd());
                }
                return c2;
            }
            return c;
        }
        @Override//序列化和反序列化,注意顺序
        public void write(DataOutput out) throws IOException {
            out.writeInt(year);
            out.writeInt(month);
            out.writeInt(day);
            out.writeInt(wd);
        }
        @Override//序列化和反序列化,注意顺序
        public void readFields(DataInput in) throws IOException {
            this.year = in.readInt();
            this.month = in.readInt();
            this.day = in.readInt();
            this.wd = in.readInt();
        }
    }
    数据
    1949-10-01 14:21:02    34c
    1949-10-02 14:01:02    36c
    1950-01-01 11:21:02    32c
    1950-10-01 12:21:02    37c
    1951-12-01 12:21:02    23c
    1950-10-02 12:21:02    41c
    1950-10-03 12:21:02    27c
    1951-07-01 12:21:02    45c
    1951-07-02 12:21:02    46c
    1951-07-03 12:21:03    47c
    结果
    1949-10-2-36
    1949-10-1-34
    1950-1-1-32
    1950-10-2-41
    1950-10-1-37
    1951-7-3-47
    1951-7-2-46
    1951-12-1-23
示例三
好友推荐,实现两个MapReduce和二次排序
    FofJobOne.java
    public class FofJobOne {
        public static void main(String[] args) throws Exception {
            Configuration conf = new Configuration();
            Job job = Job.getInstance(conf);
            FileSystem fs = FileSystem.get(conf);
            job.setJobName("fof1");
            job.setJarByClass(FofJobOne.class);
            job.setMapperClass(FofMapperOne.class);
            job.setReducerClass(FofReducerOne.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(IntWritable.class);
            FileInputFormat.addInputPath(job, new Path("/friend.txt"));
            Path outpath = new Path("/friend_output");
            if(fs.exists(outpath)) {
                fs.delete(outpath, true);
            }
            FileOutputFormat.setOutputPath(job, outpath);
            System.out.println(job.waitForCompletion(true)?"job success!":null);
        }
    }
    class FofMapperOne extends Mapper<LongWritable, Text, Text, IntWritable> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] strs = StringUtils.split(value.toString(), ' ');
            Psn psn = new Psn();
            for (int i = 0; i < strs.length; i++) {
                // 直接好友关系
                String str1 = psn.getFof(strs[0], strs[i]);
                context.write(new Text(str1), new IntWritable(0));
                for (int j = i+1; j < strs.length; j++) {
                    // 所有的二度关系
                    String str2 = psn.getFof(strs[i], strs[j]);
                    context.write(new Text(str2), new IntWritable(1));
                }
            }
        }
    }
    class Psn {
        //格式化输出,tom-hive或hive-tom都写成hive-tom格式
        public String getFof(String f1, String f2) {
            int c = f1.compareTo(f2);
            if(c < 0) {
                return f2 + "-" + f1;
            }
            return f1 + "-" + f2;
        }
    }
    class FofReducerOne extends Reducer<Text, IntWritable, Text, NullWritable> {
        @Override
        protected void reduce(Text text, Iterable<IntWritable> iterable, Context context) throws IOException, InterruptedException {
            // 亲密度
            int sum = 0;
            boolean flag = true;
            for (IntWritable iw : iterable) {
                if(iw.get() == 0) {
                    // 直接好友
                    flag = false;
                    break;
                }
                sum ++;
            }
            if(flag) {
                context.write(new Text(text.toString() + "-" + sum), NullWritable.get());
            }
        }
    }
    FofJobTwo.java
    public class FofJobTwo {
        public static void main(String[] args) throws Exception {
            Configuration conf = new Configuration();
            conf.set("fs.defaultFS", "hdfs://node1:8020");
            conf.set("yarn.resourcemanager.hostname", "node3");
            Job job = Job.getInstance(conf);
            FileSystem fs = FileSystem.get(conf);
            job.setJobName("fof2");
            job.setJarByClass(FofJobTwo.class);
            job.setMapperClass(FofMapperTwo.class);
            job.setReducerClass(FofReduceTwo.class);
            job.setMapOutputKeyClass(Friends.class);
            job.setMapOutputValueClass(IntWritable.class);
            job.setSortComparatorClass(FofSort.class);
            job.setGroupingComparatorClass(FofGroup.class);
            FileInputFormat.addInputPath(job, new Path("/user/fof/output1/"));
            Path outpath = new Path("/user/fof/output2/");
            if(fs.exists(outpath)) {
                fs.delete(outpath, true);
            }
            FileOutputFormat.setOutputPath(job, outpath);
            System.out.println(job.waitForCompletion(true)?"job success!":null);
        }
    }
    class FofMapperTwo extends Mapper<LongWritable, Text, Friends, IntWritable> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // hello-hadoop-3
            String[] strs = StringUtils.split(value.toString(), '-');
            Friends fs1 = new Friends();
            fs1.setF1(strs[0]);
            fs1.setF2(strs[1]);
            fs1.setHot(Integer.parseInt(strs[2]));
            context.write(fs1, new IntWritable(Integer.parseInt(strs[2])));
            Friends fs2 = new Friends();
            fs2.setF1(strs[1]);
            fs2.setF2(strs[0]);
            fs2.setHot(Integer.parseInt(strs[2]));
            context.write(fs2, new IntWritable(Integer.parseInt(strs[2])));
        }
    }
    class FofSort extends WritableComparator {
        public FofSort() {
            super(Friends.class, true);
        }
        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            Friends fs1 = (Friends)a;
            Friends fs2 = (Friends)b;
            int c = fs1.getF1().compareTo(fs2.getF1());
            if(c == 0) {
                // 亲密度 降序排序
                return -Integer.compare(fs1.getHot(), fs2.getHot());
            }
            return c;
        }
    }
    class FofGroup extends WritableComparator {
        public FofGroup() {
            super(Friends.class, true);
        }
        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            Friends fs1 = (Friends)a;
            Friends fs2 = (Friends)b;
            return fs1.getF1().compareTo(fs2.getF1());
        }
    }
    class FofReduceTwo extends Reducer<Friends, IntWritable, Text, IntWritable> {
        @Override
        protected void reduce(Friends friends, Iterable<IntWritable> iterable, Context context) throws IOException, InterruptedException {
            // 推荐一个好友
            int i = 0;
            for (IntWritable iw : iterable) {
                if(i >= 1) {
                    break;
                }
                String msg = friends.getF1() + "-" + friends.getF2();
                context.write(new Text(msg), new IntWritable(iw.get()));
                i++;
            }
        }
    }
    Friends.java
    public class Friends implements WritableComparable<Friends> {
        // 好友 1 和 2
        private String f1;
        private String f2;
        // 亲密度
        private int hot;
        public String getF1() {
            return f1;
        }
        public void setF1(String f1) {
            this.f1 = f1;
        }
        public String getF2() {
            return f2;
        }
        public void setF2(String f2) {
            this.f2 = f2;
        }
        public int getHot() {
            return hot;
        }
        public void setHot(int hot) {
            this.hot = hot;
        }
        @Override
        public void write(DataOutput out) throws IOException {
            out.writeUTF(f1);
            out.writeUTF(f2);
            out.writeInt(hot);
        }
        @Override
        public void readFields(DataInput in) throws IOException {
            this.f1 = in.readUTF();
            this.f2 = in.readUTF();
            this.hot = in.readInt();
        }
        @Override
        public int compareTo(Friends f) {
            int c = this.f1.compareTo(f.getF1());
            if(c == 0) {
                return Integer.compare(this.hot, f.hot);
            }
            return c;
        }
    }
    数据,第一个是当前用户,后面的就是当前用户的好友列表
    tom hello hadoop cat
    world hadoop hello hive
    cat tom hive
    mr hive hello
    hive cat hadoop world hello mr
    hadoop tom hive world
    hello tom world hive mr
    结果
    cat-hadoop    2     表示给cat推荐应该是Hadoop,路径有2条
    hadoop-hello    3
    hello-hadoop    3
    hive-tom    3
    mr-world    2
    tom-hive    3
    world-mr    2
示例三:pagerank(pr值)用于衡量指定网页相对于搜索引擎索引中的其他网页而言的重要程度,一般在计算的时候初始pr值为1,pr值越大搜索之后网页排名越靠前
算法原理
入链:到一个页面的超链接相当于一个入链
入链数量:某网页在其他网页中的超链接的个数
入链质量:被链接网页的权重,权重越高质量越高
计算:例如a,b,c,d四个网页,初始值都为1,a为1/2,b为3/2,c为3/2,d为1/2....一致迭代下去直到收敛标准
收敛标准:
    每个页面pr值和上次计算相等
    设定一个差值标准为0.0001,当所有页面和上一次计算的pr差值平均小于该标准则收敛
    设定百分比99%,当99%的页面和上次计算pr值相等则收敛
计算公式可自查,阻尼系数一般为0.85
数据
A    B    D    表示A网页投给了B和D网页
B    C
C    A    B
D    B    C
代码
    PRJob.java
    public class PRJob {
        public static enum Mycounter {
            my//枚举
        }
        public static void main(String[] args) {
            Configuration config = new Configuration();
            // 收敛值
            double d = 0.001;
            int i = 0;
            while (true) {
                i++;
                try {
                    // 记录计算的次数,将迭代的次数放到runCount中来
                    config.setInt("runCount", i);
                    FileSystem fs = FileSystem.get(config);
                    Job job = Job.getInstance(config);
                    job.setJarByClass(PRJob.class);
                    job.setJobName("pr" + i);
                    job.setMapperClass(PageRankMapper.class);
                    job.setReducerClass(PageRankReducer.class);
                    job.setMapOutputKeyClass(Text.class);
                    job.setMapOutputValueClass(Text.class);
                    //KeyValueTextInputFormat.class的作用:将这一行数据它会默认的按照制表符来分割,分割之后第一位作为key后边的作为value
                    job.setInputFormatClass(KeyValueTextInputFormat.class);
                    //分析的时候数据放在这里
                    Path inputPath = new Path("/pagerank/pagerank.txt");
                    //i是迭代的次数
                    if (i > 1) {
                        inputPath = new Path("/pagerank/pr" + (i - 1));
                    }
                    FileInputFormat.addInputPath(job, inputPath);
                    //上次输出的数据输出到这个目录
                    Path outpath = new Path("/pagerank/pr" + i);
                    if (fs.exists(outpath)) {
                        fs.delete(outpath, true);
                    }
                    FileOutputFormat.setOutputPath(job, outpath);
                    boolean f = job.waitForCompletion(true);
                    if (f) {
                        System.out.println("success.");
                        // 获取 计数器 中的差值
                        long sum = job.getCounters().findCounter(Mycounter.my).getValue();
                        System.out.println("SUM:  " + sum);
                        double avgd = sum / 4000.0;
                        if (avgd < d) {
                            break;
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        static class PageRankMapper extends Mapper<Text, Text, Text, Text> {
            @Override
            protected void map(Text key, Text value, Context context) throws IOException, InterruptedException {
                int runCount = context.getConfiguration().getInt("runCount", 1);//runcount是拿到runcount中的值
                String page = key.toString();// A    B    D
                Node node = null;
                if (runCount == 1) { //第一次计算 初始化PR值为1.0
                    node = Node.fromMR("1.0" + "\t" + value.toString());
                } else {
                    node = Node.fromMR(value.toString());
                }
                // A:1.0 B D
                // 将计算前的数据输出  reduce计算做差值
                context.write(new Text(page), new Text(node.toString()));
                if (node.containsAdjacentNodes()) {
                    double outValue = node.getPageRank() / node.getAdjacentNodeNames().length;
                    for (int i = 0; i < node.getAdjacentNodeNames().length; i++) {
                        String outPage = node.getAdjacentNodeNames()[i];
                        // B:0.5
                        // D:0.5
                        context.write(new Text(outPage), new Text(outValue + ""));
                    }
                }
            }
        }
        static class PageRankReducer extends Reducer<Text, Text, Text, Text> {
            @Override
            protected void reduce(Text key, Iterable<Text> iterable, Context context) throws IOException, InterruptedException {
                double sum = 0.0;
                Node sourceNode = null;
                for (Text i : iterable) {
                    Node node = Node.fromMR(i.toString());
                    //A:1.0 B D
                    if (node.containsAdjacentNodes()) {
                        // 计算前的数据 // A:1.0 B D
                        sourceNode = node;
                    } else {
                        // B:0.5 // D:0.5
                        sum = sum + node.getPageRank();
                    }
                }
                // 计算新的PR值  4为页面总数
                double newPR = (0.15 / 4.0) + (0.85 * sum);
                System.out.println("*********** new pageRank value is " + newPR);
                // 把新的pr值和计算之前的pr比较
                double d = newPR - sourceNode.getPageRank();
                int j = (int) (d * 1000.0);
                //求绝对值
                j = Math.abs(j);
                System.out.println(j + "___________");
                // 累加
                context.getCounter(Mycounter.my).increment(j);
                sourceNode.setPageRank(newPR);
                context.write(key, new Text(sourceNode.toString()));
            }
        }
    }
    Node.java
    public class Node {
        // 字符串中 第一个元素 初始PR值 为 1
        private double pageRank = 1.0;
        // 字符串中 后面的节点列表 数组
        private String[] adjacentNodeNames;
        // PR值 与 数组的分隔符 \t
        public static final char fieldSeparator = '\t';
        public double getPageRank() {
            return pageRank;
        }
        public Node setPageRank(double pageRank) {
            this.pageRank = pageRank;
            return this;
        }
        public String[] getAdjacentNodeNames() {
            return adjacentNodeNames;
        }
        public Node setAdjacentNodeNames(String[] adjacentNodeNames) {
            this.adjacentNodeNames = adjacentNodeNames;
            return this;
        }
        public boolean containsAdjacentNodes() {
            return adjacentNodeNames != null && adjacentNodeNames.length > 0;
        }
        @Override
        //将node对象做一个格式化的输出
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(pageRank);

            if (getAdjacentNodeNames() != null) {
                sb.append(fieldSeparator).append(
                        StringUtils.join(getAdjacentNodeNames(), fieldSeparator));
            }
            return sb.toString();
        }
        // value =1.0 B D
        //根据string串转化成一个node对象
        public static Node fromMR(String value) throws IOException {
            String[] parts = StringUtils.splitPreserveAllTokens(value, fieldSeparator);
            if (parts.length < 1) {
                throw new IOException("Expected 1 or more parts but received " + parts.length);
            }
            // 1.0 A B D
            Node node = new Node().setPageRank(Double.valueOf(parts[0]));
            if (parts.length > 1) {
                node.setAdjacentNodeNames(Arrays.copyOfRange(parts, 1, parts.length));
            }
            return node;
        }
    }
    结果
    A    0.19734410911950637    B    D  A的pr值为0.197344
    B    0.33353876774336305    C
    C    0.37429322529114195    A    B
    D    0.12175599053348046    B    C
示例五
TF-IDF:一种统计方法,用来估计一个词对于一个文件(微博文件)集中其中一份文件(一条内容)的重要程度。
原理
TF:词频,一个单词在一份文件中出现的次数除以文件集中单词的总数,如果该单词频繁的在该文件中出现,那么这个文件的重要程度比较高
IDF:逆向文件频率,描述一个词语普遍的重要度,总文件数除以包含该词语的文件数(如果这个文件数为0,那么分母为0,所以一般分母+1),再对商取对数log(对数不影响结构的走势,只是将值进行归一化),如果包含该单词的文件数越少,那么IDF越大,那么说明该单词有更好的类别区分能力
TF-IDF:将这两个指标求积,最后可以根据相同的词在不同文件中的重要程度来做推荐
做推荐要先确定单词,然后根据这些单词计算文件集中包含该单词的TF-IDF值从而得到这些文件的重要程度,最后根据文件找到用户做准确推荐。
MapRedue中要对整个文件集进行分析,拿到每个单词对应的文件的重要度,需要导入IK分词器IKAnalyzer2012_FF.jar
代码
    TFIDF_FirstJob.java
    public class TFIDF_FirstJob {
        public static void main(String[] args) {
            Configuration config = new Configuration();
            try {
                FileSystem fs = FileSystem.get(config);
                Job job = Job.getInstance(config);
                job.setJarByClass(TFIDF_FirstJob.class);
                job.setJobName("weibo1");
                job.setOutputKeyClass(Text.class);
                job.setOutputValueClass(IntWritable.class);
                job.setNumReduceTasks(4);
                job.setMapperClass(TFIDF_FirstMapper.class);
                job.setPartitionerClass(TFIDF_FirstPartition.class);
                job.setCombinerClass(TFIDF_FirstReduce.class);
                job.setReducerClass(TFIDF_FirstReduce.class);
                FileInputFormat.addInputPath(job, new Path("/tfidf/input"));
                Path path = new Path("/tfidf/output/weibo1");
                if (fs.exists(path)) {
                    fs.delete(path, true);
                }
                FileOutputFormat.setOutputPath(job, path);
                System.out.println(job.waitForCompletion(true)?"job Success !!":null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public static class TFIDF_FirstMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
            //第一个MR,计算TF和计算N(微博总数)
            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                //数组v中第一个就是我们的id,第二个就是我们的微博内容
                String[] v = value.toString().trim().split("\t");
                if (v.length >= 2) {
                    String id = v[0].trim();
                    String content = v[1].trim();
                    StringReader sr = new StringReader(content);
                    //下面这个就是ik分词器
                    IKSegmenter ikSegmenter = new IKSegmenter(sr, true);
                    Lexeme word = null;
                    //从中拿词的时候也是通过迭代器的方式
                    while ((word = ikSegmenter.next()) != null) {
                        String w = word.getLexemeText();
                        //输出是单词-微博id大方式输出的,值就是1,这样输出后在reduce拿的时候是以同一个单词在同一条微博出现的次数的方式,然后做一个累加就是TF了
                        context.write(new Text(w + "_" + id), new IntWritable(1));
                    }
                    //这个key是我们自己写的字符串,后面value是1,在reduce中累加后就是所有微博的条数了
                    context.write(new Text("count"), new IntWritable(1));
                } else {
                    System.out.println(value.toString() + "-------------");
                }
            }
        }
        public static class TFIDF_FirstPartition extends HashPartitioner<Text, IntWritable> {
            //第一个MR自定义分区
            @Override
            public int getPartition(Text key, IntWritable value, int reduceCount) {
                if (key.equals(new Text("count")))
                    return 3;
                else
                    return super.getPartition(key, value, reduceCount - 1);
            }

        }
        public static class TFIDF_FirstReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
            //c1_001,2 c2_001,1 count,10000
            @Override
            protected void reduce(Text key, Iterable<IntWritable> iterable,Context context) throws IOException, InterruptedException {
                int sum = 0;
                for (IntWritable i : iterable) {
                    sum = sum + i.get();
                }
                if (key.equals(new Text("count"))) {
                    System.out.println(key.toString() + "___________" + sum);
                }
                context.write(key, new IntWritable(sum));
            }
        }
    }
    TFIDF_TwoJob.java
    public class TFIDF_TwoJob {
        public static void main(String[] args) {
            Configuration config = new Configuration();
            try {
                Job job = Job.getInstance(config);
                job.setJarByClass(TFIDF_TwoJob.class);
                job.setJobName("weibo2");
                // 设置map任务的输出key类型、value类型
                job.setOutputKeyClass(Text.class);
                job.setOutputValueClass(IntWritable.class);
                job.setMapperClass(TFIDF_TwoMapper.class);
                job.setCombinerClass(TFIDF_TwoReduce.class);
                job.setReducerClass(TFIDF_TwoReduce.class);
                // mr运行时的输入数据从hdfs的哪个目录中获取
                FileInputFormat.addInputPath(job, new Path("/tfidf/output/weibo1"));
                FileOutputFormat.setOutputPath(job, new Path("/tfidf/output/weibo2"));

                boolean f = job.waitForCompletion(true);
                if (f) {
                    System.out.println("执行job成功");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public static class TFIDF_TwoMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
            统计df:词在多少个微博中出现过
            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                // 获取当前 mapper task的数据片段(split)
                FileSplit fs = (FileSplit) context.getInputSplit();
                if (!fs.getPath().getName().contains("part-r-00003")) {
                    String[] v = value.toString().trim().split("\t");
                    if (v.length >= 2) {
                        String[] ss = v[0].split("_");
                        if (ss.length >= 2) {
                            String w = ss[0];
                            context.write(new Text(w), new IntWritable(1));
                        }
                    } else {
                        System.out.println(value.toString() + "-------------");
                    }
                }
            }
        }
        public static class TFIDF_TwoReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
            @Override
            protected void reduce(Text key, Iterable<IntWritable> arg1, Context context) throws IOException, InterruptedException {
                int sum = 0;
                for (IntWritable i : arg1) {
                    sum = sum + i.get();
                }
                context.write(key, new IntWritable(sum));
            }
        }
    }
    TFIDF_LastJob.java
    public class TFIDF_LastJob {
        public static void main(String[] args) {
            Configuration config = new Configuration();
            config.set("fs.defaultFS", "hdfs://node1:8020");
            config.set("yarn.resourcemanager.hostname", "node1");
            try {
                FileSystem fs = FileSystem.get(config);
                Job job = Job.getInstance(config);
                job.setJarByClass(TFIDF_LastJob.class);
                job.setJobName("weibo3");
                // 2.5
                // 把微博总数加载到内存,是为了防止每次都从hdfs中拿数据,效率慢
                job.addCacheFile(new Path("/tfidf/output/weibo1/part-r-00003").toUri());
                // 把df加载到内存
                job.addCacheFile(new Path("/tfidf/output/weibo2/part-r-00000").toUri());
                // 设置map任务的输出key类型、value类型
                job.setOutputKeyClass(Text.class);
                job.setOutputValueClass(Text.class);
                job.setMapperClass(TFIDF_LastMapper.class);
                job.setReducerClass(TFIDF_LastReduce.class);
                // mr运行时的输入数据从hdfs的哪个目录中获取
                FileInputFormat.addInputPath(job, new Path("/tfidf/output/weibo1"));
                Path outpath = new Path("/tfidf/output/weibo3");
                if (fs.exists(outpath)) {
                    fs.delete(outpath, true);
                }
                FileOutputFormat.setOutputPath(job, outpath);
                System.out.println(job.waitForCompletion(true)?"执行job成功":null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        public static class TFIDF_LastMapper extends Mapper<LongWritable, Text, Text, Text> {
            // 存放微博总数
            public static Map<String, Integer> cmap = null;
            // 存放df
            public static Map<String, Integer> df = null;
            // 在map方法执行之前优先调用setup方法,这个方法执行的个数是和map的执行个数是一样的
            @Override
            protected void setup(Context context) throws IOException, InterruptedException {
                System.out.println("******************");
                if (cmap == null || cmap.size() == 0 || df == null || df.size() == 0) {
                    URI[] ss = context.getCacheFiles();
                    if (ss != null) {
                        for (int i = 0; i < ss.length; i++) {
                            //hdfs中目录的地址
                            URI uri = ss[i];
                            //然后判断是否包含"part-r-00003",如果包含那么这个文件就是存放的微博的总数
                            if (uri.getPath().endsWith("part-r-00003")) {// 微博总数
                                Path path = new Path(uri.getPath());
                                System.out.println(uri.getPath() + "   " + path.getName());
                                FileSystem fs = FileSystem.get(context.getConfiguration());
                                FSDataInputStream inputStream = fs.open(path);
                                BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
                                String line = br.readLine();
                                if (line.startsWith("count")) {
                                    String[] ls = line.split("\t");
                                    cmap = new HashMap<String, Integer>();
                                    cmap.put(ls[0], Integer.parseInt(ls[1].trim()));
                                }
                                br.close();
                            } else if (uri.getPath().endsWith("part-r-00000")) {// 词条的DF
                                df = new HashMap<String, Integer>();
                                Path path = new Path(uri.getPath());
                                System.out.println("----" + uri.getPath());
                                FileSystem fs = FileSystem.get(context.getConfiguration());
                                FSDataInputStream inputStream = fs.open(path);
                                BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
                                String line;
                                while ((line = br.readLine()) != null) {
                                    String[] ls = line.split("\t");
                                    //单词          包含当前单词微博的数量
                                    df.put(ls[0], Integer.parseInt(ls[1].trim()));
                                }
                                br.close();
                            }
                        }
                    }
                }
            }
            @Override
            protected void map(LongWritable key, Text value, Context context)
                    throws IOException, InterruptedException {
                FileSplit fs = (FileSplit) context.getInputSplit();
                // System.out.println("--------------------");
                if (!fs.getPath().getName().contains("part-r-00003")) {
                    // 样本: 早餐_3824213972412901    2
                    String[] v = value.toString().trim().split("\t");
                    if (v.length >= 2) {
                        int tf = Integer.parseInt(v[1].trim());// tf值
                        String[] ss = v[0].split("_");
                        if (ss.length >= 2) {
                            String w = ss[0];
                            String id = ss[1];
                            //s就是我们要的重要度
                            double s = tf * Math.log(cmap.get("count") / df.get(w));
                            NumberFormat nf = NumberFormat.getInstance();
                            nf.setMaximumFractionDigits(5);
                            context.write(new Text(id), new Text(w + ":" + nf.format(s)));
                        }
                    } else {
                        System.out.println(value.toString() + "-------------");
                    }
                }
            }
        }
        public static class TFIDF_LastReduce extends Reducer<Text, Text, Text, Text> {
            @Override//最终reduce拿到的是头一条微博里边所有单词的TF-IDF值
            protected void reduce(Text key, Iterable<Text> iterable, Context context) throws IOException, InterruptedException {
                StringBuffer sb = new StringBuffer();
                for (Text i : iterable) {
                    sb.append(i.toString() + "\t");
                }
                context.write(key, new Text(sb.toString()));
            }
        }
    }
    数据
    3823890201582094    今天我约了豆浆,油条。约了电饭煲几小时后饭就自动煮好,还想约豆浆机,让我早晨多睡一小时,豆浆就自然好。起床就可以喝上香喷喷的豆浆了。
    3823890210294392    今天我约了豆浆,油条
    3823890235477306    一会儿带儿子去动物园约起~
    3823890239358658    继续支持
    3823890256464940    约起来!次饭去!
    3823890264861035    我约了吃饭哦
    3823890281649563    和家人一起相约吃个饭!
    3823890285529671    今天约了广场一起滑旱冰
    3823890294242412    九阳双预约豆浆机即将全球首发啦,我要约你一起吃早餐
    3823890314914825    今天天气晴好,姐妹们约起,一起去逛街。
    3823890323625419    全国包邮!九阳(Joyoung)JYL-
    3823890335901756    今天是今年最暖和的一天,果断出来逛街!
    3823890364788305    春天来了,约好友一起出去去踏青,去赏花!
    3823890369489295    我在平湖,让你开挂练九阳真经,走火入魔毁了三叉神经了吧,改练九阴真经吧小子。   (免费下载 )
    3823890373686361    约了小伙伴一起去理发!
    3823890378201539    今天约了姐妹去逛街吃美食,周末玩得很开心啊!
    3823890382081678    这几天一直在约,因为感冒发烧了,所以和老公约好了陪我去打针,求九阳安慰,我想喝豆浆,药好苦的
    3823890399188850    和吃货的约会么就是吃
    结果,后面是每个单词的TF-IDF值,提供一个词找出包含这个词相关词语的结果然后根据TF-IDF值做降序排序,找出id
    3823890201582094    电饭煲:2.89037    的:1.38629    后:2.89037    好:5.78074    就:5.78074    豆浆机:2.19722    让:2.19722    一小时:2.89037    自然:2.89037    自动:2.89037    几小时:2.89037    饭:1.79176    了:3.29584    今天:1.09861    油条:2.19722    还:2.89037    起床:2.89037    早晨:2.89037    喝上:2.89037    我:2.19722    豆浆:5.37528    香喷喷:2.89037    多:2.89037    约:0    睡:2.89037    就可以:2.89037    煮:2.89037    想约:2.89037    
    3823890210294392    今天:1.09861    油条:2.19722    了:1.09861    豆浆:1.79176    我:1.09861    约:0    
    3823890235477306    儿子:2.89037    约:0    带:2.89037    动物园:2.89037    起:2.19722    去:0.69315    一会儿:2.89037    
    3823890239358658    支持:2.89037    继续:2.89037    
    3823890256464940    约:0    饭:1.79176    去:0.69315    起来:2.89037    次:2.89037    
    3823890264861035    了:1.09861    吃饭:2.89037    哦:2.89037    约:0    我:1.09861    
    3823890281649563    一起:1.09861    吃个:2.89037    相约:2.89037    饭:1.79176    和家人:2.89037    
    3823890285529671    广场:2.89037    滑旱冰:2.89037    一起:1.09861    约:0    今天:1.09861    了:1.09861    
    3823890294242412    首发:2.89037    预约:2.89037    啦:2.89037    即将:2.89037    要约:2.89037    豆浆机:2.19722    吃:1.79176    双:2.89037    一起:1.09861    九阳:1.38629    你:2.19722    全球:2.89037    早餐:2.89037    我:1.09861    
    3823890314914825    姐妹:2.19722    逛街:1.79176    去:0.69315    今天:1.09861    们:2.89037    起:2.19722    天气晴好:2.89037    约:0    一起:1.09861    
    3823890323625419    jyl-:2.89037    包:2.89037    全国:2.89037    joyoung:2.89037    九阳:1.38629    邮:2.89037    
    3823890335901756    果断:2.89037    出来:2.89037    逛街:1.79176    最:2.89037    一天:2.89037    今天是:2.89037    暖和:2.89037    的:1.38629    今年:2.89037    
    3823890364788305    好友:2.89037    赏花:2.89037    约:0    春天:2.89037    一起:1.09861    踏青:2.89037    去去:2.89037    来了:2.89037    出:2.89037    去:0.69315    
    3823890369489295    平湖:2.89037    小子:2.89037    我:1.09861    挂:2.89037    吧:2.89037    真经:2.89037    免费:2.89037    你:2.19722    走火入魔:2.89037    了吧:2.89037    九阴真经:2.89037    九阳:1.38629    开:2.89037    在:2.89037    三叉神经:2.89037    改:2.89037    毁了:2.89037    让:2.19722    下载:2.89037    练:5.78074    
    3823890373686361    约:0    理发:2.89037    了:1.09861    小伙伴:2.89037    去:0.69315    一起:1.09861    
    3823890378201539    周末:2.89037    玩:2.89037    逛街:1.79176    了:1.09861    约:0    美食:2.89037    得很:2.89037    啊:2.89037    开心:2.89037    今天:1.09861    吃:1.79176    姐妹:2.19722    去:0.69315    
    3823890382081678    去:0.69315    安慰:2.89037    好苦:2.89037    这几:2.89037    一直在:2.89037    因为:2.89037    和:2.19722    打针:2.89037    烧了:2.89037    想喝:2.89037    求:2.89037    天:2.89037    的:1.38629    药:2.89037    九阳:1.38629    发:2.89037    豆浆:1.79176    好了:2.89037    感冒:2.89037    我:1.09861    所以:2.89037    老公:2.89037    陪我:2.89037    约:0    
    3823890399188850    货:2.89037    么:2.89037    就是:2.89037    的:1.38629    约会:2.89037    和:2.19722    吃:3.58352    
示例六
协同过滤:userCF基于用户协同过滤,itemCF基于物品协同过滤,modelCF基于模型协同过滤
userCF:通过不同用户对物品的评分来评测用户之间的相似性,基于用户之间的相似性来做推荐,给用户推荐和该用户相似的用户喜欢的商品。
itemCF:通过用户对不同物品的评分得到物品之间的相似性,根据物品之间的相似性做推荐,给用户推荐和他之前所买商品类似的商品。
一般刚开始的时候使用itemCF(新用户没有购买行为),
如果用户急速增长,那么构建用户相似性的矩阵越来越庞大,时效性就会慢
初期和中期的时候使用userCF,后面选择itemCF
itemCF核心同现矩阵(两个商品同时出现的情况),将同现矩阵和用户评分矩阵(没有买过的商品评判为0)做乘法,结果就是最终的值
代码:
    public class ItemCFRun {
        public static void main(String[] args) {
            Configuration config = new Configuration();
            // 所有mr的输入和输出目录定义在map集合中
            Map<String, String> paths = new HashMap<String, String>();
            paths.put("Step1Input", "/itemcf/sam_tianchi_2014002_rec_tmall_log.csv");
            paths.put("Step1Output", "/itemcf/output/step1");
            paths.put("Step2Input", paths.get("Step1Output"));
            paths.put("Step2Output", "/itemcf/output/step2");
            paths.put("Step3Input", paths.get("Step2Output"));
            paths.put("Step3Output", "/itemcf/output/step3");
            paths.put("Step4Input1", paths.get("Step2Output"));
            paths.put("Step4Input2", paths.get("Step3Output"));
            paths.put("Step4Output", "/itemcf/output/step4");
            paths.put("Step5Input", paths.get("Step4Output"));
            paths.put("Step5Output", "/itemcf/output/step5");
            paths.put("Step6Input", paths.get("Step5Output"));
            paths.put("Step6Output", "/itemcf/output/step6");
            Step1.run(config, paths);
            Step2.run(config, paths);
            Step3.run(config, paths);
            Step4.run(config, paths);
            Step5.run(config, paths);
            Step6.run(config, paths);
        }
        public static Map<String, Integer> R = new HashMap<String, Integer>();
        static {//四种用户行为
            R.put("click", 1);//点击
            R.put("collect", 2);//收藏
            R.put("cart", 3);//加入购物车
            R.put("alipay", 4);//支付
        }
    }
    class Step1 {
        //去重
        public static boolean run(Configuration config, Map<String, String> paths) {
            try {
                FileSystem fs = FileSystem.get(config);
                Job job = Job.getInstance(config);
                job.setJobName("step1");
                job.setJarByClass(Step1.class);
                job.setMapperClass(Step1_Mapper.class);
                job.setReducerClass(Step1_Reducer.class);
                job.setMapOutputKeyClass(Text.class);
                job.setMapOutputValueClass(NullWritable.class);
                FileInputFormat.addInputPath(job, new Path(paths.get("Step1Input")));
                Path outpath = new Path(paths.get("Step1Output"));
                if (fs.exists(outpath)) {
                    fs.delete(outpath, true);
                }
                FileOutputFormat.setOutputPath(job, outpath);
                return job.waitForCompletion(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
        static class Step1_Mapper extends Mapper<LongWritable, Text, Text, NullWritable> {
            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                if (key.get() != 0) {
                    context.write(value, NullWritable.get());
                }
            }
        }
        static class Step1_Reducer extends Reducer<Text, IntWritable, Text, NullWritable> {
            @Override
            protected void reduce(Text key, Iterable<IntWritable> i, Context context) throws IOException, InterruptedException {
                context.write(key, NullWritable.get());
            }
        }
    }
    class Step2 {
        public static boolean run(Configuration config, Map<String, String> paths) {
            try {
                FileSystem fs = FileSystem.get(config);
                Job job = Job.getInstance(config);
                job.setJobName("step2");
                job.setJarByClass(ItemCFRun.class);
                job.setMapperClass(Step2_Mapper.class);
                job.setReducerClass(Step2_Reducer.class);
                job.setMapOutputKeyClass(Text.class);
                job.setMapOutputValueClass(Text.class);
                FileInputFormat.addInputPath(job, new Path(paths.get("Step2Input")));
                Path outpath = new Path(paths.get("Step2Output"));
                if (fs.exists(outpath)) {
                    fs.delete(outpath, true);
                }
                FileOutputFormat.setOutputPath(job, outpath);
                return job.waitForCompletion(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
        //去重
        static class Step2_Mapper extends Mapper<LongWritable, Text, Text, Text> {
            // 如果使用:用戶+物品,同时作为输出key,更好
            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                String[] tokens = value.toString().split(",");
                String item = tokens[0];
                String user = tokens[1];
                String action = tokens[2];
                Text k = new Text(user);
                Integer rv = ItemCFRun.R.get(action);
                // if(rv!=null){
                Text v = new Text(item + ":" + rv.intValue());
                context.write(k, v);
            }
        }
        /**
         * 按用户分组,计算所有物品出现的组合列表,得到用户对物品的喜爱度得分矩阵,
         u13    i160:1,(1表示点击)
         u14    i25:1,i223:1,
         u16    i252:1,
         u21    i266:1,
         u24    i64:1,i218:1,i185:1,
         u26    i276:1,i201:1,i348:1,i321:1,i136:1,
         */
        static class Step2_Reducer extends Reducer<Text, Text, Text, Text> {
            @Override
            protected void reduce(Text key, Iterable<Text> i, Context context) throws IOException, InterruptedException {
                Map<String, Integer> r = new HashMap<String, Integer>();
                for (Text value : i) {
                    String[] vs = value.toString().split(":");
                    String item = vs[0];
                    Integer action = Integer.parseInt(vs[1]);
                    action = ((Integer) (r.get(item) == null ? 0 : r.get(item))).intValue() + action;
                    r.put(item, action);
                }
                StringBuffer sb = new StringBuffer();
                for (Map.Entry<String, Integer> entry : r.entrySet()) {
                    sb.append(entry.getKey() + ":" + entry.getValue().intValue() + ",");
                }
                context.write(key, new Text(sb.toString()));
            }
        }
    }
    /**
     * 对物品组合列表进行计数,建立物品的同现矩阵
     i100:i100    3
     i100:i105    1
     i100:i106    1
     i100:i109    1
     i100:i114    1
     i100:i124    1
     */
    class Step3 {
        private final static Text K = new Text();
        private final static IntWritable V = new IntWritable(1);
        public static boolean run(Configuration config, Map<String, String> paths) {
            try {
                FileSystem fs = FileSystem.get(config);
                Job job = Job.getInstance(config);
                job.setJobName("step3");
                job.setJarByClass(ItemCFRun.class);
                job.setMapperClass(Step3_Mapper.class);
                job.setReducerClass(Step3_Reducer.class);
                job.setCombinerClass(Step3_Reducer.class);
                job.setMapOutputKeyClass(Text.class);
                job.setMapOutputValueClass(IntWritable.class);
                FileInputFormat.addInputPath(job, new Path(paths.get("Step3Input")));
                Path outpath = new Path(paths.get("Step3Output"));
                if (fs.exists(outpath)) {
                    fs.delete(outpath, true);
                }
                FileOutputFormat.setOutputPath(job, outpath);
                return job.waitForCompletion(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
        // 第二个MR执行的结果--作为本次MR的输入  样本: u2837     i541:1,i331:1,i314:1,i125:1,
        static class Step3_Mapper extends Mapper<LongWritable, Text, Text, IntWritable> {
            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                String[] tokens = value.toString().split("\t");
                String[] items = tokens[1].split(",");
                for (int i = 0; i < items.length; i++) {
                    String itemA = items[i].split(":")[0];
                    for (int j = 0; j < items.length; j++) {
                        String itemB = items[j].split(":")[0];
                        K.set(itemA + ":" + itemB);
                        context.write(K, V);
                    }
                }
            }
        }
        static class Step3_Reducer extends Reducer<Text, IntWritable, Text, IntWritable> {
            @Override
            protected void reduce(Text key, Iterable<IntWritable> i, Context context) throws IOException, InterruptedException {
                int sum = 0;
                for (IntWritable v : i) {
                    sum = sum + v.get();
                }
                V.set(sum);
                context.write(key, V);
                //  执行结果
    //            i100:i181    1
    //            i100:i184    2
            }
        }
    }
    /**
     * 把同现矩阵和得分矩阵相乘
     */
    class Step4 {
        public static boolean run(Configuration config, Map<String, String> paths) {
            try {
                FileSystem fs = FileSystem.get(config);
                Job job = Job.getInstance(config);
                job.setJobName("step4");
                job.setJarByClass(ItemCFRun.class);
                job.setMapperClass(Step4_Mapper.class);
                job.setReducerClass(Step4_Reducer.class);
                job.setMapOutputKeyClass(Text.class);
                job.setMapOutputValueClass(Text.class);
                FileInputFormat.setInputPaths(job, new Path[] { new Path(paths.get("Step4Input1")), new Path(paths.get("Step4Input2")) });
                Path outpath = new Path(paths.get("Step4Output"));
                if (fs.exists(outpath)) {
                    fs.delete(outpath, true);
                }
                FileOutputFormat.setOutputPath(job, outpath);
                return job.waitForCompletion(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
        static class Step4_Mapper extends Mapper<LongWritable, Text, Text, Text> {
            private String flag;// A同现矩阵 or B得分矩阵
            // 每个maptask,初始化时调用一次
            @Override
            protected void setup(Context context) throws IOException, InterruptedException {
                FileSplit split = (FileSplit) context.getInputSplit();
                flag = split.getPath().getParent().getName();// 判断读的数据集
                System.out.println(flag + "**********************");
            }
            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                String[] tokens = Pattern.compile("[\t,]").split(value.toString());
                if (flag.equals("step3")) {// 同现矩阵
                    // 样本:  i100:i181    1
                    //       i100:i184    2
                    String[] v1 = tokens[0].split(":");
                    String itemID1 = v1[0];
                    String itemID2 = v1[1];
                    String num = tokens[1];
                    Text k = new Text(itemID1);// 以前一个物品为key 比如i100
                    Text v = new Text("A:" + itemID2 + "," + num);// A:i109,1
                    context.write(k, v);
                } else if (flag.equals("step2")) {// 用户对物品喜爱得分矩阵
                    // 样本:  u24  i64:1,i218:1,i185:1,
                    String userID = tokens[0];
                    for (int i = 1; i < tokens.length; i++) {
                        String[] vector = tokens[i].split(":");
                        String itemID = vector[0];// 物品id
                        String pref = vector[1];// 喜爱分数
                        Text k = new Text(itemID); // 以物品为key 比如:i100
                        Text v = new Text("B:" + userID + "," + pref); // B:u401,2
                        context.write(k, v);
                    }
                }
            }
        }
        static class Step4_Reducer extends Reducer<Text, Text, Text, Text> {
            @Override
            protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
                // A同现矩阵 or B得分矩阵
                // 某一个物品,针对它和其他所有物品的同现次数,都在mapA集合中
                Map<String, Integer> mapA = new HashMap<String, Integer>();
                //和该物品(key中的itemID)同现的其他物品的同现集合//
                //其他物品ID为map的key,同现数字为值
                Map<String, Integer> mapB = new HashMap<String, Integer>();
                //该物品(key中的itemID),所有用户的推荐权重分数
                for (Text line : values) {
                    String val = line.toString();
                    if (val.startsWith("A:")) {// 表示物品同现数字
                        String[] kv = Pattern.compile("[\t,]").split(
                                val.substring(2));
                        try {
                            mapA.put(kv[0], Integer.parseInt(kv[1]));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } else if (val.startsWith("B:")) {
                        String[] kv = Pattern.compile("[\t,]").split(
                                val.substring(2));
                        try {
                            mapB.put(kv[0], Integer.parseInt(kv[1]));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                double result = 0;
                Iterator<String> iter = mapA.keySet().iterator();
                while (iter.hasNext()) {
                    String mapk = iter.next();// itemID
                    int num = mapA.get(mapk).intValue();
                    Iterator<String> iterb = mapB.keySet().iterator();
                    while (iterb.hasNext()) {
                        String mapkb = iterb.next();// userID
                        int pref = mapB.get(mapkb).intValue();
                        result = num * pref;// 矩阵乘法相乘计算
                        Text k = new Text(mapkb);
                        Text v = new Text(mapk + "," + result);
                        context.write(k, v);
                    }
                }
                //  结果样本:  u2723    i9,8.0
            }
        }
    }
    /**
     * 把相乘之后的矩阵相加获得结果矩阵
     */
    class Step5 {
        private final static Text K = new Text();
        private final static Text V = new Text();
        public static boolean run(Configuration config, Map<String, String> paths) {
            try {
                FileSystem fs = FileSystem.get(config);
                Job job = Job.getInstance(config);
                job.setJobName("step5");
                job.setJarByClass(ItemCFRun.class);
                job.setMapperClass(Step5_Mapper.class);
                job.setReducerClass(Step5_Reducer.class);
                job.setMapOutputKeyClass(Text.class);
                job.setMapOutputValueClass(Text.class);
                FileInputFormat.addInputPath(job, new Path(paths.get("Step5Input")));
                Path outpath = new Path(paths.get("Step5Output"));
                if (fs.exists(outpath)) {
                    fs.delete(outpath, true);
                }
                FileOutputFormat.setOutputPath(job, outpath);
                return job.waitForCompletion(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
        static class Step5_Mapper extends Mapper<LongWritable, Text, Text, Text> {
            @Override//原封不动输出
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                //  样本:  u2723    i9,8.0
                String[] tokens = Pattern.compile("[\t,]").split(value.toString());
                Text k = new Text(tokens[0]);// 用户为key
                Text v = new Text(tokens[1] + "," + tokens[2]);
                context.write(k, v);
            }
        }
        static class Step5_Reducer extends Reducer<Text, Text, Text, Text> {
            @Override
            protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
                Map<String, Double> map = new HashMap<String, Double>();// 结果
                for (Text line : values) {// i9,4.0
                    String[] tokens = line.toString().split(",");
                    String itemID = tokens[0];
                    Double score = Double.parseDouble(tokens[1]);
                    if (map.containsKey(itemID)) {
                        map.put(itemID, map.get(itemID) + score);// 矩阵乘法求和计算
                    } else {
                        map.put(itemID, score);
                    }
                }
                Iterator<String> iter = map.keySet().iterator();
                while (iter.hasNext()) {
                    String itemID = iter.next();
                    double score = map.get(itemID);
                    Text v = new Text(itemID + "," + score);
                    context.write(key, v);
                }
            }
            // 样本:  u13    i9,5.0
        }
    }
    /**
     * 按照推荐得分降序排序,每个用户列出10个推荐物品
     */
    class Step6 {
        private final static Text K = new Text();
        private final static Text V = new Text();
        public static boolean run(Configuration config, Map<String, String> paths) {
            try {
                FileSystem fs = FileSystem.get(config);
                Job job = Job.getInstance(config);
                job.setJobName("step6");
                job.setJarByClass(ItemCFRun.class);
                job.setMapperClass(Step6_Mapper.class);
                job.setReducerClass(Step6_Reducer.class);
                job.setSortComparatorClass(NumSort.class);
                job.setGroupingComparatorClass(UserGroup.class);
                job.setMapOutputKeyClass(PairWritable.class);
                job.setMapOutputValueClass(Text.class);
                FileInputFormat.addInputPath(job, new Path(paths.get("Step6Input")));
                Path outpath = new Path(paths.get("Step6Output"));
                if (fs.exists(outpath)) {
                    fs.delete(outpath, true);
                }
                FileOutputFormat.setOutputPath(job, outpath);
                boolean f = job.waitForCompletion(true);
                return f;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }
        static class Step6_Mapper extends Mapper<LongWritable, Text, PairWritable, Text> {
            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
                String[] tokens = Pattern.compile("[\t,]").split(value.toString());
                String u = tokens[0];
                String item = tokens[1];
                String num = tokens[2];
                PairWritable k = new PairWritable();
                k.setUid(u);
                k.setNum(Double.parseDouble(num));
                V.set(item + ":" + num);
                context.write(k, V);
            }
        }
        static class Step6_Reducer extends Reducer<PairWritable, Text, Text, Text> {
            @Override
            protected void reduce(PairWritable key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
                int i = 0;
                StringBuffer sb = new StringBuffer();
                for (Text v : values) {
                    if (i == 10)
                        break;
                    sb.append(v.toString() + ",");
                    i++;
                }
                K.set(key.getUid());
                V.set(sb.toString());
                context.write(K, V);
            }
        }
        static class PairWritable implements WritableComparable<PairWritable> {
            private String uid;
            private double num;
            @Override
            public void write(DataOutput out) throws IOException {
                out.writeUTF(uid);
                out.writeDouble(num);
            }
            @Override
            public void readFields(DataInput in) throws IOException {
                this.uid = in.readUTF();
                this.num = in.readDouble();
            }
            @Override
            public int compareTo(PairWritable o) {
                int r = this.uid.compareTo(o.getUid());
                if (r == 0) {
                    return Double.compare(this.num, o.getNum());
                }
                return r;
            }
            public String getUid() {
                return uid;
            }
            public void setUid(String uid) {
                this.uid = uid;
            }
            public double getNum() {
                return num;
            }
            public void setNum(double num) {
                this.num = num;
            }
        }
        static class NumSort extends WritableComparator {
            public NumSort() {
                super(PairWritable.class, true);
            }
            @Override
            public int compare(WritableComparable a, WritableComparable b) {
                PairWritable o1 = (PairWritable) a;
                PairWritable o2 = (PairWritable) b;
                int r = o1.getUid().compareTo(o2.getUid());
                if (r == 0) {
                    return -Double.compare(o1.getNum(), o2.getNum());
                }
                return r;
            }
        }
        static class UserGroup extends WritableComparator {
            public UserGroup() {
                super(PairWritable.class, true);
            }
            @Override
            public int compare(WritableComparable a, WritableComparable b) {
                PairWritable o1 = (PairWritable) a;
                PairWritable o2 = (PairWritable) b;
                return o1.getUid().compareTo(o2.getUid());
            }
        }
    }

执行环境
1.生产环境
    1)打jar包手动上传服务器
    2)执行hadoop jar jar路径 类名
2.生产环境(本地提交jar包上传给服务器)
    1)项目打包
    2)解压Hadoop压缩包,配置环境变量,HADOOP_HOME并在Path下配置bin和sbin
    3)将winutils.exe放在bin目录中
    4)将hdfs-site.xml,mapred-site.xml,yarn-site.xml,core-site.xml配置文件放到src目录中,添加配置到mapred-site.xml中
        <property>
            <name>mapreduce.app-submission.cross-platform</name>
            <value>true</value>
        </property>
    5)代码中添加job.setJar("jar包路径")
3.本地测试(本地模拟多线程的方式进行,但数据在hdfs上)
    1)解压Hadoop压缩包,配置环境变量,HADOOP_HOME并在Path下配置bin和sbin
    2)将winutils.exe放在bin目录中
    3)src目录中不能任何配置文件
    4)添加修改后的org.apache.hadoop.io.nativeio.NativeIO类,链接:https://pan.baidu.com/s/1UdmXxYYynvONht7kKTkI5Q 提取码:vs51
    5)代码中添加
        conf.set("fs.defaultFS", "hdfs://node2:8020");
        conf.set("yarn.resourcemanager.hostname", "node1");

 

转载于:https://www.cnblogs.com/timeTraveler/p/10775500.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值