[论文翻译]ZooKeeper: Wait-free coordination for Internet-scale systems

Patrick Hunt and Mahadev Konar Yahoo! Grid {phunt,mahadev}@yahoo-inc.com Flavio P. Junqueira and Benjamin Reed Yahoo! Research {fpj,breed}@yahoo-inc.com 摘要 在本文中,我们描述了Zookeeper,一种用于协调分布式应用程序进程的服务。由于Zookeeper是关键基础设施的一部分,它旨在为客户端构建更复杂的协调原语提供一个简单且高性能的核心。它在一个复制的集中式服务中结合了组消息传递、共享寄存器和分布式锁服务的元素。Zookeeper提供的接口具有共享寄存器的无等待特性,并带有一个类似于分布式文件系统中缓存失效的事件驱动机制,从而提供一个简单而强大的协调服务。 Zookeeper的接口支持高性能服务实现。除了无等待特性外,Zookeeper还为客户机提供了请求的先进先出(FIFO)执行保证,并对所有改变Zookeeper状态的请求提供线性一致性。这些设计决策使得实现高性能处理流水线成为可能,读请求可以由本地服务器满足。我们展示了针对目标工作负载(读写比例为2:1到100:1),Zookeeper每秒能够处理数万到数十万次事务。这种性能允许客户端应用程序广泛使用Zookeeper。 1 引言 大规模分布式应用程序需要不同形式的协调。配置是最基本的协调形式之一。在其最简单的形式中,配置只是系统进程的操作参数列表,而更复杂的系统则具有动态配置参数。组成员关系和领导者选举在分布式系统中也很常见:通常进程需要知道其他哪些进程是活跃的以及这些进程负责什么。锁是一种强大的协调原语,用于实现对关键资源的互斥访问。 一种协调的方法是为每种不同的协调需求开发特定的服务。例如,Amazon Simple Queue Service 1 专门针对队列设计。其他服务则专门为领导者选举 2 和配置 3 开发。实现更强大原语的服务可以用来实现功能较弱的原语。例如,Chubby 4 是一个具有强同步保证的锁定服务。然后可以使用锁来实现领导者选举、组成员关系等。 在设计我们的协调服务时,我们没有选择在服务器端实现特定的原语,而是选择公开一个API,使应用程序开发人员能够实现自己的原语。这种选择导致了协调内核(coordination kernel)的实现,使得无需更改服务核心即可启用新的原语。这种方法允许根据应用的需求适应多种协调形式,而不是限制开发者使用固定的一组原语。 在设计Zookeeper的API时,我们避开了锁等阻塞原语。协调服务中的阻塞原语可能会导致诸如慢速或故障客户端对更快客户端性能产生负面影响等问题。如果请求处理依赖于其他客户端的响应和故障检测,服务本身的实现也会变得更加复杂。因此,我们的系统Zookeeper实现了一个操作简单无等待(wait-free)数据对象的API,这些数据对象以类似于文件系统的方式进行层次化组织。实际上,Zookeeper的API与其他文件系统的API相似,仅从API签名来看,Zookeeper似乎就是没有锁方法、打开和关闭功能的Chubby。然而,实现无等待数据对象使Zookeeper与基于锁等阻塞原语的系统有显著区别。 尽管无等待特性对性能和容错至关重要,但对于协调来说,这还不够。我们还需要为操作提供顺序保证。特别是,我们发现保证所有操作的先进先出(FIFO)客户机顺序和写操作的线性一致性,能够实现服务的高效实施,并且足以实现对我们应用程序感兴趣的协调原语。实际上,我们可以使用我们的API为任意数量的进程实现共识,根据Herlihy的层次结构,Zookeeper实现了通用对象5。 Zookeeper服务由一组使用复制技术以实现高可用性和性能的服务器组成。其高性能使得包含大量进程的应用程序能够使用这种协调内核来管理所有方面的协调。我们能够使用一种简单的流水线架构实现Zookeeper,这种架构允许我们在仍有低延迟的情况下处理数百或数千个未完成的请求。这样的流水线自然支持单个客户端操作按FIFO顺序执行。保证FIFO客户机顺序使客户机可以异步提交操作。通过异步操作,客户机能够同时拥有多个未完成的操作。例如,当一个新的客户机成为领导者并且需要相应地操作和更新元数据时,这一特性是理想的。如果没有多未完成操作的可能性,初始化时间可能会达到数秒而不是亚秒级。 为了保证更新操作满足线性一致性,我们实现了一个基于领导者的原子广播协议6,称为Zab7。然而,一个典型的Zookeeper应用程序工作负载主要由读操作主导,因此需要扩展读吞吐量。在Zookeeper中,服务器本地处理读操作,并且不使用Zab对它们进行全局排序。 客户端侧缓存数据是提高读性能的重要技术。例如,对于一个进程来说,缓存当前领导者的标识符比每次需要知道领导者信息时都查询Zookeeper要高效得多。Zookeeper使用观察机制使客户端能够在不直接管理客户端缓存的情况下缓存数据。通过这种机制,客户端可以监视给定数据对象的更新,并在发生更新时收到通知。Chubby直接管理客户端缓存,它阻止更新以使所有缓存了正在更改数据的客户端的缓存失效。在这种设计下,如果这些客户端中有任何一个缓慢或出现故障,更新将会被延迟。Chubby使用租约防止故障客户端无限期地阻塞系统。然而,租约仅限制了缓慢或故障客户端的影响,而Zookeeper的观察机制则完全避免了这一问题。 在本文中,我们讨论 ZooKeeper 的设计和实现。借助 ZooKeeper,我们能够实现应用程序所需的所有协调原语,即使只有写入是可线性化的。为了验证我们的方法,我们展示了如何使用 ZooKeeper 实现一些协调原语。 总之,本文的主要贡献如下: 协调内核:我们提出了一种具有宽松一致性保证的无等待协调服务,用于分布式系统中。特别是,我们描述了协调内核的设计与实现,该内核已在许多关键应用中用于实现各种协调技术。 协调方案:我们展示了如何使用Zookeeper构建更高级别的协调原语,甚至是阻塞和强一致性的原语,这些原语在分布式应用中经常被使用。 协调经验:我们分享了一些使用Zookeeper的方式,并评估了其性能。通过这些实际应用和性能评估,我们可以更好地理解Zookeeper在真实环境中的表现及其对分布式应用的支持能力。 2 ZooKeeper 服务 客户通过使用Zookeeper客户端库的客户端API向Zookeeper提交请求。除了通过客户端API暴露Zookeeper服务接口外,客户端库还管理客户端与Zookeeper服务器之间的网络连接。在本节中,我们首先提供Zookeeper服务的高层视图。然后讨论客户端用于与Zookeeper交互的API。 术语。在本文中,我们使用客户端(client)表示ZooKeeper服务的用户,服务器(server)表示提供ZooKeeper服务的进程,znode表示ZooKeeper数据中的内存数据节点,这些节点在一个分层命名空间中组织,称为数据树(data tree)。我们还使用术语更新和写入来指代任何修改数据树状态的操作。客户端在连接到ZooKeeper并获取会话句柄通过其发出请求时建立会话(session)。 2.1 服务总览 ZooKeeper为其客户端提供了一组按照分层命名空间组织的数据节点(znodes)的抽象。这个层次结构中的znodes是客户端通过ZooKeeper API进行操作的数据对象。分层命名空间常见于文件系统中,这是一种理想的组织数据对象的方式,因为用户对此抽象非常熟悉,并且它能够更好地组织应用程序元数据。为了引用一个特定的znode,我们使用标准的UNIX文件系统路径表示法。例如,我们使用/A/B/C来表示znode C的路径,其中C的父节点是B,而B的父节点是A。所有znodes都存储数据,并且除了临时znodes外,所有znodes都可以有子节点。 客户端可以创建两种类型的 znode: 常规znodes(regular):客户端通过显式地创建和删除来操作常规znodes。 临时znodes(ephemeral):客户端创建此类znodes,它们要么由客户端显式删除,要么在创建它们的会话终止(无论是故意终止还是由于故障)时由系统自动移除。 此外,当创建一个新的znode时,客户端可以设置一个顺序(sequential)标志。设置了顺序标志的节点会在其名称后附加一个单调递增计数器的值。如果$n$是新的znode,$p$是父znode,则$n$的序列值永远不会小于在$p$下创建的任何其他顺序znode名称中的值。 ZooKeeper实现了监视(watches)机制,允许客户端在不进行轮询的情况下及时接收变化的通知。当客户端发出带有监视标志的读操作时,操作会正常完成,但服务器承诺在返回的信息发生变化时通知客户端。监视是一次性触发器,与一个会话相关联;一旦触发或会话关闭,它们就会被注销。监视仅指示发生了变化,但不会提供具体的变化内容。例如,如果客户端在“/foo”被更改两次之前发出getData(''/foo'', true),客户端将收到一个监视事件,告知“/foo”的数据已更改。会话事件,如连接丢失事件,也会发送到监视回调中,以便客户端知道监视事件可能会延迟。...

