Hbase核心内容总结

1 Hbase 的角色功能

1.1 HMaster

  1. 管理所有的 RegionServer,实现 RegionServer 的负载均衡。
  2. 分配 Hbase 中所有的 Region,创建表的时候,HMaster 会加载 meta 表的信息来实现 Region 在 RegionServer 上的分配。
  3. 负载均衡时,由 HMaster 来负责 Region 的迁移。
  4. 所有 meta 表以及元数据的修改都由 HMaster 来负责。meta 表存储的 Region 的信息,元数据包括 RegionServer 信息、Region 信息、表的信息、namespace 信息。
  5. HMaster 负责接收客户端 DDL 请求,用于修改元数据、修改 Zookeeper 中的信息。
  6. 通过 Zookeeper 监听每个 RegionServer 的状态,如果 RegionServer故障,就会启动恢复 Region 的操作。

1.2 HRegionServer

  1. 负责接收并处理客户端的所有读写请求。
  2. 负责管理所有的Region。
  3. 负责记录WAL预写日志。
  4. 负责维护读缓存 BlockCache 和 Memstore。

1.3 Zookeeper

  1. 负责辅助实现 HA,虽然 HMaster 可以构成 HA 架构,但是 HMaster 宕机不影响数据的读写。
  2. 负责存储 Hbase 中几乎所有元数据,节点的信息、HMaster 的主备节点、 RegionServer 节点、表的状态信息等。
  3. 所有的 RegionServer 会在 Zookeeper 注册,创建一个属于自己的节点,HMaster 会监听这些信息,如果监听到有节点消失,则表示 RegionServer 故障。 HMaster 会启动恢复程序,将之前该 RegionServer 的所有 Region 恢复到别的 RegionServer上。
  4. 存储 meta 表的 Region 所在的 RegionServer 地址。
  5. 存储 flush、SplitWAL 的状态等。

1.4 HDFS

  负责接收 RegionServer 的读写请求,实现 Hbase 中数据的持久化等等。

2 Hbase 的读写流程

2.1 架构图

2.2 读流程

  1. 客户端提交一条读的命令会先请求 Zookeeper,从 Zookeeper 中获取到 hbase 的 meta 表的地址。
  2. 客户端读取 meta 表数据,根据读命令中的表名来获取该表所有的 Region 信息。
  3. 找到要读取的 Region 以及对应的 RegionServer 的地址。如果是 get 请求,带 rowkey 来请求的,判断这个 rowkey 属于哪个 Region,返回对应的 Region。如果是 scan 请求,返回当前表所有的 Region 信息。
  4. 根据返回的 Region 的地址,将操作请求给对应的 RegionServer。
  5. 如果列簇开启了 BlockCache 缓存功能,先从 BlockCache 中读取数据,如果没有开启就操作对应的 Region。
  6. RegionServer 根据 Region 的名称来操作 Region。
  7. 根据是否指定列簇判断读取哪个 store,如果指定了列簇就直接读取对应 store。如果没有指定就读取所有的 store。
  8. 先读 Memstore,如果 Memstore 中可以读取到。就从 Memstore 直接读取返回,如果 Memstore 没有读取到,在读 storefile 文件。开启缓存的话,会把数据缓存到 BlockCache 中。

2.3 写流程

  1. 客户端提交一条写的命令会先请求 Zookeeper,从 Zookeeper 中获取到 hbase 的 meta 表的地址。
  2. 客户端读取 meta 表数据,根据写命令中的表名来获取该表所有的 Region 信息。
  3. 在根据写命令中的 rowkey 判断这些数据会写到那台 RegionServer 中。
  4. 如果没有关闭 WAL 预习日志功能,数据会先写到 RegionServer 的预写日志中。
  5. 根据在 meta 表中读取的 Region 名称,RegionServer 请求对应的 Region 中具体的列簇。
  6. 先把数据写到 Memstore 中。然后返回客户端结果。
  7. 后续 Region 会判断 Memstore 中的数据量大小,如果超过设置的阈值,会自动执行 flush 操作,把数据刷写到 storefile 文件中,也就是 HDFS 的 HFile 文件。

