首页 > 其他分享 >Apache BookKeeper Internals — Part 1 — High Level

Apache BookKeeper Internals — Part 1 — High Level

时间:2022-12-10 10:22:31浏览次数:61  
标签:Level journal Internals 写入 ledger bookie BookKeeper 线程

标题:Apache BookKeeper Internals — Part 1 — High Level
原文:https://medium.com/splunk-maas/apache-bookkeeper-internals-part-1-high-level-6dce62269125
时间:2021-10-19
社区翻译:https://mp.weixin.qq.com/s?__biz=MzUyMjkzMjA1Ng==&mid=2247489945&idx=1&sn=a9dcb508a14eaa96052e783ef79f0e75&chksm=f9c503aeceb28ab8463c1149dd5a6a3c88b6d631d82621813f009bdc676619fb17511faf065d&scene=21#wechat_redirect

本系列的主要目标是帮助人们建立一个BookKeeper服务器内部工作方式的模型。此模型是在生产环境定位问题或向项目添加/修改功能时的基础。在接下来的系列文章中,我们将研究如何将这种模型与可用的BookKeeper指标相结合,以使我们能够更有效地识别性能瓶颈。

请注意,BookKeeper可以和不同的可拔插组件配合使用,我们将特别关注为Apache Pulsar使用而配置的BookKeeper。因此,还有其他可插拔组件未在本文中介绍。我们还将关注单个bookie公司的工作原理,而不是整个集群和复制协议。要了解更广泛的BookKeeper协议,请参阅我们的博客文章以了解更多信息。

我们以读写流程为重点开始本系列,并可能在未来探索垃圾收集、压缩和CLI工具等其他领域。

如果你阅读BookKeeper代码,你会看到它被组织成不同的模块,模块内有不同的抽象。本指南不涉及这些代码抽象,因为它们只与BookKeeper贡献者真正相关。我们的重点是执行工作的线程,以及线程之间的数据流、数据结构和持久存储,可以将其视为物理模型。

High Level View

一台最简单的BookKeeper服务器节点(称为bookie),由位于顶部的网络服务器(使用Netty),底层的磁盘IO层,和中间一组buffer和cache组成。bookie只是存储节点,其主要目的是尽可能快地写入和读取ledger条目,同时保证数据安全性。

图1 一个bookie最简单的表示

隐藏在该表示中的是各种组件和线程模型。在这篇文章中,我们将逐步拨开每一层并解释所有内容。

The Components

每个ledger条目都会写入journal和持久化存储中,称为Ledger Storage。Ledger Storage是可插拔的,Pulsar集群中的bookie使用的版本称为DbLedgerStorage。

Journal提供了写持久性和低延迟写功能。一旦一个条目被写入journal并执行fsync,就可以发送一个写响应给客户端,因为该条目已安全地存储在磁盘上。DbLedgerStorage以异步方式写入磁盘,通过修改数据布局,使得它能够以更加优化的方式在磁盘上执行大批量顺序写入操作,以实现更多顺序读取。稍后我们将更详细地介绍这一点。

读取请求只会进入DbLedgerStorage,如果进展顺利,通常会命中缓存。当缓存未命中时,将从磁盘读取条目,并执行预读操作来填充读缓存,以便后续读取的下一个条目将命中缓存。稍后再详细介绍。

图2 读写请求涉及组件的高级视图

让我们开始查看有关写请求的journal和DbLedgerStorage。当Netty服务器收到写请求时,会创建一个写请求对象,并将其提交到写线程池。在这里,条目会被传给DbLedgerStorage和journal,前者通过将条目写到写缓存(内存缓存),后者通过将条目添加到内存队列中。负责journal和ledger storage的不同线程从各自的结构中获取条目,并分别将条目写入磁盘。一旦条目写入journal并执行fsync后,将返回响应给客户端。

图3 Journal和DbLedgerStorage的高级视图

即使在高层次上,需要了解的也不仅仅是涉及哪些组件,和线程模型。每个bookie都有多个线程池,每个线程池通过使用Journal和Ledger Storage API来完成所有这些工作。

The Threading Model

图4 bookie的线程和线程池

上图简单地显示了存在哪些线程和线程池,以及它们之间的通信线路。Netty负责所有请求/响应处理,然后根据请求将工作提交到四个线程池中的一个。

正常读取不会与其他线程交互,它们可以自己完成所有工作(Read Threadpool下面的虚线是什么意思?)。另一方面,当相关写入发生时,长轮询读取会得到写入线程的通知。写操作的同步流程会涉及多个线程。其他线程,比如Sync线程和DbStorage线程,也会进行写操作,但是是异步的。

