探讨NuoDB数据库的架构—1

介绍

传统的关系型数据库适用于垂直伸缩(scale-up)的架构。换句话说,为了处理更多的负载,你需要换个更强大的计算机。在几年前,这意味着要想支持水平伸缩(scale-out)的架构,大家就得放弃SQL,或者采用分区、Active-Passive复制等技巧。在灵活、富有逻辑的数据库上实现真正的ACID编程模型是不太可能的。正是这种局势引发了NewSQL运动,NuoDB的出现也是为了解决这个问题。

NuoDB是针对云伸缩设计的关系型数据库。怎么理解呢?NuoDB是一种真正的SQL服务:它拥有ACID事务的所有属性,支持标准的SQL语言,具备真正的关系型逻辑。而且从一开始,NuoDB的设计就能让它以云服务伸缩的方式进行伸缩。

这里不再对“云伸缩”进行定义。如果你真的感兴趣,可以到我们的技术博客上了解什么是云伸缩,文章里有详细的定义。简单来说,就是你需要一个水平伸缩的模型,不过我觉得它还应该是敏捷、易用、可自动化、安全、高可用的。本文就是从这个观点出发的。

需要注意的是,NuoDB“只是”个软件。这意味着无论是在笔记本电脑上,还是在私有云抑或公有云上,NuoDB都运行在Linux、Mac、Windows或Solaris上。你可以在Amazon Web Services或Google Compute Engine里使用NuoDB,或是把它和OpenStack集成起来,也可以在笔记本电脑上作为本地Windows服务运行。NuoDB很灵活,你可以随便测试、开发,然后以你自己的方式部署到任意地方。

本文将介绍NuoDB是什么,怎样的架构能让它应对现今的一些挑战,以及它能帮你解决什么问题。看完本文,你就能了解NuoDB的关键概念和架构的不同之处了。你也能理解一些实际部署和管理的功能,对自己的NuoDB数据库进行伸缩。

三层架构

了解NuoDB最简单的方式就是了解它的三层架构。NuoDB包括管理层、事务层和存储层。我们后面再介绍管理层,先看看事务层和存储层。

要想让关系型系统可伸缩,把事务和存储分割开是关键之所在。传统的SQL数据库都会对硬盘上的数据表示(页)和内存里的B树结构进行同步。这种紧耦合是有效的,但对IOPS(每秒I/O操作次数)的影响非常大,因此很难水平伸缩。把这些角色分离开的架构可以水平伸缩,几乎不会影响磁盘吞吐量等数据。

NuoDB的持久化和事务处理是完全独立的两个任务,这意味着你可以对这些层分别伸缩、分别处理故障。对事务吞吐量进行水平伸缩?不用增加磁盘就可以做到。想拥有独立的归档,防止数据中心出现故障?达到目标的同时也不会影响事务性能。这种分离不仅有利于系统的伸缩,也更利于随需伸缩、根据你的需求进行调配。

事务层负责原子性、一致性和隔离性,但并不关心持久性。这也说明事务层位于内存中,它运行速度快,任何内容都可以失败,在任何时候关闭都不会丢失数据或一致性。事务层也是个缓存层(特别是能随需缓存,我们一会儿介绍),你不需要在NuoDB数据库之上添加其他的缓存逻辑。

显而易见,存储层负责ACID中的D(持久性)。存储层始终处于活跃状态,并和所有数据保持一致。它负责在提交的时候持久化数据,事务在缓存中命中失败的情况下访问数据。

需要注意的是,NuoDB里的“提交”是可调节的。你可以在某种程度上牺牲性能和高可用性,因为它们处于不同的层。要理解调节提交协议的做法和原因,我需要解释一下这些层究竟是什么样的。

对等协调

这两个数据库层由多个进程组成,这些进程可以跨任意数量的主机运行。每个单独可执行的进程都以事务引擎(Transaction Engine,TE)或存储管理器(Storage Manager,SM)两种模式的其中之一运行。所有的进程都是对等的,没有单独的协调器或故障点,也没有主机特定的配置。默认情况下,所有对等的进程都会通过加密的会话互相认证和通讯。

每个TE都负责接受SQL客户端的连接,并处理查询。缓存保存在TE的进程空间里。SM和TE通过简单的对等协调协议互相通讯。当TE在本地缓存命中失败的时候,它可以从任意对等进程里获取所需的对象,这通常意味着,如果有另一个TE的缓存里有这个对象,就去那个TE里取,这比SM从持久存储里查询数据要快多了。

