目录

大数据面试

目录

一句话概括HBase

HBase 是一个分布式的、可伸缩的数据存储,其支持行级别的数据更新,快速查询和行级事务(不支持多行事务)。相比 Hive,Hive 是不支持单行事务的。

怎么理解支持行级事务,但是不支持多行事务呢?参考以下博客

HBase 数据会首先写入 WAL,再写入 Memstore。写入 Memstore 异常很容易可以回滚,因此保证写入/更新原子性只需要保证写入 WAL 的原子性即可。

HBase和Hadoop之间是什么关系

HBase 使用 HDFS(或其他某种分布式文件系统,如 S3等等)来持久化存储数据。为了可以提供行级别的数据更新和快速查询,HBase 也使用了内存缓存技术对数据和本地文件进行追加数据更新操作日志。持久化文件将定期地使用附加日志更新进行更新等操作。

Hive中分区表是怎么回事

对数据分区,也许最重要的原因就是为了更快的查询速度。注意分区表实际上是将数据存储到不同的分区目录里,并且可以控制每次查询扫描指定的文件目录,而非全局扫描。这就是分区的意义所在。

HBase查询的整个过程

From: http://hbasefly.com/2016/12/21/hbase-getorscan/

和写流程相比,HBase 读数据是一个更加复杂的操作流程,这主要基于两个方面的原因:

  1. 因为整个 HBase 存储引擎基于 LSM-Like 树实现,因此一次范围查询可能会涉及多个分片、多块缓存甚至多个数据存储文件
  2. 其二是因为 HBase 中更新操作以及删除操作实现都很简单,更新操作并没有更新原有数据,而是使用时间戳属性实现了多版本。删除操作也并没有真正删除原有数据,只是插入了一条打上”deleted”标签的数据,而真正的数据删除发生在系统异步执行 Major_Compact 的时候。

很显然,这种实现套路大大简化了数据更新、删除流程,但是对于数据读取来说却意味着套上了层层枷锁,读取过程需要根据版本进行过滤,同时对已经标记删除的数据也要进行过滤。所以本质上,删除是一种写的操作。

总结一句话,HBase 就是写(删)友好,读相对不友好。

/%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%9D%A2%E8%AF%95/img0.png

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 系统的交互分析,可以基本明确两点:

  1. 客户端只需要配置 zookeeper 的访问地址以及根目录,就可以进行正常的读写请求。不需要配置集群的 RegionServer 地址列表(这个是 HBase 内部协调即可)。
  2. 客户端会将 hbase:meta 元数据表缓存在本地,因此上述步骤中前两步只会在客户端第一次请求的时候发生,之后所有请求都直接从缓存中加载元数据。如果集群发生某些变化导致 hbase:meta 元数据更改,客户端再根据本地元数据表请求的时候就会发生异常,此时客户端需要重新加载一份最新的元数据表到本地。(比如 region 到达上限进行 split,那么元数据就会产生变化吗,此时客户端会重新进行加载)

Kafka broker到底是不是无状态的?

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 信息就未必是严格相同的。 只不过在实际使用场景中,这种弱一致性似乎并没有太大的问题。原因如下:

  1. clients 并不是时刻都需要去请求元数据的,且会缓存到本地;
  1. 即使获取的元数据无效或者过期了,clients 通常都有重试机制,可以去其他 broker 上再次获取元数据;
  2. cache 更新是很轻量级的,仅仅是更新一些内存中的数据结构,不会有太大的成本。因此我们还是可以安全地认为每台 broker 上都有相同的 cache 信息。具体的更新操作实际上是由 controller 来完成的。controller 会在一定场景下向特定 broker 发送 UpdateMetadata 请求令这些 broker 去更新它们各自的 cache,这些 broker 一旦接收到请求便开始全量更新——即清空当前所有 cache 信息,使用 UpdateMetadata 请求中的数据来重新填充 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 先执行这个视图,然后使用这个结果进行余下后续的查询。

关于Hive的索引功能

Hive 的索引功能有限,但是还是可以对一些字段建立索引来加速某些操作的。一张表的索引数据存储在另外一张表。当逻辑分区实际上太多太细而几乎无法使用的时候,建立索引也就成为分区的另一个选择。

建立索引可以帮助裁剪掉一张表的一些数据块,这样能够减少 MapReduce 的输入数据量。并非所有的查询都可以通过建立索引获得好处,通过 Explain 命令可以查看某个查询语句是否用到了索引。

