百万节点数据库扩展之道(5): NoSQL再讨论

96
doc001
0.1 2014.09.21 23:12* 字数 4868

本博客在http://doc001.com/同步更新。

本文主要内容翻译自MySQL开发者Ulf Wendel在PHP Submmit 2013上所做的报告「Scaling database to million of nodes」。翻译过程中没有全盘照搬原PPT,按照自己的理解进行了部分改写。水平有限,如有错误和疏漏,欢迎指正。

本文是系列的第四篇,本系列所有文章如下:

  • 百万节点数据库扩展之道(5): NoSQL再讨论

另外一些概念

组通信系统(group communication system)

设想一下,你被要求为MySQL设计一个新的副本系统。这个副本系统里有一组MySQL服务器,你会选择何种通信方式?很显然,XML-RPC、Apache、HTTP、PHP这样的技术栈肯定会很慢。于是,你不得不直接处理网络协议。你将会看到,这个处理并不是很繁琐。除了通信问题外,另外一个重要的问题是如何确定组的成员。

组通信系统能够极大地简化上述的任务。它能够提供不同保障的简单、可理解的消息传递方法。

组通信系统提供以下功能:

  • 一组节点间的消息交换
  • 知晓哪个节点在组内
  • 对网络和路由细节进行了抽象
  • 提供不同保障的通信原语(communication primitive)

虚同步(virtual synchrony)

虚同步将多播(multicast)消息和组的概念融合到一起。在虚同步中,一个消息要么传递给所有的组成员,要么一个都不传递。在消息被多播之前,组内的每一成员都同意它们是组的一部分,即形成一个组视图(group view)。如果一个节点想加入或离开组,必须多播一个"视图改变"(view change)消息。

例如,组G1={P1,P2,P3}内多播了三个消息M1、M2、M3。节点P4想加入到组中,于是多播了一个"视图改变"消息.而此时M3还在传递过程中,虚同步要求要么M3在视图改变生效前传递到G1的所有成员,要么一个都没传递到。

虚同步

一个多播消息产生后,只有一种情况下可以不被投递成功,那就是发送节点失效。继续上面的例子,组G2={P1,P2,P3,P4}多播了消息M5、M6、M7。P4刚将消息M7传递给{P3},然而,很不幸该节点崩溃了。P4的崩溃被P1注意到了,触发了一个“视图改变”。因为需同步要求消息被传递给所有组成员,因此,P3将M7丢弃掉,然后视图改变才能生效。

虚同步-发送节点崩溃

虚同步提供可靠多播。可靠性可以由OSI模型中较高层次的协议提供,如TCP。ISIS,一个早期的虚同步实现框架,使用了TCP协议的点对点连接来实现可靠服务。TCP连接能够自动处理网络错误,保证消息按序到达。但是,对于多个并发的TCP连接,消息的顺序无法得到保证,不同连接消息的排序只能在应用层实现。

原子广播(atomic broadcast)将虚同步的所有消息进行全序排序。当虚同步在80年代中期被引入的时候,实际上明确允许其它的消息排序方法。例如,应该能够支持有消息交换概念的分布式应用,利用该概念,可以改变消息发送的顺序以提高性能。如果事件在不同的节点上以不同的顺序执行,那么系统就不能称之为同步了——应该称之为虚拟同步。

上面这坨的逻辑实在没搞清楚!翻译也感觉不到位,附上原文:
Atomic broadcast means Virtual Synchrony used with total-order message ordering. When Virtual Synchrony was introduced back in the mid 80s, it was explicitly designed to allow other message orderings. For example, it should be able to support distributed applications that have a notion of finding messages that commute, and thus may be applied in an order different from the order sent to improve performace. If events are applied in different order on different processes, the system cannot be called synchronous any more – the inventors called it virtually synchronous.

CAP再讨论

脑裂

集群中孤立的节点或数据库副本失效的情况已经被讨论过了。现在考虑这种情况:集群的一半节点与另一半节点失去了连接,哪半个集群会生存下来?答案通常是实现相关的。例如,ISIS指定了一条规则,新的组视图不应该少于n/2+1个成员才能生效,其中n是当前组成员的数量。因此,在这个例子中,这两个半数节点的集群都不会生存下来。

CAP中隐藏的D

CAP中其实就隐含了ACID中的D(持久化)。

CAP中的C要求所有的节点都获得全部的更新。正如我们看到的一样,事务更新必须序列化以达到节点间的一致性。因此,节点必须以定义好的顺序获得所有更新。进一步,节点必须从来不会丢失任何一个更新。那么,更新必须被持久化!

从D到A

CAP理论说我们不可能同时实现一致性、可用性和分区容忍性。为了实现分区容忍性和一致性,我们需要法定人数协议,这就是CP系统。选举人数协议会使系统变得很慢,因为我们不能只从一个节点读取消息,取而代之,我们必须从很多个节点读取消息。这其实就是CAP中A的终极含义:数据最终会变得一致,只是整个过程很慢...

