Java程序员学习大数据之HBASE(二)

HBase数据flush刷写过程
hbase-default.xml配置文件中有这么几项配置(见下面),只要regionserver其中某一个MemStore满足第一点或者第二点,都会进行regionserver级别的flush,即所有MemStore都要flush;而满足第三点的,就会进行HRegion级别的flush,即某个HRegion下的所有MemStore都要flush。

hbase.regionserver.global.memstore.size:regionServer的全局MemStore的大小,超过该大小会触发flush到磁盘的操作,默认值是堆大小的40%,而且regionserver级别的flush会阻塞客户端读写。说明白点就是当regionserver中所有Memstore的大小加起来达到了当前regionserver堆内存的40%就触发flush操作,不管MemStore有多小,每个MemStore都要进行flush到磁盘。
hbase.regionserver.optionalcacheflushinterval:内存中的文件在自动刷新之前能够存活的最长时间,默认是1h,也就是说,当regionserver中某个MemStore存活时间达到了1h,所有MemStore都会进行flush。
hbase.hregion.memstore.flush.size:单个region里MemStore的缓存大小,超过了这个大小那么整个HRegion就会flush,默认大小为128M。

需要注意的是HBase的最小flush单元是HRegion而不是单个MemStore。

Flush是由HMaster触发的,Flush顺序是按照Memstore由大到小执行,先Flush Memstore最大的Region,再执行次大的,直至总体Memstore内存使用量低于阈值(hbase.regionserver.global.memstore.lowerLimit)。
原文链接:https://blog.csdn.net/hzj1998/article/details/99116931

hbase列族rowkey设计
1. 列簇设计
  追求的原则是:目前Hbase官方建议不超过2~3个column family。因为某个column family在flush的时候,它邻近的column family也会因关联效应被触发flush,最终导致系统产生更多的I/O。
最优设计是:将所有相关性很强的 key-value 都放在同一个列簇下,这样既能做到查询效率 最高,也能保持尽可能少的访问不同的磁盘文件。
以用户信息为例,可以将必须的基本信息存放在一个列族,而一些附加的额外信息可以放在 另一列族。

2. Rowkey 设计三原则
  一条数据的唯一标识就是 rowkey,那么这条数据存储于哪个分区,取决于 rowkey 处于哪个一个预分区的区间内,设计 rowkey 的主要目的 ,就是让数据均匀的分布于所有的 region 中,在一定程度上防止数据倾斜。接下来我们就谈一谈 rowkey 常用的设计方案。

rowkey 长度原则
  Rowkey 是一个二进制码流,Rowkey 的长度被很多开发者建议说设计在 10~100 个字节,不过建议是越短越好,不要超过 16 个字节,存为byte[]字节数组,一般设计成定长的。

原因如下:

1、数据的持久化文件 HFile 中是按照 KeyValue 存储的,如果 Rowkey 过长比如 100 个字 节,1000 万列数据光 Rowkey 就要占用 100*1000 万=10 亿个字节,将近 1G 数据,这会极大 影响 HFile 的存储效率;

2、MemStore 将缓存部分数据到内存,如果 Rowkey 字段过长内存的有效利用率会降低, 系统将无法缓存更多的数据,这会降低检索效率。因此 Rowkey 的字节长度越短越好。

3、目前操作系统是都是 64 位系统,内存 8 字节对齐。控制在 16 个字节,8 字节的整数 倍利用操作系统的最佳特性。

rowkey 散列原则
  如果 Rowkey 是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将 Rowkey 的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个 Regionserver 实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有 新数据都在一个 RegionServer 上堆积的热点现象,这样在做数据检索的时候负载将会集中 在个别 RegionServer,降低查询效率。
  
  row key是按照字典序存储,因此,设计row key时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为row key的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE - timestamp作为row key,这样能保证新写入的数据在读取时可以被快速命中。
rowkey 唯一原则
  必须在设计上保证其唯一性。rowkey 是按照字典顺序排序存储的,因此,设计 rowkey 的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问 的数据放到一块。