Hive 中的索引和那些关系型数据库的一样,需要仔细评估才可以使用,维护索引是需要额外的存储空间 的,同时创建索引也需要消耗计算资源。

关于 Hive 的索引构建过程可以参考这篇文章

Hive表中的压缩,I/O和CPU怎么取舍

MapReduce 任务往往是 I/O 密集型的(反复的读写磁盘),因此 CPU 开销通常不是问题。不过对于工作流 Pipeline,这样的 CPU 密集型场景,例如一些机器学习算法。压缩实际上肯能会从更多必要的操作中获取宝贵的 CPU 资源,从而降低性能。所以这个表是否需要压缩,取决于其业务类型,机器学习的,肯定不建议压缩的,因为解压缩是需要耗费 CPU 资源的,因为本来属于计算密集型的作业,CPU 资源比较重要,如果是 ETL 作业,那就还好,经过压缩的数据集会更小,更利于网络传输,而且 MR 本来属于 IO 密集型,可以提供一些 CPU 资源来做这个工作。

HBase是列式存储的数据库吗,或者说是传统的那种列式数据库吗

有点冷门,因为从典型的关系型数据库来看,他并不是一个列式存储的数据库(但实际会认为他是列式存储数据库),但是因为 HBase 利用了磁盘上的列式存储格式,这也是关系型数据库和 HBase 最大的相似之处,因为 HBase 以列式存储的格式在磁盘上存储数据。但它与传统的列式数据库有很大的不同,传统的列式数据库比较适合实时存储数据的场景,HBase 比较适合键值对的存取,或有序的数据存取。

说说数据库的一致性

数据库必须保证每一步操作都是从一个一致的状态到下一个一致的状态。一致性可以按照严格程度由强到弱划分,或者是按照对客户端保证程度分类。

  1. 严格一致性: 数据的变化是原子的,一经改变即时生效,这是一致性的最高形式
  2. 顺序一致性: 每个客户端看到的数据依照他们操作执行的顺序而变化
  3. 因果一致性: 客户端以因果关系顺序观察到数据的改变
  4. 最终一致性: 在没有更新数据的一段时间里,系统将通过广播保证副本之间的数据一致性
  5. 弱一致性: 没有做出保证的

什么是HBase的Region

HBase 中扩展和负载均衡的基本单元称为 region,本质上是以行键排序的连续存储空间。如果 region 太大,系统会把它们进行动态的拆分,相反地,就把多个 region 合并,以减少存储文件数量。

说说Region分区的过程

一张表初始只有一个 region,用户开始向表中插入数据的时候,系统会检查这个 region 的大小,确保其不超过配置的最大值,如果超过限制,系统会在中间键将这个 region 分成大致相等的子 region。每一个 region 只能由一台 region 服务器 RegionServer 加载,每一台 region 服务器(RegionServer)可以同时加载多个 region。一个表实际上是一个由很多 region 服务器加载的 region 集合的逻辑视图。

简单说说HBase是怎么写的

每次更新数据的时候,都会先把数据记录在提交日志(commit log)中,在 HBase 中,这叫做预写日志(WAL),然后才会把这些数据写入内存的 Memstore 中。一旦内存保存的写入数据的累计大小超过了一个给定的最大值,系统就会把这些数据移出内存,作为 HFile 文件刷写到磁盘中。数据移出内存后,系统会丢弃对应的提交日志,大小有限,只保留未持久化到磁盘的提交日志。

/%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%9D%A2%E8%AF%95/img1.png

从HBase读取的数据又是怎样的

读回数据是两部分数据合并的结果,一部分是 MemStore 中还没写入磁盘的数据,另一部分是磁盘上的存储文件。值得注意的是,数据检索的时候用不着 WAL,只有服务器内存中的数据在服务器崩溃前没有写入到磁盘(指 MemStore 的数据),而后进行恢复数据的时候才会用到 WAL。

说说HFile合并

随着 MemStore 的数据不断被刷写到磁盘中,会产生越来越多的 HFile 文件(磁盘上),HBase 内部有一个节俭空间的管家机制,即合并将多个文件合并成一个较大的文件。合并有两种类型,minor 合并和 major 压缩合并。

  1. minor 合并: 是一个多路归并的过程,把多个小文件重写为数量较少的大文件,减少存储文件的数量,合并速度很快,只受到磁盘 I/O 的影响。
  2. major 合并: 将一个 region 中的一个列族的若干个 HFile 重写为一个新的 HFile,能够扫描所有的 kv 对,顺序重写全部数据,重写数据的过程中略过做了删除标记的数据,断言删除此时生效(删除数据在此时会进行物理删除),对于那些超过版本号限制或者生存时间到期的数据,在重写数据的时候就不会刷入磁盘了。

