<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Distributed Systems on arjenzhou</title><link>/categories/distributed-systems/</link><description>Recent content in Distributed Systems on arjenzhou</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 08 Nov 2021 00:00:00 +0000</lastBuildDate><atom:link href="/categories/distributed-systems/feed.xml" rel="self" type="application/rss+xml"/><item><title>谈谈我理解的 Raft</title><link>/article/2021/11/raft-talk/</link><pubDate>Mon, 08 Nov 2021 00:00:00 +0000</pubDate><guid>/article/2021/11/raft-talk/</guid><description>&lt;p&gt;自几年前偶然得知 Raft 之后，便粗略了解了 Raft, Paxos, 共识（一致性）这些概念。当时的想法是：这些算法听起来就挺装逼的，赶紧去看看咋回事。等到认真一看 Raft 论文，20页，太多了还是算了吧。
等到过了几天，又发现了 Raft 官网有个动画，还挺易懂的，又简单看了看，明白了，可以出去装逼了。
再过几天一回忆，好像啥也没记住呢？虽然张无忌学太极拳也啥也没记住，但至少人家打出来了，咱也得找机会练练。这会儿又找到了 MIT 6.824, 正好赶上新学期开课了，跟着学吧。
没等学两天，一到 lab 就懒得做，Go 看不懂啊，又学了几天 Go。学完了感觉啥都会了，但是又啥也不会。还是先去找实习吧。&lt;/p&gt;
&lt;p&gt;前年快写毕业论文的时候，附带个翻译英文文献的任务，又到了装逼的时候了，我就直接在 arXiv 上找了篇 &lt;a href="../../../../translation/2020/11/21/paxos-vs-raft-have-we-reached-consensus-on-distributed-consensus/"&gt;Raft 和 Paxos 对比&lt;/a&gt; 的论文翻译了下，由于当时懒得排版，等到去年年底才扔到博客上。&lt;/p&gt;
&lt;p&gt;论文翻译完，又感觉啥也没记住。但是快要去上班了，又没时间研究了，上了一年班之后又找来了 Martin Kleppmann 的&lt;a href="https://www.youtube.com/playlist?list=PLeKd45zvjcDFUEv_ohr_HdUFe97RItdiB"&gt;公开课&lt;/a&gt;来看，没几天就看完了，当时看完觉得啥都懂了，现在回忆起来只记住了拜占庭那些东西，又联想到凯撒、庞贝&amp;hellip;又联想远了&amp;hellip;&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/UEAMfLPZZhE?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;从去年7月入职以来，搞了本影印的 DDIA 来看。看书么，必须得英文原文，不然咋装逼啊是不。按照每天一页的进度终于把这书看完了（其实一周200页，然后歇半年）。看到后面又提到共识机制，觉得有必要再重新捡起来 Raft 论文再读一下了。&lt;/p&gt;
&lt;p&gt;手中的 Raft 这篇&lt;a href="https://raft.github.io/raft.pdf"&gt;论文&lt;/a&gt;实际上时几个月之前在公司的打印机上打印的，并且当时详细地做了注解，但随后没有好好整理。这次又根据上次遗留的问题来作笔记，最后写下了这篇文章表达一下自己的见解。&lt;/p&gt;
&lt;h2 id="前提"&gt;前提&lt;/h2&gt;
&lt;p&gt;Raft 据说是性能相当于 Paxos, 效果相当于 Multi-Paxos 的一个一致性算法，但它更容易理解。&lt;/p&gt;</description></item><item><title>Paxos vs Raft: Have we reached consensus on distributed consensus?</title><link>/translation/2020/11/21/paxos-vs-raft-have-we-reached-consensus-on-distributed-consensus/</link><pubDate>Sat, 21 Nov 2020 00:00:00 +0000</pubDate><guid>/translation/2020/11/21/paxos-vs-raft-have-we-reached-consensus-on-distributed-consensus/</guid><description>&lt;h2 id="摘要"&gt;摘要&lt;/h2&gt;
&lt;p&gt;分布式一致性是构建具有容错、强一致性特点的分布式系统的重要原语。在众多分布式公式算法中，其中有两个统治着生产系统：经典、也是以晦涩难懂著称的 Paxos；较新的、与 Paxos 相比更易理解的 Raft。&lt;/p&gt;
&lt;p&gt;本文主要研究 Paxos 和 Raft 那个是更好的分布式一致性解决方案。我们通过使用 Raft 的术语和语用抽象来描述简化的 Paxos 算法，从而对两者进行精确分析，以确定它们之间的区别。&lt;/p&gt;
&lt;p&gt;我们发现 Paxos 和 Raft 都采用相似的方式来达成分布式一致性，它们之间的区别只在于 leader 选举。最值得注意的是，Raft 只允许有最新日志的节点成为 leader，而 Paxos 允许任何节点成为 leader，前提是之后更新其日志来保证为最新。对于这样的简单性来时，Raft 的方法出奇的高效，因为与 Paxos 不同，它在 leader 选举时不需要日志条目的交换。我们推测，Raft 的可理解性大多来自于论文的清楚陈述，而不是展现出的基本算法的基础。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;CCS 概念&lt;/em&gt;：计算机系统管理→可靠性；软件及其工程→云计算；计算理论→分布式算法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;关键词&lt;/strong&gt;&lt;/em&gt;：状态机复制，分布式一致性，Paxos，Raft&lt;/p&gt;
&lt;h2 id="1-简介"&gt;1 简介&lt;/h2&gt;
&lt;p&gt;状态机复制[32]广泛用于将一群不可靠的主机组成一个可靠的能够提供强一致性保证包括线性化[13]的服务。据此，程序员能够通过复制状态机将服务看作一个单点系统，从而轻松推断出预期的表现。状态机复制需要每个状态机以相同的顺序接受相同的操作，这可以通过分布式一致性实现。&lt;/p&gt;
&lt;p&gt;Paxos 算法[16]与分布式一致性关系密切。虽然它很成功，但是 Paxos 晦涩难懂，造成了其难以推理、正确实现和安全优化。这已经在无数次用更简单的术语解释算法的尝试[4, 17, 22, 23, 25, 29, 35]中得到明显体现了，而且这也是 Raft[28] 的目的。&lt;/p&gt;
&lt;p&gt;Raft 的作者声称 Raft 与 Paxos 同样高效而更易懂，因此为实现具体系统提供了更好的基础。Raft 以这三种不同的方式来寻求实现这点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;演示&lt;/strong&gt; 首先，Raft 论文引入了一个描述在状态机复制上下文中基于 leader 选举的一致性的新抽象。 事实证明，这种务实的演示在工程师中非常受欢迎。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简单&lt;/strong&gt; 其次，Raft 算法的优先考虑简洁性而不是性能。例如，Raft 按顺序决定日志条目而 Paxos 允许乱序决定，但需要额外的协议来填充可能出现的日志空隙。&lt;/p&gt;</description></item><item><title>Is Redlock safe?</title><link>/translation/2020/04/04/is-redlock-safe/</link><pubDate>Sat, 04 Apr 2020 00:00:00 +0000</pubDate><guid>/translation/2020/04/04/is-redlock-safe/</guid><description>&lt;p&gt;分布式系统研究者 Martin Kleppmann 昨天发布了一篇 Redlock[1]的分析文章[2]。&lt;/p&gt;
&lt;p&gt;Redlock 是我设计用于 Redis 的客户端分布式锁定算法。该算法编排了一组客户端节点，这些节点实现了具有特定功能的数据存储，以便创建一个多主容错的、安全的、具有自动释放功能的分布式锁。
你可以使用 MySQL 而非 Redis 来实现 Redlock。&lt;/p&gt;
&lt;p&gt;该算法的目的是将那些还在使用单点 Redis 还有带有故障转移的主从设置的人员转移到更可靠、更安全的方法上来实现分布式锁。该方法具有非常低的复杂性和良好的性能。&lt;/p&gt;
&lt;p&gt;自从我发布 Redlock 以来，人们就以多种语言实现了该功能并将其用于不同的目的。&lt;/p&gt;
&lt;p&gt;Martin 对这个算法的分析结论是 Redlock 不安全。我在最初的 Redis 规范[3]中请求帮助分析，很高兴 Martin 发布了一个分析文章。非常感谢 Martin，尽管我不同意他的观点。好处是，与其他编程领域不同，分布式系统在数学上是非黑即白的。因此，某种算法要么可以保证给定的属性集，要么在某些假设下算法可能会无法保证它们。在此分析中，我将分析 Martin 的分析，以便该领域的其他专家可以检查这两个文档（分析和反分析），最终我们可以了解 Redlock 是否被认为是安全的。&lt;/p&gt;
&lt;h2 id="为什么-martin-认为-redlock-不安全"&gt;为什么 Martin 认为 Redlock 不安全&lt;/h2&gt;
&lt;p&gt;分析中的论点主要有两个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;具有自动释放特性的分布式锁（互斥锁属性只在锁被获取后的固定时间内有效）需要一种方法来避免客户端在过期时间后使用锁时引发的问题，这违反了访问共享资源时的互斥性。Martin 说 Redlock 没有这样的机制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Martin 说，不管问题1如何，这个算法本质上是不安全的，因为它对系统模型做出的假设在实际系统中无法得到保证。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了清楚起见，我将从第一个问题开始，分别讨论这两个问题。&lt;/p&gt;
&lt;h2 id="分布式锁自动释放和令牌"&gt;分布式锁、自动释放和令牌&lt;/h2&gt;
&lt;p&gt;没有自动释放机制（锁的拥有者将无限期地持有它）的分布式锁基本上是没用的。如果持有锁的客户端崩溃，并且在短时间内无法恢复到完全状态，就会导致死锁，因为分布式锁试图保护的共享资源永远无法访问。这就产生了一个在大多数情况下都无法接受的活性问题，所以一个正常的分布式锁必须能够自动释放自己。&lt;/p&gt;
&lt;p&gt;实际上锁提供给客户端最长的生存时间。在过期时间之后，互斥保证（锁的&lt;em&gt;主要&lt;/em&gt;属性）就没有了：另一个客户端可能已经有了锁。如果两个客户端在两个不同的时间获得锁，但第一个因为 GC 暂停或其他调度问题非常慢，会尝试与获得锁第二个客户端在共享资源的情况下同时工作吗？&lt;/p&gt;
&lt;p&gt;Martin 说，通过让分布式锁服务器为每个锁提供令牌可以避免此问题。在他的示例中，令牌只是保证始终递增的数字。Martin 使用令牌的理由是：当两个不同的客户端同时访问锁定的资源时，我们可以在数据库写入事务时使用令牌（假定可以实现客户端所做的工作）：只有具有最大锁号的客户端才可以写入数据库。&lt;/p&gt;
&lt;p&gt;用 Martin 的话来说：&lt;/p&gt;
&lt;p&gt;“这种问题的解决方案非常简单：你需要在每个对存储服务的请求中携带一个 fencing token。在此情况下，一个 fencing token 只是一个客户端获得一次锁时递增的数字（如被锁服务递增）。”&lt;/p&gt;
&lt;p&gt;&amp;hellip;&lt;/p&gt;
&lt;p&gt;“这需要存储服务在检查 token 中扮演一个重要的角色，并且拒绝任何旧 token 的所有写请求。”&lt;/p&gt;</description></item><item><title>Google File System</title><link>/article/2020/03/google-file-system/</link><pubDate>Sun, 29 Mar 2020 00:00:00 +0000</pubDate><guid>/article/2020/03/google-file-system/</guid><description>&lt;p&gt;GFS 是谷歌开发的分布式文件系统，也是上一篇 &lt;a href="../google-map-reduce/"&gt;MapReduce&lt;/a&gt; 最终输出文件的存放位置。&lt;/p&gt;
&lt;h2 id="设计概述"&gt;设计概述&lt;/h2&gt;
&lt;p&gt;以下内容简述了 GFS 的设计架构。包括系统的的细节，以及如此设计的原因。&lt;/p&gt;
&lt;h3 id="假设"&gt;假设&lt;/h3&gt;
&lt;p&gt;该系统在以下前提设计实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该系统由许多机器组成，这些机器经常会出错。所以系统必须持续检测以便能够检查、容忍错误，并且能够恢复回来。&lt;/li&gt;
&lt;li&gt;该系统支持小型文件，但是不会对其作出优化。&lt;/li&gt;
&lt;li&gt;关注性能的应用经常对一批读操作进行排序，以便提高性能。&lt;/li&gt;
&lt;li&gt;在文件写入之后，很少会再次修改。&lt;/li&gt;
&lt;li&gt;系统必须高效地处理好并发问题。&lt;/li&gt;
&lt;li&gt;高且平稳的带宽比低延迟重要。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="接口"&gt;接口&lt;/h3&gt;
&lt;p&gt;GFS 提供了和文件系统类似的接口。文件以文件路径名严格组织。除了传统的 CRUD 之外，GFS 还提供了快照和 record append。快照能高效地复制一个文件或目录，record append 用来解决多个客户端的并发问题。&lt;/p&gt;
&lt;h3 id="架构"&gt;架构&lt;/h3&gt;
&lt;p&gt;一个 GFS 集群包括一个 master 和多个 chunkserver，同时 master 可以被多个 client 访问到。如图一。
文件通常被分为固定大小的 &lt;em&gt;chunk&lt;/em&gt; 。在 chunk 被创建时，master 分配给它一个64位全局常量 &lt;em&gt;chunk handle&lt;/em&gt; 。
chunkserver 将 chunk 作为 linux 文件存储在本地磁盘，并且根据 chunk handle 和字节区间进行读写。每个 chunk 在多个 chunkserver 上都有冗余。&lt;/p&gt;
&lt;p&gt;&lt;img src="/pic/post/2020/03/google-file-system-1.png" alt="architecture"&gt;&lt;/p&gt;
&lt;p&gt;master 上保存了所有文件系统的元数据。包括命名空间，访问控制信息，文件到 chunk 的映射和 chunk 的位置。也控制像 chunk 租约，孤儿 chunk 的 GC，chunkserver 之间的 chunk 迁移等系统活动。master 还定期向 chunkserver 发送心跳包收集其状态。
client 与 master 交互来操作元数据，而对数据的操作直接与 chunkserver 通信。
client 和 chunkserver 都不缓存文件数据。一般对客户端来说，文件都太大了。而得益于 linux buffer cache，chunkserver 也不用再做缓存了（linux 会将访问的磁盘文件缓存在内存里）。&lt;/p&gt;</description></item><item><title>Google MapReduce</title><link>/article/2020/03/google-map-reduce/</link><pubDate>Fri, 20 Mar 2020 00:00:00 +0000</pubDate><guid>/article/2020/03/google-map-reduce/</guid><description>&lt;p&gt;无论是出于对分布式系统的兴趣，还是对一个未来的 Hadooper 来说，这篇论文都值得一读。号称为 Google “三驾马车”之一，同时也作为6.824 LEC 1的 preparation, 其重要性也随之体现。趁着时间充裕，抓紧读了读并尝试着手进行后面的 lab。&lt;/p&gt;
&lt;h2 id="概念"&gt;概念&lt;/h2&gt;
&lt;p&gt;Google 提出了一种 Map-Reduce 的模型。&lt;strong&gt;map 将一对键值对处理成另外一组中间键值对， reduce 将这些中间键值对中 key 相同的合并起来。&lt;/strong&gt; 系统关注的除了处理数据本身之外，还有调度机器之间程序的运行、处理机器故障以及管理机器之间的通信。&lt;/p&gt;
&lt;h2 id="编程模型"&gt;编程模型&lt;/h2&gt;
&lt;p&gt;就如前面提到的，用户编写的 Map 将所有的输入处理成中间状态的键值对，MapReduce 根据中间状态的 Key 将其整理到一起（因为数据在不同的机器上），并将其交给 Reduce。&lt;/p&gt;
&lt;p&gt;之后，同样是用户编写的 Reduce 将 Key 相同的中间键值对合并到一起。合并的数据集可能只是一小部分。&lt;/p&gt;
&lt;p&gt;下面是一个单词计数的例子：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;map (String key, String value)
 // key document name
 // value document content
 for each word w int value:
 EmitIntermediate (w, &amp;#34;1&amp;#34;);

reduce (String key, Iterator value)
 // key a word
 // values a list of counts
 int result = 0;
 for each v in values:
 result += ParseInt (v);
 Emit(AsString (result));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;map 函数将出现过的单词与其出现过的次数相关联，这里是1。 reduce 函数将相同单词出现的次数求和。&lt;/p&gt;</description></item><item><title>How to do distributed locking</title><link>/translation/2020/02/03/how-to-do-distributed-locking/</link><pubDate>Mon, 03 Feb 2020 00:00:00 +0000</pubDate><guid>/translation/2020/02/03/how-to-do-distributed-locking/</guid><description>&lt;p&gt;我在 &lt;a href="http://redis.io/"&gt;Redis&lt;/a&gt; 的官网上偶然发现了一个叫做 &lt;a href="http://redis.io/topics/distlock%5D"&gt;Redlock&lt;/a&gt; 的算法，恰好与&lt;a href="http://dataintensive.net/"&gt;我的书&lt;/a&gt;研究的一部分相关。这个算法自称能在 Redis 上实现容错的分布式锁（准确地说是&lt;a href="https://www.semanticscholar.org/paper/Leases%3A-an-efficient-fault-tolerant-mechanism-for-Gray-Cheriton/8965057405c1de742346eba16f20eaca2612f576"&gt;租约&lt;/a&gt;[1]），并向研究分布式系统的人征求意见。看见这个算法，我出于本能地在脑海里敲响了警钟，所以我花了些时间来思考并且写下了这些笔记。&lt;/p&gt;
&lt;p&gt;因为&lt;a href="https://redis.io/topics/distlock"&gt;已经有十多个 Redlock 的实现&lt;/a&gt;了，并且我们不知道谁已经使用这个算法了。所以我认为值得把我的笔记公开分享出来。我不会深入探讨 Redis 的其他部分，因为那些已经在&lt;a href="https://aphyr.com/tags/Redis"&gt;其他地方&lt;/a&gt;讨论过了。&lt;/p&gt;
&lt;p&gt;在我深入 Redlock 的细节之前，首先要声明我十分喜欢 Redis，并且已经在生产环境中成功地使用过它了。我认为它非常适合在服务器之间共享一些频繁变化的数据，而且偶尔丢失部分数据也无关紧要。例如，维护每个 IP 地址的请求计数器（出于限制的目的）和每个用户 ID 的不同 IP 地址集（用于滥用的检测）。&lt;/p&gt;
&lt;p&gt;然而，Redis 已经逐渐进入有强一致性和持久性特点的数据管理领域，这让我很担心，因为这不是 Redis 设计的初衷。有争议的是，分布式锁也属于这一领域。让我们更详细地研究一下。&lt;/p&gt;
&lt;h2 id="你用锁来干什么"&gt;你用锁来干什么&lt;/h2&gt;
&lt;p&gt;锁的目的是用来保证当多个节点尝试做相同的工作时，实际上只有一个节点能够完成（至少在同一时刻只有一个）。这个工作可能是将一些数据写入一个共享的存储系统、完成一些计算、调用一些外部的 API 或者其他类似的工作。宏观上来看，有两个原因来说明为什么在一个分布式应用中需要一个锁：&lt;a href="http://research.google.com/archive/chubby.html"&gt;效率和正确性&lt;/a&gt;[2]。为了区别这些情况，可以继续往下看当锁失效的时候会发生什么：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;效率&lt;/strong&gt;：使用锁可以避免做不必要的重复工作（例如一些代价昂贵的计算）。如果锁失效时两个节点做相同的工作，结果会是成本略有增加（最后要比其他方式多给 AWS 支付5美分）或带来小小的不便（用户可能会收到两个相同的邮件通知）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正确性&lt;/strong&gt;：使用锁可以避免并发进程相互影响最终搞乱系统的状态。如果锁失效时两个节点同时处理同一部分数据，结果可能造成文件损坏、数据丢失、永久的不一致性、给病人服用了错误剂量的药物或其他严重的问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上两点都是需要锁的有效情况，但是你需要十分明确你是在处理哪一种情况。&lt;/p&gt;
&lt;p&gt;我的观点是：如果只是出于提高效率的目的，那么没有必要承担 Redlock 的成本和复杂的逻辑，你没必要运行5台 Redis 实例来检查是否有大多数都获得了锁。你最好只启动一个 Redis 实例，或是当主节点崩溃时异步复制到从节点。&lt;/p&gt;
&lt;p&gt;如果你使用单个 Redis 实例，在 Redis 节点断电时当然会丢失一些锁，同时可能会出现一些问题。但是如果你使用这些锁来优化效率的话，其实节点的崩溃不会频繁发生，这也没什么大不了。这个“没什么大不了”的正是 Redis 的特点。至少当你依赖于一个 Redis 实例的时候，每个查看系统的人都心知肚明这些锁不太严谨，仅用于不太关键的目的。&lt;/p&gt;
&lt;p&gt;另一方面，具有5个副本和多数投票的 Redis 算法乍一看似乎适用于对&lt;strong&gt;正确性&lt;/strong&gt;要求很高的情况。在接下来的几个章节中我将论证它&lt;strong&gt;不&lt;/strong&gt;适用于这个目的。文章的其余部分假定你的锁对保障系统的正确性至关重要，并且两个节点持有同一个锁是严重的错误。&lt;/p&gt;
&lt;h2 id="使用锁来保护资源"&gt;使用锁来保护资源&lt;/h2&gt;
&lt;p&gt;先把 Redlock 的具体应用放在一边，讨论一下通常一个分布式锁是如何使用的（不依赖于某个加锁算法）。要牢记住分布式系统中的锁和多线程应用中的互斥量不一样，这一点很重要。由于不同的节点和网络都能以各种奇葩的方式发生故障，所以它更让人觉得可怕。&lt;/p&gt;
&lt;p&gt;假如你有一个应用，其中客户端需要更新共享存储中的文件（如 HDFS 或 S3）。一个客户端首先获得一个锁，然后读取这个文件，作出一个修改，将改动写回，最终释放这个锁。这个锁避免两个客户端并发执行读-修改-写这一系列动作以防丢失更新。代码可能像下面这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// THIS CODE IS BROKEN
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;function writeData(filename, &lt;span style="color:#66d9ef"&gt;data&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; lock = lockService.acquireLock(filename);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!lock) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Failed to acquire lock&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; file = storage.readFile(filename);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; updated = updateContents(&lt;span style="color:#66d9ef"&gt;file&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;data&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; storage.writeFile(filename, updated);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;finally&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lock.release();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不幸的是，即使你写的锁看起来非常完美，上面的代码也不是安全的。下面的图展示了数据是怎么被破坏的：&lt;/p&gt;</description></item></channel></rss>