spark读写hbase,先写一下hbase的常用操作方式.
create 'hbase_test_table', 'info', {NAME=>'info', SPLITALGO => 'HexStringSplit', REPLICATION_SCOPE =>0}, SPLITS => ['S0','S1','S2', 'S3', 'S4','S5','S6','S7','S8','S9']
(1)
通过
describe ‘hbase_test_table’
可查看该表所有默认属性.实际中发现,有些属性不能同时设置,比如设置:
REPLICATION_SCOPE =>1
,则会使某些属性无效,比如我建立2个column family.
(2)
一般加上压缩属性,SPLITALGO => ‘HexStringSplit’
(3)
预分区要写明每个分区的名称,如写成NUMREGIONS => 100 实际创建的分区只有一个.
随着该分区装载的数据增多,默认达到256M的时候,hbase会自动生成一个新的分区,如果数据的rowkey是有序列的,则写入会继续写入最大rowkey所在的分区,也就是新的分区.旧的分区不会继续写入,这个会造成:
rowkey设计,我们把几个关键字段拼接,生成一个新的字段,为了使rowkey可用,好用,我们做了以下步骤:
(1)
新的字段必须唯一;
(2)
rowkey尽量简短,意义紧凑,比如时间:2018/03/01 17:54, 转换成1803011754;
(3)
每一行rowkey等长,比如每一行rowkey都是16个字母长度;
(4)
对于数据倾斜问题,一开始我们参照网络资料,使用
new_rowkey=hash(rowkey)
这个直接有一个问题不能忽视,就是rowkey查询问题,rowkey命名变成无意义了,然后改成
new_rowkey=hash(rowkey)+rowkey
方式,这种是比较好的方法.可是严谨一点的话,hash比较长,其它散列也如此,为了使rowkey尽量短,我们改进了以上方式,
new_rowkey=hash(rowkey)%分区数+rowkey
比如我们建表,预设10个分区, 那么便是:
new_rowkey=hash(rowkey)%10+rowkey
(5)
hbase java api 对rowkey主要有scan,filter,get查询.scan可以查询(startRowKey,endRowKey).当然,我们需要一些匹配查询,则会用到filter查询.
filter主要有5种方式:全部匹配;前缀匹配;子串匹配;后缀匹配;正则匹配. 根据这些匹配,我们需要调整rowkey中每个字段的顺序,来适应查询需求.
(6)
实际上,以上的scan也好,filter匹配也好,我们感觉都不实用.我们也用了get方式,尽管目前也在用,但是本人感觉还是不能满足应用需求. 其中get的查询效率,是本人还存有疑惑的地方,下文会讲到.还有get方式对条件查询无解,所以,需要引进新的组件,比较可行的有solr,elasticsearch 充当存储二级索引的仓库.
上面说的hbase的一些东西比较虚,有几点本人也没有实际测试过.大部分本人都做过,问题是:怎么辨识何为好的思路?对于rowkey相关的,本人存疑的几点:
问题(1)
查询hbase,要连hbase client,hbase client 是要查一次,连一次?还是长连接的?
问题(2)
scan有
scan.setCaching(10000)
scan.setCacheBlocks(true)
等设定.setCaching ,个人感觉不够用.hbase 默认是在内存里面放一块数据用来读取,所以读取效率比较高,可是,其余大部分数据还是在硬盘中,这个内存数据块的设定和意义,待清晰研究.
问题(3)
不使用其它索引组件,在scan 或filter 数据时,遇到效率瓶颈.好像速度就那样,流程似乎也是按照hbase官方流程,看了一些hbase IO测试表格,有的比较快;有的非常快,让我对hbase实际平均效率存有疑惑,需要作分析.
问题(4)
scan 方式比较慢,filter方式也比较慢,它们慢在哪里?
(1)对于问题(1),hbase client连接,一般代码是这样写的:
val conf = HBaseConfiguration.create()
conf.set("hbase.zookeeper.property.clientPort", "2181")
conf.set("hbase.zookeeper.quorum", "master")
//Connection 的创建是个重量级的工作,线程安全,是操作hbase的入口
val conn = ConnectionFactory.createConnection(conf)
//从Connection获得 Admin 对象(相当于以前的 HAdmin)
val admin = conn.getAdmin
//本例将操作的表名
val userTable = TableName.valueOf("user")
//创建 user 表
val tableDescr = new HTableDescriptor(userTable)
tableDescr.addFamily(new HColumnDescriptor("basic".getBytes))
println("Creating table `user`. ")
if(admin.tableExists(userTable)) {
admin.disableTable(userTable)
admin.deleteTable(userTable)
}
admin.createTable(tableDescr)
println("Done!")
try{
//获取 user 表
val table = conn.getTable(userTable)
//...
}finally {
conn.close()
}
以及spark 连接hbase:
/*把RDD中的列写进hbase
sc:SparkContext
tableName: 表名
servers: 集群服务器别名,比如Master
indataRDD:包含列数据的rdd
columnFamily:列族
newColumn:新列的命名
2017-09-04
*/
def saveNewColumnToHbase(sc:SparkContext, tableName:String, servers:String, indataRDD:RDD[Row], columnFamily:String, newColumn : String) : Unit = {
sc.hadoopConfiguration.set("hbase.zookeeper.quorum",servers)
sc.hadoopConfiguration.set("hbase.zookeeper.property.clientPort", "2181")
sc.hadoopConfiguration.set("zookeeper.znode.parent","/hbase")
sc.hadoopConfiguration.set(TableOutputFormat.OUTPUT_TABLE, tableName)
val job = new Job(sc.hadoopConfiguration)
job.setOutputKeyClass(classOf[ImmutableBytesWritable])
job.setOutputValueClass(classOf[Result])
job.setOutputFormatClass(classOf[TableOutputFormat[ImmutableBytesWritable]])
val rdd = indataRDD.map{
arr=>{
//
}}
rdd.cache()
rdd.saveAsNewAPIHadoopDataset(job.getConfiguration())
}
<property>
<name>hbase.client.ipc.pool.typename>
<value>RoundRobinPoolvalue>
property>
<property>
<name>hbase.client.ipc.pool.sizename>
<value>1value>
property>
这里配置显然也有一个线程池管理ipc请求.ipc.pool.size 默认是1.
意思是,对于每个client连接, 会创建1个线程用于与regionServer通信.
但这里,ipc.pool.size配多大,ipc.pool 内部如何工作的?并不清楚.查找资料,发现hbase论坛有一段话:
In general, sharing sockets across multiple client threads is a good idea, but limiting the number of such sockets to one may be overly restrictive for certain cases. Here, we propose a way of defining multiple sockets per server endpoint, access to which may be managed through either a load-balancing or thread-local pool. To that end, we define the notion of a SharedMap, which maps a key to a resource pool, and supports both of those pool types. Specifically, we will apply that map in the HBaseClient, to associate multiple connection threads with each server endpoint (denoted by a connection id).
Currently, the SharedMap supports the following types of pools:
- A ThreadLocalPool, which represents a pool that builds on the ThreadLocal class. It essentially binds the resource to the thread from which it is accessed.
- A ReusablePool, which represents a pool that builds on the LinkedList class. It essentially allows resources to be checked out, at which point it is (temporarily) removed from the pool. When the resource is no longer required, it should be returned to the pool in order to be reused.
- A RoundRobinPool, which represents a pool that stores its resources in an ArrayList. It load-balances access to its resources by returning a different resource every time a given key is looked up.
To control the type and size of the connection pools, we give the user a couple of parameters (viz. "hbase.client.ipc.pool.type" and "hbase.client.ipc.pool.size"). In case the size of the pool is set to a non-zero positive number, that is used to cap the number of resources that a pool may contain for any given key. A size of Integer#MAX_VALUE is interpreted to mean an unbounded pool.
大致意思是,多个client共享一个套接字在hbase这里不适用,尽管这是个好的思路.他们创建了一个SharedMap, 以密钥的方式管理线程池,应该每个线程分配一个特定密钥.这种SharedMap支持3种线程池,分别是:
ThreadLocalPool,应该是java那一套;
ReusablePool, 不清楚;
RoundRobinPool,就是上面配置里的那一种.
实际效果,感觉应该是对线程池的优化,提高线程池的效率和资源复用率.
直到这里,本人有2点总结:
1.hbase socket 使用了线程池管理client线程;
2.使用了一种优化技术,优化了线程池.
是这样吗?
这里,ipc.pool.size 默认是1,可能以为1个就够了. 当然,我可以改成更大值,比如5,就是一个client请求,regionServer开5个线程来处理通信. 可是,具体效果如何,本人还没有针对尝试,并不清楚.
现在,本人知道,hbbase的socket 确实是一次put/get/delete操作请求,就创建一个client来处理请求,内部使用优化过的线程池管理client. 默认情况,一个用户请求,regionserver会创建一个线程.
那么,用户密集型请求应用场景,以上办法是否能有效应对? 感觉这个还需要讨论.
说了问题(1),再分析下
问题(2):
hbase读取数据的方式是怎样的?
请看:
spark常见操作系列(3)–spark读写hbase(2)
参考资料:
https://jaskiratbhatiablog.wordpress.com/2016/10/06/hbase-client-tuning/
https://issues.apache.org/jira/browse/HBASE-2939
https://blog.csdn.net/gingerredjade/article/details/63703479