大数据学习day10-----zookeeper--------1.小文件合并,2 输入和输出 3 多路径输出 4.zookeeper(选举机制,安装,zk的shell客户端、java客户端)

1. 小文件合并

HDFS中不适合存储大量的小文件,原因如下;

  • 无论文件大小,namenode记录的元数据大小几乎是一致的(1KB的文件与120M的文件在namenode中的元数据都是一样的)
  • namenode的内存有限,记录的元数据条数有限,集群的存储容量受限,所以HDFS不能无限添加datanode扩容
  • 增加namenode管理元数据的压力
  • MR程序默认的是使用TextInputFormat类,计算任务的时候是以文件数量为基准的,大量的小文件会启动大量的maptask,而maptask内部处理数据是比较复杂的,这会降低处理数据的效率

所以

  尽量别在hdfs上存储小文件,如果有大量的小文件产生,最好将小文件合并以后再上传

  假如小文件真存储在了HDFS中,这是需要避免处理数据时大量maptask的产生

案例:

 将E:\wc\filemerge\input中的所有文件中的内容读取到一个文件中,文件中内容的格式为文件名:内容

 

 

 

A. 默认设置(产生多个map任务(文件的个数)处理的情况),代码如下:

public class Merge1 {
    static class MergeMapper extends Mapper<LongWritable, Text, Text,Text >{
        // 获取文件名
        String fileName = null;
        @Override
        protected void setup(Mapper<LongWritable, Text, Text, Text>.Context context)
                throws IOException, InterruptedException {
            FileSplit fs = (FileSplit)context.getInputSplit();
            fileName = fs.getPath().getName();
        }
        // 处理每行数据
        StringBuilder sb = new StringBuilder();
        @Override
        protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, Text>.Context context)
                throws IOException, InterruptedException {
            String line = value.toString();
            sb.append(line+" ");
        }
        // 将数据以filename为key,文件内容为value写出
        @Override
        protected void cleanup(Mapper<LongWritable, Text, Text, Text>.Context context)
                throws IOException, InterruptedException {
            context.write(new Text(fileName), new Text(sb.toString().trim()));
        }
    }
    static class MergeReducer extends Reducer<Text, Text, Text, NullWritable>{
        @Override
        protected void reduce(Text key, Iterable<Text> iters, Reducer<Text, Text, Text, NullWritable>.Context context)
                throws IOException, InterruptedException {
            String fileName = key.toString();
            Text next = iters.iterator().next(); //  迭代器中只有一条数据
            String content = next.toString();
            context.write(new Text(fileName+":"+content), NullWritable.get());
        }
    }
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        job.setMapperClass(MergeMapper.class);
        job.setReducerClass(MergeReducer.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);

        // 输出好输入的数据路径

        FileInputFormat.setInputPaths(job, new Path("E:/wc/filemerge/input/"));
        FileOutputFormat.setOutputPath(job, new Path("E:/wc/filemerge/output1/"));
        // true 执行成功
        boolean b = job.waitForCompletion(true);
        // 退出程序的状态码 404 200 500
        System.exit(b ? 0 : -1);
    }
}
View Code

此处的代码需要注意的点:

(1)StringBuilder的使用(节省资源,若直接使用String会在常量池创建多个空间)

 

 

 (2)迭代器中只有一个值时,直接获取值,不需要遍历

 

 

 

运行部分结果如下:

 

 

B.  比较少mapTask个数的情况(此种情况不能得到文件名)

 代码如下