需要注意的是 major 合并的时候,只会对一个列族的 HFile 进行合并。

HBase Master的作用

master 服务器主要负责跨 region 服务器的全局 region 的负载均衡,将繁忙的服务器中的 region 移到较轻的服务器中(本身负载不高)。主服务器不是实际数据存储或者检索路径的组成部分,仅仅提供了负载均衡和集群管理,不为 region 服务器或者客户端提供任务数据的服务,因此是一个轻量级的服务器,此外主服务器还提供了元数据的管理操作,例如建表和创建列族。

介绍几个常用的Hbase Shell的命令

1
2
3
# 查看 master 数量和 regionServer 数量
hbase(main):002:0> status
1 active master, 1 backup masters, 6 servers, 0 dead, 153.5000 average load

HBase安装有什么要求

一般为了能够像 MapReduce 一样有效地利用 HDFS,HBase 大多是与 Hadoop 安装在一起,这样能够很大程度减少对网络 I/O 的需求,同时加快处理速度。当在一台服务器上运行 Hadoop 和 HBase,将最少会有3个 Java 进程,DataNode, TaskTracker 和 RegionServer 在运行,而且当执行 MapReduce 操作的时候,进程数还会激增

master 服务器其实不需要太多的存储空间,不需要挂载过多的硬盘,master 重要性显然要优于 slave,所以需要通过冗余来提高硬件使用率。

关于HBase的原子性

HBase 的所有修改数据的操作都保证了行级别的原子性(能支持行级事务,Hive 不行),这会影响到这一行数据所有的并发读写操作。换句话说,其他客户端或者线程对同一行的读写操作都不会影响该行的数据的原子性,要么读到最新的修改,要么等待系统允许写入该行的操作(强一致性)。

通常正常负载和常规操作,客户端操作不会受到其他修改数据的客户端的影响,因为他们之间的冲突可以忽略不计。但是当许多客户端需要同时修改同一行数据的时候就会有问题,因此建议用户尽量使用批量处理更新,来减少单独操作做同一行数据的次数(同时)。

HBase rowlock有什么用

在初始化 Put 对象的时候,如果需要频繁地重复修改某些行(加锁),用户有必要创建一个 RowLock 实例来防止其他客户端访问这些行。

怎么理解Put操作实际是一个RPC操作

问的其实就是客户端怎么写数据

Put 操作将客户端数据传送到服务器然后返回,这只适合小数据量的操作,如果有个应用程序需要每秒存储上千行数据到 HBase 表中,这样的操作显然是不太合适的,不管是一条条操作还是一次性操作多行,因为一条条的话服务端的网络开销惊人,一批批数据量太多,带宽压力巨大。

因此 HBase 的 API 配备了一个客户端的写缓冲区,缓冲区负责收集 Put 操作,然后调用 RPC 操作一次性将 Put 送往服务器,全局交换机控制这块缓冲区是否在使用。(Flush 机制)

HBase行锁的问题

这个特性保证了只有一个客户端能获取一行数据相应的锁(强一致性有关?),同时对该行进行修改(支持行级事务操作),在实践中,大部分客户端应用程序都没有提供显示的锁,而是使用这个机制来保障每个操作的独立性。用户应该尽可能避免使用行锁,就像在关系型数据库中,两个客户端很可能在拥有对方要请求的锁时,又同时请求对方已拥有的锁,这样便形成了死锁。如果都使用锁了,就会容易死锁。

关注过HBase的Bytes类和ByteBuffer类吗

Bytes 类所有的操作都不需要创建一个新的实例,这是性能优化所考虑的,因为 HBase 内部使用了其中的许多方法并多次调用,不创建实例也就避免了许多不必要的垃圾回收。

关于HBase REST的场景

HBase批处理客户端

另一种客户端的交互使用场景是批量访问数据,不同之处是这些批量处理通常是异步运行在后台,需要扫描大量的数据,例如扫描索引、基于数学模型的机器学习或报表统计需求。

HBase与Hive的交互

用户可以直接定义将 Hive 表存储为 HBase 表,并按需映射列值,在需要的时候行健可以作为独立的一列。