January 21, 2025 · 6 min · 1232 words · Jagger

高级软件工程复习笔记

软件概述 基本定义 (1) 程序与软件的定义 程序是由程序设计语言所描述的、能为计算机所理解和处理的一组语句序列。其用程序设计语言(Programming Language)来描述的。程序严格遵循程序设计语言的各项语法和语义规定,应确保程序代码能为程序设计语言的编译器所理解,进而编译生成相应的可运行代码。 软件是指在计算机系统的支持下,能够完成特定功能与性能的程序、数据和相关文档。文档是记录软件开发活动和阶段性成果、软件配置及变更的阐述性资料。 (2) 软件的性质 复杂性 一致性:软件不能独立存在,需要依附于一定的环境(硬件网络等),软件必须遵从人为的惯例并适应已有的技术和系统,软件需要随接口不同而改变,随时间推移而变化,而这些变化是不同人设计的结果 可变性:人们总认为软件是容易修改的,但忽视了修改带来的副作用,不断的修改最终导致软件的退化,从而结束其生命周期 不可见性:软件是一种看不见、摸不着的逻辑实体,不具有空间的形体特征,开发人员可以直接看到程序代码,但源代码并不是软件本身,软件以机器代码的形式运行,开发人员无法看到源代码如何执行的。 (3) 软件的分类 类别 服务对象 软件的功能 发挥的作用 应用软件 行业和领域应用的用户 为特定行业和领域问题解决提供基于软件解决方案,创新应用领域的问题解决模式 提供更为便捷、快速、高效的服务 系统软件 各类应用软件 为应用软件运行和维护提供基础设施和服务,如加载、通讯、互操作、管理等 作为应用软件的运行环境 支撑软件 软件开发者和维护者 为软件系统的开发和维护提供自动和半自动的支持 提高软件开发效率和质量 (4) 开源许可证 软件工程概述 (1) 软件开发需要解决的问题 开发过程:基于什么样的步骤来开发软件系统 开发方法:采用怎样的方法来指导各项软件开发活动 开发管理:如何组织开发人员和管理软件产品 质量保证:如何保证软件开发活动和制品的质量 (2) 软件开发的特殊性 软件开发技术更新速度快、软件开发需求变更频繁、软件开发复杂度高、软件开发需要团队协作完成、软件开发成果需要长期维护、软件开发保证软件质量、软件开发需要评估和控制风险 (3) 软件工程的定义 软件工程是一门研究如何用系统化、规范化、可量化等工程原则和方法进行软件开发和维护的学科。系统化:提供完整和全面的解决方法,包括目标、原则、过程模型、开发活动、开发方法和技术等;规范化:支持各类软件系统的开发,包括语言标准、质量标准、编程标准、方法标准、能力极其改进标准等;可量化:工作量、成本、进度、质量等要素可以量化。 其目标是创造“足够好”的软件,对于好的软件的定义:低成本、高质量、按时交付 其内容包括市场调研、正式立项、需求分析、项目策划、概要设计、详细设计、编程、测试、试运行、产品发布、用户培训、产品复制、销售、实施、系统维护和版本升级等。 (4) 软件工程的三要素 视角 描述 目的 过程 从管理的视角,回答软件开发、运行和维护需要开展哪些工作、按照什么样的步骤和次序来开展工作 对软件开发过程所涉及的人、制品、质量、成本、计划等进行有效和可量化的管理 方法学 从技术的视角,回答软件开发、运行和维护如何做的问题 为软件开发过程中的各项开发和维护活动提供系统性、规范性的技术支持 工具 从工具辅助的视角,主要回答如何借助工具来辅助软件开发、运行和维护的问题 帮助软件开发人员更为高效地运用软件开发方法学来完成软件开发过程中的各项工作,提高软件开发效率和质量,加快软件交付进度 计算机辅助软件工程 (1) 基本概念 全称为Computer Aided Software Engineering(CASE)。起源于20世纪80年代,最初是指在信息管理系统开发过程中由各种计算机辅助软件和工具组成的软件开发环境。随着软件工程技术、工具和开发理念的不断发展,CASE逐步演进成为辅助软件工程全生命周期的开发工具和方法集合。CASE旨在帮助软件工程从业者们进行软件开发和维护,提高软件开发和运维效率,提升软件质量,为实现软件开发全生命周期的工程化、自动化和智能化提供基础支撑。 (2) CASE工具分类...

