1 背景
HBase的生产环境中,每个业务之间的重要性是不一致的,每个业务的数据量、读写需求也不一致,一个集群中往往有很多个业务,有的同学可以执行一个耗时的scan操作,整个集群的资源被大量占用,其它非常重要的业务就被挤压资源,造成一系列的事故。而当请求量异常大时,也会造成RegionServer不稳定。基于这些问题,hbase提出了Quotas,它可以限制namespace,user,table这些级别,涉及read/write以及storage的限制,包括限制单位时间的条数、size以及表或者namesapce的存储等。
本文重点介绍HBase中的资源限制方案Quotas,主要对其使用方式、实现原理进行介绍,对hbase1.2版本的限流和2.1版本的限流进行比较,最后指出当前hbase quota存在的问题。
2 使用方式介绍
2.1 Quotas的启用
Quotas默认是关闭的,如果需要使用时,只需要在hbase-site.xml配置中增加hbase.quota.enabled=true即可,相关的配置如下:
<property> <name>hbase.quota.enabled</name> <value>true</value> </property> <property> <name>hbase.quota.rate.limiter</name> <value>org.apache.hadoop.hbase.quotas.AverageIntervalRateLimiter</value> </property> |
2.2 Quota语法
- Quota主要针对用户、namespace以及表的QPS和请求大小进行限制;
- THROTTLE_TYPE可以取值read、write或者read+write(默认)
- 时间单位可以是:sec、min、hour、day
- size限制单位表示:B(字节)、K(千字节)、M(兆字节)、G(千兆字节)、T(兆兆字节)、P(PB级);
- 请求数的限制表示为整数,后跟字符串 req、req/time 或者 size/time,例如:10req/day或者100P/hour;
- table或namespace的数量表示为整数,比如一个namespace限制创建多少张表;
配置的维度 | Limit限制的类型 | 请求Size还是请求的次数 | 单位时间 |
NAMESPACE USER TABLE USER+NAMESPACE USER+TABLE | READ WRITE READ+WRITE | 请求次数:req 请求Size:bkmgtp | sec, min, hour, day |
2.3 Setting Request Quotas
你可以事先设置quota,也可以在运行时修改阈值。默认修改后5分钟生效。该时间可以通过hbase-site.xml中的hbase.quota.refresh.period配置项进行修改,该配置项的单位是毫秒。
设置quota的示例:
#将用户u1限制为每秒10个请求 hbase> set_quota TYPE => THROTTLE, USER => 'u1', LIMIT => '10req/sec' #将用户u1限制为每秒10个读取请求 hbase> set_quota TYPE => THROTTLE, THROTTLE_TYPE => READ, USER => 'u1', LIMIT => '10req/sec' #将用户u1限制为每天10 M hbase> set_quota TYPE => THROTTLE, USER => 'u1', LIMIT => '10M/day' #将用户u1限制为每秒10 M写入大小 hbase> set_quota TYPE => THROTTLE, THROTTLE_TYPE => WRITE, USER => 'u1', LIMIT => '10M/sec' # 将用户u1限制为每分钟5k在表t2 hbase> set_quota TYPE => THROTTLE, USER => 'u1', TABLE => 't2', LIMIT => '5K/min' #在表t2上将用户u1限制为每秒10次读取请求 hbase> set_quota TYPE => THROTTLE, THROTTLE_TYPE => READ, USER => 'u1', TABLE => 't2', LIMIT => '10req/sec' # 在用户名u1上删除用户u1的现有限制ns2 hbase> set_quota TYPE => THROTTLE, USER => 'u1', NAMESPACE => 'ns2', LIMIT => NONE #限制所有用户在命名空间ns1上每小时发出10个请求 hbase> set_quota TYPE => THROTTLE, NAMESPACE => 'ns1', LIMIT => '10req/hour' # 在表t1 上将所有用户限制为每小时10T hbase> set_quota TYPE => THROTTLE, TABLE => 't1', LIMIT => '10T/hour' #从用户u1中删除所有现有限制 hbase> set_quota TYPE => THROTTLE, USER => 'u1', LIMIT => NONE #列出命名空间ns2中用户u1的所有 quotahbase> list_quotas USER => 'u1, NAMESPACE => 'ns2' #列出名称空间ns2的所有quota hbase> list_quotas NAMESPACE => 'ns2' #列出表t1的所有quotas hbase> list_quotas TABLE => 't1' # list all quotas hbase> list_quotas |
可以通过应用GLOBAL_BYPASS属性来设置全局限制并从限制中排除用户或表
#列出命名空间ns1的限制 hbase> set_quota NAMESPACE => 'ns1', LIMIT => '100req/min' #用户u1不受限制 hbase> set_quota USER => 'u1', GLOBAL_BYPASS => true |
2.4 Limiting Tables Per Namespace
# 创建一个最多包含5个表的命名空间 hbase> create_namespace 'ns1', {'hbase.namespace.quota.maxtables'=>'5'} # 更改现有命名空间,最多包含8个表 hbase> alter_namespace 'ns2', {METHOD => 'set', 'hbase.namespace.quota.maxtables'=>'8'} # 显示命名空间的quota信息 hbase> describe_namespace 'ns2' # 更改现有命名空间以删除quota hbase> alter_namespace 'ns2', {METHOD => 'unset', NAME=>'hbase.namespace.quota.maxtables'} |
2.5 Limiting Regions Per Namespace
# 创建一个最多包含10个region的命名空间 hbase> create_namespace 'ns1', {'hbase.namespace.quota.maxregions'=>'10' # 显示命名空间hbase的quota信息 hbase> describe_namespace 'ns1' # 更改现有命名空间最多有20个表 hbase> alter_namespace 'ns2', {METHOD => 'set', 'hbase.namespace.quota.maxregions'=>'20'} # 更改现有命名空间以删除quota hbase> alter_namespace 'ns2', {METHOD => 'unset', NAME=> 'hbase.namespace.quota.maxregions'} |
2.6 Space Quotas(HBase2.X)
HBASE-16961引入了一种新的HBase Quotas来利用:filesystem quotas。这些'space'qutas能限制namespace和table的size大小。如果不加限制的话,用户可以无限制的往表中写数据,极端的情况是,大量的数据写入,造成没有空间可以,hbase无法预写日志或者同步数据。
可以通过set_quota和list_quota的HBase shell命令设置空间配额。空间配额的TYPE是SPACE,并且具有LIMIT和POLICY两个属性。LIMIT是一个字符串,指的是表或命名空间最大占用的空间量。例如有效的LIMIT可以是'10G','2T',或'256M'。POLICY指当配额对象的使用量超过配额时,HBase采取的行动,具体包括以下几种:
POLICY | 含义 |
NO_INSERTS | No new data may be written (e.g. Put, Increment, Append) |
NO_WRITES | Same as NO_INSERTS but Deletes are also disallowed |
NO_WRITES_COMPACTIONS | Same as NO_WRITES but compactions are also disallowed This policy only prevents user-submitted compactions. System can still run compactions |
DISABLE | The table(s) are disabled, preventing all read/write access |
Space quota的例子:
# 在表't1'上限制最大1GB, 超过时禁止Puts/Increments/Appends hbase> set_quota TYPE => SPACE, TABLE => 't1', LIMIT => '1G', POLICY => NO_INSERTS # 在'ns1'上限制最大50TB, 超过时禁止Puts/Increments/Appends/Deletes hbase> set_quota TYPE => SPACE, NAMESPACE => 'ns1', LIMIT => '50T', POLICY => NO_WRITES # 在表't3'上限制最大2TB, 超过时禁止writes and compactions hbase> set_quota TYPE => SPACE, TABLE => 't3', LIMIT => '2T', POLICY => NO_WRITES_COMPACTIONS # 在表't2'上限制最大50GB, 超过时disabling the table hbase> set_quota TYPE => SPACE, TABLE => 't2', LIMIT => '50G', POLICY => DISABLE |
2.7 Table and Namespace space quotas
hbase> create_namespace 'ns1' hbase> create 'ns1:t1' hbase> create 'ns1:t2' hbase> create 'ns1:t3' hbase> set_quota TYPE => SPACE, NAMESPACE => 'ns1', LIMIT => '100T', POLICY => NO_INSERTS hbase> set_quota TYPE => SPACE, TABLE => 'ns1:t2', LIMIT => '200G', POLICY => NO_WRITES hbase> set_quota TYPE => SPACE, TABLE => 'ns1:t3', LIMIT => '20T', POLICY => NO_WRITES |
在上面的场景中,ns1不允许命名空间中的表在文件系统上消耗超过100TB的空间。表'ns1:t2'的大小仅允许为200GB,并且当使用率超过此限制时将禁止所有写入。表'ns1:t3'的大小允许增长到20TB,超过时将禁止所有写入和删除。由于'ns1:t1'上没有表配额,它的限制是100TB减去'ns1:t2'和'ns1:t3'的当前使用量。
3 实现原理
3.1 request quota的实现过程
3.2 Limiting Tables/ Regions Per Namespace的实现过程
3.3 HBase2.1 的space quota的实现过程
4 1.2版本与2.1版本的比较
2.1版本比1.2版本新增了一个Space Quota的功能。
5 存在的问题与改进
通过查看了官网和源码,发现hbase的quota可能存在以下的不足(是否不足待讨论),并针对这些不足,提了改进意见,如下表所示:
序号 | 存在的问题 | 改进措施 |
1 | Request Quota中,如果达到限流条件,读写请求直接被拒绝 | 可以增加一个配置项,如果达到限流,该请求可以直接拒绝,也可以重新加入一个自定义的队列,由一个定时任务将限流异常请求回塞到读写请求队列。 |
2 | put/get一次请求,request num就会加1,而scan请求的request num是按照一次完整的scan发送的rpc的次数进行累加的,一次rpc请求数加1 | 因为scan的一次rpc读取的数据条数肯定远远大于put/get,是否需要给scan加一个权重 |
3 | Hbase quota对hbase的系统表未做限流 | 是否需要对meta表做限流 |
4 | 只提供read和write两种类型的粗粒度限流,read中没有区分开get和scan | 是否需要区分,前一版本的限流的改进就是将read和scan进行了区分 |
6 附录
6.1 SimpleRpcScheduler初始化读写队列和读写线程数的逻辑
- 相关的配置
<property> <name>hbase.regionserver.handler.count</name> <value>128</value> <description>默认为30,服务器端用来处理用户请求的线程数。生产线上通常需要将该值调到100~200。 </description> </property> <property> <name>hbase.ipc.server.callqueue.handler.factor</name> <value>0.1</value> <description>为0则共享全部队列,默认是0,假如该值为0.1,那么服务器就会设置handler.count * 0.1 = 30 * 0.1 = 3个队列 </description> </property> <property> <name>hbase.ipc.server.callqueue.read.ratio</name> <value>0.5</value> <description>默认为0,服务器端设置读写业务分别占用的队列百分比以及handler百分比。假如该值为0.5,表示读写各占一半队列,同时各占一半handler </description> </property> <property> <name>hbase.ipc.server.callqueue.scan.ratio</name> <value>0.2</value> </property> |
计算方法:
- count 是100
- 队列个数是100 * 0.1 = 10
- 写队列 10 * 0.5 = 5
- 读队列总值 10 * 0.5 = 5
- get 队列 5 - 5 * 0.2 = 4
- scan队列 5 * 0.2 = 1
2)对应的代码
6.2 HBase Quota未对系统表做限流
6.3 HBase space quota相关的参数
参数 | 含义 | 默认值 |
hbase.master.quotas.snapshot.chore.period | 定期扫描表存储大小线程的执行间隔时间 | 1000 * 60 * 5(5分钟) |
hbase.master.quotas.snapshot.chore.delay | 定期扫描表存储大小线程在多久后执行 | 1000L * 60L(1分钟) |
hbase.master.quotas.snapshot.chore.timeunit | 上述两个时间的单位 | ms |
hbase.regionserver.quotas.policy.refresher.chore.period | Space quota的定时线程的执行间隔时间 | 1000 * 60 * 1(1分钟) |
hbase.regionserver.quotas.policy.refresher.chore.delay | Space quota的定时线程在多久后执行 | 1000L * 15L(15秒) |
hbase.regionserver.quotas.policy.refresher.chore.timeunit | 上述两个时间的单位 | ms |
6.4 一次HBase Scan请求中RPC请求的次数
一次scan请求中RPC的次数与三个参数有关
参数 | 含义 | 默认值 |
setCaching() | 单次RPC请求的数据条数 | Integer.MAX_VALUE |
setBatch() | 单次RPC请求的数据列数量 | 默认一个result包含某row所有的列 |
setMaxResultSize | 单次RPC请求的返回数据量大小 | 2G |
计算公式:
Result 返回的个数 =(row数 * 每行的列数)/ Min(每行列数,Batch大小)
RPC 返回的个数 = (row数 * 每行的列数)/ Min(每行列数,Batch大小) / Caching大小
setCaching 是用来控制rpc的个数,setMaxResultSize 从单次rpc返回的所有Result的总大小来控制某次rpc是否结束。
6.5 Quota判断的相关的代码
对数据请求的quotas限制在region server上进行。RSRpcServices收到RPC请求后,对于get,mutate,scan操作,其执行的RegionServerQuotaManager的checkQuota如下:
multi(multiple actions on a table: get, mutate, and/or execCoprocessor)操作,调用RegionServerQuotaManager的如下checkQuota方法进行检查:
我们可以看到上面两个方法都调用了同一个checkQuota方法,只是输入参数不同
从上面代码中可以看到,这里最重要的两个方法就是getQuota和checkQuota。我们先看getQuota做了什么?
简单解释一下代码就是:首先,从Region Server的quotaCache中拿到关于此用户的UserQuotaState。然后从UserQuotaState中提取QuotaLimiter。最后新建一个OperationQuota返回。
再来看DefaultOperationQuota的checkQuota方法:
方法中,首先估计各个操作的大小,这个估计的方法很简单,如下面代码所示。不管操作类型是什么,计算方法都一样:
回到DefaultOperationQuota的checkQuota方法,最重要的是limiter.checkQuota(writeConsumed, readConsumed)
这段代码。进入TimeBasedLimiter的checkQuota()方法:
这个函数会把所有的limiter都检查一遍,但凡有一个Limiter不满足条件,就抛出异常。如果所有的判断条件都通过,下面就走到了limiter.grabQuota(writeConsumed, readConsumed)
这一步。这个方法的详细代码如下:
至此,各个操作的Quota检查就算完成了。但是,在哪调整真正的读写消费量呢?RSRPCServices的get,mutate,scan和multi方法会调用checkQuota方法。拿get方法举例:
这段代码很长,我们只看与Quota相关的部分。checkQuota部分上面已经介绍了,方法最后有两句代码quota.addGetResult(r)
和quota.close()
,它们是做什么用的呢?先看quota.addGetResult(r)
所调用的方法:
从代码里不难看出,这是在计算get操作所返回的结果的真正大小,也就是真正的消费量。
直接看quota.close()
所调用的方法:
最后进行的步骤就是把真正消费量和估计消费量之间的差额给补齐,多退少补。