首页 > 大数据 > > 大数据面试荟萃(一)
HBase 是一个分布式的、可伸缩的数据存储,其支持行级别的数据更新,快速查询和行级事务(不支持多行事务)。相比 Hive,Hive 是不支持单行事务的。
怎么理解支持行级事务,但是不支持多行事务呢?参考以下博客。
HBase 数据会首先写入 WAL,再写入 Memstore。写入 Memstore 异常很容易可以回滚,因此保证写入/更新原子性只需要保证写入 WAL 的原子性即可。
HBase 使用 HDFS(或其他某种分布式文件系统,如 S3等等)来持久化存储数据。为了可以提供行级别的数据更新和快速查询,HBase 也使用了内存缓存技术对数据和本地文件进行追加数据更新操作日志。持久化文件将定期地使用附加日志更新进行更新等操作。
对数据分区,也许最重要的原因就是为了更快的查询速度。注意分区表实际上是将数据存储到不同的分区目录里,并且可以控制每次查询扫描指定的文件目录,而非全局扫描。这就是分区的意义所在。
From: http://hbasefly.com/2016/12/21/hbase-getorscan/
和写流程相比,HBase 读数据是一个更加复杂的操作流程,这主要基于两个方面的原因:
很显然,这种实现套路大大简化了数据更新、删除流程,但是对于数据读取来说却意味着套上了层层枷锁,读取过程需要根据版本进行过滤,同时对已经标记删除的数据也要进行过滤。所以本质上,删除是一种写的操作。
总结一句话,HBase 就是写(删)友好,读相对不友好。