CP系统如果使用选举人数协议,ACID中的A就被违反了,因为并非所有的节点都应用了更新,而A,原子性,意味着“要么都做,要么都不做”。但是,如果更新是持久化的,这个更新永远不会被“忘记”,它总能被看到,我们得到了A。因此,D是A的前置条件。

C不是一个障碍

CAP中的C对于实现ACID来说也不是一个阻碍。

一致性意味着我们不得不在所有的节点上按照确定的顺序应用更新。这很容易通过主副本方案做到。在该方案中,所有的更新都会被路由到主副本,主副本确定更新的顺序,把更新发送给其它的副本。我们只需要在主副本和副本之间建立FIFO消息通道即可(例如TCP连接),这不会对延时造成负面影响。

在更新时需要锁吗?不需要!首先,我们有主副本,主副本对消息的排序能够处理写冲突;其次,我们可以引入多版本并发控制(multi version concurrency control,MVCC)消除读写冲突。

因此,ACI都不是问题。我们真正需要的是D!

避免慢的读选举人

如果我们能够立刻移除失效节点,那么就不会受到慢读选举人的影响,失效节点也不会导致不一致的过期数据。

如果一个节点组里所有的节点都是最新、一致的,那么读操作可以由任何一个成员服务。读速度将比选举人读快很多。虚同步做到了这一点。

视图改变

如果一个虚同步组的成员认为一个节点失效了,就会进行一次排除失效节点的投票,这个过程就是「视图改变」。更新操作必须等待到「视图改变」过程结束才能进行。BASE理论中也有类似的等待机制,BASE需要等待写选举人达成一致后,才能进行其它更新操作。无论在虚同步还是在BASE中,等待操作都是昂贵的。

FLP不可能理论告诉我们在异步网络中无法区分「慢」节点和「失效」节点。实际系统采取的做法是设置一个超时时间,超过超时时间未收到应答就认为节点失效了。这个超时时间决定了更新操作被禁止的最大时长。禁止期间的更新操作可以先暂时缓存起来。注意,此时系统仍然是一致的,只不过,我们在一致状态下等待操作结果。

D的成本

持久化的成本取决于网络协议:

  • 更新不能丢失,它们必须是持久的
  • 强迫组通信系统在不可靠的网络协议上使用Paxos
  • 早期的ISIS^2在800节点、千兆网络上的数据显示更新操作的平均延时是25ms,最大100ms
  • Google Spanner的数据表明,100个spanserver的平均延时是70ms,最高150ms;200个spanserver的平均延时是150ms,最高350ms

CAP与ACID结合

CAP并不禁止构建一个一致的、可用的、分区容忍的ACID兼容数据库。Spanner和ISIS^2就是这样的系统。

放宽一致性

在很多场景下,一致性能够且必须被放宽,以换取更好的性能。最出名的例子就是ATM。即使一个ATM机和银行失联了,余额未知,你仍然能够存款。但是,有个限制,就是,ATM业务的特点决定弱一致能容忍误差范围很小。

如果你能够接受99.999%的ATM显示准确的余额,不一致的窗口期最多1分钟。那么系统可以在1分钟内先使用类似于ICMP、Gossip之类的快速、不可靠协议将值传播开,然后主副本使用慢速但是100%可靠的方法传播正确的值。

一个明显的数据对比就是,如果使用不可靠的协议,ISIS^2的延时只有2ms。对比下,之前列出的数据是25ms。

一般系统的经验就是,软状态(soft-state)可以接受弱一致,硬状态(hard-state)必须是强一致。弱一致分两种,一种是时间限制的弱一致,即限制不一致的时间窗口;一种是误差限制的弱一致,即限制不一致期间值的偏差。

ACID可以变得多快?

加快事务返回速度的一个方法是使用异步API。使用异步API时,客户端发送完更新后,不等待确认消息就直接返回。客户端并不知道事务是否成功了,必须做好一段时间后发现事务失败的准备。

当MySQL使用异步客户端后,在一个30节点的集群上,写操作可以达到1950万次/s的响应速度,读操作可以达到7167万次/s的响应速度。

CAP的演进

CAP理论推出已经有十几年,其中的很多规则已经发生了变化,CAP的作者Eric Brewer说道(2012年3月):

「CAP提出后的十多年间,几被滥用,设计师和研究者发展了很多新颖独特的分布式系统。这些系统很多都可归入NoSQL,NoSQL被认为站在传统数据库的对立面...CAP中三选二的公式其实是一个误导,它将三者的关系过于简单化了。现在这个观点应该予以矫正。CAP事实上只禁止很微小的一部分设计空间,即完美的可用性和一致性在分区存在的情况下是不可能的,除此之外,别无其他...分区现象其实很罕见,因此CAP的C、A在大部分时间可以完美实现。当分区出现时,需要策略检测到分区,并保证一切井然有序。这个策略对分区的处理实际上分为三个步骤:1) 检测分区;2) 进入分区模式,禁用部分操作;3) 分区结束后,开始恢复进程,恢复一致性,处理分区期间的错误...」