HBase调优的一些经验

  1. 垃圾回收优化: 有时候写入量过大的负载,繁重的负载会迫使内存分配策略无法安全地只依赖 JRE 对程序行为的各种假设,用户需要使用 JRE 所提供的选项来调整垃圾回收策略以应对这些特殊情况。写入负载过大,memstore 在不同时期创建并释放各种不同大小的对象。因为数据是被存储在内存缓冲区的,它们会保留直到超过用户配置的最小刷写大小,可以通过 hbase.hregion.memstore.flush.size 来设置。例如一个列族中插入数据的速度过慢,对应的数据很可能被提升到老生代或终生代,年轻代和老生代的不同点在于空间大小,年轻代占用 128 到 512 MB,而老生代几乎占用了所有可以使用的堆空间,通常会是好几个 GB。而生存期长的 KV 实例一旦刷写到磁盘,就会在老生代的堆上产生空洞,申请新空间的时候,由于碎片过多,导致没有足够大的连续空间分配,JRE 会退回到使用 Full GC 这样会导致其重写整个堆空间并压缩剩余可用对象。总结一句就是容易触发老生代的 Full GC。
  2. 本地 memstore 分配缓冲区: MSLAB 只允许从堆中分配相同大小的对象,一旦这些对象分配并最终被回收,它们将在堆中留下大小一样的固定的孔洞,之后调用相同大小的对象就会重新使用这些孔洞。这是需要付出空间浪费的代价的。
  3. 压缩:
  4. 优化拆分与合并: 用户需要采用盐析主键,或者使用行键来把负载均衡到所有服务器。
  5. 负载均衡:
  6. 合并 region: 当用户向相应的表中插入数据,region 自动拆分的情况是很常见的。当然在某些特殊情况下,用户有可能需要合并 region。例如用户删除大量数据并且想减少每个服务器管理 region 的数目。

HBase读取数据的方式

同一个单元格的多个版本被单独存储为连续的单元格,当单元格被存储时还需要加必要的时间戳,单元格按照时间戳降序排列,所以 HFile 的 Reader 读取数据时,最新的值先被读取到。设定列族,可以有效地减少查询的存储文件,建议用户在查询时指定所需的特定列族。

HBase如果一行数据太大,怎么办

HBase 只能按行(rowkey)分片,因此高表(高瘦,列少)更有优势,设想用户将一个用户所有电子邮件都存在一行中,但有些人的收件箱中有大量的邮件,极端情况下,大到一行数据就超过了最大 HFile 的限制(假设都在一个列族里),此时这个 HFile 无法拆分,同时也会导致 region 无法在合适的位置进行拆分。所以合理设计行键是关键。想想可以按照发件人、收件人来分行(高瘦一点,列少点)。

HBase有多少种键结构

两种,rowkey 和 columnkey,两者都可以存储有意义的信息,这些信息有两类,一种是键本身存储的内容,另一种是键的排列顺序。

磁盘上一个列族上所有的单元格都存储在一个存储文件(HFile),不同列族的单元格不会出现在同一个存储文件中。

比较一下salting和md5的行键方式

当处理流式时间的时候,最常见的数据结构就是按照时间序列组织的数据,这些数据可能来自电网的一个传感器,一个股票交易系统等等,这些数据的突出特点是它们的行键都代表了事件发生的时间,由于 HBase 的数据组织方式,这样的数据在存储的时候会有一个问题,这些数据会被存储到一个特定的范围,也就是一个有特定起始和停止键的 region 中,这样会导致一个问题,一个 region 只能由一个服务器管理,所以所有的更新都会集中在一台服务器上。这会导致系统产生读写热点,并由写入数据过分集中而导致整个系统性能下降。

要解决这个问题,用户需要想办法把数据分散到所有的 region 服务器上。

  1. salting 方式: 用户可以使用加盐前缀来保证数据分散到所有的 region 服务器。这个方法的缺点是,当用户要扫描一个连续的范围的时候,可能需要对每个 region 服务器都发起请求,这样也带来一定的好处,就是用户可以多线程并行地读取数据(一次查多个 regionServer),有点类似小规模的 MapReduce,这样查询的吞吐量会有所提高。
  2. 行键 md5 随机化: 采用 md5 之类的散列函数,可以把行键分散到所有的 region 服务器上,对于时间连续的数据,这种方法明显不是好办法,因为随机化话,用户将不能再按时间范围扫描数据,另一方面,由于用户可以用散列的方式重新生成行键,随机化的方式很适合每次只读取一行数据的应用,如果用户的数据不需要连续扫描,而只需随机读取,用户很适合这种策略。(推荐的场景)