HBase读取流程 客户端首先会根据配置文件中 zookeeper 地址连接 zk,并读取 /<hbase-rootdir>/meta-region-server 节点信息,该节点信息存储 HBase 元数据(hbase:meta)表所在的 RegionServer 地址以及访问端口等信息。用户可以通过 zk 命令 get /<hbase-rootdir>/meta-region-server 查看该节点信息。
根据 hbase:meta 所在 RegionServer 的访问信息,客户端会将该元数据表加载到本地并进行缓存。然后在表中确定待检索 rowkey 所在的 RegionServer 信息。
根据数据所在 RegionServer 的访问信息,客户端会向该 RegionServer 发送真正的数据读取请求。服务器端接收到该请求之后需要进行复杂的处理。
通过上述对客户端以及 HBase 系统的交互分析,可以基本明确两点:
From: https://www.cnblogs.com/huxi2b/p/8440429.html From: https://blog.csdn.net/m0_38003171/article/details/80503290
正常情况下 consumer 会在消费完一条消息后线性增加这个 offset。当然,consumer 也可以将 offset 设成一个较小的值,重新消费一些消息。因为 offset 由 consumer 控制,所以 Kafka broker 是无状态的
我猜想作者的意思应该是: broker 不保存消费者 consumer 的状态。如果从这个角度来说,broker 无状态的说法倒也没有什么问题。不过实际上,broker 是有状态的服务: 每台 broker 在内存中都维护了集群上所有节点和 topic 分区的状态信息——Kafka 称这部分状态信息为元数据缓存(metadata cache)。
1 cache里面存了什么? 首先 cache 里面都存了什么,以 Kafka 1.0.0版本作为分析对象。Metadata cache 中保存的信息十分丰富,几乎囊括了 Kafka 集群的各个方面,它包含了: 1.1 controller 所在的 broker ID,即保存了当前集群中 controller 是哪台 broker。 1.2 集群中所有 broker 的信息。比如每台 broker 的 ID、机架信息以及配置的若干组连接信息(比如配置了 PLAINTEXT 和 SASL 监听器就有两套连接信息,分别使用不同的安全协议和端口,甚至主机名都可能不同)。 1.3 集群中所有节点的信息。严格来说,它和上一个有些重复,不过此项是按照 broker ID 和监听器类型进行分组的。对于超大集群来说,使用这一项缓存可以快速地定位和查找给定节点信息,而无需遍历上一项中的内容。 1.4 集群中所有分区的信息。所谓分区信息指的是分区的 leader、ISR 和 AR 信息以及当前处于 offline 状态的副本集合。这部分数据按照 topic 和分区 ID 进行分组,可以快速地查找到每个分区的当前状态。(注:AR表示assigned replicas,即创建topic时为该分区分配的副本集合)
2 每台 broker 都保存相同的 cache 吗? 是的,至少 Kafka 在设计时的确是这样的愿景:每台 Kafka broker 都要维护相同的缓存,这样客户端程序(clients)随意地给任何一个 broker 发送请求都能够获取相同的数据,这也是为什么任何一个 broker 都能处理 clients 发来的 Metadata 请求的原因: 因为每个 broker 上都有这些数据!要知道目前 Kafka 共有38种请求类型,能做到这一点的可谓少之又少。每个 broker 都能处理的能力可以缩短请求被处理的延时从而提高整体 clients 端的吞吐,因此用空间去换一些时间的做法是值得的。
3 cache 是怎么更新的? 如前所述,用空间去换时间,好处是降低了延时,提升了吞吐,但劣势就在于你需要处理 cache 的更新并且维护一致性。目前 Kafka 是怎么更新 cache 的?简单来说,就是通过发送异步更新请求(UpdateMetadata request)来维护一致性的。既然是异步的,那么在某一个时间点集群上所有 broker 的 cache 信息就未必是严格相同的。 只不过在实际使用场景中,这种弱一致性似乎并没有太大的问题。原因如下:
4 cache什么时候更新? 实际上这个问题等同于: controller 何时向特定 broker 发送 UpdateMetadata 请求? 如果从源码开始分析,那么涉及到的场景太多了,比如 controller 启动时、新 broker 启动时、更新 broker 时、副本重分配时等等。 我们只需要记住: 只要集群中有 broker 或分区数据发生了变更就需要更新这些 cache。 举个经常有人问的例子,集群中新增加的 broker 是如何获取这些 cache,并且其他 broker 是如何知晓它的?当有新 broker 启动时,它会在 Zookeeper 中进行注册,此时监听 Zookeeper 的 controller 就会立即感知这台新 broker 的加入,此时 controller 会更新它自己的缓存(注意:这是 controller 自己的缓存,不是本文讨论的 metadata cache)把这台 broker 加入到当前 broker 列表中,之后它会发送 UpdateMetadata 请求给集群中所有的 broker (也包括那台新加入的 broker)让它们去更新 metadata cache。一旦这些 broker 更新 cache 完成,它们就知道了这台新 broker 的存在,同时由于新 broker 也更新了 cache,故现在它也有了集群所有的状态信息。
5 目前的问题? 前面说过了,现在更新 cache 完全由 controller 来驱动,故 controller 所在 broker 的负载会极大地影响这部分操作(实际上,它会影响所有的 controller 操作)。根据目前的设计,controller 所在 broker 依然作为一个普通 broker 执行其他的clients请求处理逻辑,所以如果 controller broker 一旦忙于各种 clients 请求(比如生产消息或消费消息),那么这种更新操作的请求就会积压起来(backlog),造成了更新操作的延缓甚至是被取消。究其根本原因在于当前 controller 对待数据类请求和控制类请求并无任何优先级化处理——controller一视同仁地对待这些请求,而实际上我们更希望 controller 能否赋予控制类请求更高的优先级。社区目前已经开始着手改造当前的设计,相信在未来的版本中此问题可以得到解决。
视图可以允许保存一个查询,并可以像对待表一样对这个查询进行操作。这是一个逻辑结构,因为它不像一个表会存储数据。换句话说,Hive 目前不支持物化视图。当一个查询引用一个视图时候,这个视图所定义的查询语句将和用户的查询语句组合在一起,然后供 Hive 制定查询计划。从逻辑上讲,可以想象为 Hive 先执行这个视图,然后使用这个结果进行余下后续的查询。
Hive 的索引功能有限,但是还是可以对一些字段建立索引来加速某些操作的。一张表的索引数据存储在另外一张表。当逻辑分区实际上太多太细而几乎无法使用的时候,建立索引也就成为分区的另一个选择。
建立索引可以帮助裁剪掉一张表的一些数据块,这样能够减少 MapReduce 的输入数据量。并非所有的查询都可以通过建立索引获得好处,通过 Explain 命令可以查看某个查询语句是否用到了索引。
Hive 中的索引和那些关系型数据库的一样,需要仔细评估才可以使用,维护索引是需要额外的存储空间 的,同时创建索引也需要消耗计算资源。
关于 Hive 的索引构建过程可以参考这篇文章。
MapReduce 任务往往是 I/O 密集型的(反复的读写磁盘),因此 CPU 开销通常不是问题。不过对于工作流 Pipeline,这样的 CPU 密集型场景,例如一些机器学习算法。压缩实际上肯能会从更多必要的操作中获取宝贵的 CPU 资源,从而降低性能。所以这个表是否需要压缩,取决于其业务类型,机器学习的,肯定不建议压缩的,因为解压缩是需要耗费 CPU 资源的,因为本来属于计算密集型的作业,CPU 资源比较重要,如果是 ETL 作业,那就还好,经过压缩的数据集会更小,更利于网络传输,而且 MR 本来属于 IO 密集型,可以提供一些 CPU 资源来做这个工作。
有点冷门,因为从典型的关系型数据库来看,他并不是一个列式存储的数据库(但实际会认为他是列式存储数据库),但是因为 HBase 利用了磁盘上的列式存储格式,这也是关系型数据库和 HBase 最大的相似之处,因为 HBase 以列式存储的格式在磁盘上存储数据。但它与传统的列式数据库有很大的不同,传统的列式数据库比较适合实时存储数据的场景,HBase 比较适合键值对的存取,或有序的数据存取。
数据库必须保证每一步操作都是从一个一致的状态到下一个一致的状态。一致性可以按照严格程度由强到弱划分,或者是按照对客户端保证程度分类。
HBase 中扩展和负载均衡的基本单元称为 region,本质上是以行键排序的连续存储空间。如果 region 太大,系统会把它们进行动态的拆分,相反地,就把多个 region 合并,以减少存储文件数量。
一张表初始只有一个 region,用户开始向表中插入数据的时候,系统会检查这个 region 的大小,确保其不超过配置的最大值,如果超过限制,系统会在中间键将这个 region 分成大致相等的子 region。每一个 region 只能由一台 region 服务器 RegionServer 加载,每一台 region 服务器(RegionServer)可以同时加载多个 region。一个表实际上是一个由很多 region 服务器加载的 region 集合的逻辑视图。
每次更新数据的时候,都会先把数据记录在提交日志(commit log)中,在 HBase 中,这叫做预写日志(WAL),然后才会把这些数据写入内存的 Memstore 中。一旦内存保存的写入数据的累计大小超过了一个给定的最大值,系统就会把这些数据移出内存,作为 HFile 文件刷写到磁盘中。数据移出内存后,系统会丢弃对应的提交日志,大小有限,只保留未持久化到磁盘的提交日志。

