欢迎投稿

今日深度:

HBase,sql是单节点式查询

HBase,sql是单节点式查询


HBase

一、HBase介绍

1.1 HBase - - 分布式数据库

	HBase是Hadoop项目的子项目,其用于存储海量数据的结构化、半结构化和非结构化数据。
	sql是单节点式查询数据,有着丰富的索引能力,但对于TB级别的数据却难以应对,而HBase正是对大型表数据的存储和处理。
HBase解决了两大问题:
	1、海量数据的存储:单表千万级别的数据,甚至百亿级别。
	2、高并发问题
处理方式:
	1、海量数据:底层使用hdfs集群存储
	2、高并发:多台机器进行处理请求

1.2 HBase结构原理 - - 简述

1、主从架构:Hmaster 和Hregionserver    
	Hmaster管理regionserver,负责regionserver的负载均衡等;regionserver管理元数据信息等。
2、底层用hdfs存储数据:regionserver将数据存到hdfs集群中
3、整个集群有zooKeeper管理。解决了主节点宕机的问题
-----基于以上,所以使用HBase必须ZooKeeper和Hdfs开启

1.3 数据存储原理

1.4 HBase的安装启动

前提环境,需开启hdfs和zookeeper
上传 解压 更改配置 分发
设置一键启动
启动命令:
	start-hbase.sh
	stop-hbase.sh
进入客户端命令:
	hbase shell
页面端口:16010

1.5 hbase的应用场景

hbase 应用于:1、大表-千万级别
		     2、高并发
	不适用于:1、多维度查询数据
			 2、做多方面的分析 比如不能做报表
	一般用于:做标签数据/用户画像

二、shell命令

通过 hbase shell 命令 进到客户端

1、通用命令

status   查看当前hbase状态
version  查看当前hbase版本
help     查看所有命令--help 'commend' 查看命令的使用方式
whoami   查看用户信息
create_namespace 'myspace'  -----创建名称空间
create 'myspace:table1','cf1' ---指定名称空间创建表

2、DDL 表相关命令

create 建表
	  create 'tb_1','cf1','cf2'   
	  create  '表名','列族','列族'...
list     ------------------------查看当前nameSpase下的所有表列表
desc 'tb_1'    ------------------查看表的结构
scan 'tb_1'    ------------------查看表信息
put --向表中插入数据
      put 'tb_1','1','cf:id','001'
      put '表名','行数','列族:列名','值''表名','行数','列族:列名',   这个称为key 当key相同是,再插入数据便会覆盖
alter 修改命令
	alter 'tb_1' ,NAME=>'cf10'  ----增加列族
	alter  'tb_1',NAME=>'cf10',VERSION=>'3'  ---修改列族的属性,没有修改列族名字的
	alter  'tb_1','delete'=>'cf10'    -----删除列族
	
	alter  'tb_1',MAX_FILESIZE=>'1233333'  -------设置表的最大容量
	alter  'tb_1',METHOD=>'table-att-unset',NAME=>'MAX_FILESIZE'--对表的信息进行设置
	
	alter_status  ---查看信息同步状态信息
	
clone_table_schema 'old_table','new_table' ----根据旧表建新表,结构相同

create_namespace 'myspace'  -----创建名称空间
create 'myspace:table1','cf1' ---指定名称空间创建表
create 'table_regions','cf',SPLITS=>['k','p','z']  --创建多个region的表

disable 禁用表--禁用后不能执行表的操作,但可以对列族进行操作
	disable 'table1'
disable_all 禁用所有表
	disable_all 'ta.*' 禁用所有以ta开头的表
enable  开启禁用
	enable 'table1'
enable_all
	enable 'ta.*'
is_disabled 判断是否被禁用
is_enabled  判断是否不被禁用
drop	删除表,将表彻底删除
	drop 'table1'
drop_all
	drop 'ta.*'
delete  只能删除单元格
	delete 'table1','1','cf1:age'
	删除的是当前版本的值,表中仍保留之前版本的值
deleteall  删除整行
	deleteall 'table1','1' ---删除行
	deleteall 'table1','1','cf1:age'