3 Hbase 的 LSM 树模型

  Log Struct Merge这种树模型的主要特点:将随机的无序的数据变成有序的数据,通过有序的算法来加快数据的读取,会牺牲一定的写的效率,因为在写入的时候需要构建有序,都用内存来实现数据的读写的存储。
  1. 将数据操作保存到预写日志,然后写入内存。
  2. 在内存中对数据进行排序。
  3. 将内存中数据写入文件,构成一个有序数据的文件。
  4. 合并:将有序的多个数据文件进行合并成一个有序的文件。
  删除和更新:在该模型的设计中,本身没有删除和更新、通过插入来模拟的,一般都是在文件中来完成。

3.1 WAL(Write Ahead Log)预写日志

  当产生了写的请求以后,写请求提交给了 Regionserver,Regionserver 会将这个写的操作记录在 WAL 中,然后才会写入 MemStore。
  在 HDFS 上,一台机器维护一份 WAL。WAL 主要用于数据恢复。
  如果整个 Regionserver 宕机,整个 Regionserver 中的数据可以由 WAL 进行恢复。
  Hbase 的数据存储在两个地方:内存和 HDFS。HDFS 的数据有副本,如果丢失会自动恢复。内存中的数据丢失的话会通过 WAL 进行恢复。

3.2 Flush刷写

  刷写,将 MemStore 中的数据写入 HDFS。Memstore 占用的是内存,内存的容量是有限,为了提高新数据的命中率,要将老的数据写入 HDFS。数据在刷写到 HDFS 之前,会做一次 rowkey 的排序,会生成一个有序的 storefile 文件。
  触发的阈值分为三个级别,分别为memstore级别、Region级别、RegionServer级别。

3.2.1 Memstore级别

  单个 Memstore 的存储达到 128M 就会触发,这个 Memstore 中的数据就会写入 HDFS。

hbase.hregion.memstore.flush.size = 128M

3.2.2 Region 级别

  一般达不到,条件必须是整个 Region 的 Memstore 的数据达到 128M*4,就会刷写,也就是说这个表的必须要有四个列簇才有可能达到。

hbase.hregion.memstore.block.multiplier = 4

3.2.3 RegionServer 级别

  整个 RegionServer 中的 Memstore 的使用率达到堆内存的 40%。一般会存在于单个 RegionServer 上的 Region 特别多。

hbase.regionserver.global.memstore.size = 0.4

  一旦达到这个值,Regionserver 会从使用内存最多的 Region 开始刷写,将数据写入 HDFS,直到整个 Regionserver 的 Memstore 的使用率低于整个堆内存的 38%,如果堆内存是 10GB,必须低于 3.8GB。

hbase.regionserver.global.memstore.size.lower.limit=0.4*0.95=0.38

  注意:在实际工作中,不能让该过程自动触发,这三个功能如果自动触发,会导致磁盘 IO 的负载过高,会影响业务。一般会关闭自动触发,根据实际情况定期的在业务比较少的时候,手动触发。手动通过 flush 命令触发。

hbase> flush 'TABLENAME'
hbase> flush 'REGIONNAME'
hbase> flush 'ENCODED_REGIONNAME'

3.3 Compact

  合并,将 Storefile 文件进行合并,构建统一的有序文件。将多个 Storefile 文件变成整体有序,方便在读取 Storefile 的时候可以快速的定位数据返回,提高查询效率。

3.3.1 minor compaction(轻量级合并)

  通过一个算法,不定期的检查和实现 minor compaction。
  假设当前整个 store 中有 10 个 storefile 文件,默认会将 3 个 storefile 文件进行合并,合并成一个 storefile 文件。

3.3.2 major compaction(重量级合并)

  将整个 store 中所有 storefile 文件进行合并,最终生成一个文件,合并的过程中会将标记为删除或者过期的数据真正的删除。会占用大量的磁盘的 IO。