co-location(协同定位)

我们该如何扩展数据库规模?

分布式事务代价:

  • 延时,而不是吞吐量,是一个很大的问题
  • 受到同一数据中心、数据中心间的物理条件的约束(机架连线、距离等)

强一致性的代价如下:

  • Spanner:3数据中心,4K写,大约14ms
  • Gaios:4K写,小于10ms,其中超过9ms是磁盘日志耗时
  • Spinnaker:写操作比Cassandra选举人机制慢5-10%

可以看到,实现分布式事务和强一致性都需要付出一定的代价。Spanner就是一个很明显的例子。

在以太网中,可以通过广播实现数据中心内或邻近数据中心间的强一致副本。Paxos的研究系统Spinnaker的写性能只比Cassandra略差,但是前者是强一致的,后者是最终一致。Birman结合了向量钟和Paxos也是向这个方向努力。这些研究表明,只要操作的时间大于Paxos延时(即平均SQL查询时间在10ms~15ms),那么Paxos适合用于保证强一致性。

co-location是必须的

从已有的Paxos实现看,一个较小的Paxos的平均延时大约在1ms。但是他们如果在运行时需要要维护状态,延时会增加很多。即使使用fast Paxos,连ROWA(读一写全部)在数据量很大时都会成为扩展瓶颈。

因此,数据需要分区。一旦数据分区,我们又回到分布式事务的老问题。我们必须仔细考虑数据的分布式协同,以最小化分布式事务成本。

静态co-location:实体组(entity group)

Megastore中实现co-location的方法是使用明确的模式信息。和Spanner一样,Megastore让用户定义表/实体的具名、类型化的列。一组列形成主键。实体间形成层次关系,并使得它们在物理存储上保存到一起。这对于email账号、博客这类的业务是一个非常好的方法。

由于同一实体组的数据通常一起被查询,实体组是数据分区的合适粒度。实体组的数据是按照键值排序好的,这使得分区分裂操作和合并操作都相当廉价。

静态co-location:树形模式(tree schema)

一个标准的树形模式中,外键关联的数据应该位于同一个分区中。树形模式存在一个主表,其它的次表包含一个外键指向主表的主键。整个树的深度没有限制。

表之间的关系通过全局查找表来维护。全局查找表的数据很少发生变化,副本操作代价很低。

主表中所有属于同一主键的行被称之为一个行组(row group)。一个分区可存储一个或多个行组。

树形模式

静态co-location:键值表组(keyed table group)

键值表组是将任意表co-locate到一起的通用方法。该方法中,同一个分区的表共享相同的分区键(partition key)。分区键是表的某一列,并不一定是主键或外键。键值表组中同样有行组的概念,其含义与树形模式相同。

该模型被高并发分布式的云SQL所使用,例如Microsoft Azure。

键值表组

静态co-location:全副本节点(full replication node)

如果数据的规模不过分大,co-location的粒度可以更粗一些,为此,引入全副本节点。全副本节点将从许多数据分区复制数据,组合成一个完整的数据副本。需要跨分区执行的查询能够高效地运行在全副本节点上。在某种意义上,全副本节点类似于物化视图。

在这种方式中,数据分片负责处理OLTP客户端,全副本节点用于加速OLAP/join查询。

全副本节点

动态co-location:访问驱动(access driven)

Curino等人提出了一个学术模型,该模型将应用访问建模为一个图。图进行分区,通过将频繁访问的行复制多份来加速读性能,同时保证更新操作所需要的分布式事务数量尽可能地少。

这种方法能够达到很好的分区性能,但是客户端路由变得很复杂。客户端和路由服务器很难通过一个简单的查找函数来找到一个包含指定分区的节点。许多改进路由的方法被提出,但是没有一个方案简洁明了。

动态co-location:应用驱动(applicatopn driven)

考虑一个游戏场景。玩家有很多游戏可选,但是通常,一旦选定了一个游戏,就会一直玩数个小时。在玩的期间,玩家数据和游戏数据应该被co-locate到一个节点上。按照应用程序要求,为需要的数据创建组,只在组内保证数据操作的一致性。当游戏结束时,组也跟着被释放。提前切分数据,在该模式下并没有必要。

扩展阅读

  • 「In Search of an Understandable Consensus Algorithm」论文

  • 「Unbundling Transaction Services in the Cloud」论文

  • 「MoSQL: an elastic storage engine for MySQL」论文

  • NuoDB:分布式关系型数据库

MySQL扩展示意图

分片

分片

可用性

可用性

Co-location

co-location

用户视图

用户视图
技术杂烩
Web note ad 1