1,写HBase莫名卡住
之前有个多线程数据同步程序在写HBase时偶尔会卡住,从jstack分析,有一个线程block在
而其他要写同一个regionserver的线程block在上面这把锁。
在HBase client的代码中发现在构造out对象的时候,timeout设成了0.具体代码如下:
接着看write这个调用
当调用write时,先写channel的buffer,写满以后,将数据发送出去。
好了,从代码路径上看到,正是卡在selector.select这个地方,因为在构造的时候设置了timeout为0,0意味着无限等待。
select是当注册的channel就绪时就返回,对于读的,通道缓存满意味着就绪,对于写,将通道的数据清空意味着就绪。而写只是用户空间的缓存数据移到内核空间,理论上是不会失败的,除非网络有问题,Send-Q里的数据一直没发送出去。
而通过netstat看到情况证实了这一点,regionserver所在的机器在18:15时已经当机,当机原因不详,但client所在的机器在19:30依然有这样的tcp连接
tcp 0 65160 ::ffff:{client ip}:57589 ::ffff:{server ip}:30020 ESTABLISHED
连接状态还是ESTABLISHED,对于client来说,还是一个正常的链接,但是写又写不进去,咨询了内核专家,得知这是内核的一个bug。那
现在看上去可行的解决方法只能是在hbase client的代码里
加超时了
相关同事提交了issue到社区 https://issues.apache.org/jira/browse/HBASE-8558,在0.94.16已经被修复。
2,多线程读写HBase的一个注意点
还是这个程序,某天报了个错
java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.OutOfMemoryError: unable to create new native thread
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:222)
at java.util.concurrent.FutureTask.get(FutureTask.java:83)
at org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation.processBatchCallback(HConnectionManager.java:1604)
at org.apache.hadoop.hbase.client.HConnectionManager$HConnectionImplementation.processBatch(HConnectionManager.java:1456)
at org.apache.hadoop.hbase.client.HTable.flushCommits(HTable.java:1011)
at org.apache.hadoop.hbase.client.HTable.put(HTable.java:834)
at com.taobao.dump.sapclient.client.HbaseClient.set(HbaseClient.java:224)
看错误信息,不能建立新的线程,和之前集群升级时遇到的一个问题类似,分析是某个程序建了大量的线程,达到了系统的上限。看机器上跑的进程的,最大可能是这个程序自己。从出错信息分析,结合hbase源码,在每个htable建立的时候,会初始化一个线程池。
int maxThreads = conf.getInt("hbase.htable.threads.max", Integer.MAX_VALUE);
if (maxThreads == 0) {
maxThreads = 1; // is there a better default?
}
long keepAliveTime = conf.getLong("hbase.htable.threads.keepalivetime", 60);
// Using the "direct handoff" approach, new threads will only be created
// if it is necessary and will grow unbounded. This could be bad but in HCM
// we only create as many Runnables as there are region servers. It means
// it also scales when new region servers are added.
this.pool = new ThreadPoolExecutor(1, maxThreads,
keepAliveTime, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
Threads.newDaemonThreadFactory("hbase-table"));
((ThreadPoolExecutor)this.pool).allowCoreThreadTimeOut(true);
这个pool的作用是client在提交一个操作时,会建立一个callable线程提交到pool里,每个线程专门处理各自regionserver上的请求,最终聚合在一起,返回给client。所以当一个批量操作的请求过来的时候,极端情况下会在每个regionserver都会有一个线程,而每个htable都独自维护这个pool。所以当进程里htable实例多,并且集群规模较大的时候,该进程的线程数极端情况下是很多的。
具体到这个程序,确实有大量的batch操作,并且开了29个处理线程,并且hbase集群加了一批机器后,达到了近400台的规模,那极端情况下,将有1万多个线程,还不包括其他处理线程。而当时又有多个进程被同时调度到这台机器上,这时就就会出现上述异常了。
解决思路应用层面主要是降低极端情况下的并发,有以下几个方面:
1,container数和单个container中的线程数,在并发处理能力保持不变的情况下,选择合理的container数和线程数。
2,使用HTablePool来统一管理HTable实例,最好是单例,并且设定一个合理的maxSize,如并发时htable实例不够,调用方就先等待,直到有可用的实例。
3,建立htable对象时,指定自己的线程池,这个线程池全局共用,并且维护一个有界队列,当队列满时,请求就等待。
public HTable(Configuration conf,final byte[] tableName,
final ExecutorService pool)
这个case最后的解决方式是,调低了HTablePool的maxSize。
另一个解决思路,加大系统可创建的最大线程数,关于一个JVM进程最多可以创建多少个线程,请参考 http://jzhihui.iteye.com/blog/1271122 ,但在我们的环境实际测试,只有调整参数/proc/sys/vm/max_map_count才能加大可创建的线程数。如不做调整,则可创建30000+个线程。具体系统层面的因果还待进一步分析。
--转自
该贴由koei123转至本版2015-6-1 15:14:43