alter 'table1' 'delete'=>'cf1' 删除列族
exists
	exosts 'table1'
get_table	给表名起别名
	t=get_table 'table1'
	t.scan
 list 获得表的列表
 list_regions 获得region
 	list_regions 'table1'
 locate_region 获得指定行的region
 	locate_region 'table1''3'
 show_filters  获得所有的过滤命令

DML:
append  追加--在单元格之后继续追加内容
	append 'table1','1','cf1:age','0'
	 1   column=cf1:age, timestamp=1625196340807, value=180---之前
	 1  column=cf1:age, timestamp=1625210903924, value=1800---之后
count   计算表有多少行
	count 'table1'
incr  设置自增
	incr 'table1','5','cf1:age',18
get_counter 获得自增的当前值
	get_counter  'table1','5','cf1:age'

get
	get 'table1','1' ---获取一行数据
	get 'table1','1','cf1:age','cf2:job'---获取多个单元格的值
get_splits   获得切点
	get_splits 'table1'
 put  插入数据
 	一次写入一个单元格---会造成大量的交互,效率低
 	解决:缓存一批写一次
 		 将文件从hdfs中 通过MR程序读取出来,然后输出到hbase中,以hfile的形式
 scan	查看表的内容,全表检索,数据量大 ,一般不用
 	
 truncate	删除表,然后键一个与所删除表结构相同的表
 
Group name: 
snapshots 快照 给表拍摄快照 可以再回到这个快照所对应的状态
clone_snapshot  克隆一个快照 然后创建一个新表
delete_all_snapshot
delete_snapshot
delete_table_snapshots
list_snapshots
list_table_snapshots
restore_snapshot恢复快照
snapshot
手动拆分region----移动region---手动合并region
手动拆分:		split 'table','rkoo5'
移动region:	  move  'regionName','regionServerName'
平衡region分配:balance
手动合并region:	merge_region 'regionName','regionName'.....,true

三、数据(表)实际的存储位置

通过访问hdfs网页可以看到数据的真实存储位置:
Hdfs: /hbase/data/名称空间/表/region/列族/数据内容

四、java端操作HBase

创建maven项目 ,添加上各种hdfs、zookeeper和hbase等的依赖

每创建一个maven项目,之前的pmo配置文件都要重新写

4.1 获得连接对象

public class Demo1 {
    //获得连接对象
    public static void main(String[] args) throws Exception {
        Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum","linux01:2181,linux02:2181,linux03:2181");
        Connection conn = ConnectionFactory.createConnection(conf);

        Admin admin = conn.getAdmin();//除了表的一些操作,其他操作基本都可以通过admin来实现

        TableName tableName=TableName.valueOf("table1");
        Table table = conn.getTable(tableName);//表的一些操作都可以在这里进行执行 追加 添加 删除等操作

    }
}

4.2 操作

 public static void main(String[] args) throws Exception {
        Connection conn = HbaseUtils.getConnection();
        Admin admin = conn.getAdmin();
        //获取所有的表名
        TableName[] tableNames = admin.listTableNames();
        for (TableName tableName : tableNames) {
            byte[] name = tableName.getName();
            System.out.println(new String(name));
        }
        System.out.println(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
        //获得所有的名称空间
        NamespaceDescriptor[] spaces = admin.listNamespaceDescriptors();
        for (NamespaceDescriptor space : spaces) {
            String name = space.getName();
            System.out.println(name);
        }

        conn.close();

    }
//c创建表--创建多个region的表
    public static void main(String[] args) throws Exception {
        Connection conn = HbaseUtils.getConnection();
        Admin admin = conn.getAdmin();
        //列族构造器
        ColumnFamilyDescriptorBuilder cbuilder = ColumnFamilyDescriptorBuilder.newBuilder("cf1".getBytes());
                //cbuilder.setTimeToLive(100); 设置列族存活时间--时间一到,列族保留,但列族里的行数据全被删除
                //cbuilder.setMaxVersions(5);  设置列族的版本
        //用列族构造器构建列族描述器
        ColumnFamilyDescriptor column = cbuilder.build();

        //获得表的构造器
        TableDescriptorBuilder tableBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf("table_new_regions"));
        //将列族描述器加到表的构建器中
        tableBuilder.setColumnFamily(column);
        //获得描述器
        TableDescriptor descriptor = tableBuilder.build();

        //创建表--预设region的分隔点
        byte[][] regions=new byte[][]{"d".getBytes(),"k".getBytes(),"u".getBytes()};
        admin.createTable(descriptor,regions);

        conn.close();
    }