这个简单、灵活的进程模型简化了启动、水平伸缩和迁移。举例来说,假设你想要一个尽可能简单的NuoDB数据库。你在同一台主机上启动了单独的TE和SM。这时你就有了一个运行的、完全ACID的数据库,但它们都在同一台主机上。你可以在笔记本电脑上这样测试,但这么做就不太适用于真正的部署环境了。

接下来你可以在第二台主机上安装软件,发消息让它启动一个新的TE。新的TE会和已有的进程互相验证,把一些根元素加载到自己的缓存里,然后报告说自己已经做好了准备、能处理事务负载了。从发消息到TE准备好开始工作,整个过程花费的时间通常都不会超过一百毫秒。在两个独立的主机上运行TE就可以让数据库的事务吞吐量翻一番,也能增加故障的恢复能力。

美中不足的是我们只处理了持久性的一个方面。接下来,你可以搭建第三台主机,发送消息让它启动第二个SM。第二个SM会自动和运行的系统同步,准备好之后就可以积极参与数据库的处理了。至此,数据库持久性的另一个方面也就处理好了。一样很简单。

最后,让我们看看如何在不同的主机上搭建第一个TE和SM。你可以猜到,联机第四台主机,在这台新主机上启动一个TE或者一个SM,当它准备好之后关闭原先的TE或SM(无论你在新主机上启动的是什么)。你完成的是数据库组件的实时迁移,不会损失可用性。因为整个服务没有关闭过。这也配置出一个完全冗余的数据库,因为任何主机都可能失败,但你仍然有一份完整的数据归档,并能进行事务处理。

所有的工作都完成得轻松、迅速,因为NuoDB建立在一个基于进程、对等的简单模型之上。此外,也因为有简单的随需缓存Schema,以及真正被缓存和分享的数据格式。但数据格式并不是真正的SQL结构。我们称它为原子(Atom)。

一切都是原子

虽然NuoDB确实是个关系型数据库,但它的内部结构并不是。TE的前端使用SQL,也知道如何优化事务。但这层之下所有对象的逻辑操作我们都叫做原子(Atom)。原子本质上是自我协调的对象,表示信息的特定类型(比如数据、索引、Schema等)。即使是内部的元数据,也存储为原子。

原子是数据块,但不应该把它们看成是关系型数据库里传统的页。在某种程度上,原子实际上是缓存和存储层之间进行网络协调时真正的对等实体。原子包含任意的数据块,我们选用原子大小来最大限度地提高通讯效率、缓存里对象的数量、跟踪变化的复杂性等等。

除了数据库内容,原子也可以用来表示目录(Catalog)。目录是NuoDB处理其他原子的方式,本质上是一个分布式、自引导的查找服务。当TE启动的时候,它需要获取一个叫做主目录(Master Catalog)的原子。这是根原子,从它出发可以找到其他所有的原子。在前面的例子里,第二个TE会从第一个TE的缓存里获取一个原子,能这么做的原因是目录会告诉TE哪里能找到所需的原子。正是这种简单的启动机制让新的TE轻松、快速地在线。

原子只是一种很好的数据组织方式。原子能大大简化内部通信和缓存,因为我们不用考虑具体的SQL结构。所有内容都包含在相同的通用结构里,并用一致的方式识别。

内部状态的这种视图也有利于整个数据库的一致性。因为元数据、目录数据都和数据库数据一样,存储在相同的原子结构里,所有的变化都发生在同一个事务的上下文里。所以我们要是在一个SQL操作的上下文里修改一些元数据或目录数据,那所有的修改要么全部发生,要么全部不发生。不用担心会有一些不一致的修改影响数据和状态的准确性。

最后,把所有的数据都看作原子能让持久性变得相当容易。因为数据库的内容只是命名对象的一个集合,我们的持久层仅仅是一个键值对的存储。从理论上来说,它可以是任何存储。在NuoDB v1.1里,我们支持所有的文件系统、Amazon S3接口和Hadoop HDFS。在以后的版本里我们会支持更多的存储系统。在一个单一的数据库里,你甚至可以混合使用多种存储机制,举例来说,你可以同时使用本地文件系统和S3服务存储你的数据库。

多版本并发控制

