惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cisco Talos Blog
Cisco Talos Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
雷峰网
雷峰网
The Register - Security
The Register - Security
The Cloudflare Blog
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
I
InfoQ
博客园 - 三生石上(FineUI控件)
H
Help Net Security
博客园 - 司徒正美
Vercel News
Vercel News
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
B
Blog
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
L
LangChain Blog
人人都是产品经理
人人都是产品经理
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recorded Future
Recorded Future
小众软件
小众软件
Martin Fowler
Martin Fowler
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Apple Machine Learning Research
Apple Machine Learning Research
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - Franky
Hugging Face - Blog
Hugging Face - Blog
Y
Y Combinator Blog
V
Visual Studio Blog
F
Fortinet All Blogs
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
N
Netflix TechBlog - Medium
V
V2EX
Blog — PlanetScale
Blog — PlanetScale

文章列表 - 她和她的猫

使用小鹤双拼一个月 折腾 Hugo PaperMod 主题 去长鹿旅游休博园 1Password 自动化登录堡垒机 深入 Hyperf:Inject 注解是如何工作的? 译|理解容器镜像层 使用 Rust 实现高性能预写日志(Write Ahead Log) 译|使用 Linux 命名空间隔离系统 深入 Hyperf:HTTP 服务是如何处理请求的? Linux 文件权限学习笔记 还原 SM2 压缩公钥的几种方法 深入 Hyperf:HTTP 服务启动时发生了什么? 一次 Hyperf 注解失效问题分析 将博客迁移到又拍云 译|PHP 7 新的 Hashtable 实现 排查 ES 查询问题:深入了解 json_encode() 函数 浅析 Redlock 分布式锁实现原理 Yar 源码阅读笔记:RPC 服务端 Yar 源码阅读笔记:客户端的并行调用 Yar 源码阅读笔记:客户端的同步调用 Yar 源码阅读笔记:数据传输模块 Yar 源码阅读笔记:消息编码模块 Yar 源码阅读笔记:RPC 通信协议 Yar 源码阅读笔记:开篇 PHP 8 新特性介绍 基于 OpenSSL 实现国密 SM4 加解密 PHP 源码阅读笔记:编译与调试 PHP 什么是惊群问题 大端模式和小端模式 基于 GitHub Actions 定时推送网址到百度站长平台 聊聊五种 I/O 模型 Workerman 源码分析:文件上传 浅入浅出 HTTP 协议 PHP 网络编程:构建 MySQL 蜜罐获取攻击者微信 ID 使用 Workerman 接入 Bilibili 直播弹幕协议 使用 GitHub Actions 自动部署 Hexo 什么是二进制安全 【转载】PHP 程序员进阶之路 优化 Workerman 检查主进程是否存活的逻辑 基于 Redis 实现分布式锁 《PHP 实现 Base64 编码/解码》笔记 PHP 多进程下载必应壁纸 基于 Redis 实现延迟队列 【转载】PHP 实现 Base64 编码/解码 使用 MySQL FIELD() 函数自定义排序规则 Laravel 的 Facades 实现原理 C语言单链表实现约瑟夫环 数据结构学习笔记:顺序栈和链栈(C语言) 使用 QueryList + Redis 下载壁纸 在 Laravel 登录/注册中使用 mews/captcha 扩展包 使用 mews/captcha 扩展包为 Laravel 应用添加图片验证码 给 YOURLS 短网址系统编写插件《批量生成短网址》 给 YOURLS 短网址系统编写插件《Hello World!》 PHP 7.1 使用 json_encode 函数造成浮点类型数据出现精度问题 Laravel 授权策略(Policy)的基本使用 Ubuntu 系统下 WiFi 频繁掉线解决方法 Laravel 应用部署到 Nginx 服务器上的第一个坑 Ubuntu 16.04 LST 安装 Redis 和 Composer PHP 采集逐浪小说章节列表 Nginx 环境 SSL 强制 HTTP 301 跳转到 HTTPS 【工作&生活】第一周 ServiceStack.Redis 与多线程 Count the number of Duplicates C# Base64 加密及解密 ASP.NET MVC 使用动态类型传递数据 Django 使用 order_by() 对数据进行排序操作 ASP.NET + jQuery + AJAX 实现用户登录 ASP.NET 从客户端中检测到有潜在危险的 Request.Form 值 ASP.NET 使用 FormData 实现 AJAX 上传图片 Python 使用 BeautifulSoup 抓取网页 贪心算法之字符串的完美度 一言不和就造轮子之 CookieHelper ASP.NET 上传图片 DBHelper 类 C# 捕获屏幕源码 用 JavaScript 做一些有趣的事情 第一篇文章 好久不见。 如何防止非法调用 PHP 文件 清明小记。 PHP 生成随机字符串 Z-Blog PHP 程序做 301 跳转 PHP 处理数组常用的几个函数 Sublime Text 3 快捷键总结 PHP 使用 file_get_contents() 函数实现采集网页 使用 C# 编写学生信息管理系统 终于到了。 zblog报错: magic_quotes_gpc 和 safe_mode 世事无常。 如何使用 PHP 上传文件 安卓手机编写和运行 PHP 程序的软件 使用 AJAX 刷新、验证 PHP 图片验证码 PHP 生成图片验证码 PHP 之 session 的使用方法 PHP 之 cookie 的使用方法 PHP 实现分页原理详解 Thinkphp 框架中系统常量在 Js 文件中不解析的解决方法
译|了解 Bitcask:基于日志结构的 KV 存储引擎
她和她的猫 · 2023-07-19 · via 文章列表 - 她和她的猫

