并发-基于事件的并发
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
一些基于图形用户界面(GUI)的应用,或某些类型的网络服务器,常常采用另一种并发方式。这种方式称为基于事件的并发,在一些现代系统中较为流行。
基于事件的并发针对两方面的问题。一方面是多线程应用中,正确处理并发很有难度。另一方面,开发者无法控制多线程在某一时刻的调度。程序员只是创建了线程,然后就依赖操作系统能够合理地调度线程,但是某些时候操作系统的调度并不是最优的。
 
基本想法:事件循环
等待某些事件的发生,当它发生时,检查事件类型,然后做少量的相应工作(可能是I/O请求,或者调度其他事件准备后续处理)。一个典型的基于事件的服务器。这种应用都是基于一个简单的结构,称为事件循环(event loop),伪代码如下:
主循环等待某些事件发生(通过getEvents()调用),然后依次处理这些发生的事件。处理事件的代码叫作事件处理程序(event handler)。处理程序在处理一个事件时,它是系统中发生的唯一活动。因此,调度就是决定接下来处理哪个事件。这种对调度的显式控制,是基于事件方法的一个重要优点。
但这也带来一个更大的问题:基于事件的服务器如何知道哪个事件发生,尤其是对于网络和磁盘I/O?
 
重要API:select()/poll()
单机数据持久-I/O 设备
type
status
date
slug
summary
tags
category
icon
password
Property
 
 

系统架构

一个典型计算机系统的架构,CPU通过某种内存总线(memory bus)或互连电缆连接到系统内存。图像或者其他高性能I/O设备通过常规的I/O总线(I/O bus)连接到系统,在许多现代系统中会是PCI或它的衍生形式。更下面是外围总线(peripheral bus),比如SCSI、SATA或者USB。它们将最慢的设备连接到系统,包括磁盘、鼠标及其他类似设备。
notion image
为什么要用这样的分层架构?原因在于物理布局及造价成本。越快的总线越短,因此高性能的内存总线没有足够的空间连接太多设备。另外,在工程上高性能总线的造价非常高。所以,系统的设计采用了这种分层的方式,这样可以让要求高性能的设备离CPU更近一些,低性能的设备离CPU远一些。将磁盘和其他低速设备连到外围总线的好处很多,最重要的就是可以在外围总线上连接大量的设备。
 

标准设备

notion image
一个标准设备(不是真实存在的),包含两个重要组件备。
  • 第一部分是向系统其他部分展现的硬件接口,让系统软件来控制它的操作。因此,所有设备都有自己的特定接口以及典型的交互协议。
  • 第二部分是它的内部结构。这部分包含设备展示给系统的抽象接口相关的特定实现,非常简单的设备通常用一个或几个芯片来实现它们的功能。更复杂的设备会包含简单的CPU、一些通用内存、设备相关的特定芯片,来完成它们的工作。
单机数据持久-磁盘驱动器
type
status
date
slug
summary
tags
category
icon
password
Property
 
 

接口

所有现代驱动器的基本接口都很简单。驱动器由大量扇区(512字节块)组成,每个扇区都可以读取或写入。在包含n个扇区的磁盘上,扇区从0到n−1编号。因此,我们可以将磁盘视为一组扇区,0到n−1是驱动器的地址空间。
多扇区操作是可能的,实际上许多文件系统一次读取或写入4KB(或更多)。但在更新磁盘时,驱动器制造商唯一保证的是单个扇区的写入是原子的。因此,如果发生不合时宜的掉电,则只能完成较大写入的一部分 (有时称为不完整写入)。
通常可以假设访问驱动器地址空间内的连续块(顺序读取或写入)是最快的访问模式,并且通常比任何更随机的访问模式快得多。
 

基本构成