4.3 put数据-三种方式

一:put多行数据

public static void main(String[] args) throws Exception {
        Connection conn = HbaseUtils.getConnection();
        Table table = conn.getTable(TableName.valueOf("new_1"));

        //分别是三行数据
        Put put1 = new Put("rk001".getBytes());
        Put put2 = new Put("rk002".getBytes());
        Put put3 = new Put("rk003".getBytes());

        //每一行添加的数据都一样---非字符串类型,要使用Bytes工具进行包装
        put1.addColumn("cf1".getBytes(),"name".getBytes(), "zss".getBytes());
        put1.addColumn("cf2".getBytes(),"sal".getBytes(), Bytes.toBytes(10000.2000));
        put2.addColumn("cf1".getBytes(),"name".getBytes(), "zss".getBytes());
        put2.addColumn("cf2".getBytes(),"sal".getBytes(), Bytes.toBytes(10000.2000));
        put3.addColumn("cf1".getBytes(),"name".getBytes(), "zss".getBytes());
        put3.addColumn("cf2".getBytes(),"sal".getBytes(), Bytes.toBytes(10000.2000));

        List<Put> list=new ArrayList<>();
        list.add(put1);
        list.add(put2);
        list.add(put3);

        table.put(list);

        conn.close();
    }

二:缓存式插入数据

public static void main(String[] args) throws Exception {
        Connection conn = HbaseUtils.getConnection();

        BufferedMutator bm = conn.getBufferedMutator(TableName.valueOf("new_1"));
        bm.setWriteBufferPeriodicFlush(2000);//设置2秒刷新一次
        /*
        * 1、可以设置数据刷新时间
        * 2、手动flush
        * 3、数据量达到一定量的时候也会自动刷新
        * 4、当前节点整体的内存达到阀值也会刷新
        *
        * */

        //分别是三行数据
        Put put1 = new Put("rk003".getBytes());
        Put put2 = new Put("rk004".getBytes());
        Put put3 = new Put("rk005".getBytes());

        //每一行添加的数据都一样---非字符串类型,要使用Bytes工具进行包装
        put1.addColumn("cf1".getBytes(),"name".getBytes(), "lss".getBytes());
        put1.addColumn("cf2".getBytes(),"sal".getBytes(), Bytes.toBytes(10000.2000));
        put2.addColumn("cf1".getBytes(),"name".getBytes(), "lss".getBytes());
        put2.addColumn("cf2".getBytes(),"sal".getBytes(), Bytes.toBytes(10000.2000));
        put3.addColumn("cf1".getBytes(),"name".getBytes(), "lss".getBytes());
        put3.addColumn("cf2".getBytes(),"sal".getBytes(), Bytes.toBytes(10000.2000));

        List<Put> list=new ArrayList<>();
        list.add(put1);
        list.add(put2);
        list.add(put3);

        bm.mutate(list);

        //bm.flush();
        bm.close();
        conn.close();

    }

三:导入数据

