• HBase写入过快性能分析及调优


    HBase整个简单写入流程

    client api ==> RPC ==>  server IPC ==> RPC queue ==> RPC handler ==> write WAL ==> write memstore ==> flush to  filesystem

    整个写入流程从客户端调用API开始,数据会通过protobuf编码成一个请求,通过scoket实现的IPC模块被送达server的RPC队列中。最后由负责处理RPC的handler取出请求完成写入操作。写入会先写WAL文件,然后再写一份到内存中,也就是memstore模块,当满足条件时,memstore才会被flush到底层文件系统,形成HFile。


    当写入过快时会遇见什么问题?

    HBase会检查Memstore的大小,如果Memstore超过设定的blockingMemStoreSize则触发flush的操作,并抛出RegionTooBusyException,阻塞写操作的进行。如下源码所示:

      private void checkResources()

        throws RegionTooBusyException {

        // If catalog region, do not impose resource constraints or block updates.

        // 如果是Meta Region,不实施资源约束或阻塞更新

        if (this.getRegionInfo().isMetaRegion()) return;

        // 如果Region当前内存大小超过阈值

        // 这个memstoreSize是当前时刻HRegion上MemStore的大小,它是在Put、Append等操作中调用addAndGetGlobalMemstoreSize()方法实时更新的。

        // 而blockingMemStoreSize是HRegion上设定的MemStore的一个阈值,当MemStore的大小超过这个阈值时,将会阻塞数据更新操作

    if (this.memstoreSize.get() > this.blockingMemStoreSize) {

          // 更新阻塞请求计数器

          blockedRequestsCount.increment();

          // 请求刷新Region

    requestFlush();

          // 抛出RegionTooBusyException异常

          throw new RegionTooBusyException("Above memstore limit, " +

              "regionName=" + (this.getRegionInfo() == null ? "unknown" :

              this.getRegionInfo().getRegionNameAsString()) +

              ", server=" + (this.getRegionServerServices() == null ? "unknown" :

              this.getRegionServerServices().getServerName()) +

              ", memstoreSize=" + memstoreSize.get() +

              ", blockingMemStoreSize=" + blockingMemStoreSize);

        }

      }

    blockingMemStoreSize的大小由hbase.hregion.memstore.flush.size和hbase.hregion.memstore.block.multiplier共同作用,等于两者相乘,我们的hbase.hregion.memstore.flush.size设置的是256M,hbase.hregion.memstore.block.multiplier设置的是3,因此:blockingMemStoreSize=805306368。具体的blockingMemStoreSize计算的代码如下:

        this.blockingMemStoreSize = this.memstoreFlushSize *

      conf.getLong(HConstants.HREGION_MEMSTORE_BLOCK_MULTIPLIER,

        HConstants.DEFAULT_HREGION_MEMSTORE_BLOCK_MULTIPLIER);

    写入过快时,memstore的水位会马上被推高。
    你可能会看到以下类似日志:

    RegionTooBusyException: Above memstore limit, regionName=xxxxx ...

    这个是Region的memstore占用内存大小超过正常的4倍,这时候会抛异常,写入请求会被拒绝,客户端开始重试请求。当达到128M的时候会触发flush memstore,当达到128M * 4还没法触发flush时候会抛异常来拒绝写入。两个相关参数的默认值如下:

    hbase.hregion.memstore.flush.size=128M
    hbase.hregion.memstore.block.multiplier=4
    

    或者这样的日志:

    regionserver.MemStoreFlusher: Blocking updates on hbase.example.host.com,16020,1522286703886: the global memstore size 1.3 G is >= than blocking 1.3 G size
    regionserver.MemStoreFlusher: Memstore is above high water mark and block 528ms

    这是所有region的memstore内存总和开销超过配置上限,默认是配置heap的40%,这会导致写入被阻塞。目的是等待flush的线程把内存里的数据flush下去,否则继续允许写入memestore会把内存写爆

    hbase.regionserver.global.memstore.upperLimit=0.4  # 较旧版本,新版本兼容
    hbase.regionserver.global.memstore.size=0.4 # 新版本
    

    当写入请求由于达到memstore上限而被阻塞,队列会开始积压,如果运气不好最后会导致OOM,你可能会发现JVM由于OOM crash或者看到如下类似日志:

    ipc.RpcServer: /192.168.x.x:16020 is unable to read call parameter from client 10.47.x.x
    java.lang.OutOfMemoryError: Java heap space

    HBase这里我认为有个很不好的设计,捕获了OOM异常却没有终止进程。这时候进程可能已经没法正常运行下去了,你还会在日志里发现很多其它线程也抛OOM异常。比如stop可能根本stop不了,RS可能会处于一种僵死状态。


    如何避免RS OOM?

    一种是加快flush速度:

    hbase.hstore.blockingWaitTime = 90000 ms
    hbase.hstore.flusher.count = 2
    hbase.hstore.blockingStoreFiles = 10
    

    当达到hbase.hstore.blockingStoreFiles配置上限时,会导致flush阻塞等到compaction工作完成。阻塞时间是hbase.hstore.blockingWaitTime,可以改小这个时间。hbase.hstore.flusher.count可以根据机器型号去配置,可惜这个数量不会根据写压力去动态调整,配多了,非导入数据多场景也没用,改配置还得重启。

    同样的道理,如果flush加快,意味这compaction也要跟上,不然文件会越来越多,这样scan性能会下降,开销也会增大。

    hbase.regionserver.thread.compaction.small = 1
    hbase.regionserver.thread.compaction.large = 1
    

    增加compaction线程会增加CPU和带宽开销,可能会影响正常的请求。如果不是导入数据,一般而言是够了。好在这个配置在云HBase内是可以动态调整的,不需要重启。

    上述配置都需要人工干预,如果干预不及时server可能已经OOM了,这时候有没有更好的控制方法?

    hbase.ipc.server.max.callqueue.size = 1024 * 1024 * 1024 # 1G
    

    直接限制队列堆积的大小。当堆积到一定程度后,事实上后面的请求等不到server端处理完,可能客户端先超时了。并且一直堆积下去会导致OOM,1G的默认配置需要相对大内存的型号。当达到queue上限,客户端会收到CallQueueTooBigException 然后自动重试。通过这个可以防止写入过快时候把server端写爆,有一定反压作用。线上使用这个在一些小型号稳定性控制上效果不错。

    更多参照:

    https://linux.cn/thread-17641-1-1.html

    https://www.cnblogs.com/quchunhui/p/7561972.html

    正因为当初对未来做了太多的憧憬,所以对现在的自己尤其失望。生命中曾经有过的所有灿烂,终究都需要用寂寞来偿还。
  • 相关阅读:
    图片上传-下载-删除等图片管理的若干经验总结3-单一业务场景的完整解决方案
    图片上传-下载-删除等图片管理的若干经验总结2
    HDU 1195 Open the Lock
    HDU 1690 Bus System
    HDU 2647 Reward
    HDU 2680 Choose the best route
    HDU 1596 find the safest road
    POJ 1904 King's Quest
    CDOJ 889 Battle for Silver
    CDOJ 888 Absurdistan Roads
  • 原文地址:https://www.cnblogs.com/candlia/p/11919902.html
Copyright © 2020-2023  润新知