3.热点数据
 HBase 中的行是按照 rowkey 的字典顺序排序的,这种设计优化了 scan 操作,可以将相 关的行以及会被一起读取的行存取在临近位置,便于 scan。然而糟糕的 rowkey 设计是热点 的源头。 热点发生在大量的 client 直接访问集群的一个或极少数个节点(访问可能是读, 写或者其他操作)。大量访问会使热点 region 所在的单个机器超出自身承受能力,引起性能 下降甚至 region 不可用,这也会影响同一个 RegionServer 上的其他 region,由于主机无法服 务其他 region 的请求。 设计良好的数据访问模式以使集群被充分,均衡的利用。 为了避免写热点,设计 rowkey 使得不同行在同一个 region,但是在更多数据情况下,数据 应该被写入集群的多个 region,而不是一个。

防止数据热点的有效措施
  加盐
  这里所说的加盐不是密码学中的加盐,而是在 rowkey 的前面增加随机数,具体就是给 rowkey 分配一个随机前缀以使得它和之前的 rowkey 的开头不同。分配的前缀种类数量应该 和你想使用数据分散到不同的 region 的数量一致。加盐之后的 rowkey 就会根据随机生成的 前缀分散到各个 region 上,以避免热点。

例:生成随机数、hash、散列值

比如: 原本 rowKey 为 1001 的,SHA1
后变成:dd01903921ea24941c26a48f2cec24e0bb0e8cc7 原本 rowKey 为 3001 的,SHA1
后变成:49042c54de64a1e9bf0b33e00245660ef92dc7bd 原本 rowKey 为 5001 的,SHA1
后变成:7b61dec07e02c188790670af43e717f0f46e8913
在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的 rowKey 来 Hash 后作为每个分区的临界值。

哈希
  哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是 可以预测的。使用确定的哈希可以让客户端重构完整的 rowkey,可以使用 get 操作准确获取 某一个行数据

反转
  第三种防止热点的方法是反转固定长度或者数字格式的 rowkey。这样可以使得 rowkey 中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机 rowkey,但是牺牲了 rowkey 的有序性。

反转 rowkey 的例子以手机号为 rowkey,可以将手机号反转后的字符串作为 rowkey,这 样的就避免了以手机号那样比较固定开头导致热点问题

例: 字符串反转

20170524000001 转成 10000042507102 20170524000002 转成 20000042507102
这样也可以在一定程度上散列逐步 put 进来的数据。

例: 字符串拼接

20170524000001_a12e 20170524000001_93i7