hbase.hregion.majorcompaction = 604800000
hbase.hregion.majorcompaction.jitter = 0.5
# 将这两个值会通过一个公式来计算每个 store 的 compaction 触发的时间
# 如果就用7天,假设所有的 Regionserver 重启,所有的 Region 的时间都是一致的,就会导致所有的 Region 中的 store 都会在同时触发 major compatcion。

3.4 Split(分裂)

  功能:如果一个分区 Region 中的数据过多,会分裂成两个 Region。
  目的:如果一个 Region 中的数据过多,对于表来说大部分的读写请求就可能都集中在这一个 Region 中,会导致热点问题,无法实现分布式的高性能,Hbase 会判断是否需要做分裂,如果需要会由 Regionserver 将这一个分区均分为两个分区,由 Master 重新将新的两个分区分配到不同的 Regionserver 中,分配成功老的 Region 的下线。

4 Hbase 与 MapReduce 集成

4.1 读Hbase

public class ReadFromHbase extends Configured implements Tool {

    @Override
    public int run(String[] args) throws Exception {
        //构建job
        Job job = Job.getInstance(this.getConf(),"readHbase");
        job.setJarByClass(ReadFromHbase.class);
        //初始化配置Job
        //如果要读Hbase:input&map
    
        String tbname = "student:stu_info";
        Scan scan = new Scan();//可以根据需求为scan添加过滤器,将过滤以后的结果读取到MapReduce中来
        TableMapReduceUtil.initTableMapperJob(
            tbname,//指定要读取哪张hbase表
            scan,//用于使用该对象来读取Hbase的数据
            ReadHbaseMapper.class,//指定Mapper的类
            Text.class,//Mappepr输出的key的类型
            Text.class,//Mapper输出的value的类型
            job
        );
        //如果要打成jar包放到linux集群中运行的话,要调用下面这个方法,并且最后这个参数要改为false,addDependencyJars:集群中有依赖包,不需要添加
//        initTableMapperJob(table, scan, mapper, outputKeyClass, outputValueClass, job, false);

        //shuffle:本案例中不需要定义
        //reduce:本案例中不需要定义
        job.setNumReduceTasks(0);
        //output:将从Hbase中读取出来的数据写入文件
        TextOutputFormat.setOutputPath(job,new Path("datas/output/hbase/read"));
        //提交job
        return job.waitForCompletion(true) ? 0:-1;
    }

    public static void main(String[] args) throws Exception {
        Configuration conf = HBaseConfiguration.create();
        int status = ToolRunner.run(conf, new ReadFromHbase(), args);
        System.exit(status);
    }

    /**
     * 如果你需要从Hbase中读取数据,就要继承TableMapper这个类,如果不是从Hbase读取,不需要
     * extends Mapper<ImmutableBytesWritable, Result, KEYOUT, VALUEOUT>
     *     输入返回的keyvalue由TableInputFormat决定:将Hbase中的每条rowkey的数据变成一个keyvalue,key就是rowkey,value是这个rowkeky的数据
     *     ImmutableBytesWritable:hbase中的一个rowkey
     *     Result:一个Rowkey对应的所有的数据
     *     对Hbase中的每个rowkey会调用一次map方法
     */
    public static class ReadHbaseMapper extends TableMapper<Text, Text>{
        private Text outputKey = new Text();
        private Text outputValue = new Text();

        @Override
        protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException {
            //当前传递进来的这条rowkey的数据就在value
            //获取rowkey
            String rowkey = Bytes.toString(key.get());
            //获取name这一列的值
            String name = Bytes.toString(value.getValue(Bytes.toBytes("basic"), Bytes.toBytes("name")));
            //将rowkey作为输出的key,将每条数据中的名字这一列的 值作为value输出
            this.outputKey.set(rowkey);
            this.outputValue.set(name);
            context.write(this.outputKey,this.outputValue);
        }
    }
}

4.2 写Hbase

示例代码

public class WriteToHbase extends Configured implements Tool {

