赞
踩
非关系型数据数据库大量应用于数据缓存、消息队列等场景中,在大数据领域其非结构化特征与内存缓存数据的效果,适用于实时的数据分析,这里简单记录redis、hbase、kafka相关概念和使用
HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。HBase是Apache的Hadoop项目的子项目。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。
特点:
分布式
基于hadoop存储
基于列储存
分布式的实时realtime
随机的Random
大数据持久性存储(读写数据)
Hbase数据存储对象:namespace和table namespace相当于数据库,table为数据库表,但其中的列不固定
默认namespace为default。访问数据库要加上namespace名称如:
hbase:table1
hbase基于行存储,其table有特殊的结构:
行键/主键RowKey:
列族ColumnFamily:
列Qualifier/Column:
info
,如果info列族中有两列:name,age
,访问:info:name,info:age
多版本VERSIONS:HBase可以允许某一行的某一列存储多个版本的值的,默认每一列都只能存储1个版本。如果某个列有多个版本值,默认情况下查询,根据时间戳返回最新版本的值。
MySQL数据库与HBase数据库对比:
概念 | MySQL | Hbase |
---|---|---|
数据库 | DataBase | NameSpace |
数据表 | Table | Table【分布式的】 |
数据分区 | - | Region,表中数据一部分 |
数据行 | 数据【主键+其他列】 | Rowkey+数据【其他列】 |
列族 | - | ColumnFamily |
数据列 | 普通列与对应的值 | 列【timestamp】与对应的值【支持多版本】 |
HMaster:主节点,管理节点
负责元数据的管理,比如namespace、table、列簇,region等
负责所有从节点的管理
HRegionServer:从节点,存储节点
Zookeeper:分布式协作框架
HDFS:分布式文件系统
Web UI:Apache HBase 1.x之前是60010,1.x开始更改为16010,CDH版本:一直使用60010
Hbase shell
#表示在ns1的namespace中创建一张表t1,这张表有一个列族叫f1,这个列族中的所有列可以存储5个版本的值
create 'ns1:t1', {NAME => 'f1', VERSIONS => 5}
#在default的namespace中创建一张表t1,这张表有三个列族,f1,f2,f3,每个列族的属性都是默认的
create 't1', 'f1', 'f2', 'f3'
# 创建表,可以更改列族的属性
create 't1', {NAME => 'cf1'}, {NAME => 'cf2', VERSIONS => 3}
查看某个表的信息:desc
判断存在某个表是否存在:exists
表的禁用和启用:disable / enable
功能
删除某个表:drop
插入数据:每次对表中某一列族中某一列插入,指定rowkey。put时如果不存在,就插入,如果存在就更新。
HBase表数据:按照Rowkey构建字典有序
排序规则:
底层存储也是KV结构:每一列就是一条KV数据
没有更新和删除:通过插入来代替的,做了标记不再显示
# 表名+rowkey+列族+列+值 put 'ns:tbname', 'rowkey', 'cf:col', 'value' put 'people', '1001', 'info:name', 'laoda' put 'people', '1001', 'info:age', '25' put 'people', '1001', 'info:gender', 'male' put 'people', '1001', 'info:address', 'shanghai' put 'people', '1002', 'info:name', 'laoer' put 'people', '1002', 'info:address', 'beijing' put 'people', '1003', 'info:name', 'laosan' put 'people', '1003', 'info:age', '20' -- 扫描表数据 scan "people"
读取数据:get
功能:读取某个Rowkey的数据
优点:Get是HBase中查询数据最快的方式,并不是最常用的方式
缺点:get命令最多只能返回一个rowkey的数据,根据Rowkey进行检索数据
get 表名 rowkey [列族,列]
get 'ns:tbname','rowkey'
get 'ns:tbname','rowkey',[cf]
get 'ns:tbname','rowkey',[cf:col]
# 获取RowKey所有列数据
get 'people','1001'
# 获取指定列簇中所有列数据
get 'people','1001', 'info'
# 获取指定列名称数据
get 'people','1001', 'info:name'
#读取整张表的所有数据,一般不用 scan 'tbname' #根据条件查询:工作中主要使用的场景, 用到最多 scan 'tbname', {Filter} scan 'people' scan 'people', {LIMIT => 2} # 前缀过滤器 scan 'people', {ROWPREFIXFILTER => '1001'} scan 'people', {ROWPREFIXFILTER => '100'} # STARTROW:从某个rowkey开始,包含,闭区间 # STOPROW:到某个rowkey结束,不包含,开区间 scan 'people', {STARTROW=>'1001'} scan 'people', {STARTROW=>'1001', STOPROW=>'1003'}
count 'people'
#删除某列的数据 delete tbname, rowkey, cf:col #删除某个rowkey数据 deleteall tbname, rowkey #清空所有数据:生产环境不建议使用,建议删表重建 truncate tbname # 删除某一列数据 delete 'people', '1001', 'info:address' # 删除某一行row数据 deleteall 'people','1002' # 清空表数据 truncate 'people'
public class HBaseDdlTest { // 定义连接Connection变量 private Connection conn = null ; @Before public void open() throws Exception { // 1-1. 设置连接属性 Configuration conf = HBaseConfiguration.create(); // 设置HBase依赖ZK集群地址 conf.set("hbase.zookeeper.quorum", "node1,node2,node3"); // 1-2. 使用工厂创建连接 conn = ConnectionFactory.createConnection(conf); } // todo: 创建命名空间 @Test public void createNamespace() throws Exception{ // a. 获取Admin对象 HBaseAdmin admin = (HBaseAdmin) conn.getAdmin(); // b. 创建命名空间描述符对象,设置名称, todo: 使用建造者模型创建对象 NamespaceDescriptor descriptor = NamespaceDescriptor.create("hbase").build(); // c. 创建NS admin.createNamespace(descriptor); // d. 关闭资源 admin.close(); } // todo: 创建表 @Test public void createTable() throws Exception{ // a. 获取Admin对象 HBaseAdmin admin = (HBaseAdmin) conn.getAdmin(); // b. 判断表是否存在,如果存在,先删除 TableName tableName = TableName.valueOf("hbase:students"); if(admin.tableExists(tableName)){ // 先禁用表 admin.disableTable(tableName); // 删除表 admin.deleteTable(tableName); } // c. 列簇信息 ColumnFamilyDescriptor basicFamily = ColumnFamilyDescriptorBuilder .newBuilder(Bytes.toBytes("basic")) .build() ; ColumnFamilyDescriptor otherFamily = ColumnFamilyDescriptorBuilder .newBuilder(Bytes.toBytes("other")) .build() ; // d. 表的描述符 TableDescriptor desc = TableDescriptorBuilder .newBuilder(tableName) .setColumnFamily(basicFamily) .setColumnFamily(otherFamily) .build(); // e. 创建表 admin.createTable(desc); // f. 关闭资源 admin.close(); } @After public void close() throws Exception{ if(null != conn) conn.close(); } }
public class HBaseDmlTest { // 定义连接Connection变量 private Connection conn = null ; @Before public void open() throws Exception { // 1-1. 设置连接属性 Configuration conf = HBaseConfiguration.create(); // 设置HBase依赖ZK集群地址 conf.set("hbase.zookeeper.quorum", "node1,node2,node3"); // 1-2. 使用工厂创建连接 conn = ConnectionFactory.createConnection(conf); } // 创建HBase Table表的对象,方便对表进行DML操作 public Table getHTable() throws Exception{ // TableName对象 TableName tableName = TableName.valueOf("hbase:students"); // 获取Table实例 Table table = conn.getTable(tableName); // 返回实现 return table ; } // 测试Table对象获取 @Test public void testTable() throws Exception{ System.out.println(getHTable()); } // 插入数据put @Test public void testPut() throws Exception{ // a. 获取Table对象 Table table = getHTable(); // todo: b. 构建Put对象,封装每条数据, 必须指定RowKey,然后再添加列值, 一行数据就是一个Put对象 Put put = new Put(Bytes.toBytes("20220501_001")) ; // 添加列 put.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("name"), Bytes.toBytes("laoda")) ; put.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("age"), Bytes.toBytes("18")) ; put.addColumn(Bytes.toBytes("other"), Bytes.toBytes("phone"), Bytes.toBytes("110")) ; put.addColumn(Bytes.toBytes("other"), Bytes.toBytes("address"), Bytes.toBytes("beijing")) ; // c. 插入数据 table.put(put); // d. 关闭连接 table.close(); } // 批量插入数据Put @Test public void testBatchPut() throws Exception { // a. 获取Table对象 Table table = getHTable(); // b. TODO: 构建Put对象,表示1个RowKey数据,指定RowKey List<Put> listPut = new ArrayList<>(); // 第1条数据 Put put1 = new Put(Bytes.toBytes("20220501_002")) ; put1.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("name"), Bytes.toBytes("laoer")); put1.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("age"), Bytes.toBytes("16")); put1.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("gender"), Bytes.toBytes("male")); put1.addColumn(Bytes.toBytes("other"), Bytes.toBytes("phone"), Bytes.toBytes("120")); put1.addColumn(Bytes.toBytes("other"), Bytes.toBytes("address"), Bytes.toBytes("shanghai")); listPut.add(put1); // 第2条数据 Put put2 = new Put(Bytes.toBytes("20220501_003")) ; put2.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("name"), Bytes.toBytes("laosan")); put2.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("age"), Bytes.toBytes("16")); put2.addColumn(Bytes.toBytes("other"), Bytes.toBytes("address"), Bytes.toBytes("hangzhou")); listPut.add(put2); // 第3条数据 Put put3 = new Put(Bytes.toBytes("20220501_004")) ; put3.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("name"), Bytes.toBytes("laosi")); put3.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("age"), Bytes.toBytes("24")); put3.addColumn(Bytes.toBytes("other"), Bytes.toBytes("job"), Bytes.toBytes("programmer")); put3.addColumn(Bytes.toBytes("other"), Bytes.toBytes("address"), Bytes.toBytes("shanghai")); listPut.add(put3); // 第4条数据 Put put4 = new Put(Bytes.toBytes("20220501_005")) ; put4.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("name"), Bytes.toBytes("laoer")); put4.addColumn(Bytes.toBytes("other"), Bytes.toBytes("job"), Bytes.toBytes("doctor")); listPut.add(put4); // c. 插入数据 table.put(listPut); // d. 关闭连接 table.close(); } // 依据RowKey查询数据 @Test public void testGet() throws Exception{ // a. 获取Table对象 Table table = getHTable(); // b. 创建Get对象,设置RowKey,表示获取哪行数据 Get get = new Get(Bytes.toBytes("20220501_001")) ; // todo: 设置指定列簇和列 get.addFamily(Bytes.toBytes("basic")); get.addColumn(Bytes.toBytes("other"), Bytes.toBytes("address")) ; // c. 查询数据,结果封装到Result对象中, todo: 表示HBase表中一行数据 Result result = table.get(get); // d. 遍历获取每列数据 // 获取每行数据中所有列,每列数据封装在Cell对象中 Cell[] cells = result.rawCells(); // todo: HBase底层将每列数据进行封装 -> Cell 类中 for (Cell cell : cells) { /* 20220501_001 basic age 18 1651478947786 */ // 使用工具类CellUtil,获取每列Cell数据中各个字段值:cf、column、version、value String rowKey = Bytes.toString(CellUtil.cloneRow(cell)); String family = Bytes.toString(CellUtil.cloneFamily(cell)); String column = Bytes.toString(CellUtil.cloneQualifier(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); long version = cell.getTimestamp(); System.out.println(rowKey + "\tcolumn=" + family + ":" + column + ", timestamp=" + version +", value=" +value); } // e. 关闭连接 table.close(); } // 依据RowKey删除数据 @Test public void testDelete() throws Exception{ // a. 获取Table对象 Table table = getHTable(); // b. 创建Delete对象,设置RowKey Delete delete = new Delete(Bytes.toBytes("20220501_004")) ; // todo:添加删除列簇和列 delete.addFamily(Bytes.toBytes("other")) ; delete.addColumn(Bytes.toBytes("basic"), Bytes.toBytes("age")) ; // c. 执行删除 table.delete(delete); // d. 关闭连接 table.close(); } // 使用Scan扫描数据 @Test public void testScan() throws Exception{ // a. 获取Table对象 Table table = getHTable(); // b. 创建Scan对象 Scan scan = new Scan() ; // c. 扫描表的数据,TODO:ResultScanner 相当于一个集合,里面存储很多Result对象,表示HBase表中每条数据 ResultScanner resultScanner = table.getScanner(scan); // d. 循环遍历,获取每条数据Result for (Result result : resultScanner) { // 获取RowKey String rowKey = Bytes.toString(result.getRow()); System.out.println(rowKey); // 获取每条数据中所有列 Cell[] cells = result.rawCells(); for (Cell cell : cells) { // 使用工具类CellUtil,获取每列Cell数据中各个字段值:cf、column、version、value String family = Bytes.toString(CellUtil.cloneFamily(cell)); String column = Bytes.toString(CellUtil.cloneQualifier(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); long version = cell.getTimestamp(); System.out.println("\tcolumn=" + family + ":" + column + ", timestamp=" + version +", value=" +value); } } // e. 关闭连接 table.close(); } /* - 需求1:查询2022年3月和4月的数据 - 需求2:查询2022年5月的所有数据 - 需求3:查询所有age = 24的数据 */ // 需求1:查询2022年3月和4月的数据 @Test public void testScanFilter_Range() throws Exception{ /* scan 'people', {STARTROW=>'202203', STOPROW=>'202205'} */ // a. 获取Table对象 Table table = getHTable(); // b. 创建Scan对象 Scan scan = new Scan() ; // TODO: 设置startRow和stopRow 范围 scan.withStartRow(Bytes.toBytes("202203")); scan.withStopRow(Bytes.toBytes("202205")) ; // c. 扫描表的数据,TODO:ResultScanner 相当于一个集合,里面存储很多Result对象,表示HBase表中每条数据 ResultScanner resultScanner = table.getScanner(scan); // d. 循环遍历,获取每条数据Result for (Result result : resultScanner) { // 获取RowKey String rowKey = Bytes.toString(result.getRow()); System.out.println(rowKey); // 获取每条数据中所有列 Cell[] cells = result.rawCells(); for (Cell cell : cells) { // 使用工具类CellUtil,获取每列Cell数据中各个字段值:cf、column、version、value String family = Bytes.toString(CellUtil.cloneFamily(cell)); String column = Bytes.toString(CellUtil.cloneQualifier(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); long version = cell.getTimestamp(); System.out.println("\tcolumn=" + family + ":" + column + ", timestamp=" + version +", value=" +value); } } // e. 关闭连接 table.close(); } // 需求2:查询2022年5月的所有数据 @Test public void testScanFilter_Prefix() throws Exception{ /* scan 'hbase:students', {ROWPREFIXFILTER => '202205'} */ // a. 获取Table对象 Table table = getHTable(); // b. 创建Scan对象 Scan scan = new Scan() ; // todo: 添加前缀过滤器 PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("202205")); scan.setFilter(prefixFilter) ; // c. 扫描表的数据,TODO:ResultScanner 相当于一个集合,里面存储很多Result对象,表示HBase表中每条数据 ResultScanner resultScanner = table.getScanner(scan); // d. 循环遍历,获取每条数据Result for (Result result : resultScanner) { // 获取RowKey String rowKey = Bytes.toString(result.getRow()); System.out.println(rowKey); // 获取每条数据中所有列 Cell[] cells = result.rawCells(); for (Cell cell : cells) { // 使用工具类CellUtil,获取每列Cell数据中各个字段值:cf、column、version、value String family = Bytes.toString(CellUtil.cloneFamily(cell)); String column = Bytes.toString(CellUtil.cloneQualifier(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); long version = cell.getTimestamp(); System.out.println("\tcolumn=" + family + ":" + column + ", timestamp=" + version +", value=" +value); } } // e. 关闭连接 table.close(); } // 需求3:查询所有age = 24的数据 @Test public void testScanFilter_SingleValue() throws Exception{ // a. 获取Table对象 Table table = getHTable(); // b. 创建Scan对象 Scan scan = new Scan() ; // todo: 过滤器filter,过滤某个列的值 SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter( Bytes.toBytes("basic"), Bytes.toBytes("age"), CompareOperator.EQUAL, Bytes.toBytes("24") ); scan.setFilter(singleColumnValueFilter) ; // c. 扫描表的数据,TODO:ResultScanner 相当于一个集合,里面存储很多Result对象,表示HBase表中每条数据 ResultScanner resultScanner = table.getScanner(scan); // d. 循环遍历,获取每条数据Result for (Result result : resultScanner) { // 获取RowKey String rowKey = Bytes.toString(result.getRow()); System.out.println(rowKey); // 获取每条数据中所有列 Cell[] cells = result.rawCells(); for (Cell cell : cells) { // 使用工具类CellUtil,获取每列Cell数据中各个字段值:cf、column、version、value String family = Bytes.toString(CellUtil.cloneFamily(cell)); String column = Bytes.toString(CellUtil.cloneQualifier(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); long version = cell.getTimestamp(); System.out.println("\tcolumn=" + family + ":" + column + ", timestamp=" + version +", value=" +value); } } // e. 关闭连接 table.close(); } @After public void close() throws Exception{ if(null != conn) conn.close(); } }
table、region、regionserver:
Table:是一个逻辑对象,物理上不存在,供用户实现逻辑操作,存储在元数据的一个概念
Region:Hbase中数据负载均衡的最小单元
RegionServer:是一个物理对象,Hbase中的一个进程,管理一台机器的存储
类似于HDFS中DataNode
一个Regionserver可以管理多个Region
一个Region只能被一个RegionServer所管理
# Table:提供用户读写的逻辑概念
# Region:分区的概念
一张表可以划分为多个分区
每个分区都被某一台Regionserver所管理
# RegionServer:真正存储数据的物理概念
# 1. 任何一个Region都会对应一个范围
如果只有一个Region,范围:-oo ~ +oo
# 2. 范围划分:从整个-oo ~ +oo区间上进行范围划分
# 3. 每个分区都会有一个范围:根据Rowkey属于哪个范围就写入哪个分区
[startKey,stopKey) -> 前闭后开区间
默认:一张表创建时,只有一个Region,范围:-oo ~ +oo
创建一张表,有2个分区Region
create 't3', 'f1', SPLITS => ['50']
分区范围
region0:-oo ~ 50
region1:50 ~ +oo
# 举个栗子:创建一张表,有4个分区Region,20,40,60 create 'hbase:t3', {SPLITS => [20, 40, 60]} # 规则:前闭后开 region0:-oo ~ 20 region1:20 ~ 40 region2:40 ~ 60 region3:60 ~ +oo # 写入数据的rowkey: # 比较是按照ASCII码比较的,不是数值比较 # 比较规则:ASCII码逐位比较 A1234:region3 A -> ASCII:65 c6789:region3 c -> ASCII: 99 00000001:region0 2:region0 99999999:region3
put tbname, rowkey, cf:col, value
# tbname:决定了这张表的数据最终要读写哪些分区
# rowkey:决定了具体读写哪个分区
# cf:决定具体写入哪个Store
region内部结构:
# Store:
对分区的数据进行划分,按照列族划分,一个列族对应一个Store
不同列族的数据写入不同的Store中,实现了按照列族将列进行分组
根据用户查询时指定的列族,可以快速的读取对应的store
# MemStore:
每个Store都有一个: 内存存储区域
数据写入memstore就直接返回
# StoreFile:
每个Store中可能有0个或者多个StoreFile文件
逻辑上:Store
物理上:HDFS,HFILE【二进制文件】
# 伴随写入时会产生WAL日志文件,伴随读取会产生blockcache缓存
FLUSH
将内存memstore中的数据溢写到HDFS中变成磁盘文件storefile HFILE
自动触发机制:HBase 2.0之前参数:
#region的memstore的触发
#判断如果某个region中的某个memstore达到这个阈值,那么触发flush,flush这个region的所有memstore
hbase.hregion.memstore.flush.size=128M
#region的触发级别:如果没有memstore达到128,但是所有memstore的大小加在一起大于等于128*4
#触发整个region的flush
hbase.hregion.memstore.block.multiplier=4
#regionserver的触发级别:所有region所占用的memstore达到阈值,就会触发整个regionserver中memstore的溢写
#从memstore占用最多的Regin开始flush
hbase.regionserver.global.memstore.size=0.4 --RegionServer中Memstore的总大小
#低于水位后停止
hbase.regionserver.global.memstore.size.upper.limit=0.99
hbase.regionserver.global.memstore.size.lower.limit = 0.4*0.95 =0.38
自动触发机制:HBase 2.0之后:
#设置了一个flush的最小阈值
#memstore的判断发生了改变:max("hbase.hregion.memstore.flush.size / column_family_number",hbase.hregion.percolumnfamilyflush.size.lower.bound.min)
#如果memstore高于上面这个结果,就会被flush,如果低于这个值,就不flush,如果整个region所有的memstore都低于,全部flush
#水位线 = max(128 / 列族个数,16),列族一般给3个 ~ 42M
#如果memstore的空间大于42,就flush,如果小于就不flush;如果都小于,全部flush
举例:3个列族,3个memstore, 90/30/30 90会被Flush
举例:3个列族,3个memstore, 30/30/30 全部flush
hbase.hregion.percolumnfamilyflush.size.lower.bound.min=16M
# 2.x中多了一种机制:In-Memory-compact,如果开启了【不为none】,会在内存中对需要flush的数据进行合并
#合并后再进行flush,将多个小文件在内存中合并后再flush
hbase.hregion.compacting.memstore.type=None|basic|eager|adaptive
为了避免大量的Memstore将大量的数据同时Flush到HDFS上,占用大量的内存和磁盘的IO带宽,会影响业务,手动执行
# 将某个表中所有Region中MemStore数据flush到本地磁盘StroeFile
hbase> flush 'TABLENAME'
# 将某个Region中MemStoree数据flush到本地磁盘StoreFile
hbase> flush 'REGIONNAME'
# 将某个RegionServer中所有Region中MemStoree数据flush到本地磁盘StoreFile
hbase> flush 'REGION_SERVER_NAME'
Compaction
将零散的有序数据合并为整体有序大文件,提高对HDFS数据的查询性能
# 2.0版本之前,只有StoreFile文件的合并
磁盘中合并:minor compaction、major compaction
# 2.0版本开始,内存中的数据也可以先合并后Flush
内存中合并:In-memory compaction
磁盘中合并:minor compaction、major compaction
minor compaction:轻量级
功能:将最早生成的几个小的StoreFile文件进行合并,成为一个大文件,不定期触发
特点
属性: hbase.hstore.compaction.min=3
major compaction:重量级合并
功能:将整个Store中所有StoreFile进行合并为一个StoreFile文件,整体有序的一个大文件
特点
参数配置: hbase.hregion.majorcompaction=7天
In-memory compaction
原理:将当前写入的数据划分segment【数据段】
如果开启内存合并,先将第一个segment放入一个队列中,与其他的segment进行合并
内存中合并的方式: hbase.hregion.compacting.memstore.type=None|basic|eager|adaptive
禁用自动合并,使用手动处理:
Compact all regions in a table:
hbase> major_compact 't1'
hbase> major_compact 'ns1:t1'
Compact an entire region:
hbase> major_compact 'r1'
Compact a single column family within a region:
hbase> major_compact 'r1', 'c1'
Split
关闭自动分裂,手动执行
split 'tableName'
split 'namespace:tableName'
split 'regionName' # format: 'tableName,startKey,id'
split 'tableName', 'splitKey'
split 'regionName', 'splitKey'
写流程
put t1, 1001, info:name, zhangsan
依据表名称获取所有region信息
第1步、连接ZK集群,获取hbase:meta表Region所在RS地址信息
ZNODE: /hbase/meta-region-server
第2步、读取hbase:meta数据,依据表名过滤
获取操作表所有Region信息数据
依据RowKey找到对应Region及所在RegionServer地址
rowkey 和 Region范围(startKey ~ endKey)找到region,获取RS地址信息
发送put请求RS,写入数据
第1步、将put发送给对应RS
第2步、将数据写入到WAL预写日志
放置内存数据丢失,导致数据丢失
第3步、依据列簇找到Region中Store,将put数据写入到memStore中
读流程
get t1, 1001
基于表名称获取所有Region信息
第1步、连接ZK集群,获取hbase:meta表Region所在RS地址信息
ZNODE: /hbase/meta-region-server
第2步、读取hbase:meta数据,依据表名过滤
获取操作表所有Region信息数据
依据RowKey找到对应Region及所在RegionServer地址
rowkey 和 Region范围(startKey ~ endKey)找到region,获取RS地址信息
发送get请求RS,读取数据
第1、【最新】读取memStore内存中数据
写缓存
第2、【最老】如果设置读缓存BlockCache,读取BlockCache中数据
读缓存
数据从StoreFile文件读取出来
第3、【其次】读取Store中StoreFile文件
hdfs文件,磁盘读取
最终,将上面三处读取数据合并,进行返回
RowKey设计
唯一原则:Rowkey必须具有唯一性,不能重复,一个Rowkey唯一标识一条数据
业务原则:Rowkey的设计必须贴合业务的需求,一般选择最常用的查询条件作为rowkey的前缀
组合原则:将更多的经常作为的查询条件的列放入Rowkey中,可以满足更多的条件查询可以走索引查询
散列原则:为了避免出现热点问题,需要将数据的rowkey生成规则构建散列的rowkey
方案一:更换不是连续的字段作为前缀,例如用户id
方案二:反转,一般用于时间作为前缀,查询时候必须将数据反转再查询
方案三:加盐(Salt),本质对数据进行编码,生成数字加字母组合的结果
长度原则:在满足业务需求情况下,rowkey越短越好,一般建议Rowkey的长度小于100字节
内存优化
读多写少,降低MEMStore比例
读少写多,降低BlockCache比例
压缩机制
布隆过滤
环境优化
将HBase表中每一行对应的所有列构建一张完整的结构化表
集成框架:
Hive:通过MapReduce来实现
Impala:基于内存分布式计算实现
Phoenix:通过HBase API封装实现的,底层协处理器
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties("hbase.columns.mapping" = ":key,info:code,info:money,info:pay_account,info:pay_channel,info:pay_comments,info:pay_name,info:pay_way,info:rec_account,info:rec_bank_name,info:rec_name,info:status,info:timestamp")
tblproperties("hbase.table.name" = "default:bank_records");
注意:Phoenix中默认会将所有字符转换为大写,如果想要使用小写字母,必须加上双引号
注意:创建表时,必须指定主键作为Rowkey,主键列不能加列族
Phoenix 4.8版本之前,只要创建同名的Hbase表,会自动关联数据
Phoenix 4.8版本以后,不推荐关联表的方式
Phoenix中建议使用视图view的方式来关联HBase中已有的表
通过构建关联视图,可以解决大部分数据查询的数据,不影响数据
视图:Hbase中已经有这张表,写都是操作Hbase,Phoenix只提供读
建表:对这张表既要读也要使用Phoenix来写
在实际项目中,先在HBase中创建表,再在Phoenix中创建视图;写入数据时,基于HBase 操作,查询数据使用Phoenix基于SQL操作。
!table: 列出表
upsert into values :更新数据
LIMIT 1000 OFFSET 100: limit 100 1000
DELETE FROM
1、插入更新Upsert
当主键不存在时,直接插入数据
当主键存在时,更新数据
2、删除数据DELETE
语法类似MySQL数据库
3、查询数据
SELECT 语句,简易查询(可以过滤,可以简单分组聚合)
CREATE TABLE IF NOT EXISTS ORDER_REGION(
ID varchar primary key,
INFO.STATUS varchar,
INFO.MONEY float,
INFO.PAY_WAY integer,
INFO.USER_ID varchar,
INFO.OPERATION_TIME varchar,
INFO.CATEGORY varchar
)
CONPRESSION='GZ'
SPLIT ON ('3','5','7');
public class PhoenixJdbcDemo { public static void main(String[] args) throws Exception{ // 定义变量 Connection conn = null ; PreparedStatement pstmt = null ; ResultSet result = null ; try{ // 1. 加载驱动类 Class.forName("org.apache.phoenix.jdbc.PhoenixDriver") ; // 2. 获取连接 conn = DriverManager.getConnection("jdbc:phoenix:node1,node2,node3:2181") ; // 3. 创建Statement对象 pstmt = conn.prepareStatement("SELECT USER_ID, PAY_WAY, CATEGORY FROM ORDER_REGION LIMIT 10"); // 4. 执行操作,此处查询 result = pstmt.executeQuery(); // 5. 获取数据 while (result.next()){ String userId = result.getString("USER_ID"); String payway = result.getString("PAY_WAY"); String category = result.getString("CATEGORY"); System.out.println(userId + ", " + payway + ", " + category); } }catch (Exception e){ e.printStackTrace(); }finally { // 6. 关闭连接 if(null != result) result.close(); if(null != pstmt) pstmt.close(); if(null != conn) conn.close(); } } }
全局索引功能:当为某一列创建全局索引时,Phoenix自动创建一张索引表,将创建索引的这一列加上原表的rowkey作为新的rowkey
CREATE INDEX globe_index ON table(column);
查询
特点:
应用:写少读多
覆盖索引功能:在构建全局索引时,将经常作为查询条件或者结果的列放入索引表中,直接通过索引表来返回数据结果
CREATE INDEX overwrite_index ON table(column1) include(column12);
特点:
应用:
本地索引功能:将索引数据与对应的原始数据放在同一台机器,避免了跨网络传输,提高了写的性能
CREATE LOCAL INDEX LOCAL_IDX on table(column);
特点
应用:
1、全局索引:GlobalIndex
创建索引index,创建一张表:索引表,单独存储索引数据
SELECT查询时,先到索引表查询,再到原数据表查询
2、覆盖索引
与全局索引相比较,基于全局索引之上的
第一点、创建索引index,创建一张表:索引表,单独存储索引数据
第二点、创建索引,将查询字段进行包含include,存储到索引表列中
第三点、SELECT查询时,仅仅查询索引表,获取数据就返回,不再查询原数据表,由于索引表中包含查询字段
3、本地索引: LoalIndex
创建索引index,不创建表,直接将索引数据写到原数据表中,增加数据量
原数据表有10亿条数据,创建本地索引后,此时增加10亿条索引数据 = 20亿数据
SELECT查询时,先到索引表(就是原数据表)查询,再到原数据表查询
Hbase基础内容。
时光如水,人生逆旅矣。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。