public class Merge2 {
    static class MergeMapper2 extends Mapper<LongWritable, Text, Text, NullWritable>{
        StringBuilder sb = new StringBuilder();
        @Override
        protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, NullWritable>.Context context)
                throws IOException, InterruptedException {
            try {
                String line = value.toString();
                sb.append(line+" ");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        @Override
        protected void cleanup(Mapper<LongWritable, Text, Text, NullWritable>.Context context)
                throws IOException, InterruptedException {
            context.write(new Text(sb.toString().trim()), NullWritable.get());
        }
    }
    // 可以不需要reduce
    static class MergeReducer2 extends Reducer<Text, NullWritable, Text, NullWritable>{
        @Override
        protected void reduce(Text key, Iterable<NullWritable> iters,
                Reducer<Text, NullWritable, Text, NullWritable>.Context context) throws IOException, InterruptedException {
            context.write(key, NullWritable.get());
        }
    }
public static void main(String[] args) throws Exception {
        
        // 获取mr程序运行时的初始化配置
        Configuration conf = new Configuration();
        //一个maptask处理的最小数据大小
        // 参数一  name  参数二  处理数据量的最小值 单位字节  
        conf.setLong("mapreduce.input.fileinputformat.split.minsize", 1024*2);//2M
        Job job = Job.getInstance(conf);
        // 设置map和reduce类  调用类中自定义的map reduce方法的业务逻辑
        job.setMapperClass(MergeMapper2.class);
        job.setReducerClass(MergeReducer2.class);
        // 设置map端输出的key-value的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);
        // 设置reduce的key-value的类型   结果的最终输出
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);
        //设置reducetask的个数  默认1个
        //job.setNumReduceTasks(3);
        // 修改默认的输入对象
        job.setInputFormatClass(CombineTextInputFormat.class);
        // 处理的文件的路径
        FileInputFormat.setInputPaths(job, new Path("E:/wc/filemerge/input"));
        // 结果输出路径
        FileOutputFormat.setOutputPath(job, new Path("E:/wc/filemerge/output3/"));
        // 提交任务   参数等待执行
        job.waitForCompletion(true) ;
    }
}
View Code

知识点

 

 

 2. 输入和输出

  

 

 

   day07索引案例中利用MR程序进行了两次的计算,第二次的计算是以第一次的计算结果为输入文件的(由于默认是TextInputFormat,第一次MR计算结果输出是文本文件),此处可以将MR第一次计算得到的文本文件改为sequence文件(kvkvkvkv形式,比如MR程序中map阶段处理完交给reduce阶段的数据就是sequence)  

 

 

 

 若让A输出文件类型变为sequence,这样当写第二个MR程序对第一次结果作为输入文件进行计算时,map阶段就不需要进行文件的处理了(如得到offset,line等,直接就能获取到kv值,效率比较高)

 

 

 代码如下

