简述:
和写流程对比起来,HBase读数据是一个更加复杂的操作流程,这主要基于两个方面的原因:
其一是因为整个HBase存储引擎基于LSM-Like树实现,因此一次范围查询可能会涉及多个分片、多块缓存甚至多个数据存储文件;
其二是因为HBase中更新操作以及删除操作实现都很简单,更新操作并没有更新原有数据,而是使用时间戳属性实现了多版本。删除操作也并没有真正删除原有数据,只是插入了一条打上”deleted”标签的数据,而真正的数据删除发生在系统异步执行Major_Compact的时候。
很显然,这种实现套路大大简化了数据更新、删除流程,但是对于数据读取来说却意味着套上了层层枷锁,读取过程需要根据版本进行过滤,同时对已经标记删除的数据也要进行过滤。
1、scan一次,现在内存中找,如果找到了后,就直接返回给客户端
2、如果没有找到再在 blockcache hfile memcache 中一行一行进行扫描,扫到100行之后,就返回给客户端
3、客户端将这100行数据缓存到内存中并返回给一条给上层业务。
这里是每次都会调用100行数据,客户端拿到之后,再扫描100条数据,直到数据被全部拿到
上层业务不断一条一条获取扫描数据,在数据量大的情况下实际上HBase客户端会不断发送next请求到HBase服务器。有的朋友可能会问为什么scan需要设计为多次next请求的模式?个人认为这是基于多个层面的考虑:
1、HBase本身存储了海量数据,所以很多场景下一次scan请求的数据量都会比较大。如果不限制每次请求的数据集大小,很可能会导致系统带宽吃紧从而造成整个集群的不稳定。
2、如果不限制每次请求的数据集大小,很多情况下可能会造成客户端缓存OOM掉。
3、如果不限制每次请求的数据集大小,很可能服务器端扫描大量数据会花费大量时间,客户端和服务器端的连接就会timeout。
get的批处理操作是安装目标region进行分组,不同分组的get请求会并发执行读取。然而scan并没有这样实现。
也就是说,scan不是并行操作。
所以从客户端视角来看整个扫描时间=客户端处理数据时间+服务器端扫描数据时间,这能不能优化?
小结:
根据上面的分析,scan API的效率很大程度上取决于扫描的数据量。通常建议OLTP业务中少量数据量扫描的scan可以使用scan API,大量数据的扫描使用scan API,扫描性能有时候并不能够得到有效保证。
引出问题:HBase作为列式存储,为什么它的scan性能这么低呢,列式存储不是更有利于scan操作么?Parquet格式也是列式,但它的scan这么优秀,他们的性能差异并不是因为数据组织方式造成的么?kudu也是采用的类LSM数据结构,但是却能达到parquet的扫描速度(kudu是纯列式的),kudu的一个列也会形成很多文件,但是好像并没影响它的性能
小结:
个人对Kudu不是很懂,不过旁边有Kudu大神。我的理解是这样的:
所以说hbase相比parquet,这两个方面都是scan的劣势。
参考链接:
HBase原理-数据读取流程解析 http://hbasefly.com/2016/12/21/hbase-getorscan/
HBase最佳实践 – Scan用法大观园 http://hbasefly.com/2017/10/29/hbase-scan-3/
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。