Hbase写入原理-常用操作-过滤器
hbase是基于大表的数据库 ===================================== 随机访问和实时读写 hbase和hive的区别: hbase:低延迟实时性,不支持分析 hive:高延迟,分析工具 awk '{print $1}' //默认以'\t'分割,截串取第一个成员 hbase原理: ======================== hdfs://mycluster/user/hbase/data //hbase数据 namespace //文件夹 Htable //文件夹 Hregion //区域,文件夹 CF //列族, 文件夹 数据 //文件形式存在(row,cf,col,value) hbase在写入数据时,先写入到memstore中,到达一定的值或重启的时候会实例化为文件 scan 'hbase:meta' ns1:t1,,1522573626932.02aab65df5b31 column=info:regioninfo, timestamp=1522630951985, value={ENCODED => 02aab65df5b31c1145a8b799ced42c92, NAM c1145a8b799ced42c92. E => 'ns1:t1,,1522573626932.02aab65df5b31c1145a8b799ced42c92.', STARTKEY => '', ENDKEY => ''} ns1:t1,,1522573626932.02aab65df5b31 column=info:seqnumDuringOpen, timestamp=1522630951985, value=\x00\x00\x00\x00\x00\x00\x00\x15 c1145a8b799ced42c92. ns1:t1,,1522573626932.02aab65df5b31 column=info:server, timestamp=1522630951985, value=s102:16020 c1145a8b799ced42c92. ns1:t1,,1522573626932.02aab65df5b31 column=info:serverstartcode, timestamp=1522630951985, value=1522629569862 c1145a8b799ced42c92. hbase:meta表存放的是所有表的元数据,即表(区域)和regionserver的映射关系 问题1:hbase:meta表是谁负责管辖? //每个表(包括元数据表),都分别由一个regionserver负责的 问题2:元数据中的元数据,hbase:meta的元数据存放位置? //hbase:meta表的数据信息,由zk负责管辖 普通表: 表体由regionserver负责管理,元数据是由hbase:meta表负责管理 hbase:meta表: 表体由regionserver负责管理,元数据是由zk负责管辖 写(put)流程: 1、客户端先联系zk,找到hbase:meta表的regionserver,在此请求数据 2、通过hbase:meta的数据,找到表(区域)的映射关系,通过regionserver定位到指定表(区域) 3、查询信息一旦被触发,会被缓存,以便下次使用 4、用户向区域服务器(RegionServer)发起put请求时,会将请求交给对应的区域(region)实例来处理。 5、决定数据是否需要写到由HLog实现的预写日志(WAL)中。 6、一旦数据被写入到WAL中,数据会被放到MemStore中 7、memstore如果写满,则会被刷写(flush)到磁盘中,生成新的hfile 区域:region管理 ====================================== 是regionserver的基本管理单位 rowKey是以字节数组进行排序 表切割: split 'ns1:t1','row500' 区域切割: split 'ns1:t1,row500,1522634115455.b88d6163f7e2eced2f2297c7fe938b3b.','row750' 创建表,进行预切割: create 'ns1:t2', 'f1', SPLITS => [ '20', '30', '40'] 移动区域: move '43e14524a984a4bf6a813a4f2a8ac8eb' , 's103,16020,1522634665677' 表切割之后,之前表所在的区域会被切割成两个新的区域,原区域处于离线状态,再次启动hbase时,原区域相关信息会被清空 区域合并 merge_region '28d725266b30bcef636e994f08c9e8d9','976bc7a3b8769184c1a0a5aca1339d99' 紧凑compact: ==================================== 解决flush产生的小文件(hfile),将其进行合并 compact 'ns1:t1' //合并小文件,重启后生效 compact_rs 's103,16020,1522634665677' //合并regionserver中所有region major_compact //合并小文件,即刻生效 regionname //区域名,eg:ns1:t1,row500,1522634115455.b88d6163f7e2eced2f2297c7fe938b3b. encoded_regionname //编码区域名,eg:b88d6163f7e2eced2f2297c7fe938b3b 注意不加'.' server_name //regionserver名字, eg:s102,16020,1522634665523 hbase数据写入流程,代码分析 ========================================================== 在数据写入的时候,hbase会初始化一个2M的mutator缓冲区 通过Arrays.asList(m)将Mutation对象,即数据转换成List writeAsyncBuffer证明,数据写入是异步写入 每次写入数据都会在内部进行自动清理 写入时将传来的Mutation对象,即数据放在LinkedList中 put命令时:1、传入put对象 在传每个数据时候,都会进行autoflush自动清理,且需要一次rpc通信 2、传入put集合 将集合传入并且处理之后,进行一次flush,需要一次rpc通信 10000 关闭自动flush 关闭写前日志 关闭flush+WAL ---------------------------------------------------------------------- put 38,532ms 3,227ms 10,769ms 2,959ms putBatch 3,507ms 在put中设置关闭自动flush HTable.setAutoFlush(false,false); 在put中关闭WAL写入 put.setDurability(Durability.SKIP_WAL); WAL ============================= Write ahead log 适用于容灾,当机器发生断电,memstore数据很可能会丢失 WAL:数据写入到WAL则证明写入成功,存储对数据的修改,类似于hadoop的edits文件 如果数据库崩溃,可以有效地回放日志 ***** 强烈建议用户不要关闭WAL 实现类:org.apache.hadoop.hbase.regionserver.wal.FSHLog WAL在0.x版本中使用的是SeqFile 新版中不是 memstore: ================================ 1、刷写到磁盘,阈值是5M <property> <name>hbase.hregion.preclose.flush.size</name> <value>5242880</value> </property> 2、关闭regionserver会使memstore强制刷写到磁盘 hbase基本命令: ============================ create_namespace 'ns1' //create_namespace 'ns1' drop_namespace 'ns1' //drop_namespace 'ns1' create 'ns1:t1','f1','f2' //create 'ns1:t1','f1','f2' put 'ns1:t1','row1','f1:name','tom' //put 'ns1:t1','row1','f1:name','tom' scan 'ns1:t1' //scan 'ns1:t1' alter 'ns1:t5', 'f3' //添加列族 //alter 'ns1:t5' , 'f3' hbase文件: =========================== 根级文件:/user/hbase WALs //预写日志,和hadoop中的edits文件类似,作用是容灾 //通过以下配置进行周期性滚动 <property> <name>hbase.regionserver.logroll.period</name> <value>3600000</value> <description>毫秒为单位,默认一小时</description> </property> oldWALs //旧的预写日志,当时间超过1小时,WAL会被回滚到此处 //十分钟后此文件会被清除,配置文件如下 <property> <name>hbase.master.logcleaner.ttl</name> <value>600000</value> <description>毫秒为单位,默认十分钟</description> </property> hbase.id //hbase唯一id hbase.version //hbase集群版本信息 corrupt //损坏日志文件 表级文件:/user/hbase/data/ns1/t1 .tabledesc/.tableinfo.0000000001 //串行化后的表描述符desc 'ns1:t1' region级文件: .regioninfo //region的描述,和tableinfo类似 recovered.edits //WAL日志恢复文件 当region足够大的时候,会自动切分成两个region,将原文件一分为二 region默认大小是10G ================================================== <property> <name>hbase.hregion.max.filesize</name> <value>10737418240</value> <description> Maximum HStoreFile size. If any one of a column families' HStoreFiles has grown to exceed this value, the hosting HRegion is split in two.</description> </property> 注意:当所有region同时达到阈值时候,会产生切割风暴,严重消耗资源 1、设置预切割: create 'ns1:t2', 'f1', SPLITS => [ '20', '30', '40'] 2、设置一个非常大的值,然后手动切割 HFile: ============================= 是hbase的文件格式,以k/v形式存储,k/v均是字节数组 HFile包括以下内容: 读取或写入压缩块的存储空间。 每个块所指定的I/O操作的压缩编解码器 临时的key存储 临时的value存储 hfile索引,存在于内存,占用空间约为(56+AvgKeySize)*NumBlocks. 性能优化建议 **** 最小块大小,推荐在 8KB to 1MB之间 顺序读写推荐大块,但不便于随机访问(因为需要解压更多的数据) 小块便于随机读写,但是需要占用更多内存,但是创建起来更慢(因为块多,每次压缩都需要flush操作) 由于压缩缓存,最小块大小应该在20KB-30KB. 查看hfile: ==================== hbase hfile -a -b -p -v -h -f hdfs://mycluster/user/hbase/data/ns1/t1/77c55f893ecec3bbb2dfd02e3737c0c2/f1/5406345aabc640309be64abd21a2a500 key:row1/f1:age/1522634241444/Put/vlen=1/seqid=22 value:1 Cell: ============================= * 1) row * 2) column family * 3) column qualifier * 4) timestamp * 5) type * 6) MVCC version //multiple version concurrency contorl 多版本并发控制 * 7) value scan操作: ======================= 限定列扫描: HTable.getScanner(Bytes.toBytes("f1"),Bytes.toBytes("name")); //HTable.getScanner(Bytes.toBytes("f1"),Bytes.toBytes("name")); 指定列族扫描: HTable.getScanner(Bytes.toBytes("f1")); //Htable.getScanner(Bytes.toBytes("f1")); 全表扫描 HTable.getScanner(Scanner) //Htable.getScanner(Scanner) 限定row范围进行扫描: new Scan(Byte[] startKey,Byte[] stopKey) //new Scan(Byte[] startKey,Byte[] stopKey) catch和batch 每次it.next()的时候,都会调用一次rpc,效率很差 避免此问题,引入cache的概念 catch:将指定数量行数进行缓存,到达阈值,通过一个RPC共同发给客户端 <property> <name>hbase.client.scanner.caching</name> <value>2147483647</value> <description>每次查询缓存的数量</description> </property> cache 10 1000 10000 1 ------------------------------------------------------------- time 6,423ms 5,434ms 5,831ms 14,339ms batch: 缓存指定的列数,到达阈值,通过一个RPC共同发给客户端 RPC次数: row x col / min(cache,rowNo) / min(batch, colNo) +1 result次数: row x col / min(batch, colNo) //只和batch有关 hbase过滤器: =================================================== 相当于sql的where子句 使用谓词下推的原理,通过server端过滤,返回给client数据 hbase过滤器中的比较方法: CompareFilter.CompareOp.EQUAL ====> '=' 使用过滤器: 1、初始化过滤器 //new RowFilter 2、初始化参数:比较方法 //CompareFilter.CompareOp.EQUAL 比较器 // 过滤器: RowFilter //行过滤器 //RowFilter FamilyFilter //列族过滤器 //FamilyFilter QualifierFilter //列过滤器 //QualifierFilter ValueFilter //值过滤器 //ValueFileter SingleColumnValueFilter //单列值过滤器,通过搜索制定列族和制定col的值,返回整行数据 //SingleColumnValueFilter //相当于select * from xxx where id=1; TimeStampFilter //时间戳过滤器 //TimeStampFilter 比较器: BinaryComparator //二进制比较器 //BianryComparator RegexStringComparator //正则比较器, 比较时最好使用EQUAL //RegexStringComparator SubstringComparator //子串比较器, 比较时最好使用EQUAL //SubstringComparator /** * 组合过滤器 * @throws Exception */ @Test public void testCombineFilter() throws Exception { long start = System.currentTimeMillis(); //初始化配置信息 //Configuration conf = new Configuration(); Configuration conf = HBaseConfiguration.create(); //入口点,创建连接 Connection conn = ConnectionFactory.createConnection(conf); //通过getTable方法获取表的实例 HTable table = (HTable) conn.getTable(TableName.valueOf("ns1:t5")); //HTable table = (HTable) conn.getTable(TableName.valueOf("ns1:t5")); //Scan scan = new Scan(); Scan scan = new Scan(); //行过滤器,使用正则过滤器,过滤出111结尾的rowKey RowFilter filter1 = new RowFilter(CompareFilter.CompareOp.EQUAL, new RegexStringComparator(".*111")); //RowFilter filter = new RowFilter(CompareFilter.CompareOpEQUAL,new RegexStringComparator(".*111")); //列族过滤器,使用二进制过滤器,过滤出f2列族的数据 FamilyFilter filter2 = new FamilyFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("f2"))); //FamilyFilter filter2 = new FamilyFilter(ComparaFilter.CompareOp.EQUAL,new BinaryComparator(Bytes.toBytes("f2"))); FilterList filterList = new FilterList(); //FilterList filterList = new FilterList(); filterList.addFilter(filter1); //filterList.addFilter(filter1); filterList.addFilter(filter2); //filterList.addFilter(filter2); scan.setFilter(filterList); //scan.setFilter(filterList); //通过扫描器得到结果集 ResultScanner rs = table.getScanner(scan); ResultScanner rs = table.getScanner(scan); //得到迭代器 Iterator<Result> it = rs.iterator(); rs.iterator(); TestFilter.printVal(it); TestFilter.printVal(it); table.close(); //table.close(); System.out.println(System.currentTimeMillis() - start); } public static void printVal(Iterator<Result> it){ while (it.hasNext()){ Result next = it.next(); List<Cell> cells = next.listCells(); //next.listCells(); for (Cell cell : cells) { String val = Bytes.toString(CellUtil.cloneValue(cell)); //Bytes.toString(CellUtil.cloneValue(cell)); String clo = Bytes.toString(CellUtil.cloneQualifier(cell)); //Bytes.toString(CellUtil.cloneQualifier(cell)) String cf = Bytes.toString(CellUtil.cloneFamily(cell)); //Bytes.toString(CellUtil.cloneFamily(cell)); String row = Bytes.toString(CellUtil.cloneRow(cell)); //Bytes.toString(CellUtil.cloneRow(cell)); System.out.println(row+"/"+cf+"/"+clo+"/"+val); } } }