1、介绍:
大约一年前,有人要求我为一个时间序列产品调优 HBase 的读写性能。该产品在 AWS i2.4XL 中使用 10 个数据节点,并有 15 个计算节点,其中 10 个用于连续写入,5 个用于读取并运行来自这些数据节点的批处理作业。大多数计算节点都在 r3.xl 上
我们的目标是实现每分钟 1000 万个指标的提取和读取。有一些批处理作业将在协处理器内运行,用于处理这些数据节点上过去 10 分钟和 1 小时的数据。需要对整个集群进行调整。
在本文中,我将仅关注 HBase 客户端调优以及客户端面临的问题和调优方法。当然,这还需要调优 HBase 服务器端,我将在另一个主题中介绍这一点。本文将介绍以下 3 个客户端配置
- 不要使用单个Put,而是批量List<Put>
- 如何控制每个 RegionServer 的 HBase 客户端连接数/Socket 数
- 使用通用连接并共享池
2、开始压测:
我使用 2 Mil/min 指标进行了负载测试,运行了大约一个小时,希望这会起作用。但与大多数假设一样,这个假设也是短暂的,并经历了失败。看起来系统无法处理压力。
2.1)Call queue is full is ipc.server.max.callqueue.size too small?
当我检查日志时,我发现几乎每个计算节点都抛出了这个错误:
java.util.concurrent.ExecutionException: java.io.IOException: Call queue is full is ipc.server.max.callqueue.size too small? ...
看起来callqueue正在排队,我应该增加这个大小吗?
从以往的经历来看:"要解决问题,总是先看大局"。原因如下:从异常来看,增加 hbase.ipc.server.max.callqueue.size大小似乎可以解决问题。但是,您需要解释或证明此更改的合理性,以及为什么默认值不够?
当阅读有关此参数的信息时,发现默认值为1G(1024*1024*1024),并且增加此值会对 HBase 内存产生一些副作用,RegionServer可能会内存不足------如果队列大小超过 1GB ,虽然区域服务器将智能地丢弃新调用,但仍可能出现 OOM。
因此,在进行此更改之前,让我们先找出为什么队列已满,或者为什么这个队列也被使用?
理论上,如果您对一个区域服务器进行大量 IPC 调用,或者所有调用都集中在一个区域服务器上,就会发生这种情况,热点?
在这种情况下,数据分布均匀,我们在这个集群上没有看到任何热点。所以我怀疑我们是否对服务器进行了大量调用?
然后我深入研究我们的代码,看到了这个:
java
Put p = new Put(Bytes.toBytes("xxxxxx"));
p.add(Bytes.toBytes("xxxx"), Bytes.toBytes("someQualifier"),
Bytes.toBytes("Some Value"));
table.put(p);
从上面的代码片段中,可以看到整个集群在一分钟内将执行多少个 put 操作。这里的复杂度是 O(n),在我们的例子中,O(n) 将解析为我们发送的指标数量。这里的负载是 200 万/分钟,所以在一分钟内,我们调用 put 200 万次,并且每个 read 调用也将使用自己的一组 IPC。看起来现在很明显为什么我们在每个计算节点上都会遇到异常。
这里的更改是批处理 PUT 并重新运行测试。现在,我们不再逐一写入,而是使用以下更改每 10 秒写入一次。
java
table.put(List<Put> puts)
自从更改以来,我们从未遇到过这种异常。
2.2)SocketTimeOut:
对于 2 百万/分钟公制来说,它有效。耶!!!现在,让我们升级游戏并运行 5 mil/min 的公制负载。开始加载,一切看起来都很好,没有更多的队列大小警告或错误。但是,嘿,它又失败了!
这次集群经历了很多事情。由于所有这些问题的相关性,我将一次性涵盖所有这些问题,以下是面临的 4 个问题的列表:
- HBase 客户端/计算节点在写入时永远卡住,并且 HBase 客户端线程永远处于 TIMED_WAIT 状态。
- 在高负载的计算节点上,开始在写入和读取时抛出套接字超时。
- 写入和读取吞吐量缓慢。
- 无法达到每分钟 1000 万次的目标。
我们开始在所有计算节点上收到以下异常,这里的异常是读取,但也出现了相同的写入堆栈。
java
2016-01-22 22:10:47.257 HBase READ-23 WARN o.a.h.hbase.client.ScannerCallable -- Ignore, probably already closed
java.net.SocketTimeoutException: Call to ip-xx-xxx-xxx-xxx/xx.xxx.xxx.xxx:60020 failed because java.net.SocketTimeoutException: 60000 millis timeout while waiting for channel to be ready for read. ch : java.nio.channels.SocketChannelconnected local=/xx.xxx.xxx.xxx:44064 remote=ip-xx.xxx.xxx.xxx/xx.xxx.xxx.xxx:60020
at org.apache.hadoop.hbase.ipc.RpcClient.wrapException(RpcClient.java:1486) ~hbase-client-0.98.12.1-hadoop2.jar:0.98.12.1-hadoop2
at org.apache.hadoop.hbase.ipc.RpcClient.call(RpcClient.java:1461) ~hbase-client-0.98.12.1-hadoop2.jar:0.98.12.1-hadoop2
at org.apache.hadoop.hbase.ipc.RpcClient.callBlockingMethod(RpcClient.java:1661) ~hbase-client-0.98.12.1-hadoop2.jar:0.98.12.1-hadoop2
at
为什么会出现套接字超时异常? 我能想到的这个例外有两个原因:
- HBase服务器读写返回慢
- 客户端无法连接到服务器并超时。线程/连接拥塞?
如果您遇到"1",那么增加 hbase.rpc.timeout 可能是您的解决方案,但您很可能最终也会遇到"2"。对于我们的案例 1 不是原因。怎样说呢?我们在RegionServer上启用了调试日志记录,它会打印扫描所需的时间。每次调用几乎不需要 5-10 毫秒。所以对于我们来说,HBase 服务器响应不是问题。
现在让我们关注 2,如果您使用默认的 hbase-client 属性,大多数人也会遇到这种情况。 在研究了其中一个计算节点后,我注意到 HBase 客户端默认情况下只为每个 RegionServer 创建一个连接。当负载启动并运行时,我使用以下命令查看与 RegionServer 建立的 HBase 连接数。
java
netstat -an | grep 60020 | grep EST
令我惊讶的是,对于每个 RegionServer,该进程只建立了一个连接。这解释了超时。只有一个连接?似乎这是默认的 HBase 客户端行为。还不知道为什么?
我随后搜索了 HBase 连接设置并找到了以下属性:
java
<property>
<name>hbase.client.ipc.pool.type</name>
<value>RoundRobinPool</value>
</property>
<property>
<name>hbase.client.ipc.pool.size</name>
<value>20</value>
</property>
这个 hbase.client.ipc.pool.size 属性有什么作用? 对于每个客户端连接,根据负载,它将创建到每个 RegionServer 的 20 个连接。并且连接将以循环方式使用。对于这个测试台,上述值已经足够好了。 我们再次搞定了它,再次运行测试,从那以后再也没有见过套接字超时异常。