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之前的操作。
本站文章为和通数据库网友分享或者投稿,欢迎任何形式的转载,但请务必注明出处.
同时文章内容如有侵犯了您的权益,请联系QQ:970679559,我们会在尽快处理。