January 18, 2025 · 10 min · 2094 words · Jagger

Raft实验报告(2A、2B)

原理 具体可以看翻译的论文。 状态转换图 Raft的系统主要包含节点角色、领导者选举和日志复制。Raft算法基于复制状态机模型,将集群中的节点分为领导者(Leader)、追随者(Follower)和候选者(Candidate)三种角色。 领导者:负责处理所有客户端请求,并将操作同步到其他节点,确保系统在出现故障时仍能保持一致性。 追随者:响应领导者的请求并保存日志条目,通常处于被动状态。追随者向领导者发送心跳以维持领导地位。 候选者:在选举过程中,节点可以变为候选者,尝试成为领导者。候选者通过请求投票来获得足够的支持。 三种状态之间的转换如下: 任务流程 在不发生特殊情况的前提下,该系统进行领导者选举、日志复制的时序图如下: sequenceDiagram participant Node1 as Node 1 (Follower) participant Node2 as Node 2 (Follower) participant Node3 as Node 3 (Follower) participant Node4 as Node 4 (Follower) participant Node5 as Node 5 (Follower) %% Node1 超时,转变为 Candidate 并发起选举 Note over Node1: 超时 Node1->>Node1: 增加当前任期编号<br>转变为 Candidate Node1->>Node2: RequestVote RPC (Term, CandidateId, LastLogIndex, LastLogTerm) Node1->>Node3: RequestVote RPC (Term, CandidateId, LastLogIndex, LastLogTerm) Node1->>Node4: RequestVote RPC (Term, CandidateId, LastLogIndex, LastLogTerm) Node1->>Node5: RequestVote RPC (Term, CandidateId, LastLogIndex, LastLogTerm) %% 其他节点响应投票请求 Node2-->>Node1: VoteGranted (true) Node3-->>Node1: VoteGranted (true) Node4-->>Node1: VoteGranted (false) : 日志较新 Node5-->>Node1: VoteGranted (true) %% Node1 成为 Leader Note over Node1: 获得多数票 Node1->>Node1: 转变为 Leader %% 持续发送心跳 loop 每个周期 Node1->>Node2: Heartbeat(Term, LeaderID, PrevLogIndex, PrevLogTerm, nil, LeaderCommit) Node1->>Node3: Heartbeat(Term, LeaderID, PrevLogIndex, PrevLogTerm, nil, LeaderCommit) Node1->>Node4: Heartbeat(Term, LeaderID, PrevLogIndex, PrevLogTerm, nil, LeaderCommit) Node1->>Node5: Heartbeat(Term, LeaderID, PrevLogIndex, PrevLogTerm, nil, LeaderCommit) Node2-->> Node1: Success(true) Node3-->> Node1: Success(true) Node4-->> Node1: Success(true) Node5-->> Node1: Success(true) end %% 更新日志 Node1->>Node2: AppendEntries RPC(Term, LeaderID, PrevLogIndex, PrevLogTerm, LogEntries, LeaderCommit) Node1->>Node3: AppendEntries RPC(Term, LeaderID, PrevLogIndex, PrevLogTerm, LogEntries, LeaderCommit) Node1->>Node4: AppendEntries RPC(Term, LeaderID, PrevLogIndex, PrevLogTerm, LogEntries, LeaderCommit) Node1->>Node5: AppendEntries RPC(Term, LeaderID, PrevLogIndex, PrevLogTerm, LogEntries, LeaderCommit) Node2-->> Node1: Success(true) Node3-->> Node1: Success(false):日志不匹配 Node4-->> Node1: Success(true) Node5-->> Node1: Success(true) Note over Node1: 多数派复制成功 Node1->>Node1: 提交日志 其主要可以分为两个部分:领导者选举和日志复制:...