时间戳反转
  一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为 rowkey 的一部分对这个问题十分有用,可以用 Long.Max_Value - timestamp 追加到 key 的末尾,例 如 [key][reverse_timestamp] , [key] 的最新值可以通过 scan [key]获得[key]的第一条记录,因 为 HBase 中 rowkey 是有序的,第一条记录是最后录入的数据。比如需要保存一个用户的操 作记录,按照操作时间倒序排序,在设计 rowkey 的时候,可以这样设计 [userId 反转][Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指 定 反 转 后 的 userId , startRow 是 [userId 反 转 ][000000000000],stopRow 是 [userId 反 转][Long.Max_Value - timestamp]

如果需要查询某段时间的操作记录,startRow 是[user 反转][Long.Max_Value - 起始时间], stopRow
是[userId 反转][Long.Max_Value - 结束时间]

HBase的写表优化
1 多HTable并发写
创建多个HTable客户端用于写操作,提高写数据的吞吐量,一个例子:

static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;
wTableLog = new HTable[tableN];
for (int i = 0; i < tableN; i++) {
    wTableLog[i] = new HTable(conf, table_log_name);
    wTableLog[i].setWriteBufferSize(5 * 1024 * 1024); //5MB
    wTableLog[i].setAutoFlush(false);
}

2 HTable参数设置
Auto Flush
通过调用HTable.setAutoFlush(false)方法可以将HTable写客户端的自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存时,才实际向HBase服务端发起写请求。默认情况下auto flush是开启的。

Write Buffer
通过调用HTable.setWriteBufferSize(writeBufferSize)方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值。

WAL Flag
在HBae中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会先写WAL(Write Ahead Log)日志(即HLog,一个RegionServer上的所有Region共享一个HLog),只有当WAL日志写成功后,再接着写MemStore,然后客户端被通知提交数据成功;如果写WAL日志失败,客户端则被通知提交失败。这样做的好处是可以做到RegionServer宕机后的数据恢复。

因此,对于相对不太重要的数据,可以在Put/Delete操作时,通过调用Put.setWriteToWAL(false)或Delete.setWriteToWAL(false)函数,放弃写WAL日志,从而提高数据写入的性能。

值得注意的是:谨慎选择关闭WAL日志,因为这样的话,一旦RegionServer宕机,Put/Delete的数据将会无法根据WAL日志进行恢复。

3 批量写
通过调用HTable.put(Put)方法可以将一个指定的row key记录写入HBase,同样HBase提供了另一个方法:通过调用HTable.put(List)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高,网络传输RTT高的情景下可能带来明显的性能提升。

4 多线程并发写
在客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被flush(如1秒内),同时又保证在数据量大的时候,写buffer一满就及时进行flush。下面给个具体的例子:

for (int i = 0; i < threadN; i++) {
    Thread th = new Thread() {
        public void run() {
            while (true) {
                try {
                    sleep(1000); //1 second
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
synchronized (wTableLog[i]) {
                    try {
                        wTableLog[i].flushCommits();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
}
    };
    th.setDaemon(true);
    th.start();
}

四. HBase的读表优化
1 多HTable并发读
创建多个HTable客户端用于读操作,提高读数据的吞吐量,一个例子:

static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;

rTableLog = new HTable[tableN];
for (int i = 0; i < tableN; i++) {
    rTableLog[i] = new HTable(conf, table_log_name);
    rTableLog[i].setScannerCaching(50);
}

2 HTable参数设置
Scanner Caching
hbase.client.scanner.caching配置项可以设置HBase scanner一次从服务端抓取的数据条数,默认情况下一次一条。通过将其设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的行记录。

有三个地方可以进行配置:三者的优先级越来越高。

1)在HBase的conf配置文件中进行配置;

2)通过调用HTable.setScannerCaching(int scannerCaching)进行配置;

3)通过调用Scan.setCaching(int caching)进行配置。

Scan Attribute Selection
scan时指定需要的Column Family,可以减少网络传输数据量,否则默认scan操作会返回整行所有Column Family的数据。

Close ResultScanner
通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。

3 批量读
通过调用HTable.get(Get)方法可以根据一个指定的row key获取一行记录,同样HBase提供了另一个方法:通过调用HTable.get(List)方法可以根据一个指定的row key列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高而且网络传输RTT高的情景下可能带来明显的性能提升。

4 多线程并发读
在客户端开启多个HTable读线程,每个读线程负责通过HTable对象进行get操作。

5 缓存查询结果
对于频繁查询HBase的应用场景,可以考虑在应用程序中做缓存,当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不再查询HBase;否则对HBase发起读请求查询,然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑LRU等常用的策略。

6 Blockcache
HBase上Regionserver的内存分为两个部分,一部分作为Memstore,主要用来写;另外一部分作为BlockCache,主要用于读。

写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64MB以后,会启动 flush刷新到磁盘。当Memstore的总大小超过限制时(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。

读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache达到上限(heapsize * hfile.block.cache.size * 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。

一个Regionserver上有一个BlockCache和N个Memstore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而Memstore为0.4。对于注重读响应时间的系统,可以将 BlockCache设大些,比如设置BlockCache=0.4,Memstore=0.39,以加大缓存的命中率。

读写方面的信息主要包括了读写的次数以及速度,这个通过ganglia看其实不怎么方便,最好还是自己改造下实现,读写次数反映了集群的使用程度,一般来说需要根据压力测试中形成的报告来设置一个读写次数的阈值,以保护系统的稳定。

读写速度方面主要是显示当前的读写速度状况(当然,也需要有历史的报表),如读写速度是在可接受范围,其实就可以不用过多的关心了,如读写速度不OK的话,则需要进行一定的处理。

如读速度不OK,则需要关注以下几点:

  • 集群均衡吗?
    集群是否均衡主要需要通过三个方面来判断:各region server的region数是否均衡、table的region是否均衡分布还有就是读请求是否均衡分布。

通常各region server的region数是均衡的,这个是目前hbase master的balance策略来保证的,顶多就是有短暂时间的不均衡现象。
table 的region分布则不一定是均衡的,对于有多个table的情况,完全有可能出现某张表的一堆的region在同一台上的现象,对于这种情况,一种方法 是修改master的balance策略,让其在balance时考虑table的region的balance,我们目前采用的是另外一种方法,提供了 一个界面来手工balance table的region,如果是由于table的region不均衡导致了读速度的不OK,可以采用这种办法解决下。

读 请求是否均衡分布一方面取决于table的region的分布状况,另一方面则取决于应用的使用方法,如table的region分布均衡,读请求仍然不 均衡分布的话,说明应用的请求有热点的状况,如这种状况造成了读速度的不OK,可以手工将region进行拆分,并分配到不同的region server上,这是hbase很简单的一种应对热点的解决方法。

  • cache的命中率如何?
    cache的命中率目前通过ganglia以及region server的log都不是很好看,因此我们也进行了改造,更直白的显示cache的命中率的变化状况。

如 cache的命中率不高,首先需要看下目前系统用于做LRUBlockcache的大小是不是比较小,这主要靠调整region server启动的-Xmx以及hfile.block.cache.size参数来控制,可通过修改hbase-env.sh,增加export HBASE_REGIONSERVER_OPTS=”-Xmx16g”来单独的调整region server的heap size,如LRUBlockCache已足够大,cache命中率仍然不高的话,则多数情况是由于随机读无热点造成的,这种情况如果要提升cache命 中率的话,就只能靠加机器了。
在cache的使用率上要关注应用对数据的读取方式,经常整行数据读取的适合设计在同一cf里,不经常整行读取的适合设计在多cf里。

  • bloomfilter打开了吗?
    默 认情况下创建的table是不打开bloomfilter的(可通过describe table来确认,如看到BLOOMFILTER => ‘NONE’则表示未打开),对于随机读而言这个影响还是比较明显的,由于bloomfilter无法在之后动态打开,因此创建表时最好就处理好,方法类 似如此:create ‘t1′, { NAME => ‘f1′, BLOOMFILTER => ‘ROWCOL’ }。

  • Compact
    在某些特殊的应用场景下,为了保证写速度的平稳,可能会考虑不进行Compact,不进行compact会导致读取数据时需要扫描大量的文件,因此compact还是有必要做的。

  • 应用的使用方式
    应用在读取数据时是随机读、有热点的随机读还是连续读,这个对读速度也有很大的影响,这个需要结合业务进行分析,总的来说,hbase在随机读上效率还是很一般的,这和它的存储结构有一定关系。
    另外,应用在读取时如每次都是读取一行的所有数据的话,schema设计的时候在同一个cf下就比较合适。

如写速度不OK,则需要关注以下几点:

  • 集群均衡吗?
    除 了和读一样的集群均衡性问题外,还有一个问题是rowKey的设计问题,因为hbase是按rowKey连续存储的,因此如应用写入数据时rowKey是 连续的,那么就会造成写的压力会集中在单台region server上,因此应用在设计rowKey时,要尽可能的保证写入是分散的,当然,这可能会对有连续读需求的场景产生一定的冲击。

  • 日志中是否出现过以下信息?
    ** Flush thread woke up with memory above low water.
    日 志中出现这个信息说明有部分写出现过阻塞等待的现象,造成这个现象的原因是各个region的memstore使用的大小加起来超过了总的阈值,于是阻塞 并开始找一个region进行flush,这个过程会需要消耗掉一些时间,通常来说造成这个的原因是单台region server上region数太多了,因此其实单台region server上最好不要放置过多的region,一种调节方法是调大split的fileSize,这样可以适当的减少region数,但需要关注调整后 读性能的变化。

** delaying flush up to
当日志中出现这个信息时,可能会造成出现下面的现象,从而产生影响,这个通常是store file太多造成的,通常可以调大点store file个数的阈值。

** Blocking updates for
当 日志中出现这个信息时,表示写动作已被阻塞,造成这个现象的原因是memstore中使用的大小已超过其阈值的2倍,通常是由于上面的delaying flush up to造成的,或者是region数太多造成的,或者是太多hlog造成的,这种情况下会造成很大的影响,如内存够用的话,可以调大阈值,如其他原因则需要 对症下药。

  • split造成的?
    split会造成读写短暂的失败,如写的数据比较大的话,可能会有频繁的split出现,对于这种情况主要需要依靠调大split的filesize(hbase.hregion.max.filesize)来控制。

BLOOMFILTER
BloomFilter的原理是,集合的大小为n,当一个元素被加入集合S时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。

原文链接:https://blog.csdn.net/lrxcmwy2/article/details/81603402

ROWFILTER
RowFilter使用

说明:筛选出匹配的所有的行,支持基于行键过滤数据,可以执行精确匹配,子字符串匹配或正则表达式匹配,过滤掉不匹配的数据。

用法:使用BinaryComparator可以筛选出具有某个行键的行,或者通过改变比较运算符来筛选出符合某一条件的多条数据

比较过滤器
比较过滤器共有五个(Hbase 1.x 版本和2.x 版本相同),见下图:
在这里插入图片描述

RowFilter :基于行键来过滤数据;
FamilyFilterr :基于列族来过滤数据;
QualifierFilterr :基于列限定符(列名)来过滤数据;
ValueFilterr :基于单元格(cell) 的值来过滤数据;
DependentColumnFilter :指定一个参考列来过滤其他列的过滤器,过滤的原则是基于参考列的时间戳来进行筛选 。

前四种过滤器的使用方法相同,均只要传递比较运算符和运算器实例即可构建,然后通过setFilter方法传递给scan:

 Filter filter  = new RowFilter(CompareOperator.LESS_OR_EQUAL,
                                new BinaryComparator(Bytes.toBytes("xxx")));
  scan.setFilter(filter);    

DependentColumnFilter的使用稍微复杂一点,这里单独做下说明。

可以把DependentColumnFilter理解为一个valueFilter和一个时间戳过滤器的组合。DependentColumnFilter有三个带参构造器,这里选择一个参数最全的进行说明:

DependentColumnFilter(final byte [] family, final byte[] qualifier,
                               final boolean dropDependentColumn, final CompareOperator op,
                               final ByteArrayComparable valueComparator)

family :列族
qualifier :列限定符(列名)
dropDependentColumn :决定参考列是否被包含在返回结果内,为true时表示参考列被返回,为false时表示被丢弃
op :比较运算符
valueComparator :比较器
这里举例进行说明:

DependentColumnFilter dependentColumnFilter = new DependentColumnFilter( 
    Bytes.toBytes("student"),
    Bytes.toBytes("name"),
    false,
    CompareOperator.EQUAL, 
    new BinaryPrefixComparator(Bytes.toBytes("xiaolan")));

首先会去查找student:name中值以xiaolan开头的所有数据获得参考数据集,这一步等同于valueFilter过滤器;
其次再用参考数据集中所有数据的时间戳去检索其他列,获得时间戳相同的其他列的数据作为结果数据集,这一步等同于时间戳过滤器;
最后如果dropDependentColumn为true,则返回参考数据集+结果数据集,若为false,则抛弃参考数据集,只返回结果数据集。
————————————————

原文链接:https://blog.csdn.net/m0_37809146/article/details/91128097

预分区

1.通过随机取样随机生成一定数量的RowKey,将取样数据按升序排序放到一个集合里
2.根据预分区的Region个数,对集合进行平均切割
3.HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定预分区的splitKey,即是指定region间的rowkey临界值.
————————————————
原文链接:https://blog.csdn.net/lrxcmwy2/article/details/81603402

Hbase 集群中 HRegionServer 宕机如何解决
HBASE HA
HMaster 会将其所管理的 region 重新分布到其他活动的 RegionServer 上,由于数据和日志都持久在 HDFS中,该操作不会导致数据丢失。
https://blog.csdn.net/weixin_42348333/article/details/81747459

HBase原理-RegionServer宕机数据恢复
https://blog.csdn.net/yongjian_luo/article/details/53084257?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7

发布了2 篇原创文章 · 获赞 2 · 访问量 153

猜你喜欢

转载自blog.csdn.net/qq_41130274/article/details/105282104
今日推荐