必须熟悉ps相关的命令

From: https://blog.csdn.net/dream2009gd/article/details/8887597

要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而 ps 命令(Process Status)就是最基本同时也是非常强大的进程查看命令。使用该命令 可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵尸、哪些进程占用了过多的资源等等。总之大部分信息都是可以通过执行该命令得到的。

ps 为我们提供了进程的一次性的查看,它所提供的查看结果并不动态连续的;如果想对进程时间监控,应该用 top 工具。如果直接用 ps 命令,会显示所有进程的状态,通常结合 grep 命令查看某进程的状态。

grep 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

使用示例:

1
2
3
[root@node20 updateDB]## ps aux|grep ./update_stock
root 14232 0.0 0.0 5928 724 pts/1 S+ 18:16 0:00 grep ./update_stock
root 25021 0.0 0.0 3056 816 ? S Apr12 0:03 ./update_stock

ps aux 输出格式: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND

什么是Actor解释一下

Actor 是一个并发原语,可以把一个 Actor 看成一个工人,就像能够工作做或是处理任务的进程和线程一样。

一般 OO 多线程工作的时候,需要进行同步并加锁。Actor 和 OO 不一样,不能被直接读取、修改和调用。只能通过消息传递的方式和外界通信,既可以发送消息也可以接收消息做出回复。与方法调用不一样,消息传递是异步的。

因此 Actor 的一大特点就是减少共享状态来解决并发 Bug。

Actor系统ActorSystem是个啥

一般表示多个 Actor 的集合以及所有与该 Actor 集合相关的东西,包括地址、邮箱和配置等。

说说你了解的Actor发送消息的模型

  1. Ask: 向 Actor 发送一条消息,返回一个 Future,当 Actor 返回响应的时候,会完成 Future,不会向消息发送者的邮箱返回任何消息(异步)
  2. Tell: 向 Actor 发送一条消息,所有发送到 sender() 的响应都会返回给发送消息的 Actor
  3. Forward: 将接受到的消息再发送到另一个 Actor,所有发送至 sender() 的响应都会返回给原始消息的发送者
  4. Pipe: 用于将 Future 的结果返回给 sender() 或另一个 Actor。如果正使用 Ask 或是处理一个 Future,那么使用 Pipe 可以正确地返回 Future 的结果。

介绍一下Akka的失败检测

如果集群中的每台服务器都需要和其他所有服务器进行通信,那么集群性能不会随着节点的增加而线性增加,原因在于每增加一个节点,需要的通信开销都会指数增加。为了降低监控其他节点健康程度的复杂度,Akka 中的失败检测只会监控某个节点附近特定数目的节点。在 Akka Cluster 中,每个节点相邻的最大节点数默认是5。(设计原理)

知道什么是CAP么

  1. C: 一致性 Consistency
  2. A: 可用性 Availability
  3. P: 分区容错性 Partition Tolerance

一个分布式系统只能满足其中两个。

Actor邮箱介绍一下

消息都会被发送到内存中的邮箱,如果服务处理消息的速度比邮箱接受到的消息的速度要慢,那么邮箱中的消息会越来越多。如果继续下去,会导致一个严重的问题,内存溢出,服务崩溃。

Actor 都有自己的邮箱,除了 Balancing Dispatcher 的 BalancingPool,因为他的 Actor 共享一个邮箱。一般情况下,一个邮箱的消息只有一个消费者。

如何限制邮箱的消息呢?有两种,阻塞和非阻塞的。所有邮箱都基于队列,这种情况下,阻塞意味着当邮箱满了的时候,再向邮箱发送消息会导致线程等待直到邮箱腾出空间,非阻塞就会导致消息丢失。

那如果选择丢失,又会发生什么事呢?下游系统进行通信的系统应该实现超时重试机制,因此需要记录请求的状态,无论任何原因导致请求失败,都要重发请求,可以设置一个重试的次数阈值。

另外邮箱是可以区分消息优先度的。接受到消息之后可以对消息进行排序,给每个消息赋予一个优先级,这会造成性能的开销,但大多数时候这不重要。(需要了解其原理)

Redis的可伸缩性