一个磁盘可能有一个或多个盘片(platter),每个盘片有两面,称为表面。这些盘片通常由一些硬质材料(如铝)制成,然后涂上薄薄的磁性层,即使驱动器断电,驱动器也能持久存储数据位。
所有盘片都围绕主轴(spindle)连接在一起,主轴以一个恒定的速度旋转盘片。旋转速率通常以每分钟转数(Rotations Per Minute,RPM)来测量,典型的现代数值在7200~15000 RPM范围内。
数据在扇区的同心圆中的每个表面上被编码,称这样的同心圆为一个磁道(track)。一个表面包含数以千计的磁道,紧密地排在一起。
磁盘的读写过程由磁头(disk head)完成,驱动器的每个表面有一个这样的磁头。磁头连接到单个磁盘臂(disk arm)上,磁盘臂在表面上移动,将磁头定位在期望的磁道上。
单机数据持久-RAID
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
廉价冗余磁盘阵列(Redundant Array of Inexpensive Disks, RAID) 技术使用多个磁盘一起构建更快、更大、更可靠的磁盘系统。从外部看 RAID 看起来像一个磁盘,一组可以读取或写入的块。在内部 RAID 是一个复杂的庞然大物,由多个磁盘、内存(包括易失性和非易失性)以及一个或多个处理器来管理系统。硬件RAID非常像一个计算机系统,专门用于管理一组磁盘。
 
RAID 的优点
与单个磁盘相比,RAID 具有许多优点:
  1. 性能高:并行使用多个磁盘可以大大加快 I/O 时间;
  1. 容量大:大型数据集需要大型磁盘;
  1. 提高可靠性:通过某种形式的冗余,RAID 可以容许损失一个磁盘并保持运行;
  1. 可部署性:操作系统和客户端程序无需修改就能使用,而不必担心软件兼容性问题。
 
RAID 的组成
单机数据持久-文件和目录
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
操作系统应该如何管理持久存储设备?都需要哪些API?实现有哪些重要方面?
 
随着时间的推移,有关存储虚拟化形成了两个关键的抽象。
  • 第一个是文件(file):文件就是一个线性字节数组,每个字节都可以读取或写入。每个文件都有某种低级名称,通常是某种数字,用户通常不知道这个名字。由于历史原因,文件的低级名称通常称为inode号。每个文件都有一个与其关联的inode号。
  • 第二个是目录(directory):一个目录,像一个文件一样,也有一个低级名字(即inode号),但是它的内容非常具体:它包含一个(用户可读名字,低级名字)对的列表。例如,假设存在一个低级别名称为“10”的文件,它的用户可读的名称为“foo”。“foo”所在的目录因此会有条目(“foo”,“10”),将用户可读名称映射到低级名称。目录中的每个条目都指向文件或其他目录。通过将目录放入其他目录中,用户可以构建任意的目录树(directory tree,或目录层次结构,directory hierarchy),在该目录树下存储所有文件和目录。
    • 目录层次结构从根目录(root directory)开始(在基于UNIX 的系统中,根目录记为“/”),并使用某种分隔符(separator)来命名后续子目录(sub-directories),直到命名所需的文件或目录。例如,如果用户在根目录中创建了一个目录foo,然后在目录foo中创建了一个文件bar.txt,就可以通过它的绝对路径名(absolute pathname)来引用该文件
      notion image
      这个例子中,它将是/foo/bar.txt。示例中的有效目录是/,/foo,/bar,/bar/bar,/bar/foo,有效的文件是/foo/bar.txt 和/bar/foo/bar.txt。目录和文件可以具有相同的名称,只要它们位于文件系统树的不同位置(例如图中有两个名为bar.txt 的文件:/foo/bar.txt 和/bar/foo/bar.txt)。
 

创建文件

单机数据持久-文件系统实现
type
status
date
slug
summary
tags
category
icon
password
Property
 
文件系统是纯软件。与CPU 和内存虚拟化的开发不同不会添加硬件功能来使文件系统的某些方面更好地工作(但需要注意设备特性,以确保文件系统运行良好)。由于在构建文件系统方面具有很大的灵活性,因此人们构建了许多不同的文件系统,从AFS(Andrew 文件系统)到ZFS(Sun 的Zettabyte 文件系统)。所有这些文件系统都有不同的数据结构,在某些方面优于或逊于同类系统。
介绍一个简单的文件系统实现,称为VSFS(Very Simple File System,简单文件系统),它是典型UNIX文件系统的简化版本。
 
