导读
其实HBase随着这几年大数据的发展已经慢慢淡出视线,因为HBase架构设计以及部署运维的复杂性,很多人都是尽量使用其他原生的解决方案,如Cassandraclickhouse等。但是这不代表HBase就没有研究的价值了,内部的原理与设计还是值得参考和研究的。今天就从使用的第一步创建连接开始分析其源码,学习更多程序设计与Java编程的知识。
研究的版本:1.2.0
1 创建连接的流程
在当前版本使用时,官方推荐的是使用ConnectionFactory创建连接,然后获得Table执行增删改查。
可以看到HBase Client通过ConnectionFactory工厂方法创建连接,在连接中创建Table执行查询。那么使用的时候如何才能高效使用呢?这就要借助源码分析下它的实现逻辑了。
2 连接的实现思路
HBase的Connection通过工厂模式设计,在工厂方法中通过反射实现,从而剥离创建与实现的关系;如果我们想要替换连接的实现类,只需要替换对应hbase.client.connection.impl即可。工厂方法会自动加载自定义的实现类,通过反射的方式调用构造方法。其实这种设计思路在hadoop生态甚至一些Java类的开源框架中比比皆是。
在Connection实现类的构造方法中,主要的工作就是创建与zk的连接(一会需要去Zk中查询master和meta信息);rpc请求工厂(之后的增删盖茶都通过它来发送);rpcController工厂(暂时不理解它的作用);线程池(以第一种方式为例,这里还是null);用户信息;集群信息等。
到了这里,其实connection就创建好了。
3 线程池实现思路
在分布式系统中经常需要一个客户端需要发送多个请求给不同的机器同时查询,而不是挨个机器串联查询,这也是为什么分布式查询要更高效。如果需要同时发起多个机器请求,一般都会通过线程池来进行维护,线程池中同时开启多个线程,同时向不同的机器发起查询。
此时就需要考虑很多问题:
1 客户端的线程池如何共享?
2 线程池创建的时机?
3 是否需要考虑线程在空闲状态下的释放?
4 如果线程池已满,是否需要维护等待队列?
先抛开这几个问题,HBase提供了三种创建线程池的方式:
1 使用createConnection(conf)方法创建连接,在调用getTable时自动创建连接池
2 使用createConnection(conf, pool)方法创建连接,根据用户传入的pool创建连接池
3 使用getTable(name, null)方法创建表,该表单独使用一个连接池
以第一种方式为例,就看看getTable时发生了什么吧!如果没有传线程池参数,会执行getBatchPool()方法。
getBatchPool()通过线程同步,使得每个connection对象只会执行一次该方法:
getBatchPool()里面调用了getThreadPool创建真正的线程池。
线程池的创建,会优先读取用户指定的参数,如核心线程数、最大线程数、存活时间,如果读取不到则会按照默认值创建。线程数的默认值通过本机的处理器个数*8得出。线程池是通过ThreadPoolExecutor创建,配置对应的线程数、存活时间、等待队列、命名工厂等。
至此,一个分布式客户端连接就完成了。
4 总结
通过阅读上面部分:
1 需要了解Java中反射、Synchronized的使用、线程池的创建等基础知识。
2 通过学习源码,我们就应该知道,如果想要避免客户端线程膨胀,需要手动调节哪些参数。
3 在日常的使用中尽量创建一个可复用的connection,从而使得请求都维护在同一个线程池中。