最近我在微信上看到了一篇介绍 rosedb 的文章,从这篇文章中,我了解到 rosedb 是一个基于 Bitcask 存储模型的 KV 存储引擎,并在此基础上做了大量了优化工作,它兼容 Redis 协议、支持丰富的数据结构和批量操作。实际上,可以将 rosedb 看作是 Go 语言版本的 Redis。

为了更深入地了解 Bitcask 是什么,我开始在 Google 上搜索相关资料,而这篇文章恰好排在搜索结果的前几位。文章详细介绍了 Bitcask 的关键实现细节,我觉得这篇文章可以帮助我们快速地了解 Bitcask,于是决定将其翻译成中文。

原文链接:https://arpitbhayani.me/blogs/bitcask/

Bitcask 是专为处理生产级流量而设计的、最高效的嵌入式键值(KV)数据库之一。介绍 Bitcask 的论文称它是一个用于快速键值的日志结构哈希表,用更简单的话来说,这意味着数据会被按顺序写入到仅附加日志文件中,并且每个键都会有一个指向其日志条目所在位置的指针。基于仅附加日志文件构建 KV 存储,这看起来似乎是一个非常奇怪的设计选择,但是 Bitcask 不仅提高了效率,而且还提供了非常高的读写吞吐量。

Bitcask 被引入作为一个名为 Riak 的分布式数据库的后端,其中每个节点都运行了一个 Bitcask 实例来保存它所负责的数据。在这篇文章中,我们将会深入研究 Bitcask 及其设计,并找到使其性能如此出色的秘诀。

Bitcask 使用了很多日志结构文件系统的原理,并从许多涉及日志文件合并的设计中汲取了灵感,例如 LSM 树的合并。从本质上来说,它就是一个具有固定结构和内存索引的仅附加(日志)文件的目录,内存索引中保存了键映射到点查找所需的大量信息(指数据文件中的条目)。

点查找(Point lookup)是指一次获取单个键值。类似的还有范围查找(Range lookup),是指一次获取多个键值。

数据文件

数据文件是一个仅附加日志文件,用来保存键值对以及一些元信息。单个 Bitcask 实例可以有很多个数据文件,但其中只会有一个数据文件处于活跃状态并打开用于写入操作,而其它数据文件则被认为是不可变的,仅用于读取操作。

Bitcask 的数据文件

如上图所示,数据文件中的每个条目都有一个固定的结构,其中存储了 crctimestampkey_sizevalue_size、实际的 key、实际的 value。在引擎上的所有写操作(创建、更新、删除)都会转换为活跃数据文件中的条目。当这个活跃的数据文件的大小达到阈值时,它将会被关闭并创建一个新的处于活跃状态的数据文件;正如前面说的那样,当数据文件被关闭时(无论是有意或无意),就会被认为是不可变的,并且永远都不会再次打开用于写入操作。

键目录

键目录是一个内存哈希表,它存储了 Bitcask 实例中存在的所有键,并将其映射到日志条目(值)所在的数据文件中的偏移量;从而使点查找更加方便。哈希表中的映射值是一个包含 file_idoffset 和一些元信息(例如 timestamp )的结构体,如下图所示。