高优先级线程池负责处理标记为高优先级的读写请求。这通常包括fencing操作和与恢复操作相关的读/写操作。在稳定状态下,这个线程池应该很少工作。

线程通过以下方式之一进行交互:

  • 工作被提交到另一个线程或线程池(Java executors)。每个executor都有自己的任务队列,工作在队列中排队,直到可以执行为止。
  • 在内存队列(如阻塞队列)中,一个线程向队列中添加一个对象,另一个线程从队列中获取这些对象并对其进行处理。
  • 缓存——一个线程向写缓存添加一个对象,另一个线程读取这些对象以将其写入磁盘。

在详细介绍线程和组件如何协同工作之前,我们应该先介绍计算(线程)和IO组件(Journal/Ledger Storage)是如何能够并行化的。

Parallelization of Work and Ordering Guarantees

BookKeeper允许并行执行计算和IO操作。通过使用线程池来使计算并行化,通过将磁盘IO分散到多个目录(每个目录可以挂载到不同的卷)来使IO并行化。

四个线程池:写入、读取、长轮询和高优先级都是OrderedExecutor类的实例,OrderedExecutor类是一个线程池,可以根据正在写入或读取的条目的ledger id将工作分配给不同线程。

图5 OrderedExecutor是一个线程池,基于ledger id进行任务分配

通过ledger id确定路由,可以并行化和排序相关的操作。每个单线程执行器都有一个任务队列,允许向队列中提交任务,executor一次完成一个任务。

设置线程数的配置包括:

  • serverNumIOThreads (Netty threads, defaults to 2xCPU threads)
  • numAddWorkerThreads (defaults to 1)
  • numReadWorkerThreads (defaults to 8)
  • numLongPollWorkerThreads (defaults to 0 meaning that long poll reads are submitted to the read thread pool)
  • numHighPriorityWorkerThreads (defaults to 8)
  • numJournalCallbackThreads (defaults to 1)

在磁盘IO端,我们可以通过将工作分散到多个目录来并行化Journal和Ledger Storage。

为每个配置的journal目录创建一个单独的journal实例。每个journal实例都有自己的内部线程模型,用于写入磁盘并调用写请求回调函数以发送写响应。

图6 多个journal实例可以提高写入吞吐量

您可以通过配置journalDirectories来配置多个journal目录。

对于每个配置的ledger目录,DbLedgerStorage都会创建一个单独的SingleDirectoryDbLedgerStorage实例,其中包含一个写缓存、读缓存、DbStorage线程及其自己的ledger entry log file和RockDB索引文件。每个实例都是独立的,即不共享文件或缓存。

图7 多个SingleDirectoryDbLedgerDirectory实例可以增加写入吞吐量

您可以通过配置ledgerDirectories配置多个ledger目录。

SingleDirectoryDbLedgerStorage有点拗口,所以将其简单的称为DbLedgerStorage实例。

所以,请求的最终路径取决于线程池大小以及journal和ledger目录的数量。

图8 读取请求可能采用的路径包括8个读取线程和2个ledger目录

如果DbLedgerStorage实例个数为1,那么是不是上层的读线程池都是串行读写?

默认情况下,写线程池只有一个线程,因为这个线程池没有做太多工作,我们将在下一篇文章中解释。

图9 写入请求可能采用的路径包括1个写入线程、4个journal目录和2个ledger目录

这种可并行化的体系结构允许bookie在具有大量CPU核数和多个磁盘的大型服务器上纵向扩展其计算和磁盘IO能力。

当然,我们不要忘记,扩大BookKeeper规模的最简单方法之一就是扩大bookie的数量。但对于那些想纵向扩容的人来说,BookKeeper也提供了这种能力。

Thread Naming

如果你生成过bookie进程的火焰图或者dump过内存内容,你可以看到以下带有前缀的线程和线程池:

  • bookie-io (Netty threads)
  • BookieReadThreadPool-OrderedExecutor
  • BookieWriteThreadPool-OrderedExecutor
  • BookieJournal-3181 (if you use the default port)
  • ForceWriteThread
  • bookie-journal-callback
  • SyncThread
  • db-storage

Summary

在这篇文章中,我们讨论了组件和线程方面的high level体系结构,以及如何将请求确定地路由到这些线程和组件。

在下一篇文章中,我们将详细介绍写操作,包括线程模型和组件模型如何结合。

标签:Level,journal,Internals,写入,ledger,bookie,BookKeeper,线程
From: https://www.cnblogs.com/oyld/p/16970870.html

相关文章