考虑文件系统时,通常考虑它们的两个不同方面:
  • 第一个方面是文件系统的数据结构(data structure)。换言之,文件系统在磁盘上使用哪些类型的结构来组织其数据和元数据?简单的文件系统(包括下面的VSFS)使用简单的结构,如块或其他对象的数组,而更复杂的文件系统(如SGI 的XFS)使用更复杂的基于树的结构。
  • 文件系统的第二个方面是访问方法(access method)。如何将进程发出的调用,如open()read()write()等,映射到它的结构上?在执行特定系统调用期间读取哪些结构?改写哪些结构?所有这些步骤的执行效率如何?
 

整体组织

需要做的第一件事是将磁盘分成块(block)。简单的文件系统只使用一种块大小,这里正是这样做的,选择常用的4KB。
构建文件系统的磁盘分区:一系列块,每块大小为4KB。在大小为N个4KB块的分区中,这些块的地址为从0到N−1。假设有一个非常小的磁盘,只有64块:
notion image
单机数据持久-局部性和快速文件系统
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
老UNIX 文件系统的数据结构在磁盘上看起来像这样:
notion image
超级块(S)包含有关整个文件系统的信息:卷的大小、有多少inode、指向空闲列表块的头部的指针等等。磁盘的inode 区域包含文件系统的所有inode。最后,大部分磁盘都被数据块占用。
老文件系统的好处在于它很简单,支持文件系统试图提供的基本抽象:文件和目录层次结构。与过去笨拙的基于记录的存储系统相比,这个易于使用的系统真正向前迈出了一步。与早期系统提供的更简单的单层次层次结构相比,目录层次结构是真正的进步。
 
问题:性能不佳
老UNIX文件系统将磁盘当成随机存取内存。数据遍布各处,而不考虑保存数据的介质是磁盘的事实,因此具有实实在在的、昂贵的定位成本。例如,文件的数据块通常离其inode非常远,因此每当第一次读取inode 然后读取文件的数据块时,就会导致昂贵的寻道。
更糟糕的是,文件系统最终会变得非常碎片化(fragmented),因为空闲空间没有得到精心管理。空闲列表最终会指向遍布磁盘的一堆块,并且随着文件的分配,它们只会占用下一个空闲块。结果是在磁盘上来回访问逻辑上连续的文件,从而大大降低了性能。
 
例如,假设以下数据块区域包含4 个文件(A、B、C 和D),每个文件大小为两个块:
单机数据持久-崩溃一致性
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
与大多数数据结构不同,文件系统数据结构必须持久(persist),即它们必须长期存在,存储在断电也能保留数据的设备上(例如硬盘或基于闪存的SSD)。
文件系统面临的一个主要挑战在于,如何在出现断电(power loss)或系统崩溃(systemcrash)的情况下,更新持久数据结构。由于断电和崩溃,更新持久性数据结构可能非常棘手,并导致了文件系统实现中一个有趣的新问题,称为崩溃一致性问题(crash-consistency problem)。
为了完成特定操作,必须更新两个磁盘上的结构A和B。由于磁盘一次只为一个请求提供服务,因此其中一个请求将首先到达磁盘(A 或B)。如果在一次写入完成后系统崩溃或断电,则磁盘上的结构将处于不一致(inconsistent)的状态。因此,遇到了所有文件系统需要解决的问题:
系统可能在任何两次写入之间崩溃或断电,因此磁盘上状态可能仅部分地更新。崩溃后,系统启动并希望再次挂载文件系统(以便访问文件等)。鉴于崩溃可能发生在任意时间点,如何确保文件系统将磁盘上的映像保持在合理的状态?
 
我们需要一种工作负载(workload),它以某种方式更新磁盘结构。这里假设工作负载很简单:将单个数据块附加到原有文件。通过打开文件,调用lseek()将文件偏移量移动到文件末尾,然后在关闭文件之前,向文件发出单个4KB 写入来完成追加。
 