December 23, 2024 · 14 min · 2790 words · Jagger

MapReduce实验报告

原理 具体可以看翻译的论文 系统架构 MapReduce的整体系统架构图如上图所示。总体上可以分为三个部分:Client、Master、Worker 在Client中,编写的MapReduce程序提交到master端,用户可通过Client提供的一些接口查看作业运行状态。 在Master中,又分为两个部分: Task Scheduler负责调度mapreduce作业,它将作业分解为多个Map任务和Reduce任务,然后将这些任务分配给集群中的不同节点来处理。 JobTracker负责它监控任务的执行状态,负责处理来自worker的数据,并负责将数据转发与存储。同时也负责任务的失败处理。 在Worker中,分为了三个部分: TaskTracker任务: 分布在集群各个节点上的工作节点,负责执行由JobTracker分配的Map和Reduce任务。每个TaskTracker节点都可以并行执行多个任务。TaskTracker会将任务的执行进度和状态报告给mastre中的JobTracker。 Map任务: Map任务是MapReduce处理的第一阶段。输入数据被分为多个分片,每个分片被分配给一个Map任务。Map任务处理数据片段,将其转化为键值对<key, value>的形式。然后,它们将这些键值存储到一定位置并告知master,以供后续的Reduce任务使用。 Reduce任务: Reduce任务是MapReduce处理的第二阶段。Reduce任务负责处理由Map任务产生的中间键值对。它将相同键的数据进行汇总和归并处理,然后输出最终的计算结果。通常,Reduce任务会聚合、筛选或合并相同键的数据,实现对Map结果的进一步处理。 任务流程 流程图如上图所示,关键流程有以下几步: 用户程序中的MapReduce库首先将输入文件划分为M个分片,通常每个分片为16MB到64MB(用户可通过可选参数控制)。随后,库会在集群中的机器上启动程序的一些副本。 这些程序的副本中,有一份很特殊,它是master副本。其他的副本是被master分配了任务的worker副本。总计要分配 M个map任务和R个reduce任务。master选取闲置的worker并为每个选取的worker分配map或reduce任务。 被分配map任务的worker从输入数据分片中读取内容。其解析输入数据中的键值对,并将每个键值对传给用户定义的map函数。map函数输出的中间键值对在内存中缓存。 内存中缓存的键值对会定期地写入本地磁盘,写入的数据会被分区函数划分为R个区域。这些在磁盘中缓存的键值对的位置会被发送给master,master会将这些位置信息进一步传递给reduce worker。 当master通知reduce worker中间键值对的位置信息后,reduce worker会通过RPC的方式从map worker的本地磁盘中读取缓存的数据。当reduce worker读取完所有中间数据后,它会对中间数据按照键进行排序,以便将所有键相同的键值对分为一组。因为通常来说,需对键不同的数据会被映射到同一个reduce任务中,所以需要对数据排序。如果中间数据总量过大以至于无法放入内存中,则会使用外排序算法。 reduce worker遍历每一个遇到的中间键值对的,它会将键和该键对应的一系列值传递给用户定义的reduce函数。reduce函数的输出会被追加到该reduce分区的最终输出文件中。 当所有的map和reduce任务都执行完毕后,master会唤醒用户程序。此时,调用MapReduce的调应用序会返回到用户代码中。 总览 本次实验最终实现的思路,其时序图如下: 实验的类图如下: classDiagram class Coordinator { +state: TaskType +tasks: map[TaskType][]Task +mapping: map[int]int +reducing: map[int]int +GetTask(args: GetTaskArgs, reply: *GetTaskReply) error +WorkerComplete(args: *GetTaskArgs, reply: *GetTaskReply) error } class Worker { +doMap(mapf: func(string, string) []KeyValue, reply: *GetTaskReply) bool +doReduce(reducef: func(string, []string) string, reply: *GetTaskReply) bool +merge(taskId: int, mMap: int) []KeyValue +devide(kva: []KeyValue, taskId: int, nReduce: int) bool } class Task { +TaskId: int +TaskType: TaskType +TaskState: TaskState +startTime: time....

December 22, 2024 · 10 min · 2118 words · Jagger

[论文翻译]In Search of an Understandable Consensus Algorithm (Extended Version)

Diego Ongaro and John Ousterhout Stanford University 摘要 Raft 是一种用于管理复制日志的共识算法。它能够产生与(多)Paxos 相同的结果,并且效率与 Paxos 相当,但其结构与 Paxos 不同。这种结构上的差异使得 Raft 比 Paxos 更容易理解,同时也为构建实际系统提供了更好的基础。为了提升可理解性,Raft 将共识的关键要素进行了分离,如领导者选举、日志复制和安全性保证等,并且强化了一致性要求以减少需要考虑的状态数量。用户研究的结果表明,相比 Paxos,学生们更容易掌握 Raft。此外,Raft 还引入了一种新的集群成员变更机制,该机制通过使用重叠的多数派来保证安全性。 1 引言 共识算法使得一组机器能够作为一个连贯的整体工作,并且能够在部分成员发生故障时继续运行。正因如此,这类算法在构建可靠的大规模软件系统中发挥着关键作用。在过去的十年里,Paxos1 2主导了关于共识算法的讨论:大多数共识算法的实现要么基于 Paxos,要么受到它的影响,而且 Paxos 已经成为向学生教授共识知识的主要载体。 不幸的是,尽管已经有许多让它变得更容易理解的尝试,Paxos 仍然非常难以理解。此外,它的架构需要进行复杂的改动才能支持实际系统。因此,系统构建者和学生都在与 Paxos 作斗争。 在我们自己也经历过与 Paxos 搏斗之后,我们着手寻找一种新的共识算法,希望它能为系统构建和教育提供更好的基础。我们采用了一种不同寻常的方式,即将 可理解性(understandability) 作为首要目标:我们能否定义一个用于实际系统的共识算法,并以一种比 Paxos 容易得多的方式来描述它?此外,我们希望这个算法能够帮助系统构建者形成必要的直观认识。重要的不仅是算法能够工作,更重要的是能够清楚地理解为什么它能工作。 这项工作的成果就是一个称为 Raft 的共识算法。在设计 Raft 时,我们应用了特定的技术来提高可理解性,包括分解(Raft 将领导者选举、日志复制和安全性分开)和状态空间简化(相比 Paxos,Raft 减少了不确定性的程度以及服务器之间可能产生不一致的方式)。在两所大学进行的涉及 43 名学生的用户研究表明,Raft 比 Paxos 更容易理解:在学习了这两种算法之后,其中 33 名学生对 Raft 的相关问题回答得比 Paxos 的问题要好。 Raft 在许多方面与现有的共识算法相似(最值得注意的是 Oki 和 Liskov 的 Viewstamped Replication 3 4),但它有几个新颖的特点:...

November 6, 2024 · 10 min · 1960 words · Jagger

[论文翻译]MapReduce: Simplified Data Processing on Large Clusters

Jeffrey Dean and Sanjay Ghemawat jeff@google.com, sanjay@google.com Google, Inc. 摘要 MapReduce 是一种用于处理和生成大型数据集的编程模型和相关实现。用户指定一个 map 函数来处理键/值对,生成一组中间键/值对,并指定一个 reduce 函数来合并与同一中间键相关的所有中间值。如本文所示,许多现实世界中的任务都可以用这个模型来表达。 以这种函数式风格编写的程序会自动并行化,并在大型商用机器集群上执行。运行时系统会处理分割输入数据、调度程序在一组机器上的执行、处理机器故障以及管理所需的机器间通信等细节。这样,没有任何并行和分布式系统经验的程序员也能轻松利用大型分布式系统的资源。 我们的 MapReduce 实现运行在大型商用机器集群上,并且具有高度可扩展性:典型的 MapReduce 计算在数千台机器上处理数 TB 的数据。程序员发现该系统易于使用:数百个 MapReduce 程序已经实现,并且每天在 Google 集群上执行超过一千个 MapReduce 作业。 1 引言 在过去五年中,本文作者和 Google 的许多其他人已经实现了数百种特殊用途的计算,这些计算处理大量原始数据(例如抓取的文档、Web 请求日志等),以计算各种派生数据(例如倒排索引、Web 文档图形结构的各种表示、每个主机抓取的页面数量摘要、给定一天中最频繁的查询集等)。大多数此类计算在概念上都很简单。但是,输入数据通常很大,并且计算必须分布在数百或数千台机器上才能在合理的时间内完成。如何并行化计算、分发数据和处理故障的问题共同掩盖了原始的简单计算,并使用大量复杂的代码来处理这些问题。 为了应对这种复杂性,我们设计了一种新的抽象,它允许我们表达我们试图执行的简单计算,但隐藏了库中的并行化、容错、数据分布和负载平衡的复杂细节。我们的抽象受到 Lisp 和许多其他函数式语言中存在的 map 和 Reduce 原语的启发。我们意识到,我们的大多数计算都涉及将 map 操作应用于输入中的每个逻辑“记录”,以计算一组中间键/值对,然后对共享相同键的所有值应用 Reduce 操作,以便适当地组合派生数据。我们使用具有用户指定的 map 和 Reduce 操作的函数模型,使我们能够轻松地并行化大型计算,并使用重新执行作为容错的主要机制。 这项工作的主要贡献在于提供了一个简单而功能强大的接口,可实现大规模计算的自动并行化和分布式处理,同时还实现了该接口在大型商用 PC 集群上的高性能。 第 2 节描述了基本编程模型并给出了几个示例。第 3 节描述了针对我们基于集群的计算环境定制的 MapReduce 接口实现。第 4 节描述了我们发现有用的编程模型的几个改进。第 5 节对我们的实现进行了各种任务的性能测量。第 6 节探讨了 MapReduce 在 Google 中的使用情况,包括我们使用它作为重写生产索引系统的基础的经验。第 7 节讨论了相关工作和未来工作。...

November 6, 2024 · 10 min · 2076 words · Jagger

[论文翻译]The Google File System

Sanjay Ghemawat, Howard Gobioff, and Shun-Tak Leung Google∗ 摘要 我们设计并实现了谷歌文件系统,这是一个可扩展的分布式文件系统,适用于大型分布式数据密集型应用。该系统可在廉价的商品硬件上运行,同时提供容错功能,并能为大量客户端提供高聚合性能。 我们的设计目标与之前的分布式文件系统有许多相同之处,但我们对应用工作负载和技术环境(包括当前和预期环境)的观察结果表明,我们的设计明显偏离了之前的一些文件系统假设。这促使我们重新审视传统的选择,探索完全不同的设计要点。 文件系统成功地满足了我们的存储需求。它在谷歌内部被广泛部署,作为生成和处理我们的服务所使用的数据以及需要大型数据集的研发工作的存储平台。迄今为止,最大的集群在一千多台机器上的数千个磁盘上提供了数百 TB 的存储空间,并被数百个客户端并发访问。 在本文中,我们介绍了为支持分布式应用而设计的文件系统接口扩展,讨论了我们设计的许多方面,并报告了微基准测试和实际使用的测量结果。 1. 引言 我们设计并实施了谷歌文件系统(GFS),以满足谷歌快速增长的数据处理需求。GFS 与以前的分布式文件系统有许多相同的目标,如性能、可扩展性、可靠性和可用性。但是,在设计 GFS 时,我们对当前和预期的应用工作负载和技术环境进行了重要观察,这反映出我们明显偏离了之前的一些文件系统设计假设。我们重新审视了传统的选择,并探索了设计空间中完全不同的点。 首先,组件故障是常态而非例外。文件系统由数百甚至数千台存储机组成,这些存储机都是用廉价的商品部件制造的,并被数量相当的客户机访问。这些组件的数量和质量几乎可以保证,在任何特定时间都会有一些组件无法正常工作,而且有些组件无法从当前故障中恢复。我们见过应用程序错误、操作系统错误、人为错误以及磁盘、内存、连接器、网络和电源故障造成的问题。因此,持续监控、错误检测、容错和自动恢复必须成为系统的组成部分。 其次,按照传统的标准,文件是巨大的。多 GB 的文件很常见。每个文件通常包含许多应用对象,如网络文档。当我们经常处理由数十亿个对象组成的多 TB 快速增长的数据集时,即使文件系统可以支持,要管理数十亿个约 KB 大小的文件也很不方便。因此,必须重新审视 I/O 操作和块大小等设计假设和参数。 第三,大多数文件都是通过添加新数据而不是覆盖现有数据来改变的。文件内的随机写入几乎不存在。文件一旦写入,就只能读取,而且通常只能按顺序读取。各种数据都具有这些特征。有些可能是数据分析程序扫描过的大型存储库。有些可能是运行中的应用程序持续生成的数据流。有些可能是档案数据。有些可能是在一台机器上生成并在另一台机器上处理的中间结果,无论是同时处理还是稍后处理。鉴于巨型文件的这种访问模式,追加成为性能优化和原子性保证的重点,而在客户端缓存数据块则失去了吸引力。 第四,共同设计应用程序和文件系统 API 可以提高我们的灵活性,从而使整个系统受益。例如,我们放宽了 GFS 的一致性模型,大大简化了文件系统,而不会给应用程序带来沉重负担。我们还引入了原子追加操作,使多个客户端可以同时追加文件,而无需额外的同步。本文稍后将详细讨论这些内容。 目前,为不同目的部署了多个 GFS 集群。最大的集群有超过 1000 个存储节点,超过 300 TB 的磁盘存储空间,数百个客户端在不同的机器上持续大量访问。 2. 设计概述 2.1 假设 在设计满足我们需求的文件系统时,我们遵循的假设既是挑战也是机遇。我们在前面提到了一些重要的观察结果,现在详细介绍一下我们的假设。 该系统由许多经常发生故障的廉价商品组件构成。系统必须不断进行自我监控,日常检测、容忍和及时恢复组件故障。 系统存储的大文件数量不多。我们预计会有几百万个文件,每个文件的大小通常为 100 MB 或更大。多 GB 文件是常见情况,应得到有效管理。我们必须支持小文件,但无需对其进行优化。 工作负载主要包括两种读取:大型流式读取和小型随机读取。在大数据流读取中,单个操作通常读取数百 KB,更常见的是 1 MB 或更多。来自同一客户端的连续操作通常会读取文件的连续区域。小型随机读取通常在某个任意偏移位置读取几个 KB 的数据。注重性能的应用程序通常会对小规模读取进行批处理和排序,以稳定地读取文件,而不是来回读取。 这些工作负载中还有许多向文件追加数据的大型连续写入操作。典型的操作大小与读取类似。文件一旦写入,就很少再修改。支持在文件任意位置进行小规模写入,但不一定要高效。 系统必须有效地实现多个客户端同时追加到同一文件的定义明确的语义。我们的文件通常用作生产者-消费者队列或多路合并。数以百计的生产者(每台机器运行一个)将同时追加到一个文件。同步开销最小的原子性至关重要。文件可能会在稍后被读取,或者消费者可能会同时读取文件。 高持续带宽比低延迟更重要。我们的大多数目标应用都非常重视高速批量处理数据,而很少有应用对单个读取或写入的响应时间有严格要求。 2.2 接口 GFS 提供了一个熟悉的文件系统接口,尽管它没有实现标准的 API(如 POSIX)。文件在目录中按层次组织,并用路径名标识。我们支持创建、删除、打开、关闭、读取和写入文件的常规操作。...

October 25, 2024 · 7 min · 1344 words · Jagger

C语言程序设计第二版课后习题--第八章

部分答案参考了官方题解和网上的答案,仅供参考,可能也有部分bug未发现或解决。 exercise8-1 用 read、write、open 和 close 系统调用代替标准库中功能等价的函数,重写第 7 章的 cat 程序,并通过实验比较两个版本的相对执行速度。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void filecopy(int ifp, int ofp) { char buf[BUFSIZE]; int n; while ((n = read(ifp, buf, BUFSIZE)) > 0) if (write(ofp, buf, n) !...

October 23, 2024 · 9 min · 1869 words · Jagger

C语言程序设计第二版课后习题--第七章

部分答案参考了官方题解和网上的答案,仅供参考,可能也有部分bug未发现或解决。 exercise7-1 编写一个程序,根据它自身被调用时存放在argv[0]中的名字,实现将大写字母转换为小写字母或将小写字母转换为大写字母的功能。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> int main(int argc, char **argv) { int (*handler[2])(int) = {tolower, toupper}; int type = strcmp(argv[0], "./lower") == 0 ? 0 : strcmp(argv[0], "./upper") == 0 ? 1 : -1; if (type == -1) { fprintf(stderr, "Usage: %s [lower|upper]\n", argv[0]); } else { int c; while ((c = getchar()) !...

October 19, 2024 · 7 min · 1380 words · Jagger

C语言程序设计第二版课后习题--第六章

部分答案参考了官方题解和网上的答案,仅供参考,可能也有部分bug未发现或解决。 exercise6-1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #define NKEYS (sizeof keytab / sizeof(keytab[0])) int binsearch(char *word, struct key tab[], int n) { int cond; int low, high, mid; low = 0; high = n - 1; while (low <= high) { mid = (low + high) / 2; if ((cond = strcmp(word, tab[mid]....

October 18, 2024 · 17 min · 3577 words · Jagger