原子结构是架构里强大、简化的一部分,有助于我们进行伸缩,但要是没有处理冲突、保证一致性的方法,NuoDB还是不能支持ACID语义。针对这个问题,我们使用了多版本并发控制(Multi-Version Concurrency Control,MVCC)。MVCC和全局锁管理器、分布式事务协调器不同,它对数据进行了版本化,将整个数据库看作更新的追加集合。

MVCC对分布式数据库进行水平伸缩的时候有很多优良的特性。首先,在数据发生变化的时候,我们实际上给数据创建了一个新的“挂起”版本(事务提交之前一直是“挂起”状态)。缓存里可以有多个“挂起”版本,以及当前的“标准”版本,所以缓存里就不会再修改内容了。这样的话,回滚就变得不那么重要了(事务不会提交,所以“挂起”的更改会被丢弃),反过来看,更新消息的处理就是乐观的了。

这些消息的处理不仅仅是乐观的,在NuoDB里通常还是异步的。也就是说,当一个事务试图修改数据时,事务会立即发送关联的消息(下面会详细介绍),然后再继续处理。如果在事务准备提交之前,TE已经知道允许修改,那就不会有消息传递的开销了。否则事务会阻塞,只能等到最后再弄清楚是否允许提交。结合异步和乐观行为,以及宽松的消息批处理,NuoDB在云环境里的预期延迟峰值较小、不可预期的网络行为也较少。

版本控制的第二个好处是,它为可见性提供了清晰的模型。在缺省模式下,NuoDB提供快照隔离级别。换句话说,从事务开始的时刻起,就存在一致的数据视图了,你也能获取到这个一致的数据视图。我们也支持读提交隔离级别和SQL操作select-for-update,但我觉得分布式数据库里真正实用的还是快照隔离级别。

在实践中,就是指某个TE上的一个事务能读取一些数据,那另一个TE上的事务就可以修改那个数据,而不会出现冲突。只要所有的内容(除了与第一个事务交互的)相对于这个对象的版本来说是一致的,那就保持了一致性。MVCC能让系统知道所有“挂起”和“已提交”的版本,所以只需要很少的全局协调消息和冲突解决,系统就能维护一个始终一致的视图。

需要协调的是“写-写”冲突。对于这一点,NuoDB挑选一些主机作为对象的Chairman(主持人)。Chairman只是一个新奇的名称,它实际上是在两个事务更新同一个对象的时候决定由哪个事务更新。能扮演对象Chairman的只能是缓存里有给定对象的TE,所以对象如果只缓存在一个TE里,那所有的协调都在本地进行。关闭某个TE,或者从它的缓存里找不到对象的时候,不需要通讯就可以挑选出下一个Chairman。

最后,MVCC在系统性能上也有更重要的优势。SM不仅维护着数据库的归档内容,而且可以维护日志(推荐大家使用这个功能)。NuoBD是一个版本化的系统,日志只以追加的方式保存一组修改信息,往往非常小。这种方式能提高记日志的效率,添加一个有日志记录的SM对一般事务整体的运行时也不会有太大的影响。

更新消息的发送是乐观的,不仅如此,更新也会在事务提交前写入日志。如果事务没提交,我们会进行标注,在整体处理日志的时候,未提交事务关联的更新并不会纳入归档内容的批量修改中。所以日志不仅仅是内存中的协调器(可以是乐观的),也是数据的持久化。有一个细节很重要,版本化是针对原子内的记录的,而不是原子本身。原子可以包含很多记录,也可以是粗粒度的,以便有效减少冲突。

在这篇文章的第二部分,我们会分析事务系统的实现原理、管理层的作用、所有组件的协同工作方式,以及NuoDB以后的规划。

作者简介

Seth Proctor是NuoDB公司的CTO,在可伸缩系统的研究、设计和实现上有超过十五年的经验。这些经验来自于网络、语言、操作系统、安全、数据库和分布式环境等领域,然后凝结成了NuoDB产品。在NuoDB之前,Seth就职于Nokia,负责内部私有云的架构。再往前,Seth在Sun公司的研究实验室工作,和产品组、大学进行合作。

Seth拥有八项跨多个技术领域的先进专利。还有五项专利正在等待批准,这五项专利都是NuoDB去中心化数据库部署中提升数据库效率和最终用户灵活度相关的。你可以从www.nuodb.com联系到他。

查看英文原文:Exploring the Architecture of the NuoDB Database, Part 1

你可能感兴趣的:(探讨NuoDB数据库的架构—1)