    @Override
    public int run(String[] args) throws Exception {
        //构建job
        Job job = Job.getInstance(this.getConf(),"writeHbase");
        job.setJarByClass(WriteToHbase.class);
        //初始化配置Job
        //input
        TextInputFormat.setInputPaths(job,new Path("datas/hbase/hbaseinput.txt"));
        //map
        job.setMapperClass(WriteHbaseMap.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);
        //shuffle:本案例中不需要定义
        //要将数据写入Hbase:reduce%output:
  
        String tbname = "student:mrwrite";// create 'student:mrwrite','info'
        TableMapReduceUtil.initTableReducerJob(
            tbname,//指定将数据写入哪个Hbase 表
            WriteHbaseReduce.class,//指定reduce的类,为什么不用指定reduce输出的keyvalue,因为key是什么不重要,而value定死了
            job
        );
        //提交job
        return job.waitForCompletion(true) ? 0:-1;
    }

    public static void main(String[] args) throws Exception {
        Configuration conf = HBaseConfiguration.create();
        //如果不放配置文件,要在conf中定义zookeeper的地址
//        conf.set("hbase.zookeeper.quorum","node-01:2181,node-02:2181,node-03:2181");
        int status = ToolRunner.run(conf, new WriteToHbase(), args);
        System.exit(status);
    }

    public static class WriteHbaseMap extends Mapper<LongWritable,Text,Text,Text>{
        private Text outputKey  = new Text();
        private Text outputValue = new Text();
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] split = value.toString().split("\t");
            //将文件的第一列作为key
            this.outputKey.set(split[0]);
            //将其他列作为value
            this.outputValue.set(split[1]+"\t"+split[2]+"\t"+split[3]);
            context.write(outputKey,outputValue);
        }
    }

    /**
     * 如果要将数据写入Hbase,必须继承自tableReducer
     * extends Reducer<KEYIN, VALUEIN, KEYOUT, Mutation>
     *     写入Hbase的对象是ValueOut,KeyOut是什么无所谓,一般都用put对象作为ValueOutput
     */
    public static class WriteHbaseReduce extends TableReducer<Text,Text, NullWritable>{

        private NullWritable outputKey = NullWritable.get();

        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
            //用文件的 第一列就是当前的key作为rowkey,其他的都是表的:name/age/sex
            for (Text value : values) {
                //构建put对象
                Put put = new Put(Bytes.toBytes(key.toString()));
                //取出这个rowkey对应的散列
                String[] split = value.toString().split("\t");
                put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("name"), Bytes.toBytes(split[0]));
                context.write(outputKey,put);
                put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("age"), Bytes.toBytes(split[1]));
                context.write(outputKey,put);
                put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("sex"), Bytes.toBytes(split[2]));
                context.write(outputKey,put);
            }
        }
    }

}

5 Hbase 的 BulkLoad

  Hbase 在大数据量导入的时候提供了一种 bulkload 导入方式,这种导入方式不走内存和 WAL,导入不影响正常的业务交易。Hbase 提供了自带的 jar 包可以实现 bulkload 导入,也可以自己开发。下面提供 hbase 自带的 bulkload 方式。

  1. 需要两步,先把数据转换成 Hfile 文件,加载到 HDFS 上。

yarn jar \
/export/servers/hbase-1.2.0-cdh5.14.0/lib/hbase-server-1.2.0-cdh5.14.0.jar \
importtsv -Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:age,info:sex  \
-Dimporttsv.bulk.output=/bulkload/output2 \
mrhbase \
/bulkload/input
#-Dimporttsv.bulk.output=/bulkload/output2:指定生成的HFILE文件所在的hdfs的位置

  2. 把 HFile 文件导入到指定的表中。

yarn jar \
/export/servers/hbase-1.2.0-cdh5.14.0/lib/hbase-server-1.2.0-cdh5.14.0.jar \
completebulkload \
/bulkload/output2 \
mrhbase

6 Hbase 协处理器 Coprocessor

  Hbase 的协处理器分为两类,一类是 Observer 观察者协处理器,这种协处理器主要是监听某个动作,例如监听 Master 操作、监听 RegionServer 操作、监听 Region 等等。另一个是 endpoint 终端者协处理器。这种协处理器主要用作分布式聚合统计。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值