public static void main(String[] args) {
        /*
        * 此方法是比较好的方法,前提是在hdfs中有着静态的数据
        * 然后将静态数据在hdfs中转为hfile的格式
        * 最后将hfile文件的内容直接导入hbase表中
        *
        * put1:弊端是每次put都要进行一次写数据的流程,比较低效
        * put2:缓存一批,再put,虽提升了效率,但当表的数据很大的时候,代码无疑会非常多,				并不适合
        * put3:使用直接将数据导入到表中的方法,很是高效,但前提是在hdfs中有着静态数据
        * */
        
        
        
        /*
        * 使用命令将数据插入到表中
        * rz将数据上传到linux中
        * --linux将数据上传到hdfs中(hdfs dfs -put)
        * ---通过命令,将文件转成hfile文件保存在hdfs中
        * --再执行命令将hfile文件插入到hbase指定的·表·中
        *3 操作的数据在HDFS上
*	hbase  org.apache.hadoop.hbase.mapreduce.ImportTsv -Dimporttsv.separator=, -Dimporttsv.columns='HBASE_ROW_KEY,cf:name,cf:id,cf:gender,cf:city' -Dimporttsv.bulk.output=/student/output tb_student /student/student.txt
        *4 将生成的hfile文件导入到hbase 的指定表中
*   hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles   /student/output   tb_student
        * */
    }

4.4 读取数据–scan

 获取每行数据--获取单元格--从单元格获取行键、列族、属性、值
 
 public static void main(String[] args) throws Exception {
        Connection conn = HbaseUtils.getConnection();
        Table table = conn.getTable(TableName.valueOf("tb_student"));
        Scan scan = new Scan();

        //得到所有的行数据
        ResultScanner results = table.getScanner(scan);
            //遍历每一行数据
        for (Result result : results) {
            while(result.advance()){
                //获得每个单元格
                Cell cell = result.current();
                    //从单元格中再获取每个单元格锁对应的行键、列族、属性、值
                byte[] cloneRow = CellUtil.cloneRow(cell);
                byte[] cloneFamily = CellUtil.cloneFamily(cell);
                byte[] cloneQualifier = CellUtil.cloneQualifier(cell);
                byte[] cloneValue = CellUtil.cloneValue(cell);
                System.out.println(
                        new String(cloneRow)+"--"+
                        new String(cloneFamily)+"--"+
                        new String(cloneQualifier)+"--"+
                        new String(cloneValue)
                );
            }
        }
    }

4.5 MR式插入数据

