当一个事件通过一个生产者被发布出去,一个或多个消费者组合将收到消息,我们把事件写入 HBase 表的一个或多个行上,那么这条记录就被设计成适用于每个消费者组。事件的有效负荷和元数据被存储在独立的列上,那么行的值就是下面这样的格式:
两个有趣的部分是行的值是分区 ID 和整个 ID。分区 ID 通过限定行值前缀再提供给消费者。消费者只被允许读数据,并在出队的时候使用前缀扫描。分区 ID 由两部分组成:一个消费者组 ID 和一个消费者 ID。生产者计算出每个消费者组的分区 ID,并通过入队写到那些行。
行关键字中的入口 ID(Entry ID)包含了事务信息。它由 Tephra 触发的生产者事务写指针和单向增长的计数器组成。这个计数器由本地的生产者生成,同时,针对事件,计数器需要让行关键字唯一,因为生产者可以在同一个事务中将多个事件加入队列。
出队列的时候,计数器会使用事务写指针来决定,队列入口是否已经提交,以及是否可以消费了。事务写指针和计数器的组合,使得行关键字总是唯一的。这让生产者可以独立的操作,而不会有写冲突。
为了生成分区 ID(Partition ID),生产者需要知道大小和每个消费者组的分区关键字。当应用程序启动,以及组大小发生任何变化的时候,消费者组信息都会被记录下来。
改变生产者和消费者增加或减少生产者是很直接的,因为每个生产者都是独立操作的。增加或减少生产者进程就可以满足这个要求。然而,当消费者组的大小需要改变的时候,就需要协调来正确更新消费者组的信息。可以用下面的图表来概括所需的步骤:
由于暂停和恢复是由 Apache ZooKeeper 来协调的,同时它们也是并行执行的,所以它们是两个非常快速的操作。例如,之前我们提到的 Web 访问日志分析应用程序,改变消费者组信息的过程可能看起来像这样:
基于这个队列的设计,入队列和出队列的性能,与单独的批量 HBase Puts 和 HBase Scans 不相上下,这样也带来与 Tephra 服务器进行通讯的开销。通过在同一个业务处理中将多个事件批量处理,可以大大降低这个开销。
最后,为了避免,我们基于簇的大小提前分割了 HBase 表,同时,在行关键字(row key)上采用 加盐(salting) 的方式来更好的分配写。否则,由于是单调的增加业务处理写指针,行关键字就会是连续的。
性能值我们在小型的 10 节点的 HBase 集群上已经测试过性能,结果令人印象深刻。使用 1K 字节负载,以 500 个事件为一个批次大小,我们完成了生产和消费 100K 个事件/秒的吞吐量,其中运行了 3 个生产者和 10 个消费者。我们也观察到当我们增加消费者和消费者的时候,吞吐量线性增加:例如,当我们将生产者和消费者数量加倍的时候,吞吐量增加到 200K 个事件/秒。
在 HBase 的帮助下,结合最佳实践,我们成功的创建了一个线性可伸缩的,分布式事务队列系统。同时,在 CDAP 中使用这个系统提供实时流处理框架:动态可伸缩,强一致性,以及一次交付的传输保证。
Hadoop+HBase搭建云存储总结 PDF