6.824 Introduction
构建分布式系统的驱动力
- 需要获得更高的计算性能:大量的并行运算,大量CPU、大量内存、以及大量磁盘在并行的运行。
- 提供容错(tolerate faults):比如两台计算机运行完全相同的任务,其中一台发生故障,可以切换到另一台。
- 一些问题天然在空间上是分布的:例如银行间转账,银行A在纽约有一台服务器,银行B在伦敦有一台服务器
- 担心安全问题,我们把系统分成多个的计算机,这样可以限制出错域:比如有一些代码并不被信任,但是你又需要和它进行交互,你不想要信任这些代码,所以你的代码在另一台计算机运行,我的代码在我的计算机上运行,我们通过一些特定的网络协议通信。
挑战
- 并发编程和各种复杂交互所带来的问题
- 局部错误:分布式系统有多个组成部分,再加上计算机网络,你会会遇到一些意想不到的故障。
- 性能
基础架构
基础架构的类型主要是存储,通信(网络)和计算。
对于存储和计算,我们的目标是为了能够设计一些简单接口,让第三方应用能够使用这些分布式的存储和计算,它看起来就像一个非分布式存储和计算系统一样(尽管这几乎是无法实现的梦想),但是实际上又是一个有极高的性能和容错性的分布式系统。在这些基础架构之上,构建第三方应用程序。
实现工具
人们在构建分布系统时,使用了很多的工具,例如:
- RPC(Remote Procedure Call)。RPC的目标就是掩盖我们正在不可靠网络上通信的事实。
- 线程。使得我们可以利用多核计算机
- 因为我们会经常用到线程,我们需要在实现的层面上考虑并发控制,比如锁。
性能
可扩展性 Scalability
如果我用一台计算机解决了一些问题,当我买了第二台计算机,我只需要一半的时间就可以解决这些问题,或者说每分钟可以解决两倍数量的问题。两台计算机构成的系统如果有两倍性能或者吞吐,就是可扩展性。
假设我们建立了一个常规网站,一般来说一个网站有一个 HTTP服务器,还有一些用户和浏览器,用户与一个基于Python或者PHP的web服务器通信,web服务器进而跟一些数据库进行交互。
当你只有1-2个用户时,一台计算机就可以运行web服务器和数据,或者一台计算机运行web服务器,一台计算机运行数据库。
你该怎么修改你的网站,使它能够在一台计算机上支持一亿个用户?可以购买更多的web服务器,然后把不同用户分到不同服务器上。这样,一部分用户可以去访问第一台web服务器,另一部分去访问第二台web服务器。所有的web服务器都与后端数据库通信。
但是这种可扩展性并不是无限的。很可能在某个时间点你有了10台,20台,甚至100台web服务器,它们都在和同一个数据库通信。现在,数据库突然成为了瓶颈,并且增加更多的web服务器都无济于事了。单个数据库或者存储服务器不能支撑这样规模的网站,所以才需要分布式存储。
可用性 Availability
容错特性的一种,另一种是可恢复性recoverability,是一个比可用性更弱的需求。
大型分布式系统中有一个大问题,那就是一些很罕见的问题会被放大。例如在我们的1000台计算机的集群中,总是有故障,在一个有1000台计算机的网络中,会有大量的网络电缆和网络交换机,也经常有故障。大规模系统会将一些几乎不可能并且你不需要考虑的问题,变成一个持续不断的问题。
必须要在设计时就考虑,系统能够屏蔽错误,或者说能够在出错时继续运行。我们需要为第三方应用开发人员提供方便的抽象接口,尽可能多的对应用开发人员屏蔽和掩盖错误。
可恢复性(recoverability):对于一个具备可用性的系统,为了让系统在实际中具备应用意义,也需要具备可恢复性。因为可用的系统仅仅是在一定的故障范围内才可用,如果故障太多,可用系统也会停止工作,停止一切响应。但是当足够的故障被修复之后,系统还是需要能继续工作。所以,一个好的可用的系统,某种程度上应该也是可恢复的。当出现太多故障时,系统会停止响应,但是修复之后依然能正确运行。这是我们期望看到的。
为了实现可用性和可恢复性,有很多工具。其中最重要的有两个:
- 非易失存储(non-volatile storage,类似于硬盘)。我们可以存放一些checkpoint或者系统状态的log在这些存储中,当电力恢复,我们还是可以从硬盘中读出系统最新的状态,并从那个状态继续运行。但构建一个高性能,容错的系统,聪明的做法是避免频繁的写入非易失存储,因为很慢。
- 复制(replication)。任何一个多副本系统中,都会有一个关键的问题:比如我们有两台服务器,它们本来应该是有着相同的系统状态,但这两个副本总是会意外的偏离同步的状态,而不再互为副本。
一致性
例:假设我们在构建一个分布式存储系统,并且这是一个KV服务。
在一个分布式系统中,由于复制或者缓存,数据可能存在于多个副本当中,于是就有了多个不同版本的key-value对。假设服务器有两个副本,那么他们都有一个key-value表单,两个表单中key 1对应的值都是20。
现在某个客户端发送了一个put请求,并希望将key 1改成值21,这个put请求发送给了第一台服务器。之后会发送给第二台服务器,因为相同的put请求需要发送给两个副本,这样这两个副本才能保持同步。
但是就在客户端准备给第二台服务器发送相同请求时,这个客户端故障了,现在我们处于一个不好的状态,我们发送了一个put请求,更新了一个副本的值是21,但是另一个副本的值仍然是20。
如果现在某人通过get读取key为1的值,那么他可能获得21,也可能获得20,取决于get请求发送到了哪个服务器。
强一致性:比如get请求可以得到最近一次完成的put请求写入的值。但分布式系统的各个组件需要做大量的通信,才能实现强一致性。
弱一致性:可以得到旧的数据,为了让弱一致更有实际意义,人们还会定义更多的规则。人们常常会使用弱一致系统,你只需要更新最近的数据副本,并且只需要从最近的副本获取数据。