/ DISTRIBUTEDSYSTEM

回顾MIT 6.824

上图为MIT 6.824教授Robert Morris。

大概是在六月底,经过了一段时间的划水,决定开始学一点研究生该学的东西。之前 一直学的后端开发入门的技术,比如怎么使用Spring,MyBatis之类的框架,在学这些技术的过程中虽然听说了一些分布式系统的组件和原理,但是在那个时候觉得这些都是进阶的知识,大可以工作以后再认真学,就匆匆掠过。研究生嘛,总要学得深入一点,正好在知乎上听闻这门课的风评很好,今年又录了高清的授课视频,就决心跟着这门课学下去。

6.824差不多是一节课读一篇论文,所以要想更好理解上课的内容,还是在课前就读完对应的论文比较好。于是我安排周一周三读论文,周二周四看视频,周五做实验,周日写论文读后感。这里说是读后感,也不过就是中文的摘要罢了。按照这样的进度,经过了大约一个的时间,我在八月第一个星期完成了全部的学习,这期间又被知乎安利去看了《Design Data-Intensive Application》。通过这些学习,我大概算是对分布式系统有了一个大致的了解。

分布式系统结构

分布式系统中有三个子系统,分别是分布式计算系统,分布式存储系统和分布式管理系统。在6.824中,主要讨论分布式存储系统,但通过两篇论文聊了聊分布式计算系统,但是没有涉及分布式管理系统。虽然这里分得很清楚,但是实际上来说,分布式存储系统提供数据给分布式计算系统,分布式管理系统负责监测相关信息,保证正常运行。

分布式系统我们得从Google的三驾马车讲起。谷歌将分布式系统分成三级,GFS负责分布式文件系统,在GFS之上是Bigtable,负责分布式数据库,再上面是分布式计算系统MapReduce。这种分层几乎沿用至今,最多只在分布式数据库之前再加一层缓存。

从更高的维度来看,当我们解决一致性问题以后,我们甚至可以把分布式系统看做是单机的。磁盘存文件对应文件系统,分布式数据库对应单机数据库,而计算则对应我们的代码逻辑。或者说,这反映了分布式系统设计的目标,让客户端感受不到“分布式”的存在,用起来跟单机完全一样。

那么,我们以自底向上的顺序聊一聊整个架构中可能用到的技术和解决方案。

分布式文件系统

分布式文件系统设计之初的目标是存储更多的文件,也就是可拓展性(Scalability),但是在使用的过程中发现容错(Fault-Tolerance或者Resilience)也是一个很重要的目标,那么整个的系统的设计就必须围绕这两个点。

首先考虑怎么实现可拓展性,GFD采用的办法是使用一个服务器存储所有文件的位置,读写文件都要访问这个节点拿到想要访问或添加的文件。这个想法类似于Linux的文件系统,元文件块存储FCB的位置,要读取FCB就要访问元文件块。这个方法简单而且有效,但是很遗憾,我不能拿其他的方案作比较,因为我暂时还没有读过其他的分布式论文,之后会去看看Ceph和PolarFS是怎么实现的。

实现容错的通用方案是使用备份(Replica),当前节点宕机以后就使用备份接替工作。根据6.824和《DDIA》的内容,目前主要的备份方案分为主从复制和链式复制,其中主从复制又根据主节点的个数分为单主节点复制,多主节点复制和无主节点复制。单主节点的方案使用比较多,Spanner和许多系统都使用这种方案。根据《DDIA》的说法,MySQL的Tungsten Replicator,用于PostgreSQL的BDR以及用于Oracle的GoldenGate都使用了多主复制,但是我对这些还没有了解。无主复制在相当长的时间里都不是很流行,但是当Amazon开发了无主复制的Dynamo以后,就有相当多的公司和组织尝试用无主复制的方案开发工具。相较之下,链式复制就要低调得多,但是Amazon Aurora中使用了这一方案。

虽然我们已经使用备份解决了容错的问题,但是另一个问题随之而生,如何保证多个备份之间的数据一致性?在主从复制中,经常使用Paxos,Raft和Zab来解决这些问题,而在链式复制中,由于数据会流经每一个节点,所以必然能够保证数据的一致性,只不过需要花费更久的延迟。

另外一方面,为了追求高吞吐量,分布式文件系统会使用分片技术将数据分发到多个节点上,这样对数据的请求压力也能够被多个节点一起承担。在这里,为了实现数据均匀分布,会使用哈希算法决定数据位置。同时还会使用一致性哈希算法减少节点崩溃时需要的数据传输量。

分布式数据库

说完了分布式文件系统,我们继续聊分布式数据库。分布式数据库的目标包括数据一致性和事务管理两大目标。当然,肯定不仅限于这两点,上面提到的容错和可拓展性也是重要的目标,但是解决方案类似,也就不再讨论。