假定磁盘上使用标准的简单文件系统结构。包括一个inode 位图(inode bitmap,只有8 位,每个inode一个),一个数据位图(data bitmap,也是8 位,每个数据块一个),inode(总共8 个,编号为0 到7,分布在4 个块上),以及数据块(总共8 个,编号为0~7)。
notion image
看图中的结构,可以看到分配了一个inode(inode 号为2),它在inode位图中标记,单个分配的数据块(数据块4)也在数据中标记位图。inode表示为I,因为它是此inode的第一个版本。它将很快更新(由于工作负载)。再来看看这个简化的inode。在I中,看到:
单机数据持久-日志结构文件系统
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
在 20 世纪 90 年代早期,由 John Ousterhout 教授和研究生 Mendel Rosenblum 领导的伯克利小组开发了一种新的文件系统,称为日志结构文件系统(LFS)。他们这样做的动机是基于以下观察:
  • 内存大小不断增长。
    • 随着内存越来越大,可以在内存中缓存更多数据。随着更多数据的缓存,磁盘流量将越来越多地由写入组成,因为读取将在缓存中进行处理。因此,文件系统性能很大程度上取决于写入性能。
  • 随机 I/O 性能与顺序 I/O 性能之间存在巨大的差距,且不断扩大:传输带宽每年增加约 50%~ 100%。
    • 寻道和旋转延迟成本下降得较慢,可能每年 5%~ 10%。因此,如果能够以顺序方式使用磁盘,则可以获得巨大的性能优势,随着时间的推移而增长。
  • 现有文件系统在许多常见工作负载上表现不佳。
    • 例如,FFS 会执行大量写入,以创建大小为一个块的新文件:一个用于新的 inode,一个用于更新 inode 位图,一个用于文件所在的目录数据块,一个用于目录 inode 以更新它,一个用于新数据块,它是新文件的一部分,另一个是数据位图,用于将数据块标记为已分配。因此,尽管 FFS 会将所有这些块放在同一个块组中,但 FFS 会导致许多短寻道和随后的旋转延迟,因此性能远远低于峰值顺序带宽。
  • 文件系统不支持 RAID。
    • 例如,RAID-4 和 RAID-5 具有小写入问题(small-write problem),即对单个块的逻辑写入会导致 4 个物理 I/O 发生。现有的文件系统不会试图避免这种最坏情况的 RAID 写入行为。
因此,理想的文件系统会专注于写入性能,并尝试利用磁盘的顺序带宽。此外,它在常见工作负载上表现良好,这种负载不仅写出数据,还经常更新磁盘上的元数据结构。最后,它可以在 RAID 和单个磁盘上运行良好。
单机数据持久-数据完整性和保护
type
status
date
slug
summary
tags
category
icon
password
Property
 

磁盘故障模式

磁盘并不完美,并且可能会发生故障(有时)。在早期的RAID 系统中,故障模型非常简单:要么整个磁盘都在工作,要么完全失败,而且检测到这种故障很简单。这种磁盘故障的故障—停止(fail-stop)模型使构建RAID 相对简单。
而现代磁盘似乎大部分时间正常工作,但是无法成功访问一个或几个块。具体来说,两种类型的单块故障是常见的,值得考虑:潜在扇区错误(Latent-SectorErrors,LSE)和块讹误。
 
  • 潜在扇区错误(Latent-Sector Errors,LSE)
    • 如果磁头由于某种原因接触到表面(磁头碰撞,head crash,在正常操作期间不应发生的情况),则可能会讹误表面,使得数据位不可读。宇宙射线也会导致数据位翻转,使内容不正确。幸运的是,驱动器使用磁盘内纠错码(Error Correcting Code,ECC)来确定块中的磁盘位是否良好,并且在某些情况下,修复它们。如果它们不好,并且驱动器没有足够的信息来修复错误,则在发出请求读取它们时,磁盘会返回错误。
  • 块讹误(block corruption)
    • 磁盘块出现讹误,但磁盘本身无法检测到。例如,有缺陷的磁盘固件可能会将块写入错误的位置。在这种情况下,磁盘 ECC 指示块内容很好,但是从客户端的角度来看,在随后访问时返回错误的块。类似地,当一个块通过有故障的总线从主机传输到磁盘时,它可能会讹误。由此产生的讹误数据会存入磁盘,但它不是客户所希望的。这些类型的故障特别隐蔽,因为它们是无声的故障(silent fault)。返回故障数据时,磁盘没有报告问题。