大型数据集可伸缩性的关键策略,通过分区或者拆分的方式,将大型数据集分布到多个 Redis 实例上。通过分区的方式,单一 Redis 实例的计算能力和资源将不再成为限制。

Redis 分区有三种方式,客户端,辅助代理分区以及查询路由

  1. 客户端: 分区逻辑包含在客户端代码中,基于算法或者存储额外信息或者兼而有之,用来选择正确分区或者 Redis 节点。就是在客户端实现的分区机制。
  2. 辅助代理分区: 客户端连接到代理中间件,由它将客户端的请求路由到正确的 Redis 节点,最为流行的是 Twemproxy。
  3. 查询路由: 目前 Redis 集群实现的方法,任一客户端查询集群中的随机节点将会被路由到包含键的正确节点上。

Hive执行查询的完整过程

  1. UI(user interface): 用户提交查询或者其他操作,现在标准 UI 有 CLI(command line interface), Thrift Serve, Hive web interface(HWI)。
  2. Driver(驱动): 负责接收查询及其他操作,Driver 实现了会话句柄的概念,并提供在基于 JDBC/ODBC 的 execute 和 fetch API。
  3. Compiler(编译器): 解析查询的 sql,对不同的块和不同的查询表达式进行语义分析,借助 Metastore 中的表和分区的元数据定义生成执行计划。
  4. Metastore: 存储所有表及分区的结构信息,包含列名,列的数据类型,读取和写入的序列化器和反序列化器以及相关的 HDFS 文件存储目录。
  5. Execution Engine(执行引擎): 执行 compiler 所产生的执行计划。该执行计划是一个阶段的 DAG,执行引擎关联执行计划中不同阶段的之间依赖,并负责在不同的系统组件中执行不同的阶段。

执行流程详细解析:

  1. Step 1: UI(user interface)调用的 Driver 的 execute 接口
  2. Step 2: Driver 为查询创建会话句柄,并将查询发送给 compiler 以生成执行计划,
  3. Step 3 and 4: compiler 从 metastore 获取相关的元数据定义
  4. Step 5: 元数据用于对查询树中的表达式进行类型检查,以及基于查询谓词调整分区,生成计划
  5. Step 6, 6.1, 6.2 and 6.3: 由 compiler 生成的执行计划是阶段的 DAG,每个阶段都会涉及到 Map/Reduce job,元数据的操作或者 HDFS 文件的操作。在 Map/Reduce 阶段,执行计划包含 Map 操作树(操作树在 Mappers 上执行)和 reduce 操作树(Reduce 操作树在 Reducers 上执行),Execution Engine 将各个阶段提交个适当的组件执行。
  6. Step 7, 8 and 9: 在每个任务(mapper / reducer)中,表或者中间输出相关的反序列化器从 HDFS 读取行,并通过相关的操作树进行传递。一旦这些输出产生,将通过序列化器生成零时的的 HDFS 文件(这个只发生在只有 Map 没有 reduce 的情况),生成的 HDFS 零时文件用于执行计划后续的 Map/Reduce 阶段。对于 DML 操作,零时文件最终移动到表的位置。该方案确保不出现脏数据读取(文件重命名是 HDFS 中的原子操作),对于查询,临时文件的内容由 Execution Engine 直接从 HDFS 读取,作为从 Driver Fetch API 的一部分。
/%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%9D%A2%E8%AF%95/img2.png

解释一下Hadoop的HDFS的读写流程

From: https://www.jianshu.com/p/7d1bdd23c460

/%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%9D%A2%E8%AF%95/img3.png /%E5%A4%A7%E6%95%B0%E6%8D%AE%E9%9D%A2%E8%AF%95/img4.png

Hive left semi join和inner join的问题

可以使用前者的语法的时候尽量不要使用后者,前者效率高。

HBase找数据的过程是每次都读一次ZK吗

先访问 Root 表再是 Meta 表,中间需要多次网络操作,但是 Client 端会做 cache 缓存。

说说HMaster的功能

  1. 管理用户对 Table 的增删改查
  2. 管理 HRegionServer 的负载均衡,调整 Region 分布
  3. 在 Region Split 后,负责新 Region 的分配
  4. 在 HRegionServer 停机后,负责失效的 HRegionServer 的 Regions 的迁移

为什么最好将具备共同IO特性的column放在一个ColumnFamily中

因为每个 HStore 对应 Table 中的一个 ColumnFamily 的存储,可以看出每个 ColumnFamily 其实就是一个集中的存储单元