数据--MR--导入到hbase的表中   这个就很流批
案例:将movie数据 导入到hbase的表中
 //MR阶段
    static class MR_Hbase_Mapper extends Mapper<LongWritable, Text,Text,MovieWritable>{
    //使用Gson来解析JSON数据
        Gson gson=new Gson();
       Text k=new Text();
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            try{
                String line = value.toString();
                //使用gson进行解析json数据,将line转为movieBean
                MovieWritable movieWritable = gson.fromJson(line, MovieWritable.class);
                //设置key
                String mid = movieWritable.getMovie();
                String timeStamp = movieWritable.getTimeStamp();
                //给key定长
                String padMid = StringUtils.leftPad(mid, 5, "0");
                String padTime = StringUtils.leftPad(timeStamp, 10, "0");
                //得到设定好的行键
                String rk=padMid+"-"+padTime;
                k.set(rk);
                //此时输出的key是设置好的行键,value是movieBean
                context.write(k,movieWritable);
                
            }catch(Exception e){
			//一定要tey--catch 且要抛最大的异常,因为数据会有错误
			//防止程序中断
            }
        }
    }

    //Reducer阶段 继承的类有所区别 继承TableReducer
    static class MR_Hbase_Reducer extends TableReducer<Text,MovieWritable, ImmutableBytesWritable>{
        @Override
        protected void reduce(Text key, Iterable<MovieWritable> values, Context context) throws IOException, InterruptedException {
            String rk = key.toString();
            MovieWritable movieBean = values.iterator().next();
            //put 给行键 然后把movieBean中的数据都放到put中去
            Put put=new Put(rk.getBytes());
            put.addColumn("cf".getBytes(),"movie".getBytes(), Bytes.toBytes(movieBean.getMovie()));
            put.addColumn("cf".getBytes(),"rate".getBytes(), Bytes.toBytes(movieBean.getRate()));
            put.addColumn("cf".getBytes(),"timeStamp".getBytes(), Bytes.toBytes(movieBean.getTimeStamp()));
            put.addColumn("cf".getBytes(),"uid".getBytes(), Bytes.toBytes(movieBean.getUid()));

            //输出 key为null ;value为put
            context.write(null,put);
        }
    }

    public static void main(String[] args) throws Exception {
        //得到的conf是hbase和hadoop相联系的一个conf
        Configuration conf = HBaseConfiguration.create();
       conf.set("hbase.zookeeper.quorum","linux01:2181,linux02:2181,linux03:2181");
        Job job = Job.getInstance(conf, "Hbase_Movie");

        //设置mapper端
        job.setMapperClass(MR_Hbase_Mapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(MovieWritable.class);

        //数据的读取位置
        FileInputFormat.setInputPaths(job,new Path("E:\\work2\\mrdata\\movie\\input"));
        //设置输出到hbase中
   TableMapReduceUtil.initTableReducerJob("tb_MR_movie",MR_Hbase_Reducer.class,job);
        boolean b = job.waitForCompletion(true);
        System.out.println(b);
    }

五、HBase原理

5.1 读数据原理

1、客户端想zookeeper请求读取数据
2、zookeeper返回元数据所在的机器RegionServer
3、客户端请求RegionServer获取元数据
4、解析元数据,定位到对应的region
5、每个region都在hdfs中对应一个文件夹,从该文件夹中获取数据

5.1.2 读取数据-布隆过滤器



1、客户端想zookeeper请求读取数据
2、zookeeper返回元数据所在的机器RegionServer
3、客户端请求RegionServer获取元数据
4、解析元数据,定位到对应的region
----
5、三种读取数据的方式:
	一:若内存中有,则直接从内存中获取
	二:从region的缓存中读取,当从hdfs中获取后,会在region中再缓存一份,供下次更快的读取
	三:从hdfs中获取
总结:若内存中没有,则会同时从region的缓存中和hdfs中获取,然后比较,拿到最新更新的数据
当写数据的时候,每次内存的刷新都会在hdfs中的region中生成hfile文件,不断的刷新,便会生成很多的hfile文件,那么读取数据的时候,怎么高效的定位到数据所在的hfile文件呢?
1、布隆过滤器:
		在每个hfile文件中都有一小块区域,用于做hfile文件的标签,存放的是字节数组,key是		 0-..而value初始全是0。
		在put数据的时候,行键取哈希值,对应的map数组中这个hash值的位置的值就由0变为1.
		在get数据的时候,根据行键取hash值,然后扫描hfile文件中有这个hash值处为1的hfile		文件。
		当数据不在这个hfile文件中的时候,便一定不在这个文件中;也有可能出现哈希碰撞,所以		当取到多个hfile文件的时候,再扫描内容,拿到确定的数据。
		在每个hfile文件内存都会流出一小块的存储空间【字节数组】,来记录key是否存在存储的标记信息,存在标记为1,不存在标记为0
		
2、数据块索引
		就算找到了这个hfile文件,这个文件中也有很多行,怎么取到想要的那一行呢?
		datdablock中存的是数据kv,在hfile中还有一块区域是数据块索引,每个索引都管着几行数据,这样通过数据块索引可以快速的定位到想要的那一行数据。
3、概念
	稀疏索引:一个索引管理者好几个
	稠密索引:一 一对应,一个索引对应一个
	二级索引,a->A->001 通过a找到A,然后通过A找到对应的001

5.2 写数据原理

1、客户端请求写入数据,
2、zookeeper返回元数据所在的机器RegionServer
3、客户端找到Regionserver获得元数据
4、解析元数据,定位到写的位置region
5、一方面:在region中写到内存中,在内存中进行排序,然后刷写到hdfs中的·对应文件中,最终落入磁盘
   另一方面:生成写数据的日志,并将日志传到hdfs中,这样即使传输中断,未来得及刷新,有了日		   志也不会造成数据丢失。
*在RegionServer中有名称空间-->表-->region-->cf-->内容
*每个region中会有多个列族,每个列族都对应一个cf-store;每个cf-store中有一个memoryStore
 写数据的时候·定位到region·定位到cf·定位到cf-store·定位到内存store(memoryStore)·写到   内存中
*当达到刷新的机制,内存开始溢出·刷新·每刷新一次就会生成一个StoreFile对象
*然后就会在对应的目录生成hfile文件,将内容写到hfile文件中
刷新机制:
	1、当数据大小达到阀值(128M)
	2、手动刷新
	3、达到刷新的预设时间
	4、当内存中所有的memoryStore整体达到一定值的时候,为了防止内存溢出,也会刷新
 
hdfs中不支持随机写和修改数据:
	*所有的更新和删除都是写操作
	*在每次的操作中都添加一个标签,比如put标签,delete标签
	*在取值的时候,会比较标签来执行最新的操作
		比如做更新操作,在取值的时候,就回比较两个标签,发现是更新操作,就回返回最新的时		间戳所对应的数据。若最新的操作有delete标签,则认为是删除操作,则取不出值。
		后续会将这些文件进行合并,按照最新的时间戳进行合并,合并的时候才是数据真正进行更		新和删除的时候。
比如put一次 又更新一次 又删除一次,则会分别生成三个文件,后续会将这三个文件进行合并,发现最新的时间戳有删除标记,于是便将这三个冗余文件都删除掉。
	

5.3 合并


1、hfile的合并:小合并
	所有的更新和删除数据都是写操作,每次操作都会有更新和删除的标记,应该保留最新的更新,而之前的旧数据就会进行合并、删除---指定列族下的文件的合并。(会保留设定的历史版本数)
2、region的合并:大合并
	当执行大量的删除操作时,region所管理的行范围会大量减小,当region所管理的行范围很少的时候,则没必要再维持多个region,于是会进行region的合并,生成一个新的region,将数据复制过来。之后再将旧的region删除。
	region级别的合并,要进行大量的IO,网络通信,占用的资源比较多。region合并最好在业务的低峰期。手动合并。

---手动更换region1所在的regionServer: 
move   '04980472336896c55bd96bae4271a909' , 'linux02,16020,1625467987938'
move  'regionName','regionServerName'
---手动合并:
merge_region '28eea66d36680849e4abdbfb36d25f7a','04980472336896c55bd96bae4271a909'
merger_region 'regionName','regionName','regionName'...,true

5.4 region拆分

拆分region
1、预拆分
	shell客户端:create 'table','cf1',SPLITS=>['d','h','p']
	java客户端:createTable(,byte二维数组)
2、自动拆分
	当插入的数据越来越多,region所管理的行数据越来越多,于是便会进行自动拆分
	拆分的大小:256M--2G--6.75G--10G..10G..10G....
3、拆分策略
	默认大小
	Rk前缀
	分隔符
	强制拆分:split 'table','rkoo9'
	.......

5.5 rowKey的设计

rowKey有很多重要的作用,所以rowKey的设计极为重要:
两个原则:
	能够解决热点问题,能够满足查询维度(没法满足多维度查询)
*rowKey要具有唯一性
*要定长:方便排序
*长度不宜过长

5.6 二级索引的设计

针对movie这个数据,想要两个查询维度:movieID和Uid
	设置二级索引
	mid_time 对应着cell
	uid_time 对应着mid_time
	根据uid查询的时候,先根据uid找到movieID,然后根据movieID再找到cell数据

具体实现:

实现二级索引:可以是建立两张表,一张表rowKey为mid_time;另一张表为uid_time。但这样无疑浪费了很多的资源,所以可以设置二级索引,也是建立两张表,
不过第二张表的内容为,rowKey--uid_time   value---mid_time
这样便极大的节省了资源。

	在put数据之前进行拦截,获取单元格的属性,然后如果属性是uid则将其取出作为第二张表的rowKey2,然后再取出mid和time作为roeKey2的值。第一张表正常put数据,rowkey为mid_time值为电影的各种属性。
	做拦截功能的类时prePut,即在put之前的操作。

www.htsjk.Com true http://www.htsjk.com/hbase/45641.html NewsArticle HBase,sql是单节点式查询 HBase 一、HBase介绍 1.1 HBase - - 分布式数据库 HBase是Hadoop项目的子项目其用于存储海量数据的结构化、半结构化和非结构化数据。sql是单节点式查询数据有着丰富...
评论暂时关闭