notion image
该表显示了在研究过程中至少出现一次 LSE 或块讹误的驱动器百分比(大约 3 年,超过 150 万个磁盘驱动器)。该表进一步将结果细分为“廉价”驱动器(通常为 SATA驱动器) 和“昂贵”驱动器(通常为 SCSI 或 FibreChannel)。
 
联机数据持久-分布式系统
type
status
date
slug
summary
tags
category
icon
password
Property
 
 
分布式系统改变了世界的面貌。当你的Web 浏览器连接到地球上其他地方的Web 服务器时,它就会参与似乎是简单形式的客户端/服务器(client/server)分布式系统。当你连上Google 和Facebook 等现代网络服务时,不只是与一台机器进行交互。在幕后,这些复杂的服务是利用大量机器(成千上万台)来提供的,每台机器相互合作,以提供站点的特定服务。
构建分布式系统时会出现许多新的挑战。我们关注的主要是故障(failure)。机器、磁盘、网络和软件都会不时故障,因为我们不知道(并且可能永远不知道)如何构建“完美”的组件和系统。但是,构建一个现代的Web 服务时,我们希望它对客户来说就像永远不会 失败一样。怎样才能完成这项任务?
其他重要问题也存在。系统性能(performance)通常很关键。对于将分布式系统连接在一起的网络,系统设计人员必须经常仔细考虑如何完成给定的任务,尝试减少发送的消息数量,并进一步使通信尽可能高效(低延迟、高带宽)。
最后,安全(security)也是必要的考虑因素。连接到远程站点时,确保远程方是他们声称的那些人,这成为一个核心问题。此外,确保第三方无法监听或改变双方之间正在进行的通信,也是一项挑战。
 

通信基础

现代网络的核心原则是,通信基本是不可靠的。无论是在广域Internet,还是Infiniband等局域高速网络中,数据包都会经常丢失、损坏,或无法到达目的地。
数据包丢失或损坏的原因很多。有时,在传输过程中,由于电气或其他类似问题,某些位会被翻转。有时,系统中的某个元素(例如网络链接或数据包路由器,甚至远程主机)会以某种方式损坏,或以其他方式无法正常工作。网络电缆确实会意外地被切断,至少有时候。
然而,更基本的是由于网络交换机、路由器或终端节点内缺少缓冲,而导致数据包丢失。具体来说,即使我们可以保证所有链路都能正常工作,并且系统中的所有组件(交换机、路由器、终端主机)都按预期启动并运行,仍然可能出现丢失,原因如下。想象一下数据包到达路由器。对于要处理的数据包,它必须放在路由器内某处的内存中。如果许多此类数据包同时到达,则路由器内的内存可能无法容纳所有数据包。此时路由器唯一的选择是丢弃(drop)一个或多个数据包。同样的行为也发生在终端主机上。当你向单台机器发送大量消息时,机器的资源很容易变得不堪重负,从而再次出现丢包现象。
因此,丢包是网络的基本现象。所以问题变成:应该如何处理丢包?
联机数据持久-AFS 与NFS
type
status
date
slug
summary
tags
category
icon
password
Property
 

Sun 的网络文件系统(NFS)

分布式客户端/服务器计算的首次使用之一,是在分布式文件系统领域。在这种环境中,有许多客户端机器和一个服务器(或几个)。服务器将数据存储在其磁盘上,客户端通过结构良好的协议消息请求数据。
notion image
服务器有磁盘,发送消息的客户端通过网络,访问服务器磁盘上的目录和文件。这种设置允许在客户端之间轻松地共享(sharing)数据。第二个好处是集中管理(centralized administration)。例如,备份文件可以通过少数服务器机器完成,而不必通过众多客户端。另一个优点可能是安全(security),将所有服务器放在加锁的机房中。
 
 
 
 
 

Andrew 文件系统(AFS)