HBase(八):表结构设计优化,HBase(六):HBase体系结构剖析(上),HBase(七):HBase体系结构剖析(下)
作者:洋索菲命_563 | 来源:互联网 | 2023-09-18 17:57
在HBase(六):HBase体系结构剖析(上) 介绍过,Hbase创建表时,只需指定表名和至少一个列族,基于HBase表结构的设计优化主要是基于列族级别的属性配置,如下图:
在 HBase(六): HBase体系结构剖析(上) 介绍过,Hbase创建表时,只需指定表名和至少一个列族,基于HBase表结构的设计优化主要是基于列族级别的属性配置,如下图:
目录:
BLOOMFILTER BLOCKSIZE IN_MEMORY COMPRESSION/ENCODING VERSIONS TTL BLOOMFILTER :
Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法 。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合 bloom filter的数据存在StoreFile的meta中,一旦写入无法更新,因为StoreFile是不可变的。Bloomfilter是一个列族(cf)级别的配置属性,如果在表中设置了Bloomfilter,那么HBase会在生成StoreFile时包含一份bloomfilter结构的数据,称其为MetaBlock;MetaBlock与DataBlock(真实的KeyValue数据)一起由LRUBlockCache维护。所以,开启bloomfilter会有一定的存储及内存cache开销 对于已经存在的表,可以使用alter表 的方式修改表结构,但这种修改对于之前的数据不会生效,只针对修改后插入的数据 包含三种类型:NONE、ROW、ROWCOL ROW: 根据KeyValue中的row来过滤storefile,举例如下:假设有2个storefile文件sf1和sf2sf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v) sf2包含kv3(r3 cf:q1 v)、kv4(r4 cf:q1 v) 如果设置了CF属性中的bloomfilter为ROW,那么get(r1)时就会过滤sf2,get(r3)就会过滤sf1 ROWCOL: 根据KeyValue中的row+qualifier来过滤storefile如上例:若设置了CF属性中的bloomfilter为ROW,无论get(r1,q1)还是get(r1,q2),都会读取sf1+sf2; 而如果设置了CF属性中的bloomfilter为ROWCOL,那么get(r1,q1)就会过滤sf2,get(r1,q2)就会过滤sf1 region下的storefile数目越多,bloomfilter的效果越好 region下的storefile数目越少,HBase读性能越好 BLOCKSIZE:
从上图可发现,默认的BlockSize 为 65536B (64KB),在 HBase(七): HBase体系结构剖析(下) 介绍HBase读原理,如果在blaock cache 、memostore中都没查到符合条件的数据,则循环遍历 storeFile 文件,而hbase读取磁盘文件是按其基本I/O单元(即 hbase block)读数据的,因此HFile块大小是影响性能的重要参数 参见Get\Scan场景下测试不同BlockSize大小(16K,64K,128K)对性能的影响,如下图:对比结果参考:http://hbasefly.com/2016/07/02/hbase-pracise-cfsetting/ 可见,如果业务请求以Get 请求为主,可以考虑将块大小设置较小 ;如果以Scan 请求为主,可以将块大小调大 ;默认的64K块大小是在Scan和Get之间取得的一个平衡 平均键值对规划,如下 [root@HDP0 bin]# hbase hfile -m -f /apps/hbase/data/data/default/PerTest/7685e6c39d1394d94e26cf5ddafb7f9f/d/3ef195ca65044eca93cfa147414b56c2 SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/usr/hdp/2.4.2.0-258/hadoop/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class ] SLF4J: Found binding in [jar:file:/usr/hdp/2.4.2.0-258/zookeeper/lib/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class ] SLF4J: See http: //www.slf4j.org/codes.html# multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory] 2016-09-11 12:54:40,514 INFO [main] hfile.CacheConfig: CacheConfig:disabled Block index size as per heapsize: 6520 reader =/apps/hbase/data/data/default/PerTest/7685e6c39d1394d94e26cf5ddafb7f9f/d/3ef195ca65044eca93cfa147414b56c2, compression =none, cacheConf =CacheConfig:disabled, firstKey =00123ed7-5af8-49b1-bd13-9e086a5bd5f2/d:Action/1471406616120/Put, lastKey =fffbc8f7-55f2-4c49-804f-444f6ccbc903/d:UserID/1471406614464/Put, avgKeyLen =55, avgValueLen=10 , entries =54180, length =4070738
从上面输出的信息可以看出,该HFile的平均键值对规模为55B + 10B = 65B,相对较小,在这种情况下可以适当将块大小调小(例如8KB或16KB)。这样可以使得一个block内不会有太多kv,kv太多会增大块内寻址的延迟时间,因为HBase在读数据时,一个block内部的查找是顺序查找
选择较小块的大小的目的是使随机读取更快,而付出的代价是块索引变大,会消耗更多的内存。相反,如果平均键值对规模很大,或者磁盘速度慢造成了性能瓶颈,那就应该选择一个较大的块大小,以便使一次磁盘IO能够读取更多的数据 思考:实际场景大部分是Scan读,但平均键值规划较小,如何设置BlockSize? IN_MEMORY:
Block Cache 包含三个级别的优先级队列: Single: 如果一个Block被第一次访问,则放在这一级的队列中Multi: 如果一个Block被多次访问,则从Single队列移动Multi队列In Memory: 它是静态指定的(在column family上设置),不会像其他两种cache会因访问频率而发生改变,这就决定了它的独立性,另外两种block访问次数再多也不会被放到in-memory的区段里去,in-memory的block不管是第几次访问,总是被放置到in-memory的区段中LRU(Least Recently Used)淘汰数据时,Single会被优先淘汰,其次是Multi, 最后是In Memory, 这三个队列分别占用 BlockCache的 25%、50%、25% 每load一个block到cache时,都会检查当前cache的size是否已经超过了“警戒线”,这个“警戒线”是一个规定的当前block cache总体积占额定体积的安全比例,默认该值是0.85,即当加载了一个block到cache后总大小超过了既定的85%就开始触发异步的evict操作了 evict的逻辑是这样的:遍历cache中的所有block,根据它们所属的级别(single,multi,in-memory)分拨到三个优先级队列中,队头元素是最旧(最近访问日间值最小)的那个block。对这个三队列依次驱逐头元素,释放空间 注意: 标记 IN_MEMORY=>’true’ 的column family的总体积最好不要超过in-memory cache的大小(in-memory cache = heap size * hfile.block.cache.size * 0.85 * 0.25 ),特别是当总体积远远大于了in-memory cache时,会在in-memory cache上发生严重的颠簸 换个角度再看,普遍提到的使用in-memory cache的场景是把元数据表的column family声明为IN_MEMORY=>’true。实际上这里的潜台词是:元数据表都很小。其时我们也可以大胆地把一些需要经常访问的,总体积不会超过in-memory cache的column family都设为IN_MEMORY=>’true’从而更加充分地利用cache空间。普通的block永远是不会被放入in-memory cache的,只存放少量metadata是对in-memory cache资源的浪费 操作命令如下(建表时或alter已创建的表): hbase(main):002:0> create ' Test ' ,{NAME=>' d ' ,IN_MEMORY=>' true ' } 0 row(s) in 4.4970 seconds => Hbase::Table - Test hbase(main): 003:0> describe ' Test ' Table Test is ENABLED Test COLUMN FAMILIES DESCRIPTION {NAME => ' d ' , BLOOMFILTER => ' ROW ' , VERSIOnS=> ' 1 ' , IN_MEMORY => 'true' , KEEP_DELETED_CELLS => ' FALSE ' , DATA_BLOCK_ENCODING => ' NONE ' , TTL => ' FOREVER ' , COMPRESSION => ' NONE ' , MIN_VERSIOnS=> ' 0 ' , BLOCKCACHE => ' true ' , BLOCKSIZE => ' 65536 ' , REPLICATION_SCOPE => ' 0 ' } 1 row(s) in 0.2530 seconds hbase(main): 004:0> create ' Test1 ' ,' d ' 0 row(s) in 2.2400 seconds => Hbase::Table - Test1 hbase(main): 005:0> disable ' Test1 ' 0 row(s) in 2.2730 seconds hbase(main): 006:0> alter ' Test1 ' ,{NAME=>' d ' ,IN_MEMORY=>' true ' } Updating all regions with the new schema... 1/1 regions updated. Done. 0 row(s) in 2.4610 seconds hbase(main): 007:0> enable ' Test1 ' 0 row(s) in 1.3370 seconds hbase(main):00 8:0> describe ' Test1 ' Table Test1 is ENABLED Test1 COLUMN FAMILIES DESCRIPTION {NAME => ' d ' , BLOOMFILTER => ' ROW ' , VERSIOnS=> ' 1 ' , IN_MEMORY => ' true ' , KEEP_DELETED_CELLS => ' FALSE ' , DATA_BLOCK_ENCODING => ' NONE ' , TTL => ' FOREVER ' , COMPRESSION => ' NONE ' , MIN_VERSIOnS=> ' 0 ' , BLOCKCACHE => ' true ' , BLOCKSIZE => ' 65536 ' , REPLICATION_SCOPE => ' 0 ' } 1 row(s) in 0.0330 seconds hbase(main):00 9:0>
COMPRESSION/ENCODING :
Compression就是在用CPU资源换取磁盘空间资源,对读写性能并不会有太大影响,HBase目前提供了三种常用的压缩方式:GZip | LZO | Snappy HBase在写入数据块到HDFS之前会首先对数据块进行压缩 ,再落盘 ,从而可以减少磁盘空间使用量 读数据的时候首先从HDFS中加载出block块 之后进行解压缩 ,然后再缓存到BlockCache ,最后返回给用户。写路径和读路径分别如下 结合上图,来看看数据压缩对资源使用情况以及读写性能的影响: 资源使用情况: 压缩最直接、最重要的作用即是减少数据硬盘容量 ,理论上snappy压缩率可以达到5:1,但是根据测试数据不同,压缩率可能并没有理论上理想;压缩/解压缩无疑需要大量计算,需要大量CPU资源 ;根据读路径来看,数据读取到缓存之前block块会先被解压,缓存到内存中的block是解压后的,因此和不压缩情况相比,内存前后基本没有任何影响读写性能: 因为数据写入是先将kv数据值写到缓存,最后再统一flush的硬盘,而压缩是在flush这个阶段执行的,因此会影响flush的操 作,对写性能本身并不会有太大影响;而数据读取如果是从HDFS中读取的话,首先需要解压缩,因此理论上读性能会有所下降 ;如果数据是从缓存中读取,因为缓存中的block块已经是解压后的,因此性能不会有任何影响;一般情况下大多数读都是热点读,缓存读占大部分比例,压缩并不会对读有太大影响官方分别从压缩率,编解码速率三个方面对其进行对比如下图: 综合来看,Snappy的压缩率最低,但是编解码速率最高,对CPU的消耗也最小,目前一般建议使用Snappy 从上图看数据编码对资源使用情况以及读写性能的影响: 资源使用情况: 和压缩一样,编码最直接、最重要的作用也是减少数据硬盘容量 ,但是数据编码压缩率一般没有数据压缩的压缩率高,理论上只有5:2;编码/解码一般也需要大量计算,需要大量CPU资源 ;根据读路径来看,数据读取到缓存之前block块并没有被解码,缓存到内存中的block是编码后的,因此和不编码情况相比,相同数据block快占用内存更少,即内存利用率更高 读写性能: 和数据压缩相同,数据编码也是在数据flush到hdfs阶段执行的,因此并不会直接影响写入过程;前面讲到,数据块是以编码形式缓存到blockcache中的,因此同样大小的blockcache可以缓存更多的数据块,这有利于读性能。另一方面,用户从缓存中加载出来数据块之后并不能直接获取KV ,而需要先解码,这却不利于读性能 。可见,数据编码在内存充足的情况下会降低读性能 ,而在内存不足的情况下需要经过测试才能得出具体结论HBase目前提供了四种常用的编码方式:Prefix | Diff | Fast_Diff | Prefix_Tree 。 压缩与编码使用测试结果示例,来源于:http://hbasefly.com/2016/07/02/hbase-pracise-cfsetting/ 结果分析: 数据压缩率并没有理论上0.2那么高,只有0.7左右,这和数据结构有关系。其中压缩、编码、压缩+编码三种方式的压缩率基本相当 随机读场景:和默认配置相比,snappy压缩在性能上没有提升,CPU开销却上升了38%;prefix_tree性能上没有提升,CPU利用率也基本相当;snappy+prefix_tree性能没有提升,CPU开销上升了38% 区间扫描场景:和默认配置相比,snappy压缩在性能上略有10%的提升,但是CPU开销却上升了23%;prefix_tree性能上略有4%左右的下降,但是CPU开销也下降了5%,snappy+prefix_tree在性能上基本没有提升,CPU开销却上升了23% 在任何场景下开启prefix_tree编码都是安全的 在任何场景下都不要同时开启snappy压缩和prefix_tree编码 通常情况下snappy压缩并不能比prefix_tree编码获得更好的优化结果,如果需要使用snappy需要针对业务数据进行实际测试 VERSIONS :
用于定义某列族所能记录的最多的版本数量,默认值是3,即每个单元格的最大版本数量是3对于更新频繁的应用,建设设置为1,可以快速淘汰无用的数据,节省存储空间同时还能提升查询效率 同样道理,可在建表时指定或通过alter修改表结构实现 TTL :
TTL:Time To Live 用于定义列族中单元格存活时间,过期数据自动删除 TTL属性特性: 单位是秒,默认值:FOREVEN (永不过期) 当一行所有列都过期后,RowKey也会被删除 若TTL设置为两个月,则时间戮为2个月之前的数据不能插入
推荐阅读
publicclassBindActionextendsActionSupport{privateStringproString;privateStringcitString; ...
[详细]
蜡笔小新 2024-11-21 16:25:41
本文详细介绍了在 CentOS 系统中如何创建和管理 SWAP 分区,包括临时创建交换文件、永久性增加交换空间的方法,以及如何手动释放内存缓存。 ...
[详细]
蜡笔小新 2024-11-21 19:01:54
本文详细介绍如何使用MySQL数据库进行环境搭建,包括创建数据库表并插入示例数据。随后,逐步指导如何配置Maven项目,整合Spring框架与MyBatis,实现高效的数据访问。 ...
[详细]
蜡笔小新 2024-11-21 18:39:23
本文介绍如何通过继承TabActivity来创建Android应用中的顶部选项卡。通过简单的步骤,您可以轻松地添加多个选项卡,并实现基本的界面切换功能。 ...
[详细]
蜡笔小新 2024-11-21 17:47:42
本文详细介绍了在Windows系统中如何配置Nginx以实现高效的缓存加速功能,包括关键的配置文件设置和示例代码。 ...
[详细]
蜡笔小新 2024-11-21 16:19:57
Web动态服务器Python基本实现 ...
[详细]
蜡笔小新 2024-11-21 08:01:30
本文详细介绍了在 Ubuntu 16.04 系统上安装和配置 PostgreSQL 数据库的方法,包括如何设置监听地址、启用密码加密、更改默认用户密码以及调整客户端访问控制。 ...
[详细]
蜡笔小新 2024-11-20 22:17:50
本文详细介绍了如何正确设置Shadowsocks公共代理,包括调整超时设置、检查系统限制、防止滥用及遵守DMCA法规等关键步骤。 ...
[详细]
蜡笔小新 2024-11-20 20:41:33
本文探讨了如何通过Service Locator模式来简化和优化在B/S架构中的服务命名访问,特别是对于需要频繁访问的服务,如JNDI和XMLNS。该模式通过缓存机制减少了重复查找的成本,并提供了对多种服务的统一访问接口。 ...
[详细]
蜡笔小新 2024-11-20 19:26:30
本文详细介绍了如何利用 Bootstrap Table 实现数据展示与操作,包括数据加载、表格配置及前后端交互等关键步骤。 ...
[详细]
蜡笔小新 2024-11-20 17:21:26
本文介绍了SIP(Session Initiation Protocol,会话发起协议)的基本概念、功能、消息格式及其实现机制。SIP是一种在IP网络上用于建立、管理和终止多媒体通信会话的应用层协议。 ...
[详细]
蜡笔小新 2024-11-21 17:42:08
在开发微信公众号或小程序的时候,由于微信平台规则的限制,部分接口需要通过线上域名才能正常访问。但我们一般都会在本地开发,因为这能快速的看到 ...
[详细]
蜡笔小新 2024-11-21 11:15:53
近期尝试从www.hub.sciverse.com网站通过编程手段获取数据时遇到问题,起初尝试使用WebBrowser控件进行数据抓取,但发现使用GET方法翻页时,返回的HTML代码始终相同。进一步探究后了解到,该网站的数据是通过Ajax异步加载的,可通过HTTP查看详细的JSON响应。 ...
[详细]
蜡笔小新 2024-11-21 10:36:30
本文详细介绍在CentOS操作系统上安装和配置ProFTPD服务的方法,包括基本配置、安全设置及高级功能的启用。 ...
[详细]
蜡笔小新 2024-11-21 09:45:56
阅读目录1.hashchange2.pushState本文也是一篇基础文章。继上文之后,本打算去研究pushState,偶然在一些信息中发现了锚点变 ...
[详细]
蜡笔小新 2024-11-20 20:05:37