读回数据是两部分数据合并的结果,一部分是 Memstore 中还没写入磁盘的数据,另一部分是磁盘上的存储文件。值得注意的是,数据检索的时候用不着 WAL,只有服务器内存中的数据在服务器崩溃前没有写入到磁盘(指 Memstore 的数据),而后进行恢复数据的时候才会用到 WAL。
随着 Memstore 的数据不断被刷写到磁盘中,会产生越来越多的 HFile 文件(磁盘上),HBase 内部有一个节俭空间的管家机制,即合并将多个文件合并成一个较大的文件。合并有两种类型,minor 合并和 major 压缩合并。
需要注意的是 major 合并的时候,只会对一个列族的 HFile 进行合并。
master 服务器主要负责跨 region 服务器的全局 region 的负载均衡,将繁忙的服务器中的 region 移到较轻的服务器中(本身负载不高)。主服务器不是实际数据存储或者检索路径的组成部分,仅仅提供了负载均衡和集群管理,不为 region 服务器或者客户端提供任务数据的服务,因此是一个轻量级的服务器,此外主服务器还提供了元数据的管理操作,例如建表和创建列族。
# 查看 master 数量和 regionServer 数量
hbase(main):002:0> status
1 active master, 1 backup masters, 6 servers, 0 dead, 153.5000 average load一般为了能够像 MapReduce 一样有效地利用 HDFS,HBase 大多是与 Hadoop 安装在一起,这样能够很大程度减少对网络 I/O 的需求,同时加快处理速度。当在一台服务器上运行 Hadoop 和 HBase,将最少会有3个 Java 进程,DataNode, TaskTracker 和 RegionServer 在运行,而且当执行 MapReduce 操作的时候,进程数还会激增。
master 服务器其实不需要太多的存储空间,不需要挂载过多的硬盘,master 重要性显然要优于 slave,所以需要通过冗余来提高硬件使用率。
HBase 的所有修改数据的操作都保证了行级别的原子性(能支持行级事务,Hive 不行),这会影响到这一行数据所有的并发读写操作。换句话说,其他客户端或者线程对同一行的读写操作都不会影响该行的数据的原子性,要么读到最新的修改,要么等待系统允许写入该行的操作(强一致性)。
通常正常负载和常规操作,客户端操作不会受到其他修改数据的客户端的影响,因为他们之间的冲突可以忽略不计。但是当许多客户端需要同时修改同一行数据的时候就会有问题,因此建议用户尽量使用批量处理更新,来减少单独操作做同一行数据的次数(同时)。
在初始化 Put 对象的时候,如果需要频繁地重复修改某些行(加锁),用户有必要创建一个 RowLock 实例来防止其他客户端访问这些行。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31537575/viewspace-3128025/ ,如需转载,请注明出处,否则将追究法律责任。