Bitcask 的键目录

Bitcask 上的操作

现在我们已经了解了 Bitcask 的整体设计和组件,我们可以开始探索它支持的操作及其实现的细节。

创建新的键值

当一个新的键值对被提交并保存到 Bitcask 时,引擎首先会将它追加到活跃的数据文件中,然后在键目录中创建一个新的条目,并指定存储该值的偏移量和文件。这两个操作都是以原子方式执行的,意味着要么在两个结构中都创建条目,要么都不创建。

创建一个新的键值对只需要一个原子操作,包含了一次磁盘写入和几次内存访问和更新。由于活跃的数据文件是一个仅附加文件,因此磁盘写入操作不必执行任何磁盘寻址,使写入操作以最佳速率进行,从而提供高写入吞吐量。

更新已存在的键值

该键值存储不支持部分更新,开箱即用,但是它支持整值替换。因此,更新操作与创建一个新的键值对非常相似,唯一的不同是不在键目录中创建条目,而是用新的位置更新已存在的条目,新的位置可能是在新的数据文件中。

与旧值对应的条目现在处于悬挂状态,并且将会在合并和压缩的过程中显式地进行垃圾回收。

删除键

删除键是一种特殊的操作,其中引擎以原子方式在活跃的数据文件中追加一个新条目,该条目的值等于墓碑值(表示删除),并从内存中的键目录中删除该条目。需要选择一个非常特别的值作为墓碑值,这样才不会让它干扰现有的值空间。

与更新操作一样,删除操作非常的轻量级,需要磁盘写入和内存更新。同样在删除操作中,与已删除的键相对应的旧条目将保持悬挂状态,并且将会在合并和压缩的过程中显式地进行垃圾回收。

读取键值

从存储中读取键值对,首先需要引擎找到数据文件以及提供的键在其中的偏移量;这个步骤是使用键目录完成的。一旦该信息是有效的,引擎就会从相应数据文件的偏移量处执行一次磁盘读取,以检索日志条目。根据存储的 CRC 对检索到的值进行正确性检查,然后将该值返回给客户端。

从本质上来说,该操作的速度很快,因为它只需要一次磁盘读取和几次内存访问,但使用文件系统预读缓存可以使其速度更快。

合并和压缩

正如我们在更新和删除操作期间所看到的,与键相对应的旧条目会保持不变并处于悬挂状态,这会导致 Bitcask 消耗了大量的磁盘空间。为了提高磁盘利用率,引擎每隔一段时间就会将已关闭的、旧的数据文件压缩到一个或多个与现有数据文件结构一致的合并文件中。

合并过程会遍历 Bitcask 中的所有不可变文件,并生成一组数据文件,这些数据文件仅包含当前每个键的实时最新版本。这样,在新数据文件中将会忽略未使用和不存在的键,从而节省大量的磁盘空间。由于记录现在存储于不同的合并数据文件中,并且位于新的偏移量,因此需要对键目录中条目进行原子更新。

快速启动

如果 Bitcask 发生故障并需要重新启动,它必须读取所有的数据文件并构建一个新的键目录。在这里合并和压缩确实是有所帮助的,因为它减少了读取一些最终将会被驱逐的数据的需要。但还有另一种操作可以帮助加快启动时间。

为每个数据文件创建一个提示文件,它保存了数据文件中除了值以外的所有内容(键和相应的元信息)。因此,这个提示文件只是一个包含对应数据文件中所有键的文件。由于提示文件非常小,因此通过读取该文件,引擎可以快速地创建整个键目录并更快地完成启动过程。

Bitcask 的优点和缺点

优点

  • 读写操作低延迟。
  • 高写入吞吐量。
  • 一次磁盘 IO 可以检索任意值。
  • 可预测的查找和插入性能。
  • 故障恢复快速且稳定。
  • 备份简单,只需要拷贝目录即可。

缺点

键目录必须在内存中维护所有的键,这给系统增加的巨大的限制,因为它需要有足够的内存来容纳整个键空间以及文件系统缓冲区等其他必需品。因此,Bitcask 的限制因素是可用于保存键目录的 RAM 有限。

尽管看起来这个缺点很严重,但解决方案相当简单。通常情况下,我们可以对键进行分片并水平扩展,而不会对创建、读取、更新和删除等基本操作造成太大损失。

参考资料