在
上一篇文章中,我们研究了当您需要水平扩展数据并保证事务的ACID属性时出现的限制和障碍。 在本文中,我们讨论了FoundationDB技术,并了解了它在开发关键任务应用程序时如何帮助克服这些限制。
FoundationDB是具有可序列化ACID事务的NoSQL分布式数据库,可存储排序的键值存储对。 键和值可以是字节的任意序列。 它没有单一的入射点-所有群集计算机都是相同的。 它本身可以在群集服务器之间分配数据并快速扩展:当您需要向群集添加资源时,您只需在配置服务器上添加新计算机的地址,数据库便会自行对其进行处理。
在FoundationDB中,事务永远不会相互阻塞。 读取是通过
多版本控制 (MVCC)来实现的,读取是通过开放式
并发控制 (OCC)来实现的。 开发人员声称,当群集中的所有计算机都在同一个数据中心中时,写入延迟为2-3毫秒,读取延迟小于一毫秒。 该文档包含10-15毫秒的估计值,这可能更接近实际条件下的结果。
*不支持多个分片上的ACID属性。FoundationDB具有独特的优势-自动重新分片。 DBMS本身可确保群集中机器的均匀加载:当一台服务器已满时,它将数据重新分配给后台的相邻服务器。 同时,保留了所有事务的可序列化级别的保证,并且对客户而言唯一可见的效果是响应延迟稍有增加。 该数据库可确保负载最大和负载最小的群集服务器上的数据量相差不超过5%。
建筑学
从逻辑上讲,FoundationDB群集是在不同物理计算机上的一组相同类型的进程。 进程没有自己的配置文件,因此可以互换。 几个固定的过程具有专门的角色-协调器,并且每个群集过程在启动时都知道其地址。 协调程序崩溃应尽可能独立,这一点很重要,因此最好将它们放在不同的物理计算机上,甚至放在不同的数据中心中。

协调员之间通过
Paxos共识算法达成共识。 他们选择集群控制器进程,然后将角色分配给其余集群进程。 集群控制器不断通知所有协调员他还活着。 如果大多数协调员认为他已经死了,他们就选一个新的。 群集控制器和协调器均不参与事务处理;它们的主要任务是消除
大脑分裂的情况。
当客户想要连接到数据库时,他立即联系所有协调员以获取当前集群控制器的地址。 如果大多数答案匹配,它将从群集控制器接收完整的当前群集配置(如果不匹配,它将再次调用协调器)。

集群控制器知道可用进程的总数并分配角色:这5个将是Proxy,这2个将是Resolver,这将是Master。 如果他们中的任何一个死亡,那么他将立即为他找到替代者,将必要的角色分配给任意的自由程序。 所有这些都在后台发生,对于应用程序程序员而言是不可见的。
Master进程负责数据集的当前版本号(随着数据库中的每个记录的增加而增加),并负责将许多密钥分配给存储服务器和速率调节(在重负载下人为地降低性能:如果集群知道客户端)会提出很多小的要求,他将等待,将它们分组并立即回答整个请求。
事务日志记录和存储是两个独立的存储子系统。 第一个是临时存储,用于按接收顺序将数据快速写入磁盘,第二个是永久存储,其中,磁盘上的数据按键的升序排序。 每次事务提交时,至少有三个tLog进程必须保存数据,然后集群才能向客户端报告成功。 同时,后台的数据从tLog服务器移动到存储服务器(存储也是冗余的)。
要求处理
所有客户端请求都处理代理过程。 打开一个事务,客户端访问任何代理,它轮询所有其他代理,并返回集群数据的当前版本号。 所有后续读数均以该版本号出现。 如果在打开事务后另一个客户记下了数据,我将根本看不到它的更改。
记录事务有点复杂,因为您需要解决冲突。 这包括解析程序进程,该进程将所有已修改的密钥存储在内存中一段时间。 当客户端完成提交事务时,解析器检查以查看其读取的数据是否过时。 (也就是说,是否在我的后面打开的事务已完成并更改了我读取的密钥。)如果发生这种情况,事务将回滚,并且客户端库本身(!)进行第二次提交尝试。 开发人员唯一需要考虑的是事务是幂等的,也就是说,重复使用应该得出相同的结果。 一种实现方法是在事务内保存一些唯一值,并在事务开始时检查其在数据库中的存在。