Index1:在原先的代码中改数据输出类型为:SequenceFileOutputFormat,如下图

 

 Index2:改变处

  • 声明以SequenceFile文件的格式输入数据处理,如下图

 

  •  接收的数据是kv的形式,不需要再像以前那样写成LongWritable,Text

 

 

 

 3. 多路径输出

 可以将计算结果输出到不同的目录中(具体可见教案5.1.2.2

 

 

 4. zookeeper

 4.1 简介:

  •  是什么

   是一个分布式协调工具,一个集群(不会存在单点故障:多台zk,并且记录相同的数据

  •  用处是什么

  (1)记录数据(重要状态数据,大小不要超过64k) 

  (2)为客户提供读取数据的功能

  (3)当客户端读取的数据发生变化时,zk会以时间的形式告知客户端(事件监听)  

  •  运用场景

  

 

 4.2 选举机制

4.2.1 知识补充

 leader  领导(只有一个)

 flower  小弟(有多个)

 Zookeeper会在集群中安装多个,在每个zk中都有一个唯一的不重复的ID,数据在存储的时候有一个数据VERSION,版本越新就越大

 常见的几个端口

2181:leader和flower接收客户端请求的端口

2888:leader和flower内部通信的端口

3888:选举的端口

 

 4.2.2  选举过程

 (1)初次启动(3台机器为例)【3-5台,一般为奇数台】

  • id为1的服务器启动,它向局域网发送广播寻找leader(端口2888)。发现没有leader,这台服务器就进入选举状态(3888端口),并向局域网广播投票(投自己);
  • id=2的服务器启动,它向局域网寻找leader,发现没有,进入投票状态(3888端口),收到服务器1所投的票;然后发现1<自己2,向局域网广播投票(投2);此时,服务1也会收到2的投票,发现2>自己,重新投(投2);此时,服务器2会收到两票;然后通过配置文件获知集群总共有3台机器,从而知道自己已经得多数票(当选);服务器2就切换到leader状态(2888);

  •  

    id=3的服务器启动了,它向局域网寻找leader,发现服务器2是leader,自己主动进入follower状态

(2)leader宕机后

   leader宕机以后集群中所有的节点进入投票状态 , (先按照数据的version 版本最新的节点当选leader , 如果多个节点的数据版本一致再按照id比较选举)  

 

4.3 数据结构和类型 

 4.3.1  前言:

  zookeeper将数据存储于内存中,具体而言,Znode是存储数据的最小单元,而Znode被以层次化的结构进行组织,形容成一棵树。其对外提供的视图类似于Unix文件系统。树的根Znode节点相当于Unix文件系统根路径。正如Unix中目录下可以有子目录一样,Znode节点下也可以挂载子节点,最终形成如下所示结构:

 

 

   以文件系统进行类比的话,Znode天然具有目录和文件两重属性:即Znode既可以当做文件往里面写东西,又可以当做目录在下面挂载其他Znode。当然,由于Znode具有不同的类型,后半部分并不完全准确。

  ZK可以记录用户的一些状态信息(数据) ,不易过大,要求在64k以内,它是以一种特殊的key/value的形式存储数据的  , key类似于文件系统中的路径 ,目的是为了维护key的层级关系

 

 

 4.3.2  ZK的数据节点类型

  • 临时节点

      临时结点的生命周期和客户端会话保持一致。客户端段会话存在的话临时结点也存在,客户端会话断开则临时结点会自动被服务端删除。临时节点下不能创建子节点。

  • 临时有序节点:在具有临时节点的基本特性的基础上,会通过在结点路径后缀一串序号来区分多个子结点创建的先后顺序。这工作由Zookeeper服务端自动给我们做,只要在创建Znode时指定结点类型为该类型。
  • 持久节点:最常见的Znode类型,一旦创建将已知存在于服务端,除非客户端通过删除操作进行删除,持久节点下可以创建子节点
  • 持久顺序节点

 

 4.4 安装

 (1)上传------->rz

 (2)解压配置

  •  tar -xzvf zookeeper-3.4.6.tar.gz  -C /usr/apps/
  • 进入conf文件,将zoo_sample.cfg  文件名改为zoo.cfg
  • 在 zookeeper-3.4.6文件中创建一个用于存放数据的文件夹,此处自己为zk_data
  • 修改zoo.cfg中的内容 ,如下   

   dataDir=/usr/apps/zookeeper-3.4.6/zk_data 

  在末尾

server.1=feng01:2888:3888
server.2=feng02:2888:3888
server.3=feng03:2888:3888

(3)分发

    scp zookeeper-3.4.6 feng02:$PWD,同理第三台机器(此处的pwd表示的是命令行当前的路径)

(4)分发完成后分别在各台机器中的/usr/apps/zookeeper-3.4.6/zk_data 目录中执行------->echo 1/2/3  > myid 

(5)环境变量的配置如下图(vi /etc/profile)

 

 (6)启动

  • 启动ZK服务:zkServer.sh start
  • 查看节点的状态:zkServer.sh status
  • 进入shell客户端:zkCli.sh 

 4.5 zk客户端

 4.5.1 shell

 进入shell客户端:zkCli.sh 

 

ls  /  查看根目录下所有的节点
create  /user   lisi  在根目录下创建节点  key(/user)  value(lisi)
get  /user   获取节点的数据值
set /user  新值    更新节点的值

4.5.1.1 创建节点(一定要有值)

create  /节点名   值     默认创建永久节点
create  -e   /节点名  值   创建临时节点  断开以后数据删除
create -s /BIGDATA  hbase    永久的有序节点
create -e -s   /BIGDATA hive    临时有序节点
-e  临时节点  EPHEMERAL   ephemeral
-s 有序节点   sort

4.5.1.2 查看节点

 ls    ls2(相比ls,其会输出二外的信息,如下图)

 

 

 4.5.1.3 修改节点数据

 修改以后数据的版本号会递增

 

  4.5.1.4 删除节点

 

 

 4.5.1.5 时间监听演示

  •  数据的变化
  • 子节点个数的变化

 

 (1)数据变化:

 

 (2)子节点变化(删除子节点,添加子节点)

 

 

4.5.1 java客户端

(1)java客户端连接zk以及获取zk的java客户端对象

public class ConnectionDemo {
    public static void main(String[] args) throws Exception {
        // 连接zk的地址  2181  2888  3888
        // zk所有节点的机器的位置
        String connectString = "feng01:2181,feng02:2181,feng03:2181";
        //超时时间
        int sessionTimeout = 2000;
        // 获取zk的客户端对象
        ZooKeeper zk = new ZooKeeper(connectString, sessionTimeout, null) ;
        // 操作zk节点   获取/路径下所有的子节点  ls /
        List<String> nodes = zk.getChildren("/", null);
        for (String node : nodes) {
            //[zookeeper, user0000000007, user, BIGDATA]
            System.out.println(node); 
        }
        // 释放客户端对象
        zk.close();
    }
}
View Code

(2)在zk的java客户端创建节点

public class CreateNode {
    public static void main(String[] args) throws Exception{
        // 获取zk
                String connectString = "doit01:2181,doit02:2181,doit03:2181";
                //超时时间
                int sessionTimeout = 2000;
                // 获取zk的客户端对象
                ZooKeeper zk = new ZooKeeper(connectString, sessionTimeout, null) ;
                 // 创建节点
                /*
                 * 参数一  key(路径)  Path must start with / character
                 * 参数二 值  字节
                 * 参数三  权限  OPEN_ACL_UNSAFE开放所有的权限
                 * 参数四  节点类型
                 *  PERSISTENT  永久
                 *  PERSISTENT_SEQUENTIAL  有序
                 *  EPHEMERAL   临时 
                 *  EPHEMERAL_SEQUENTIAL
                 */
                String create = zk.create("/BIG2", "big".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT) ;
                // 1 临时节点
                // 2 永久节点
                // 3 有序节点
                System.out.println(create);
                //释放zk
                zk.close();
    }
}
View Code

(3)获取节点数据

public class GetNodeData {

    public static void main(String[] args) throws Exception {

        // 获取zk
        String connectString = "doit01:2181,doit02:2181,doit03:2181";
        // 超时时间
        int sessionTimeout = 2000;
        // 获取zk的客户端对象
        ZooKeeper zk = new ZooKeeper(connectString, sessionTimeout, null);
        // 获取/BIG节点的值数据
        byte[] data = zk.getData("/BIG", null, null);
        // 打印
        System.out.println(new String(data));
        zk.close();

    }

}
View Code

(4)删除节点delete(只能删除没有子节点的节点),以下是演示代码

/**
 * 测试Zk的其他方法
 * 创建节点
 * 删除节点
 * 获取节点数据
 * 判断节点是否存在
 * 获取所有的子节点
 * 修改节点
 * 
 * @author ThinkPad
 *
 */
public class ClientTest {
    // zk所有节点的机器的位置
    String connectString = "doit01:2181,doit02:2181,doit03:2181";
    // 超时时间
    int sessionTimeout = 2000;
    ZooKeeper zk = null;

    /**
     * 获取zk对象
     * 
     * @throws Exception
     */
    @Before
    public void init() throws Exception {
        zk = new ZooKeeper(connectString, sessionTimeout, null);
    }

    /**
     * 单元测试的类中不要写main方法 所有的方法是公共 的 所有的方法不能有参数 不能有返回值
     * 
     * @throws InterruptedException
     * @throws KeeperException
     */
    @Test
    public void exists() throws KeeperException, InterruptedException {

        Stat stat = zk.exists("/BIG22", null);
        System.out.println(stat != null ? "存在" : "不存在");
        /*
         * if(stat!=null) { System.out.println("存在"); System.out.println(stat); }else {
         * System.out.println("不存在"); }
         */
    }

    /*
     * 删除空节点
     *  删除任意节点  /BIGDATA
     *     递归  遍历下面的所有的节点   依次删除
     *        都有出口
     */
    @Test
    public void deleteNode() throws Exception {
        /*
         * 参数1 路径 参数2 数据版本 -1 所有的版本
         */
        // 判断节点是否存在
        // 1 NoNode for /BIG
        // 2 Directory not empty for /BIGDATA
        // zk.delete("/BIGDATA", -1);
        if (zk.exists("/BIGDATA", null) != null) {
            // 是否没有子节点
            if (zk.getChildren("/BIGDATA", null).size() == 0) {
                zk.delete("/BIGDATA", -1);
            } else {
                System.out.println("有子节点不能删除");
            }
        } else {
            System.out.println("节点不存在");
        }
    }

    @After
    public void closeZk() throws Exception {
        zk.close();

    }

}
View Code

(5)递归删除

public class RmrNode {
    static ZooKeeper zk = null;

    static {

        try {
            String connectString = "doit01:2181,doit02:2181,doit03:2181";
            // 超时时间
            int sessionTimeout = 2000;
            // 获取zk的客户端对象
            zk = new ZooKeeper(connectString, sessionTimeout, null);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws Exception {
        delete("/BIGDATA") ;
    }
/**
 * 递归  函数
 *   
 * @param path
 * @throws Exception
 */
    public static  void delete(String path) throws Exception {
        // 获取路径的节点
        List<String> list = zk.getChildren(path, null);
        if(list.size()>0) {
            //  /user/user2/user3
            for (String cnode : list) {
                // 调用自己
                delete(path + "/" + cnode); //  递归删除子节点
            }
        }
        // 删除自己
        zk.delete(path, -1);

    }
}
View Code

 

 

 

 

posted @ 2019-11-13 21:38  一y样  阅读(237)  评论(0编辑  收藏  举报