<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Translations on arjenzhou</title><link>/translation/</link><description>Recent content in Translations on arjenzhou</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sat, 21 Nov 2020 00:00:00 +0000</lastBuildDate><atom:link href="/translation/feed.xml" rel="self" type="application/rss+xml"/><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="摘要">摘要&lt;/h2>
&lt;p>分布式一致性是构建具有容错、强一致性特点的分布式系统的重要原语。在众多分布式公式算法中，其中有两个统治着生产系统：经典、也是以晦涩难懂著称的 Paxos；较新的、与 Paxos 相比更易理解的 Raft。&lt;/p>
&lt;p>本文主要研究 Paxos 和 Raft 那个是更好的分布式一致性解决方案。我们通过使用 Raft 的术语和语用抽象来描述简化的 Paxos 算法，从而对两者进行精确分析，以确定它们之间的区别。&lt;/p>
&lt;p>我们发现 Paxos 和 Raft 都采用相似的方式来达成分布式一致性，它们之间的区别只在于 leader 选举。最值得注意的是，Raft 只允许有最新日志的节点成为 leader，而 Paxos 允许任何节点成为 leader，前提是之后更新其日志来保证为最新。对于这样的简单性来时，Raft 的方法出奇的高效，因为与 Paxos 不同，它在 leader 选举时不需要日志条目的交换。我们推测，Raft 的可理解性大多来自于论文的清楚陈述，而不是展现出的基本算法的基础。&lt;/p>
&lt;p>&lt;strong>&lt;em>CCS 概念&lt;/em>：计算机系统管理→可靠性；软件及其工程→云计算；计算理论→分布式算法&lt;/strong>&lt;/p>
&lt;p>&lt;em>&lt;strong>关键词&lt;/strong>&lt;/em>：状态机复制，分布式一致性，Paxos，Raft&lt;/p>
&lt;h2 id="1-简介">1 简介&lt;/h2>
&lt;p>状态机复制[32]广泛用于将一群不可靠的主机组成一个可靠的能够提供强一致性保证包括线性化[13]的服务。据此，程序员能够通过复制状态机将服务看作一个单点系统，从而轻松推断出预期的表现。状态机复制需要每个状态机以相同的顺序接受相同的操作，这可以通过分布式一致性实现。&lt;/p>
&lt;p>Paxos 算法[16]与分布式一致性关系密切。虽然它很成功，但是 Paxos 晦涩难懂，造成了其难以推理、正确实现和安全优化。这已经在无数次用更简单的术语解释算法的尝试[4, 17, 22, 23, 25, 29, 35]中得到明显体现了，而且这也是 Raft[28] 的目的。&lt;/p>
&lt;p>Raft 的作者声称 Raft 与 Paxos 同样高效而更易懂，因此为实现具体系统提供了更好的基础。Raft 以这三种不同的方式来寻求实现这点：&lt;/p>
&lt;p>&lt;strong>演示&lt;/strong> 首先，Raft 论文引入了一个描述在状态机复制上下文中基于 leader 选举的一致性的新抽象。 事实证明，这种务实的演示在工程师中非常受欢迎。&lt;/p>
&lt;p>&lt;strong>简单&lt;/strong> 其次，Raft 算法的优先考虑简洁性而不是性能。例如，Raft 按顺序决定日志条目而 Paxos 允许乱序决定，但需要额外的协议来填充可能出现的日志空隙。&lt;/p></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>分布式系统研究者 Martin Kleppmann 昨天发布了一篇 Redlock[1]的分析文章[2]。&lt;/p>
&lt;p>Redlock 是我设计用于 Redis 的客户端分布式锁定算法。该算法编排了一组客户端节点，这些节点实现了具有特定功能的数据存储，以便创建一个多主容错的、安全的、具有自动释放功能的分布式锁。
你可以使用 MySQL 而非 Redis 来实现 Redlock。&lt;/p>
&lt;p>该算法的目的是将那些还在使用单点 Redis 还有带有故障转移的主从设置的人员转移到更可靠、更安全的方法上来实现分布式锁。该方法具有非常低的复杂性和良好的性能。&lt;/p>
&lt;p>自从我发布 Redlock 以来，人们就以多种语言实现了该功能并将其用于不同的目的。&lt;/p>
&lt;p>Martin 对这个算法的分析结论是 Redlock 不安全。我在最初的 Redis 规范[3]中请求帮助分析，很高兴 Martin 发布了一个分析文章。非常感谢 Martin，尽管我不同意他的观点。好处是，与其他编程领域不同，分布式系统在数学上是非黑即白的。因此，某种算法要么可以保证给定的属性集，要么在某些假设下算法可能会无法保证它们。在此分析中，我将分析 Martin 的分析，以便该领域的其他专家可以检查这两个文档（分析和反分析），最终我们可以了解 Redlock 是否被认为是安全的。&lt;/p>
&lt;h2 id="为什么-martin-认为-redlock-不安全">为什么 Martin 认为 Redlock 不安全&lt;/h2>
&lt;p>分析中的论点主要有两个：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>具有自动释放特性的分布式锁（互斥锁属性只在锁被获取后的固定时间内有效）需要一种方法来避免客户端在过期时间后使用锁时引发的问题，这违反了访问共享资源时的互斥性。Martin 说 Redlock 没有这样的机制。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Martin 说，不管问题1如何，这个算法本质上是不安全的，因为它对系统模型做出的假设在实际系统中无法得到保证。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>为了清楚起见，我将从第一个问题开始，分别讨论这两个问题。&lt;/p>
&lt;h2 id="分布式锁自动释放和令牌">分布式锁、自动释放和令牌&lt;/h2>
&lt;p>没有自动释放机制（锁的拥有者将无限期地持有它）的分布式锁基本上是没用的。如果持有锁的客户端崩溃，并且在短时间内无法恢复到完全状态，就会导致死锁，因为分布式锁试图保护的共享资源永远无法访问。这就产生了一个在大多数情况下都无法接受的活性问题，所以一个正常的分布式锁必须能够自动释放自己。&lt;/p>
&lt;p>实际上锁提供给客户端最长的生存时间。在过期时间之后，互斥保证（锁的&lt;em>主要&lt;/em>属性）就没有了：另一个客户端可能已经有了锁。如果两个客户端在两个不同的时间获得锁，但第一个因为 GC 暂停或其他调度问题非常慢，会尝试与获得锁第二个客户端在共享资源的情况下同时工作吗？&lt;/p>
&lt;p>Martin 说，通过让分布式锁服务器为每个锁提供令牌可以避免此问题。在他的示例中，令牌只是保证始终递增的数字。Martin 使用令牌的理由是：当两个不同的客户端同时访问锁定的资源时，我们可以在数据库写入事务时使用令牌（假定可以实现客户端所做的工作）：只有具有最大锁号的客户端才可以写入数据库。&lt;/p>
&lt;p>用 Martin 的话来说：&lt;/p>
&lt;p>“这种问题的解决方案非常简单：你需要在每个对存储服务的请求中携带一个 fencing token。在此情况下，一个 fencing token 只是一个客户端获得一次锁时递增的数字（如被锁服务递增）。”&lt;/p>
&lt;p>&amp;hellip;&lt;/p>
&lt;p>“这需要存储服务在检查 token 中扮演一个重要的角色，并且拒绝任何旧 token 的所有写请求。”&lt;/p></description></item><item><title>English has been my pain for 15 years</title><link>/translation/2020/02/04/english-has-been-my-pain-for-15-years/</link><pubDate>Tue, 04 Feb 2020 00:00:00 +0000</pubDate><guid>/translation/2020/02/04/english-has-been-my-pain-for-15-years/</guid><description>&lt;p>Paul Graham 提出了一个很重要的问题：英语作为 IT 工作者的必备语言之一，始终受新闻站点和软件开发者的关注[1]。当他提到“外国口音”时引起了很大的争议，因为互联网上到处都是看热闹不嫌事大的人，但这是问题中最无聊的部分，因此我将跳过这一部分。重要的是，通常没有人会谈论“英语问题”，而一想到这点我总是感到有点孤独，就像是这是一个只影响我一个人的问题似的，所以我想在这篇文章中分享我有关学习英语的经历。&lt;/p>
&lt;h2 id="一个长故事">一个长故事&lt;/h2>
&lt;p>回到1998年，我一直记得当时我们正在研究一个网络攻击，我和 &lt;a href="http://www.isg.rhul.ac.uk/sullivan/">sullivan&lt;/a> 在我米兰的家中喝醉了之后，把我们得到的可怜的结果发布在了 &lt;a href="http://seclists.org/bugtraq/1998/Dec/79">BUGTRAQ 用户能够理解的帖子&lt;/a>中。&lt;/p>
&lt;p>请注意第二句中的 “Instead all others”。虽然现在我的英语水平也不高，但是我确实在15年中有所进步，而且 sullivan 现在在美国和英国的大学任教，所以我认为他有一口非常流利的英语（剧透警告：我并没有）。但是重点是：我们正在进行研究新的 TCP/IP 攻击，但是我们无法用英语撰写关于这个技术的文章。在1998年，我无法沟通这一事实就已经使我感到举步维艰，如果我不花很多精力的话就无法阅读用英语撰写的技术文档，所以我用了大脑50％的精力的单纯用来读，而剩下的精力用来理解我正在读什么。&lt;/p>
&lt;p>但是我一直认为英语是个好东西。我总建议人们不要翻译技术的主题，因为我认为有一个通用的语言来注释源码会更好，并且实际上对于大多数人来说，理解用英语写的技术文档非常简单。&lt;/p>
&lt;p>因此，从1998年开始，我逐渐学会了流利地阅读英语，与阅读意大利语相比无需付出更多的努力。
我甚至能够用意大利语写东西的速度来写英语，虽然这只达到了最低的标准，就如你在阅读这篇文章时所看到的：基本上，我学会了非常快地写一小段英语，虽然通常不足以表达我在编程领域的想法，但是写一般的主题已经足够了。我不知道大多数厨房里的物件对应的单词，也不会表达复杂句子、假设结构的语法。但是现在，我可以轻松地就自己最关心的主题进行交流，并且其他人可以或多或少地理解我写的内容，因此需要提高英语的压力大大减轻了……但是，我最近发现，这只是我遇到的次要问题。&lt;/p>
&lt;h2 id="欧式英语有趣的语言">欧式英语，有趣的语言&lt;/h2>
&lt;p>尽管我最终能够在写作和阅读方面达到自己的要求，但是我几乎从未在一个讲英语的国家体验过真正的交流。在此之前，我总是与其他欧洲（除了英国）人一起使用英语交流，例如法国，德国和西班牙人。&lt;/p>
&lt;p>现在这些国家/地区使用的英语是在英语学校上课时使用的英语&amp;hellip;从语音上讲，它几乎与美国或英国英语无关。他们说这是“BBC英语”，但实际上不是。这是使用英国英语语法而在语音上大大简化的英语。&lt;/p>
&lt;p>&lt;strong>那个&lt;/strong>版本的英语，实际上能让世界各地的人们都可以轻松交流。基本语法很容易掌握，几个月的练习后就可以进行交谈。在欧洲所有非英语国家中，单词的发音几乎相同，因此效果很好。&lt;/p>
&lt;p>只有一个问题，它与在英国，美国，加拿大和其他以英语为母语的国家/地区所说的英语一点关系也没有。&lt;/p>
&lt;h2 id="毕竟英语有点蹩脚">毕竟，英语有点蹩脚&lt;/h2>
&lt;p>现在我要告诉你们一个秘密，除了没有人在英语与世界对比的语境中说：从语音上讲，英语是一种支离破碎的语言，其他的都不是秘密[2]。在意大利，我们历史悠久，但政治统一很晚。不同地区使用不同的方言，人们的口音非常重。在1950年“电视语言统一”之前，每个人都在说“方言”，而意大利语只被一小部分人掌握。&lt;a href="http://en.wikipedia.org/wiki/Sicilian_language">西西里语&lt;/a>是我们家族说得最多的语言，它比意大利语要早几个世纪。&lt;/p>
&lt;p>尽管如此，所有人都能听得懂另一个地区甚至是瑞士人说的话。意大利语在语音上是世界上最简单的语言之一，而且充满了冗余。事实上，它的信息熵很低，通常单词很长，每个单词中都有辅音和声母的很好的混合。一个单词的发音没有特别的规则，如果你知道每个字母的发音和一些特殊的字母组合的声音，比如“gl&lt;vocal>”，“sc&lt;vocal>”，你第一次读它们基本上就可以99.9%地正确发音。&lt;/p>
&lt;p>来自不同英语国家的人在交流方面存在问题，这一点已经充分说明了英语语音有多么奇怪。例如，对我和其他许多非英语母语人士来说，很难理解一个英国人到底在说些什么，而听懂北美人通常要简单得多。&lt;/p>
&lt;p>对我来说，正是由于英语的这种“特点”，我的问题不仅仅在于我的口音，而是能够理解人们在说什么。如果我付出足够的努力，口音根本不算是个问题。恕我直言，Paul Graham 提到的“口音”问题是英美人在这方面的一种消极态度，嗨，伙计们，你不了解我们，我们也听不懂你说的话，很难找到“只要你的理解力很有限，就会试图减慢对话的速度”这样的人。即使我说我听不懂，他们也会以光速的重复同样的话。&lt;/p>
&lt;h2 id="第一次接触书面英语是致命的">第一次接触书面英语是致命的&lt;/h2>
&lt;p>在我看来，学习英语如此缓慢的一个原因是从未听过英语就开始阅读英语。我的大脑充满了文字和有趣的声音之间的关联，而这些关联在实际语言中是不存在的。我的建议是，如果你现在正在学习英语，尽快开始听英语口语。&lt;/p>
&lt;p>osx 的 “say” 程序是一个很好的助手，它能够以一种合适的方式说出大多数英语单词。学习一个新单词一定要先学习它的发音。&lt;/p>
&lt;h2 id="内向还是外向">内向还是外向？&lt;/h2>
&lt;p>在我的英语学习经历中，最令我震惊的一件事是，不精通一门语言会让你变成一个内向的人。我是一个性格外向的人，在意大利那里大多数人都是性格外向的，在西西里岛那里有更多的性格外向的人，在我的家庭里大部分人都是性格外向的人。我认为我是一个有点惹人注意的人（我希望我不是，但是实际上我是一个非常外向的人）。现在由于沟通障碍，当我必须用英语交谈时，我就不是一个外向的人了，每次我去开会或被介绍给别人时，我都会感到后悔。这是一场噩梦。&lt;/p>
&lt;h2 id="为时已晚让我们学习英语吧">为时已晚，让我们学习英语吧&lt;/h2>
&lt;p>我认为英语只是语法上简单，但作为通用语言不是好的选择。但是事实是它已经赢了，已经改变不了了，更好地讲英语是一个好主意，即使这意味着要付出很多努力。这就是我自己在做的事，我正在努力改变。&lt;/p>
&lt;p>我发现自己确实需要提高英语水平的另一个原因是，十年后，我可能不再会专业编写代码，而合理的选择是转换到 IT 的管理方面，或者去掌管不用写很多代码的大型项目。如果你认为作为开发人员需要会英语的话，那么即使在传统的 IT 公司的其他部门工作，也需要会更多的英语，即使“只是”要管理许多程序员。&lt;/p>
&lt;p>但是以英语为母语的人应该真正意识到，很多人正在认真学习一种难学的语言：这不是一种爱好，掌握英语是很多人为简化沟通作出的很大努力。只要停止交谈/聆听几周英语，就要重新学了。&lt;/p>
&lt;p>我的长期愿望是，不同的口音迟早会融合成一种标准的、易于理解的口音，让说英语的人可以把它作为一种通用语言。&lt;/p>
&lt;hr>
&lt;p>[1] &lt;a href="http://paulgraham.com/accents.html">http://paulgraham.com/accents.html&lt;/a>&lt;br>
[2] Now I&amp;rsquo;ve a secret for you, that is everything but a secret except nobody says it in the context of English VS The World: English is a broken language, phonetically.&lt;/p></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>我在 &lt;a href="http://redis.io/">Redis&lt;/a> 的官网上偶然发现了一个叫做 &lt;a href="http://redis.io/topics/distlock%5D">Redlock&lt;/a> 的算法，恰好与&lt;a href="http://dataintensive.net/">我的书&lt;/a>研究的一部分相关。这个算法自称能在 Redis 上实现容错的分布式锁（准确地说是&lt;a href="https://www.semanticscholar.org/paper/Leases%3A-an-efficient-fault-tolerant-mechanism-for-Gray-Cheriton/8965057405c1de742346eba16f20eaca2612f576">租约&lt;/a>[1]），并向研究分布式系统的人征求意见。看见这个算法，我出于本能地在脑海里敲响了警钟，所以我花了些时间来思考并且写下了这些笔记。&lt;/p>
&lt;p>因为&lt;a href="https://redis.io/topics/distlock">已经有十多个 Redlock 的实现&lt;/a>了，并且我们不知道谁已经使用这个算法了。所以我认为值得把我的笔记公开分享出来。我不会深入探讨 Redis 的其他部分，因为那些已经在&lt;a href="https://aphyr.com/tags/Redis">其他地方&lt;/a>讨论过了。&lt;/p>
&lt;p>在我深入 Redlock 的细节之前，首先要声明我十分喜欢 Redis，并且已经在生产环境中成功地使用过它了。我认为它非常适合在服务器之间共享一些频繁变化的数据，而且偶尔丢失部分数据也无关紧要。例如，维护每个 IP 地址的请求计数器（出于限制的目的）和每个用户 ID 的不同 IP 地址集（用于滥用的检测）。&lt;/p>
&lt;p>然而，Redis 已经逐渐进入有强一致性和持久性特点的数据管理领域，这让我很担心，因为这不是 Redis 设计的初衷。有争议的是，分布式锁也属于这一领域。让我们更详细地研究一下。&lt;/p>
&lt;h2 id="你用锁来干什么">你用锁来干什么&lt;/h2>
&lt;p>锁的目的是用来保证当多个节点尝试做相同的工作时，实际上只有一个节点能够完成（至少在同一时刻只有一个）。这个工作可能是将一些数据写入一个共享的存储系统、完成一些计算、调用一些外部的 API 或者其他类似的工作。宏观上来看，有两个原因来说明为什么在一个分布式应用中需要一个锁：&lt;a href="http://research.google.com/archive/chubby.html">效率和正确性&lt;/a>[2]。为了区别这些情况，可以继续往下看当锁失效的时候会发生什么：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>效率&lt;/strong>：使用锁可以避免做不必要的重复工作（例如一些代价昂贵的计算）。如果锁失效时两个节点做相同的工作，结果会是成本略有增加（最后要比其他方式多给 AWS 支付5美分）或带来小小的不便（用户可能会收到两个相同的邮件通知）。&lt;/li>
&lt;li>&lt;strong>正确性&lt;/strong>：使用锁可以避免并发进程相互影响最终搞乱系统的状态。如果锁失效时两个节点同时处理同一部分数据，结果可能造成文件损坏、数据丢失、永久的不一致性、给病人服用了错误剂量的药物或其他严重的问题。&lt;/li>
&lt;/ul>
&lt;p>以上两点都是需要锁的有效情况，但是你需要十分明确你是在处理哪一种情况。&lt;/p>
&lt;p>我的观点是：如果只是出于提高效率的目的，那么没有必要承担 Redlock 的成本和复杂的逻辑，你没必要运行5台 Redis 实例来检查是否有大多数都获得了锁。你最好只启动一个 Redis 实例，或是当主节点崩溃时异步复制到从节点。&lt;/p>
&lt;p>如果你使用单个 Redis 实例，在 Redis 节点断电时当然会丢失一些锁，同时可能会出现一些问题。但是如果你使用这些锁来优化效率的话，其实节点的崩溃不会频繁发生，这也没什么大不了。这个“没什么大不了”的正是 Redis 的特点。至少当你依赖于一个 Redis 实例的时候，每个查看系统的人都心知肚明这些锁不太严谨，仅用于不太关键的目的。&lt;/p>
&lt;p>另一方面，具有5个副本和多数投票的 Redis 算法乍一看似乎适用于对&lt;strong>正确性&lt;/strong>要求很高的情况。在接下来的几个章节中我将论证它&lt;strong>不&lt;/strong>适用于这个目的。文章的其余部分假定你的锁对保障系统的正确性至关重要，并且两个节点持有同一个锁是严重的错误。&lt;/p>
&lt;h2 id="使用锁来保护资源">使用锁来保护资源&lt;/h2>
&lt;p>先把 Redlock 的具体应用放在一边，讨论一下通常一个分布式锁是如何使用的（不依赖于某个加锁算法）。要牢记住分布式系统中的锁和多线程应用中的互斥量不一样，这一点很重要。由于不同的节点和网络都能以各种奇葩的方式发生故障，所以它更让人觉得可怕。&lt;/p>
&lt;p>假如你有一个应用，其中客户端需要更新共享存储中的文件（如 HDFS 或 S3）。一个客户端首先获得一个锁，然后读取这个文件，作出一个修改，将改动写回，最终释放这个锁。这个锁避免两个客户端并发执行读-修改-写这一系列动作以防丢失更新。代码可能像下面这样：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-kotlin" data-lang="kotlin">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// THIS CODE IS BROKEN
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>function writeData(filename, &lt;span style="color:#66d9ef">data&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> lock = lockService.acquireLock(filename);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (!lock) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">throw&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Failed to acquire lock&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> file = storage.readFile(filename);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> updated = updateContents(&lt;span style="color:#66d9ef">file&lt;/span>, &lt;span style="color:#66d9ef">data&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> storage.writeFile(filename, updated);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">finally&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lock.release();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不幸的是，即使你写的锁看起来非常完美，上面的代码也不是安全的。下面的图展示了数据是怎么被破坏的：&lt;/p></description></item></channel></rss>