HLog在HBase的作用

一旦 HRegionServer 意外退出,MemStore 中的内存数据将会丢失,这就需要 HLog 了。

描述一下ZK选主过程

  1. 选举线程由当前Server发起选举的线程担任,主要功能是对投票结果进行统计,并选出推荐的Server
  2. 选举线程首先向所有Server发起一次询问(包括自己)
  3. 选举线程收到回复后,验证是否是自己发起询问,验证zxid是否一致,然后获取对方的id(myid),并存储当前询问对象的列表中,最后获取对方提议的Leader相关信息,并把这些信息存储到当次选举的投票记录表里
  4. 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server的相关信息设置成下一次要投票的Server
  5. 线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2+1的Server票数,设置当前推荐的Leader为获胜的Server,将根据获胜的Server相关信息设置成自己的状态,否则,继续这个过程,直到Leader被选举出来

Redis持久化的方式

  1. RDB方式: 把内存中的数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置自动快照持久化的方式,配置再n秒内如果超过m个key被修改就会自动做快照保存。Redis借助于fork命令的copy on write的机制,在生成快照的时候,将当前进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成RDB文件,这也是主从复制的关键
  2. AOF方式: 比RDB方式有更好的持久化性

为什么要自研作业调度系统

Crontab 和 Quartz 是偏单机的定时调度程序。开源的分布式作业调度系统由像 oozie, azkaban, chronos, zeus 等。

资源调度系统和作业调度系统的区别,前者关注的重点是底层物理资源的分配管理,目标是最大化地利用集群机器的 CPU,磁盘,网络等硬件资源。后者关注的重点是正确的时间点启动正确的作业,确保作业按照正确的依赖关系及时准确地执行。业务流程是其最主要的关注点。

既然有这么多开源的,为什么还要自研。因为作业调度系统需要和大量的周边组件对接,不仅仅是各种存储计算框架,还可能要处理或使用到血缘管理、权限控制、负载流控、监控报警、质量分析等各种服务或事务。这些事务环节,每个公司都有自己的解决方案,所以作业调度系统所处的整体外部环境千差万别。

目前作业调度系统主要有两类,定时分片类作业调度系统和 DAG 工作流作业调度系统。

Mesos 真正的价值在于运行无状态的服务和应用。静态的资源配置往往是比较低效的。Mesos 提供一套分布式、容错性架构来为数据中心提供资源的细粒度分配。调度器可以协助多个应用间分发工作负载和实现资源共享

至于为什么动态修改 DAG 如此重要,是因为有时候有些场景是需要这么做的,比如一些算法模型,增加一个任务来修改某个特征的大小,这时候就需要在原来的 DAG 的基础上,增加一个任务节点

hive任务的mappe-reducer数量

From: https://blog.csdn.net/iteye_3893/article/details/82645512

hive mapper 数量:

hadoop mapper数量以及hive对此的优化:

hadoop2中,5000个100M左右的文件,则会产生5000个map任务,建立5000个进程来执行销毁成本较高,hive默认是使用CombileFileInputFormat,对小文件进行合并,来减少mapper数,每个进程的开启和关闭耗时,涉及cpu io 带宽 jvm 等。

hive-default.xml.template中默认设置如下:

1
2
3
4
5
<property>
    <name>hive.input.format</name>
    <value>org.apache.hadoop.hive.ql.io.CombineHiveInputFormat</value>
    <description>The default input format. Set this to HiveInputFormat if you encounter problems with CombineHiveInputFormat.</description>
</property>

这样在hive执行mapper task时,会对大量小文件进行合并,让涉及到要操作的文件数据所存储的小文件先进行文件块合并,减少操作的split后在对合并后的文件进行mapper task操作(比如要对 10个50M的存储hdfs文件进行mapper操作,先处理成5个100M的在对这5个文件进行处理这样从10个mapper进程减少到5个)

总结: hive mapper数量由两个因素决定: 1 inputsplit 2 CombineHiveInputFormat

hive reducer 数量:

这是默认reducer task任务个数, hive-default.xml.template中设置为:

1
2
3
4
5
6
7
8
<property>
    <name>hive.exec.reducers.max</name>
    <value>1009</value>
    <description>
      max number of reducers will be used. If the one specified in the configuration parameter mapred.reduce.tasks is
      negative, Hive will use this one as the max number of reducers when automatically determine number of reducers.
    </description>