在分布式数据库中,数据的一致性从脏读,幻读等隔离级别变成了如下的级别。遗憾的是,虽然6.824中用两三篇论文讨论过了这些问题,我对这部分的理解仍然相当有限,所以很难作进一步的解释。

Linearizable(线性)> Sequential(顺序)> Causal(因果)> RyW(read your write)。

不过可以确定的是,分布式数据库通过对事务的管理尽可能实现强的数据一致性。最广为人知的事务管理算法是2PC,但是2PC在两段提交的过程中可能会由于某一方节点宕机而失败,并且其效率很低,所以被渐渐抛弃。之后有人提出了TCC的方式,但是我也不是很了解。在这之后,分布式系统中引入了消息队列,由于队列的性质能够保证FIFS,所以在一定程度上解决了事务的先后问题,但是消息队列仍然有消息丢失等问题。特别要说的是,Spanner使用TrueTime实现了全球一致的时间并且使用这个时间一定程度上进行事务管理,是我看到的最好的方案。目前分布式事务仍然有很多问题需要解决,不失为一个不错的研究方向。

数据库的另一个重要性能指标是数据读写的速度。当然,我们不能通过软件提高硬件的性能,但是可以通过合理的数据模型减少检索数据所需要的时间。Bigtable就使用了SSTable将数据有序存储提高性能,Spanner和F1数据库则使用父子表的设计支持关系型数据存储并加快检索。

分布式计算

分布式计算在课程中占比很少,主要讨论了两个方案,MapReduce和Spark。

前者的特点是模型简单,但是需要已经准备好的数据,有点类似于批处理,这就注定了MapReduce不能用于快速响应的情景。另外,由于基础模型过于简单,在希望实现复杂逻辑的时候,编程就比较困难。这些问题导致MapReduce没有在今天大范围使用。

Spark使用了流式计算的方案,每当有数据输入就能生成对应的结果。另外,Spark也支持比较复杂的操作,像JOIN和GROUP,相当程度上弥补了MapReduce的缺点。这使得在其推出后大受欢迎,在今天也有很多人使用。

中间件

目前的分布式系统中,除了上述的层级还有一些组件用于在各层之间协调,6.824介绍的Memcache就是其中的一种。

Memcache用于用户层和数据库之间,由于查询数据需要经过磁盘,读写效率很低。所以Memcache将一些数据放在内存中,只有当Memcache中没有用户需要的数据时才去查询数据库,这就大大提高了查询效率。

在分布式系统中还经常使用Zookeeper作为协调组件。Zookeeper提供一个运行在内存中的小型文件系统,系统可以使用这个文件系统实现锁服务,成员管理等相当多的功能。

上面我们还提到了消息队列,现在的系统中也广泛使用类似于Kafka,RabbitMQ等组件来实现消息解耦,同时减少服务器压力。不过消息队列似乎仅在可以异步处理的情况下比较有效。

课程内容

可能是囿于课程时间的限制,6.824没有完整地讨论很多东西,比如Bigtable和Paxos就没有讨论。另外,我觉得课程的安排也有一些问题,比如它把MapReduce作为第一课,虽然MapReduce够简单,也很合适实验设置。但是我想一课可以安排Paxos或者Raft作为基础,在这之后可以完整详细地讨论谷歌的三驾马车,这之后再讨论分布式事务,最后讨论一下分布式计算。这门课程花了三节课的时间讨论区块链,这实在是不值得,我想最多只能花两节课时间再这上面。个人认为区块链的唯一用处是用Work Proof解决了拜占庭问题,但是Work Proof的花费太大以至于几乎不可能在实际中使用。

另外要说的是这门课的实验。很难,真的很难,从实验二开始就相当地难。实验二是实现Raft算法,这就要求对Raft算法有很深入的了解,至少Figure2是要懂透的,这对于初学者来说过于痛苦了。很丢人地说,我只完成了0.5个实验,我连实验一的case都没有全部通过,实验二三四就都是读别人的代码,只能在感叹别人强悍的同时自我反省。

总结

哪怕我认为6.824有一些不足,但是对于初学分布式系统的人来说仍然是最好的课程,没有之一。课程中大量的论文能帮助初学者快速入门,足够系统的知识点保证在学习完课程之后能够对分布式系统的体系有大致的把握。我个人推荐把网上对论文的分析解读和实际的论文以及视频中的内容结合起来,这样能够更好地理解论文中一些的设计意图和巧妙之处。虽然实验很难,但是如果能够独立完成全部实验的话,那么我想对理解Raft算法和分片,分布式数据库的理解都会更进一步。在完成6.824的课程之后,可以去读一读《DDIA》,这本书通俗易懂,知识丰富,如果对哪个知识点感兴趣的话完全可以去读书中的参考文献。