像在任何客户机/服务器系统中一样,在某些情况下,交易成功完成,但是由于断开连接,客户机未收到确认。 客户端库将它们像其他任何错误一样对待-它只是重试。 这可能导致重新执行整个交易。 但是,如果事务是幂等的,则没有问题-不会影响最终结果。
缩放比例
一个存储子系统中可以有成千上万的服务器。 客户需要特定密钥上的数据时,客户应与谁联系? 客户端从群集控制器了解整个群集的完整配置,并且包括每个存储服务器上的密钥范围。 因此,它只是直接访问所需的存储服务器,而无需任何中间请求。
如果所需的存储服务器不可用,则客户端库从群集控制器获取新配置。 如果由于服务器崩溃而导致群集知道冗余不足,那么它将立即开始从其他存储中收集新节点。
假设您在事务中保存了1 GB的数据。 您如何快速回应? 没办法,因此,FoundationDB只能将一个事务的大小限制为10 MB。 此外,这是对事务
涉及的所有数据(读取或写入)的限制。 数据库中的每个条目也受到限制-密钥不能超过10 KB,值是100 KB。 (同时,为了获得最佳性能,开发人员建议使用长度为32字节的键和长度为10 KB的值。)
任何交易都可能成为冲突的根源,然后必须将其回滚。 因此,为了提高速度,在提交命令之前,将当前更改保留在RAM中而不是磁盘中是有意义的。 假设您正在以1GB /秒的负载将数据写入数据库。 然后,在极端情况下,您的群集将每秒分配3GB RAM(我们在3台计算机上写入事务)。 如何限制使用过的记忆的这种雪崩般的增长? 限制最大交易时间非常简单。 在FoundationDB中,事务持续时间不能超过5秒。 如果客户端在打开事务后5秒钟尝试访问数据库,则群集将忽略其所有命令,直到打开新命令为止。
指标
假设您保留一个人员列表,每个人都有一个唯一的标识符,我们将其用作键,并在值中写入所有其他属性-姓名,性别,年龄等。
关键 | 价值 |
12345 | (Ivanov Ivan Ivanovich,男,35岁) |
如何在不进行详尽搜索的情况下获取所有30岁的人的名单? 通常,为此在数据库中创建一个索引。 索引是另一个旨在快速搜索其他属性的数据视图。 我们可以简单地添加以下形式的条目:
现在,要获取所需的列表,只需搜索键的范围(30,*)。 由于FoundationDB存储按键排序的数据,因此这种查询将很快执行。 当然,索引占用了额外的磁盘空间,但很少。 请注意,并非所有属性都重复,只有年龄和标识符重复。
重要的是,在一个事务中执行将记录本身和索引添加到记录的操作。
可靠度
FoundationDB用C ++编写。 作者于2009年开始研究它,2013年发布了第一个版本,2015年3月,苹果公司购买了它们。 三年后,苹果意外地打开了源代码。
有传言称苹果除其他外还使用它来存储iCloud服务数据。
经验丰富的开发人员通常不会立即信任新的解决方案。 该技术可能需要几年的时间才能可靠地建立起来,并开始在产品中大量使用。 为减少此时间,作者对C ++:
Flow语言进行了有趣的扩展。 它使您可以优雅地模拟使用不可靠的外部组件,并有可能完全可预测地重复执行程序。 对网络或磁盘的每次调用都包装在某个包装器(Actor)中,并且每个Actor具有多个实现。 标准实现按预期将数据写入磁盘或网络。 另一个写入磁盘的次数为1000的999次,丢失的次数为1000的1次。 替代性网络实施方式可以例如交换网络分组中的字节。 甚至有Actor模仿粗心的系统管理员的工作。 这可能会删除数据文件夹或交换两个文件夹。 开发人员
驱动数千个仿真 ,替换不同的Actor,并使用Flow获得100%的可重复性:如果某些测试失败,他们可以重新启动仿真并在同一位置发生崩溃。 特别是,为了消除OS调度程序切换线程带来的不确定性,每个FoundationDB进程严格都是单线程的。
当一位要求
研究人员
在几乎所有流行的NoSQL解决方案中发现
数据丢失场景的
研究人员被要求测试FoundationDB时,他拒绝了,并指出他没有明白这一点,因为作者
做了大量的工作 ,并且比他自己更深入,更彻底地
对其进行了测试 。
通常认为集群故障是随机的,但是经验丰富的开发人员知道,情况远非如此。 如果您有同一制造商的磁盘一万,其他制造商的磁盘数相同,则故障率将有所不同。 在FoundationDB中,可以使用所谓的“机器感知”配置,在该配置中,您可以告诉集群哪些机器在同一数据中心中,哪些进程在同一机器上。 在计算机之间分配负载时,数据库将考虑到这一点。 集群中的机器通常具有不同的特征。 FoundationDB还考虑了这一点,查看请求队列的长度并以平衡的方式重新分配负载:较弱的计算机接收的请求较少。
因此,FoundationDB在成千上万台计算机的群集上提供ACID事务和最高级别的隔离(可序列化)。 加上惊人的灵活性和高性能,这听起来像魔术。 但是您必须支付所有费用,因此存在一些技术限制。
局限性
除了已经提到的交易大小和长度限制外,重要的是还要注意以下功能:
- 查询语言不是SQL,也就是说,具有SQL经验的开发人员必须重新学习。
- 客户端库仅支持5种高级语言(Phyton,Ruby,Java,Golang和C)。 目前还没有C#的正式客户。 由于没有REST API,支持另一种语言的唯一方法是在标准C库的顶部为其编写包装。
- 没有共享机制,所有这些逻辑应由您的应用程序提供。
- 没有记录数据存储格式(尽管通常也没有记录在商业数据库中)。 这是有风险的,因为如果群集突然无法组装,那么尚无法立即确定要做什么,而必须深入研究源文件。
- 对于新手开发人员来说,严格的异步编程模型似乎很复杂。
- 您需要不断考虑事务的幂等性。
- 如果您必须将多头交易分解成小笔交易,那么您需要自己在全球范围内维护完整性。
从基础翻译成英文,“ Foundation”的意思是“ Foundation”,该DBMS的作者以这种方式看到了它的作用:在简单记录级别提供了高度的可靠性,任何其他数据库都可以作为基本功能的附件来实现。 因此,在FoundationDB之上,您可以潜在地创建其他不同的层-文档,图形等。 问题仍然是这些层将如何扩展而不损失性能。 例如,CockroachDB的作者已经走了这条路-通过在RocksDB(本地键值存储)之上构建SQL层并解决关系联接中固有的性能问题。
迄今为止,Apple已在FoundationDB之上开发和发布了两层:
文档层 (支持MongoDB API)和
记录层 (将记录存储为
协议缓冲区格式的字段集,支持索引,仅在Java中可用)。 令人欣喜和令人惊讶的是,今天这家历史悠久的苹果公司紧随Google和Microsoft的脚步,并发布了内部使用的技术的源代码。
前景展望
软件开发中存在着这样一种存在的冲突:企业不断希望获得产品的变化和改进。 但同时他需要可靠的软件。 这两个要求相互矛盾,因为当软件更改时,会出现错误,并使企业因此而遭受损失。 因此,如果在您的产品中可以依靠一些可靠的,经过验证的技术并且自己编写更少的代码,那么这样做总是值得的。 从这个意义上说,尽管有一定的局限性,但不能将拐杖雕刻到不同的NoSQL数据库,而是使用经过生产验证的具有ACID属性的解决方案,这很酷。
一年前,我们对另一种技术-CockroachDB
感到乐观 ,但是它没有达到我们对性能的期望。 从那时起,我们就对分布式键值存储上的SQL层的想法失去了胃口,因此没有仔细看待例如
TiDB 。 我们计划仔细尝试将FoundationDB用作我们项目中最大数据集的辅助数据库。 如果您已经有在生产中实际使用FoundationDB或TiDB的经验,我们将很高兴在评论中听到您的意见。