</property>

hive 中查看当前设置的 reducer 任务个数:

1
2
hive>set mapred.reduce.tasks
mapred.reduce.tasks=-1

数值为-1时,hive会自动推测决定reduce task数量,而最大数值在上面配置文件中配置好了为1009

如果reduce task个数超过这个数值时,就会排队等待, 设置hivereducer个数:

1
2
hive (default)> set mapred.reduce.tasks;  
mapred.reduce.tasks=3

设置后就意味着hive.exec.reducer.max=3

每个reduce任务处理的数据量也是由限定的,hive-default.xml.template中设置为:

1
2
3
4
5
<property>
    <name>hive.exec.reducers.bytes.per.reducer</name>
    <value>256000000</value>
    <description>size per reducer.The default is 256Mb, i.e if the input size is 1G, it will use 4 reducers.</description>
</property>

默认是256M,如果给reducer输入的数据量是1G,那么按照默认规则就会分拆成4个reducer。

reducer 任务个数:

min(向上取整(reduce输入文件大小/reducer默认处理大小) , hive.exec.reducer.max)

eg: 1G的reduce输入数据 hive.exec.reducers.bytes.per.reducer=256M mapred.reduce.tasks=-1 或者 hive.exec.reducer.max=1009 那么真实产生recuder个数为: min(向上取整(1G/256m), 1009) = 4; 1T的reduce数据则为min(向上取整(1T/256m), 1009) = 1009

Hive sql调优常见

Hive的Join操作一般问题都是数据倾斜,尤其是在小表(较大)和大表关联的情况下,如果小表比较大,此时可以设置参数,对小表进行broadcast达到map端操作的目的,此时容易产生OOM,此外当小表比较大的时候,网络传小表的时候也是一个非常大的开销。例如说该小表1000w条记录,而大表10亿条记录,这个其实可以通过调整SQL语句解决。提取id和另外的表关联……

你是怎么理解Akka的

From: https://www.cnblogs.com/tiger-xc/archive/2017/04/25/6760658.html

Akka 程序是由多个 Actor 组成的。它的工作原理是把一项大运算分割成许多小任务然后把这些任务托付给多个 Actor 去运算。Actor 不单可以在当前 JVM 中运行,也可以跨 JVM 在任何机器上运行,这基本上就是 Akka 程序实现分布式运算的关键了。当然,这也有赖于 Akka 提供的包括监管、监视各种 Actor 角色,各式运算管理策略和方式包括容错机制、内置线程管理、远程运行管理(remoting)等,以及一套分布式的消息系统来协调、控制整体运算的安全进行。

Actor 是 Akka 系统中的最小运算单元。每个 Actor 只容许单一线程,这样来说 Actor 就是一种更细小单位的线程。Akka 的编程模式和其内置的线程管理功能使用户能比较自然地实现多线程并发编程。Actor 的主要功能就是在单一线程里运算维护它的内部状态,那么它的内部状态肯定是可变的(mutable state),但因为每个 Actor 都是独立的单一线程运算单元,加上运算是消息驱动的(message-driven),只容许线性流程,Actor 之间运算结果互不影响,所以 从 Akka 整体上来讲 Actor 又好像是纯函数不可变性的(pure immutable)。Actor 的内部状态(internal state)与函数式编程不可变集合(immutable collection)的元素差不多,都是包嵌在一个类型内,即F[A] »> Actor[A]从类型款式来讲很相像,那么我们可否对 Actor 进行函数组合(functional composition),然后实现函数式编程模式的 Akka 编程呢?应该是不可能的,因为我们无法对Actor的运算结果进行固定。一是我们无法防止Actor的运算产生副作用,再就是Actor的运算结果是无法预料的,例如它可能把结果发送给任何其它Actor,这样对同样的输入就可以产生不同的结果。我们可以把Actor视作不纯函数(impure function),对同样的输入可能会产生不同的输出结果,如此就无法把对Actor的编程归类为函数式编程了,但Actor编程的确是一种有别于其它编程模式、别具风格的编程模式,而且Akka还有一套领域特定语言DSL,是一种独立的编程模式,即Actor编程模式了。这是一种需要掌握的崭新编程模式。

Akka中的如果Actor mailbox爆了怎么办

如果情况允许就丢消息,因为有 retry 机制,否则就加内存或者加机器分布式。

警告
本文最后更新于 2017年2月1日,文中内容可能已过时,请谨慎参考。