From babdb43b2dd8551a287cb772ea45edc228eee219 Mon Sep 17 00:00:00 2001 From: cjc7373 Date: Sat, 1 Jun 2024 17:11:43 +0000 Subject: [PATCH] deploy: bffdd555d5063609fa3c35ca01cabe2425539843 --- index.html | 2 +- index.xml | 20 +++++++++++++------ page/2/index.html | 2 +- page/3/index.html | 2 +- page/4/index.html | 2 +- page/4/index.xml | 2 +- page/5/index.html | 2 +- .../domjudge-config/index.html | 2 +- .../git_\345\205\245\351\227\250/index.html" | 2 +- posts/2023/mit_6.824_2_raft/index.html | 3 ++- posts/2024/japan_trip/index.html | 2 +- posts/index.xml | 12 ++++++----- sitemap.xml | 2 +- tags/distributed-system/index.xml | 10 ++++++---- tags/index.xml | 2 +- 15 files changed, 40 insertions(+), 27 deletions(-) diff --git a/index.html b/index.html index a4dfec2..93aa739 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ -Coherence's Blog +Coherence's Blog
存档 标签 分类 diff --git a/index.xml b/index.xml index 34bfe6e..d3b60c0 100644 --- a/index.xml +++ b/index.xml @@ -120,7 +120,7 @@ <p>(东京台场附近的一列车)</p> <p><img src="./image-20240427021228647.png" alt="image-20240427021228647"></p> <p>(大阪梅田附近的一条铁路)</p> -<p>第一次坐 toei subway 的线路时感觉非常迷惑,地铁居然还分车的类型?还开往不同的终点站?一列车还能同时运行在两条线路上?到后来就习惯了。</p> +<p>第一次坐 toei subway 的线路时感觉非常迷惑,地铁居然还分车的类型?还开往不同的终点站?一列车还能同时运行在两条线路上(即<a href="https://zh.wikipedia.org/zh-cn/%E7%9B%B4%E9%80%9A%E9%81%8B%E8%A1%8C">贯通运营</a>)?到后来就习惯了。</p> <p><img src="./image-20240424014310226.png" alt="image-20240424014310226"></p> <p>日本每条线路都会有一个或两个英文字母的编号,每个站有个数字编号,所以看 Google Maps 的时候只需要看比如乘坐 T(Tozai Line) T02-&gt;T03 换乘 JY(Yamanote Line) 到 JY20,而不需要去记站名了。但是 Google Maps 只对东京市内铁路有这个提示,其他铁路只会写线路编号,而没有站名编号了。</p> <p>常开式的闸机也是好文明!就在不久前,<a href="https://mp.weixin.qq.com/s/iR9kZtepYqPwEkyct5NWCg">上海地铁也开始试点</a>这种模式了,真就学习国外先进经验了。</p> @@ -164,6 +164,9 @@ <p>(7-11 门口贴的支付方式一览)</p> <p>在米原吃饭的时候,Moo 想用微信支付结账,店员还操作了好一会(他们那块牌牌上写了支持微信支付),显然是第一次用(<del>有种县城小饭店刷信用卡的感觉</del>)。</p> </li> +<li> +<p>关于垃圾分类,日本的垃圾分类感觉没有一个统一的标准,每个城市甚至每个行政区都有细微的差别。另一个直观的感受是在街上见不到垃圾桶,能见到的也是只能扔瓶子的垃圾桶,到最后我们的垃圾基本只能去便利店扔。</p> +</li> </ul> - https://blog.coherence.codes/posts/2024/japan_trip/ - Coherence. 本站遵循 CC BY-NC-SA 4.0 协议Go 包管理(一)入门和设计原则https://blog.coherence.codes/posts/2024/go_pkg_mgmt_1_toturial/Sat, 06 Jan 2024 00:00:00 +0000https://blog.coherence.codes/posts/2024/go_pkg_mgmt_1_toturial/Coherence's Blog https://blog.coherence.codes/posts/2024/go_pkg_mgmt_1_toturial/ -<p>在我初学 Go 的时候,曾被网上的过时教程和各种对 <code>$GOPATH</code> 的操作搞得云里雾里,而现在我们已经基本用不到 <code>$GOPATH</code> 了,因为在 2023 年, Go Modules 已经一统天下了。但是在这之前,是群魔乱舞的时代,对这段历史感兴趣的同学可以参考<a href="https://blog.wolfogre.com/posts/golang-package-history/">这篇博客</a>。</p> <p>顺便提一下,GOPATH 时代包没有“版本”的概念,这可能是因为 Google 内部采用 monorepo 的方式(即所有代码都放在一个仓库中)管理代码,所有人都基于 HEAD 来 build,所以当有人的改动 break 了其他人的代码时,很容易在 build 时反映出来。Go 作者之一 Rob Pike 的文章 <a href="https://commandcenter.blogspot.com/2024/01/what-we-got-right-what-we-got-wrong.html">What We Got Right, What We Got Wrong</a> 中提到了这一点。</p> @@ -298,11 +301,16 @@ <p>由于 vlc/chromium 同样实现 mpris 接口,所以上面观察到的现象基本可以确认也是 kdeconnect 导致的了。</p> <h2 id="怎么办呢">怎么办呢?</h2> <p>目前的 workaround 是关闭 KDE Connect 中的 Multimedia control receiver 插件,drawback 是其他设备无法再控制本机的媒体播放器了,不过我也不怎么用,问题不大。</p> -- https://blog.coherence.codes/posts/2023/mpv_debug/ - Coherence. 本站遵循 CC BY-NC-SA 4.0 协议MIT 6.824 学习笔记(二) Rafthttps://blog.coherence.codes/posts/2023/mit_6.824_2_raft/Sun, 01 Oct 2023 00:00:00 +0000https://blog.coherence.codes/posts/2023/mit_6.824_2_raft/Coherence's Blog https://blog.coherence.codes/posts/2023/mit_6.824_2_raft/ -<blockquote> -<p>Note: 为了保持准确性,我会尽量使用英文术语。</p> -</blockquote> +- https://blog.coherence.codes/posts/2023/mpv_debug/ - Coherence. 本站遵循 CC BY-NC-SA 4.0 协议MIT 6.824 学习笔记(二) Rafthttps://blog.coherence.codes/posts/2023/mit_6.824_2_raft/Sun, 01 Oct 2023 00:00:00 +0000https://blog.coherence.codes/posts/2023/mit_6.824_2_raft/Coherence's Blog https://blog.coherence.codes/posts/2023/mit_6.824_2_raft/ -<p>本文主要是对 Raft 论文的翻译,为了保持准确性,我会尽量使用英文术语。</p> <h2 id="introduction">Introduction</h2> -<p>在过去十年, Leslie Lamport 的 Paxos 几乎成了共识的同义词.. Paxos 首先定义了一种协议来对单个决定达成共识, 比如一条单个的 log entry, 这被称为 single-decree Paxos. 其支持多个决定的版本 (比如 log) 被称为 muti-Paxos。然而,Paxos 的缺点是难以理解,并且没有提供一个良好的基础来构建可行的实现。</p> +<p>在过去十年,Leslie Lamport 的 Paxos 协议几乎成为了共识的同义词。Paxos 首先定义了一种协议来对单个决定达成共识, 比如一条单个的 log entry, 这被称为 single-decree Paxos。 其支持多个决定的版本 (比如 log) 被称为 muti-Paxos。然而,Paxos 的缺点是难以理解,并且没有提供一个良好的基础来构建可行的实现。</p> +<blockquote> +<p>试图为这个主题增添一点幽默的尝试以惨淡的失败告终。……这个希腊寓言显然使阅读论文的人们分心了,以致于他们无法理解这个算法。我把论文发给了一些人,其中包括 Nancy Lynch, Vassos Hadzilacos 和 Phil Bernstein,他们声称读过了论文。几个月后我发邮件给他们问了如下问题:</p> +<pre><code>你能否实现一个分布式数据库,它能容忍任何进程的故障(可能是所有进程)而不牺牲一致性,并且在超过半数进程恢复之后继续正常工作? +</code></pre> +<p>没有人察觉到这个问题和 Paxos 算法之间有任何联系。</p> +<p>—— Leslie Lamport <a href="https://lamport.azurewebsites.net/pubs/pubs.html?from=https://research.microsoft.com/users/lamport/pubs/pubs.html&amp;type=path#lamport-paxos">对 The Part-Time Parliament 的评论</a></p> +</blockquote> <p>相较于 Paxos,Raft 的目标是易于理解且符合直觉。为了使 Raft 易于理解,作者采取了解耦 (Raft 将共识问题分解成几个子问题 leader election, log replication, safety, and membership changes) 和缩减状态空间的方式。</p> <p>Raft 和已有的共识算法类似(尤其是 Viewstamped Replication),但它有一些新特性。Raft 采取了强 leader 的设计,例如 log entry 只会从 leader 向其他节点分发。这可能是为了性能考虑(比无 leader 要更快,RPC 也更少)Raft 采用基于随机计时器的 leader 选举, 从而用一种简单的方法来解决冲突。另外还有处理成员变更方面的改进。</p> <p>为了解决单点故障问题,Raft 采用了 Majority Vote,基本上任何操作都需要得到多数确认才能够执行。为了避免 tie,Raft 的节点数量必须为奇数。因此在一个 2f+1 个节点的系统中,最多允许 f 个节点故障,f+1 即为法定人数(quorum)。</p> @@ -519,7 +527,7 @@ </span></span><span style="display:flex;"><span>Turn off this advice by setting config variable advice.detachedHead to false </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>HEAD is now at f25ea8916 fix: Add CHANGELOG link -</span></span></code></pre></div><p>在这种状态作出的 commits 将成为 dangleing commits,因为没有 refs 能够间接引用到它们。我们可以在 <code>git reflog</code> 中看到它们,但它们在一段时间后可能会被 git 垃圾回收掉。如果因为误操作“丢失”了一些 commit,大部分情况下只是没有 ref 能够间接引用它们了,所以我们能在 reflog 中找到并恢复它们。</p> +</span></span></code></pre></div><p>在这种状态作出的 commits 将成为 dangling commits,因为没有 refs 能够间接引用到它们。我们可以在 <code>git reflog</code> 中看到它们,但它们在一段时间后可能会被 git 垃圾回收掉。如果因为误操作“丢失”了一些 commit,大部分情况下只是没有 ref 能够间接引用它们了,所以我们能在 reflog 中找到并恢复它们。</p> <p>除了分支之外,另一种 ref 是 tag。tag 被存储在 <code>.git/refs/tags</code> 中,和 branch 不同的是,tag 是不可变的。git 中有两种类型的 tag:</p> <ul> <li> diff --git a/page/2/index.html b/page/2/index.html index c582d99..3b4bc91 100644 --- a/page/2/index.html +++ b/page/2/index.html @@ -1,4 +1,4 @@ -Coherence's Blog +Coherence's Blog
存档 标签 分类 diff --git a/page/3/index.html b/page/3/index.html index 93173e0..68d715d 100644 --- a/page/3/index.html +++ b/page/3/index.html @@ -1,4 +1,4 @@ -Coherence's Blog +Coherence's Blog
存档 标签 分类 diff --git a/page/4/index.html b/page/4/index.html index 05672f8..d80fcf9 100644 --- a/page/4/index.html +++ b/page/4/index.html @@ -1,4 +1,4 @@ -Coherence's Blog +Coherence's Blog
存档 标签 分类 diff --git a/page/4/index.xml b/page/4/index.xml index 5bc78e3..cfeffa7 100644 --- a/page/4/index.xml +++ b/page/4/index.xml @@ -439,7 +439,7 @@ account_type fullname username password <p>这台服务器的网络结构比较复杂,首先,他是Windows Server,通过一个软件把某些端口的流量转发至一台Debian虚拟机中(为了方便管理证书等),然后由Debian上的Nginx反代至Domjudge的虚拟机中。这样我自己就不用配置证书也能有HTTPS。</p> <p>但是Windows有一个老生常谈的坑:时间问题。Windows的硬件时钟是本地时间,而Linux为UTC时间,这导致了一开始Domjudge里的时间是假的,需要手动设置。</p> <p>本次Domjudge的配置基本与之前相同,用Docker-compose一键部署。</p> -<p>之前一直没有做过压力测试一直是我的遗憾,这次在研究了Domjudge的<a href="https://www.domjudge.org/demoweb/api/doc">API文档</a>之后,写了一个<a href="Domjudge-config/submit.py">自动交题的脚本</a>,并且用Locust辅助做压力测试,然后评测机不负众望炸了。。</p> +<p>之前一直没有做过压力测试一直是我的遗憾,这次在研究了Domjudge的<a href="https://www.domjudge.org/demoweb/api/doc">API文档</a>之后,写了一个<a href="./submit.py">自动交题的脚本</a>,并且用Locust辅助做压力测试,然后评测机不负众望炸了。。</p> <p><img src="./image-20191208184458561.png" alt="image-20191208184458561"></p> <p>其实还不止这个错,还有各种奇怪的错误。幸好在正式比赛中评测机表现得很稳健,并没有出锅,应该是压力还不够大。。</p> <p>比赛时还碰到一个bug,在一道题重测后这道题的一血变更了,但是榜单上的一血是错的,重新刷新榜单也未解决。查看数据库表发现有一张叫<code>scorecache</code>的表,推测该表即为榜单的缓存,于是更改<code>is_first_solved</code>字段后,成功解决。</p> diff --git a/page/5/index.html b/page/5/index.html index fb1eb0a..e8f9367 100644 --- a/page/5/index.html +++ b/page/5/index.html @@ -1,4 +1,4 @@ -Coherence's Blog +Coherence's Blog
存档 标签 分类 diff --git a/posts/2020_and_before/domjudge-config/index.html b/posts/2020_and_before/domjudge-config/index.html index 781f168..3c1e7ed 100644 --- a/posts/2020_and_before/domjudge-config/index.html +++ b/posts/2020_and_before/domjudge-config/index.html @@ -86,7 +86,7 @@

参考CSL的博客发现:

在导入teams表的时候,在Country Code后追加一列,填写Institution External ID。 这步是阅读源代码后猜测的,当时的实际操作是直接在数据库中的team_affiliation用SQL语句更新ExternalID的。

博主采用了这种方式,并在导入队伍后通过 Web 界面手动添加 Affiliation 信息。

事实上,institution_name institution_short_name country_code这三个字段都不是必要的,只需要有External ID就可以。而如果没有External ID的话,会创建和队伍数量一样多的Affiliation。

accounts 格式:

accounts	1
 account_type	fullname	username	password
-

一种(可能)简单的方式是先导入队伍再导入用户,并且用户 username 的后缀数字与 team_id 相同。不需要的字段可以直接留空。(不能缺少分隔符)

tsv 文件可以采用 Excel 生成。在 Excel 中输入完数据后选择另存为文本文件(制表符分隔)即可。

如需使用institution_name institution_short_name country_code 三项需在 Team Affiliations 中提前创建相应的条目。

API

参考官方的 API 说明,DOMjudge 包含 ICPC 定义的 API 自有的 API

1545220336183

滚榜

尝试使用官方的 ICPC tools,发现总是提示比赛未结束,查阅日志认为是 DOMjudge 的 event feed 格式与 ICPC tools 提供的 resolver 工具的格式不兼容。

尝试 DOMjura 发现同样不能正确读取 event-feed 。


2019年校赛补充:滚榜采用了 Github 上的这个项目。这比官方的滚榜工具好用多了,但是也有坑。

首先它使用bazel作为构建工具,但是bazel对于Windows很不友好(至少对于这个项目而言),会有奇怪的报错。所以我使用了Ubuntu。其次,由于构建过程中大部分源都在国外,对于国内的网络环境来说很不友好。我的解决办法是使用proxychains代理bazel,实测无需任何配置即可支持Domjudge 7.1.1(2019/12/7)。

2018冬季新生赛概况

闲扯几句。

热身赛一切良好。出题人数据出锅,被批判了一个多小时2333。

正式赛赛前比赛账号导入的时候,有个人是后来添加的,和之前的版本没有对应上,导致部分队伍登录到了后一个队伍的账号,重新导入 team 后发现无法登录,还需导入 accounts。这导致比赛推迟了 5 分钟。

由于 Submission 的输出数据是保留的,而赛前我的服务器硬盘空间本来就不多了,比赛时当服务器的硬盘占用达到 90% 时,所有 judgehost 就都被关了。我只能胡乱删几个软件包腾出空间。还好没有造成大的影响。

然后直到最后还是没有搞出滚榜。

GG。总体来说还是挺顺利的,服务器没有崩,甚至平均 CPU load 只有 1.0 左右,就是感觉比较吃 I/O,还好我用的是固态(先见之明)。

总结一下,DOMjudge 体验极佳,可以在校赛再推广一波。

2019冬季新生赛概况

由于今年懒得去嫖学校的服务器了,所以打算用公网服务器。本来在GCP和阿里云之间纠结用哪个,给我恰好滑稽给我提供了他在镇江的独服,于是建了一个16C,16G内存的虚拟机用作评测服务器(顺带送了个域名)。

这台服务器的网络结构比较复杂,首先,他是Windows Server,通过一个软件把某些端口的流量转发至一台Debian虚拟机中(为了方便管理证书等),然后由Debian上的Nginx反代至Domjudge的虚拟机中。这样我自己就不用配置证书也能有HTTPS。

但是Windows有一个老生常谈的坑:时间问题。Windows的硬件时钟是本地时间,而Linux为UTC时间,这导致了一开始Domjudge里的时间是假的,需要手动设置。

本次Domjudge的配置基本与之前相同,用Docker-compose一键部署。

之前一直没有做过压力测试一直是我的遗憾,这次在研究了Domjudge的API文档之后,写了一个自动交题的脚本,并且用Locust辅助做压力测试,然后评测机不负众望炸了。。

image-20191208184458561

其实还不止这个错,还有各种奇怪的错误。幸好在正式比赛中评测机表现得很稳健,并没有出锅,应该是压力还不够大。。

比赛时还碰到一个bug,在一道题重测后这道题的一血变更了,但是榜单上的一血是错的,重新刷新榜单也未解决。查看数据库表发现有一张叫scorecache的表,推测该表即为榜单的缓存,于是更改is_first_solved字段后,成功解决。


最后修改于 2019-12-08

在这种状态作出的 commits 将成为 dangleing commits,因为没有 refs 能够间接引用到它们。我们可以在 git reflog 中看到它们,但它们在一段时间后可能会被 git 垃圾回收掉。如果因为误操作“丢失”了一些 commit,大部分情况下只是没有 ref 能够间接引用它们了,所以我们能在 reflog 中找到并恢复它们。

除了分支之外,另一种 ref 是 tag。tag 被存储在 .git/refs/tags 中,和 branch 不同的是,tag 是不可变的。git 中有两种类型的 tag:

  • lightweight tag: 使用 git tag 创建,其 ref 直接指向一个 commit

  • annotated tag: 使用 git tag -a 创建,需要像 commit 一样写一段 tag message,其 ref 指向 git 中的第四种 object 类型,tag。

    在这种状态作出的 commits 将成为 dangling commits,因为没有 refs 能够间接引用到它们。我们可以在 git reflog 中看到它们,但它们在一段时间后可能会被 git 垃圾回收掉。如果因为误操作“丢失”了一些 commit,大部分情况下只是没有 ref 能够间接引用它们了,所以我们能在 reflog 中找到并恢复它们。

    除了分支之外,另一种 ref 是 tag。tag 被存储在 .git/refs/tags 中,和 branch 不同的是,tag 是不可变的。git 中有两种类型的 tag:

    • lightweight tag: 使用 git tag 创建,其 ref 直接指向一个 commit

    • annotated tag: 使用 git tag -a 创建,需要像 commit 一样写一段 tag message,其 ref 指向 git 中的第四种 object 类型,tag。

      MIT 6.824 学习笔记(二) Raft

      Note: 为了保持准确性,我会尽量使用英文术语。

      Introduction

      在过去十年, Leslie Lamport 的 Paxos 几乎成了共识的同义词.. Paxos 首先定义了一种协议来对单个决定达成共识, 比如一条单个的 log entry, 这被称为 single-decree Paxos. 其支持多个决定的版本 (比如 log) 被称为 muti-Paxos。然而,Paxos 的缺点是难以理解,并且没有提供一个良好的基础来构建可行的实现。

      相较于 Paxos,Raft 的目标是易于理解且符合直觉。为了使 Raft 易于理解,作者采取了解耦 (Raft 将共识问题分解成几个子问题 leader election, log replication, safety, and membership changes) 和缩减状态空间的方式。

      Raft 和已有的共识算法类似(尤其是 Viewstamped Replication),但它有一些新特性。Raft 采取了强 leader 的设计,例如 log entry 只会从 leader 向其他节点分发。这可能是为了性能考虑(比无 leader 要更快,RPC 也更少)Raft 采用基于随机计时器的 leader 选举, 从而用一种简单的方法来解决冲突。另外还有处理成员变更方面的改进。

      为了解决单点故障问题,Raft 采用了 Majority Vote,基本上任何操作都需要得到多数确认才能够执行。为了避免 tie,Raft 的节点数量必须为奇数。因此在一个 2f+1 个节点的系统中,最多允许 f 个节点故障,f+1 即为法定人数(quorum)。

      如果 server 不出错,网络也很稳定,共识算法是很简单的,比如在 Raft 中,leader 被选举出来,client 的请求由 leader 处理并转发给 followers,所有 server 都和 leader 保持一致,这个过程是很简单并且直观的。这些算法真正处理的、细节繁杂的地方是 server 出错、RPC 受网络影响(包括包的延迟,重复,顺序改变,丢失,网络分区)的时候。

      Replicated state machines (复制状态机)

      通常实现主从复制有两种方法:

      • 状态转移(State trasnsfer),主服务器将存储的所有状态都发送给从服务器
      • 复制状态机(Replicated State Machine),将服务器视为确定性的状态机,主服务器将所有输入发送给从服务器,那么他们在任意时刻的状态都是相同的

      image-20230907175938021

      Replicated state machines 通常使用 replicated log 实现. 每个节点存储一份 log (一系列指令), 其状态机按顺序执行它们. 每个状态机获得的 log 都是完全一致的, 所以它们是确定性的, 存储有完全相同的状态. Replicated log 的一致性由共识算法保证, 一个节点上的共识模块从 client 接收指令, 并把它们加到 log 中, 它和其他节点的共识模块通信以保证 log 存储了相同顺序的指令。在 Raft 中,状态机由上层应用负责处理。

      共识算法需要具有以下属性:

      • 在非拜占庭错误 (包括 network delays, partitions, and packet loss, duplication, and reordering) 下保证安全性 (safety), 即不会返回不正确的结果
      • 只要多数 (即超过一半) 节点工作, 服务就是可用的
      • 不依赖时间来确保一致性, 错误的时间或是极端情况下的延迟只会导致可用性问题
      • 只要多数节点回应, 一条命令就能够完成, 少数 slow servers 不会影响整个系统的性能

      Raft 共识算法

      image-20230908182459520

      这张图(论文中的 Figure 2)总结了 Raft 协议(除了成员变更和 log 压缩),我们在实现 Raft 协议时会反复看这张图。

      Raft 主要由两个 RPC 构成:

      • RequestVote RPC,由 candidate 请求,用于收集投票
      • AppendEntries RPC,由 leader 请求,用于心跳和复制 log entries

      Raft 首先将选举一个 leader, leader 的存在能够简化对 replicated log 的管理,比如 leader 能够独自决定把新的 entry 放在哪里,数据自然地由 leader 流向其他 servers。leader 拥有完全管理 replicated log 的权力,leader 从 clients 接受 log entries, 分发给其他节点, 并告诉它们何时能应用这些 log entries 到它们自己的状态机。

      关键性质:

      • Election Safety: 一个任期中只有一个 leader
      • Leader Append-Only: leader 不会覆盖或删除自己的 log entry, 只会 append
      • Log Matching: 如果两份 log 中某一个 entry 的 index 和 term 相同, 那么两份 log 从头开始直到该 entry 都是相同的
      • Leader Completeness: 如果一个 log entry 在某一 term 中 commit 了, 这个 entry 会存在于任何更高任期的 leader 的 log 中
      • State Machine Safety: 如果一个 server 将一个指定 index 的 log entry 应用到了 state machine 中(i.e. commit 了这个 entry), 没有其他 server 会在那个 index 上会应用其他的 entry

      这些性质看起来比较抽象,然而是保证 Raft 的安全性所必须的,下文会详细解释。

      leader 选举

      任何一个节点处于以下三种状态: leader, follower, candidate

      image-20230907175951530

      上图描述了 server 所有的状态变化,下面描述几个关键的步骤:

      • Follower -> Candidate:开始选举,follower 递增 term 并进入 candidate 状态,它接着给自己投票并向其他 server 发送 RequestVote RPC。每个 server 在一个 term 只能给一个 candidate 投票,基于先来先得的原则
      • Candidate -> Leader:当 candidate 接收到了多数票(majority)之后将赢得选举,接着它会马上发送心跳包宣告自己的权威并阻止新的选举。leader 会一直保持 leader 状态,除非 a). 它 fail 了 b). 由于丢包等原因导致某个 follower 的选举计时器超时了
      • Candidate -> Follower:如果 candidate 收到其他 server 的 AppendEntries RPC 宣称是 leader,并且其 term 不小于 candidate 的当前 term,则该 leader 合法,candidate 回退到 follower 状态
      • Candidate -> Candidate:如果没有产生多数票,在超时后选举将重复进行,为了打破僵局 Raft 使用随机的选举超时时间
      • 无论何时,candidate 和 leader 只要收到一个 term 比自己大的 RPC,它必须转变为 follower

      由于在任何一个 term 中,一个 server 只能够投一次票,而只有得到多数票才能成为 leader,所以 Election Safety(一个任期中只有一个 leader)得以满足。

      log 复制 (replication)

      log 由 entry 组成,每个 entry 都有一个唯一递增的 log index(可以理解为在数组中的 index)。entry 中包含 term 和给状态机的指令。

      当一个 log entry 能够被应用到状态机上时 (i.e. 当 leader 知道这个 entry 已经被 replicate 到多数 server 上时,但下文会说明一种例外情况), 我们称这个 entry 为 committed. leader 决定哪些 entry 能够 commit,并在下一次 AppendEntries RPC 中将这个状态告知 followers. Raft 保证了 commited entries 是持久的并最终会应用到所有 servers 上。

      leader 维护 commitIndex,即最高的 committed entry index,并且会将这个 index 包含在 AppendEntries RPC 中。

      Raft 协议中的 Log Matching 性质实际上由两部分组成:

      • 如果两份 log 中某一个 entry 的 index 和 term 相同, 那么这两个 entry 相同
        • 这是因为一个 term 只有一个 leader,而一个 leader 对于给定 log index 只能创建最多一个 entry,并且 log entry 从不改变它们在 log 中的位置
      • 如果两份 log 中某一个 entry 的 index 和 term 相同, 它们之前的 entries 都是相同的
        • 这个性质由 AppendEntries 的一个一致性检查保证。leader 会把新 entries 的前一个 entry 的 index 和 term 包含在 AppendEntries RPC 中(prevLogIndexprevLogTerm),这样如果 follower 找不到这样一个 entry,它就会拒绝新的 entries

      image-20230907180012141

      正常情况下所有 server 的 log 会保持一致,然而当 leader crash 时便会产生不一致。follower 可能会缺一些 entries,也可能多一些,也可能两者都有。leader 通过强制 follower 的 log 和自己同步来解决不一致。leader 为每个 follower 维护一个 nextIndex,即下一个将会被发给 follower 的 log entry。在一开始 leader 会初始化 nextIndex 为它最新一个 log entry 的下一个位置,当某个 follower 拒绝了一次 AppendEntries 之后,leader 递减 nextIndex 并重试 AppendEntries RPC。最终 nextIndex 会到达一个 leader 和 follower 都一致的位置,follower 在那个位置之后的所有 entries 都会被 leader 的覆盖。上述描述同时也符合了 Leader Append-Only(leader 只会追加自己的 log) 这个性质。

      安全性

      如果 Figure 7 中的 (f) 被选举为了下一个 leader,那么 term 4 及之后的更改岂不是都丢失了?因此 Raft 限制了谁能够被选为 leader,以便满足 Leader Completeness 性质(committed entry 会存在于任何更高任期的 leader 的 log 中)。首先给出 log 新旧的定义:如果 candidate 的 log 至少和多数 log 一样新(at least as up-to-date as any other log in the majority)那么它包含了所有 committed entries;如果 log 的最后一个 entry term 更大,那么它就更新。如果 term 相同,那么更长的 log 更新。在 RequestVote RPC 中,candidate 会包含 lastLogIndexlastLogTerm,只有当 candidate 的 log 更新时一个 server 才会投票给它。

      image-20230909175357672

      当 leader commit 一个来自它之前 term 的 entry 时,可能会遭遇上图的问题,在 (c) 中,来自 term 2 的 entry 已经被复制到多数 server 上了,然而如果 (d) 发生了,它仍然会被来自 term 3 的 entry 覆盖。因此 Raft 将 commit 定义为只有来时当前 term 的 entry 才能够通过 replica 计数来 commit,而来自之前任期的 entry 只能够通过在它之后的 entry 被 commit 来间接 commit(Log Matching 性质)。

      我们可以用反证法来证明这么做的安全性(非严格证明),假设节点数量为 2f+1,log index m 已经被 commit 了,并且位于 f+1 个 server 上。此时一个不包含 m 的 server x 想要竞选 leader。为了得到多数票,至少有一个包含 m 的 server y需要投票给 x,此时需要 x 比 y 更新。根据 up-to-date 的定义,可能有以下两种情况:

      • x 和 y 的最后一个 log entry term 相同,且 x 的 log 至少和 y 一样长。此时根据 Log Matching 性质,x 必然包含 m,故产生矛盾。
      • x 的最后一个 log entry(设为 p)term 更大。那么 p 的 term 必然大于 m 的 term。既然 m 已经 commit 了,创建了 p 的那一任 leader 必然在 log 中包含了 m,所以 x 也必然包含 m,产生矛盾。

      时机和可用性

      在 leader 选举中,时机(Timing)是很关键的,如果 RPC 用时过长,那么 candidate 在超时之前将无法赢得选举。Raft 需要满足以下时机约束:$broadcastTime ≪ electionTimeout ≪ MTBF$,broadcastTime 是平均一次 RPC 的时间,electionTimeout 是选举超时时间,MTBF(Mean Time Between Failures)是发生两次 server 故障的平均间隔时间。

      集群成员变更

      这里指的是增减 server。虽然我们总是能够通过下线整个集群、更新配置、上线集群的方式来增减 server,但这样就减少了可用性。为了使配置变更是安全的,必须保证同一 term 不可能有两个 leader 被选举出来。然而,任何直接由旧配置转变为新配置的做法都是不安全的,如下图所示:

      image-20230928111923370

      为了保证安全性,Raft 采取了一种两阶段的策略,集群会切换到一个过渡的配置,称为 joint consensus。论文没有给出这个过程的具体实现,我对这部分也理解得不是很清楚。

      log 压缩(compaction)

      如果没有压缩,log 的大小会无限增长,并且在 server 故障重启后重新应用 log 的时间会非常长,这显然是不划算的。

      快照(snapshot)是最简单的压缩方式。在一次快照中,当前的系统状态被写入到非易失存储中,然后到这个时刻的所有 log 将被丢弃。

      image-20230928141311077

      然而,快照带来了新的复杂度。如果一个 leader 的 log 如上图所示,在一次快照之后 log 中的第一个 entry 为 6,但是如果一个 follower 的 log 还停留在最后一个 entry 为 4,那么这个 follower 永远无法获得 entry 5 了。Raft 的做法是引入了一个新的 InstallSnapshot RPC,用于把 leader 的当前 snapshot 发送给 follower。

      snapshot 的创建过程依赖于上层应用,因为 Raft 对于状态机里的状态知之甚少。但是何时创建 snapshot 由 Raft 决定,一个简单的策略是当 log 达到固定大小时创建。

      每个 server 能够独立创建 snapshots,因而其偏离了 Raft 的强 leader 原则,但是由于在创建 snapshot 时共识已经达成了,所以这不会有问题。如果只有 leader 能创建 snapshot,那么有两个缺点 a) 每次发送 snapshot 都会浪费网络带宽 b) leader 的实现会变得复杂,因为它必须同时发送 snapshot 和新的 entries。

      客户端交互

      client 只会和 leader 交互,在 client 刚启动时,它可能会连接到一个随机的 server,若该 server 不为 leader,则 server 会拒绝这个请求并提供 leader 的信息。

      Raft 的目标是提供可线性化(linearizable)的语义,即每个操作会在调用和回复之间的某个时间立刻执行执行一次(exactly once)。然而,Raft 可能会执行一个操作多次,如果 leader 在 commit log entry 之后回复 client 之前崩溃了,client 会重试请求,导致一个操作执行了两次。这个问题的解决方案是每个 client 为对每个操作分配一个递增的序列号,leader 维护每个 client 最新的序列号。如果 leader 收到了一个请求包含已经被执行的序列号,那么它就不会再次执行这个请求。

      我们能够不向 log 写任何东西就处理读请求(Read-only operations),然而如果没有额外的措施,读请求可能会读到过时的(stale)数据。如由于网络分区当前 leader 已经被新的 leader 取代了,但是当前 leader 并不知道这一点。为了保证不会读到过时的数据,在 leader 刚当选时它必须发送一个 no-op entry,以便找出哪些 entry 已经 commit 了。其次 leader 必须检查它是否被新的 leader 取代了,这可以通过发送心跳包来检查是否有多数 server 回应来实现。

      Notes

      • Raft 是强一致性的协议,因为 commit 过的 log 总是一致的,如果出现网络分区则只有多数区能够达成共识,所以其符合了 CAP 原则中的 CP。另一种应对 split-brain 的方式是允许 split-brain,在网络恢复之后通过一些机制来调谐分叉的状态(reconcile the diverging state),通常这被称为最终一致性。

      • Q:一个 Raft 集群通常位于一个物理位置还是说可以部署在地理上不同的数据中心里?

        A:典型的部署方式是一个数据中心。我们会在之后看到一些运行 Paxos 的系统(如 Spanner)能够跨数据中心,其通常是一种无 leader 的设计,以便一个 client 能够同一个本地 server 通信。

        Google 的 Chubby 论文报告了它们的 Chubby 部署通常是在一个数据中心里,除了 root Chubby 是跨数据中心的(chubby 并未使用 raft,而是用了基于 Paxos 的一个内部库)

      可线性化(Linearizability)

      并发和不确定性的存在,导致分布式系统很容易存在一些微妙的 bug。除非使用形式化方法,否则我们只能依赖测试来验证系统的正确性。

      通过 Linearizability 这样一种一致性模型,我们可以基于一种顺序的规格形式地确定并发操作的正确性。在一个 linearizable 的系统中,每个操作看起来是在调用和回复之间的某个时间点原子、瞬间执行。可线性化可以被视作强一致性的同义词。

      Definition of linearizability:

      Execution history is linearizable if there exists a total order of the operations in the history that matches real time for non-concurrent requests. Each read sees the value of the most recent write in order.

      下图所示的执行历史是可线性化的:

      image-20231001151607479

      而下图则不是:

      image-20231001151618755

      我的理解是 Linearizability 是从外部观测获得的一种性质,它并没有说明具体的实现。

      参考


      最后修改于 2023-10-11

      本文主要是对 Raft 论文的翻译,为了保持准确性,我会尽量使用英文术语。

      Introduction

      在过去十年,Leslie Lamport 的 Paxos 协议几乎成为了共识的同义词。Paxos 首先定义了一种协议来对单个决定达成共识, 比如一条单个的 log entry, 这被称为 single-decree Paxos。 其支持多个决定的版本 (比如 log) 被称为 muti-Paxos。然而,Paxos 的缺点是难以理解,并且没有提供一个良好的基础来构建可行的实现。

      试图为这个主题增添一点幽默的尝试以惨淡的失败告终。……这个希腊寓言显然使阅读论文的人们分心了,以致于他们无法理解这个算法。我把论文发给了一些人,其中包括 Nancy Lynch, Vassos Hadzilacos 和 Phil Bernstein,他们声称读过了论文。几个月后我发邮件给他们问了如下问题:

      你能否实现一个分布式数据库,它能容忍任何进程的故障(可能是所有进程)而不牺牲一致性,并且在超过半数进程恢复之后继续正常工作?
      +

      没有人察觉到这个问题和 Paxos 算法之间有任何联系。

      —— Leslie Lamport 对 The Part-Time Parliament 的评论

      相较于 Paxos,Raft 的目标是易于理解且符合直觉。为了使 Raft 易于理解,作者采取了解耦 (Raft 将共识问题分解成几个子问题 leader election, log replication, safety, and membership changes) 和缩减状态空间的方式。

      Raft 和已有的共识算法类似(尤其是 Viewstamped Replication),但它有一些新特性。Raft 采取了强 leader 的设计,例如 log entry 只会从 leader 向其他节点分发。这可能是为了性能考虑(比无 leader 要更快,RPC 也更少)Raft 采用基于随机计时器的 leader 选举, 从而用一种简单的方法来解决冲突。另外还有处理成员变更方面的改进。

      为了解决单点故障问题,Raft 采用了 Majority Vote,基本上任何操作都需要得到多数确认才能够执行。为了避免 tie,Raft 的节点数量必须为奇数。因此在一个 2f+1 个节点的系统中,最多允许 f 个节点故障,f+1 即为法定人数(quorum)。

      如果 server 不出错,网络也很稳定,共识算法是很简单的,比如在 Raft 中,leader 被选举出来,client 的请求由 leader 处理并转发给 followers,所有 server 都和 leader 保持一致,这个过程是很简单并且直观的。这些算法真正处理的、细节繁杂的地方是 server 出错、RPC 受网络影响(包括包的延迟,重复,顺序改变,丢失,网络分区)的时候。

      Replicated state machines (复制状态机)

      通常实现主从复制有两种方法:

      • 状态转移(State trasnsfer),主服务器将存储的所有状态都发送给从服务器
      • 复制状态机(Replicated State Machine),将服务器视为确定性的状态机,主服务器将所有输入发送给从服务器,那么他们在任意时刻的状态都是相同的

      image-20230907175938021

      Replicated state machines 通常使用 replicated log 实现. 每个节点存储一份 log (一系列指令), 其状态机按顺序执行它们. 每个状态机获得的 log 都是完全一致的, 所以它们是确定性的, 存储有完全相同的状态. Replicated log 的一致性由共识算法保证, 一个节点上的共识模块从 client 接收指令, 并把它们加到 log 中, 它和其他节点的共识模块通信以保证 log 存储了相同顺序的指令。在 Raft 中,状态机由上层应用负责处理。

      共识算法需要具有以下属性:

      • 在非拜占庭错误 (包括 network delays, partitions, and packet loss, duplication, and reordering) 下保证安全性 (safety), 即不会返回不正确的结果
      • 只要多数 (即超过一半) 节点工作, 服务就是可用的
      • 不依赖时间来确保一致性, 错误的时间或是极端情况下的延迟只会导致可用性问题
      • 只要多数节点回应, 一条命令就能够完成, 少数 slow servers 不会影响整个系统的性能

      Raft 共识算法

      image-20230908182459520

      这张图(论文中的 Figure 2)总结了 Raft 协议(除了成员变更和 log 压缩),我们在实现 Raft 协议时会反复看这张图。

      Raft 主要由两个 RPC 构成:

      • RequestVote RPC,由 candidate 请求,用于收集投票
      • AppendEntries RPC,由 leader 请求,用于心跳和复制 log entries

      Raft 首先将选举一个 leader, leader 的存在能够简化对 replicated log 的管理,比如 leader 能够独自决定把新的 entry 放在哪里,数据自然地由 leader 流向其他 servers。leader 拥有完全管理 replicated log 的权力,leader 从 clients 接受 log entries, 分发给其他节点, 并告诉它们何时能应用这些 log entries 到它们自己的状态机。

      关键性质:

      • Election Safety: 一个任期中只有一个 leader
      • Leader Append-Only: leader 不会覆盖或删除自己的 log entry, 只会 append
      • Log Matching: 如果两份 log 中某一个 entry 的 index 和 term 相同, 那么两份 log 从头开始直到该 entry 都是相同的
      • Leader Completeness: 如果一个 log entry 在某一 term 中 commit 了, 这个 entry 会存在于任何更高任期的 leader 的 log 中
      • State Machine Safety: 如果一个 server 将一个指定 index 的 log entry 应用到了 state machine 中(i.e. commit 了这个 entry), 没有其他 server 会在那个 index 上会应用其他的 entry

      这些性质看起来比较抽象,然而是保证 Raft 的安全性所必须的,下文会详细解释。

      leader 选举

      任何一个节点处于以下三种状态: leader, follower, candidate

      image-20230907175951530

      上图描述了 server 所有的状态变化,下面描述几个关键的步骤:

      • Follower -> Candidate:开始选举,follower 递增 term 并进入 candidate 状态,它接着给自己投票并向其他 server 发送 RequestVote RPC。每个 server 在一个 term 只能给一个 candidate 投票,基于先来先得的原则
      • Candidate -> Leader:当 candidate 接收到了多数票(majority)之后将赢得选举,接着它会马上发送心跳包宣告自己的权威并阻止新的选举。leader 会一直保持 leader 状态,除非 a). 它 fail 了 b). 由于丢包等原因导致某个 follower 的选举计时器超时了
      • Candidate -> Follower:如果 candidate 收到其他 server 的 AppendEntries RPC 宣称是 leader,并且其 term 不小于 candidate 的当前 term,则该 leader 合法,candidate 回退到 follower 状态
      • Candidate -> Candidate:如果没有产生多数票,在超时后选举将重复进行,为了打破僵局 Raft 使用随机的选举超时时间
      • 无论何时,candidate 和 leader 只要收到一个 term 比自己大的 RPC,它必须转变为 follower

      由于在任何一个 term 中,一个 server 只能够投一次票,而只有得到多数票才能成为 leader,所以 Election Safety(一个任期中只有一个 leader)得以满足。

      log 复制 (replication)

      log 由 entry 组成,每个 entry 都有一个唯一递增的 log index(可以理解为在数组中的 index)。entry 中包含 term 和给状态机的指令。

      当一个 log entry 能够被应用到状态机上时 (i.e. 当 leader 知道这个 entry 已经被 replicate 到多数 server 上时,但下文会说明一种例外情况), 我们称这个 entry 为 committed. leader 决定哪些 entry 能够 commit,并在下一次 AppendEntries RPC 中将这个状态告知 followers. Raft 保证了 commited entries 是持久的并最终会应用到所有 servers 上。

      leader 维护 commitIndex,即最高的 committed entry index,并且会将这个 index 包含在 AppendEntries RPC 中。

      Raft 协议中的 Log Matching 性质实际上由两部分组成:

      • 如果两份 log 中某一个 entry 的 index 和 term 相同, 那么这两个 entry 相同
        • 这是因为一个 term 只有一个 leader,而一个 leader 对于给定 log index 只能创建最多一个 entry,并且 log entry 从不改变它们在 log 中的位置
      • 如果两份 log 中某一个 entry 的 index 和 term 相同, 它们之前的 entries 都是相同的
        • 这个性质由 AppendEntries 的一个一致性检查保证。leader 会把新 entries 的前一个 entry 的 index 和 term 包含在 AppendEntries RPC 中(prevLogIndexprevLogTerm),这样如果 follower 找不到这样一个 entry,它就会拒绝新的 entries

      image-20230907180012141

      正常情况下所有 server 的 log 会保持一致,然而当 leader crash 时便会产生不一致。follower 可能会缺一些 entries,也可能多一些,也可能两者都有。leader 通过强制 follower 的 log 和自己同步来解决不一致。leader 为每个 follower 维护一个 nextIndex,即下一个将会被发给 follower 的 log entry。在一开始 leader 会初始化 nextIndex 为它最新一个 log entry 的下一个位置,当某个 follower 拒绝了一次 AppendEntries 之后,leader 递减 nextIndex 并重试 AppendEntries RPC。最终 nextIndex 会到达一个 leader 和 follower 都一致的位置,follower 在那个位置之后的所有 entries 都会被 leader 的覆盖。上述描述同时也符合了 Leader Append-Only(leader 只会追加自己的 log) 这个性质。

      安全性

      如果 Figure 7 中的 (f) 被选举为了下一个 leader,那么 term 4 及之后的更改岂不是都丢失了?因此 Raft 限制了谁能够被选为 leader,以便满足 Leader Completeness 性质(committed entry 会存在于任何更高任期的 leader 的 log 中)。首先给出 log 新旧的定义:如果 candidate 的 log 至少和多数 log 一样新(at least as up-to-date as any other log in the majority)那么它包含了所有 committed entries;如果 log 的最后一个 entry term 更大,那么它就更新。如果 term 相同,那么更长的 log 更新。在 RequestVote RPC 中,candidate 会包含 lastLogIndexlastLogTerm,只有当 candidate 的 log 更新时一个 server 才会投票给它。

      image-20230909175357672

      当 leader commit 一个来自它之前 term 的 entry 时,可能会遭遇上图的问题,在 (c) 中,来自 term 2 的 entry 已经被复制到多数 server 上了,然而如果 (d) 发生了,它仍然会被来自 term 3 的 entry 覆盖。因此 Raft 将 commit 定义为只有来时当前 term 的 entry 才能够通过 replica 计数来 commit,而来自之前任期的 entry 只能够通过在它之后的 entry 被 commit 来间接 commit(Log Matching 性质)。

      我们可以用反证法来证明这么做的安全性(非严格证明),假设节点数量为 2f+1,log index m 已经被 commit 了,并且位于 f+1 个 server 上。此时一个不包含 m 的 server x 想要竞选 leader。为了得到多数票,至少有一个包含 m 的 server y需要投票给 x,此时需要 x 比 y 更新。根据 up-to-date 的定义,可能有以下两种情况:

      • x 和 y 的最后一个 log entry term 相同,且 x 的 log 至少和 y 一样长。此时根据 Log Matching 性质,x 必然包含 m,故产生矛盾。
      • x 的最后一个 log entry(设为 p)term 更大。那么 p 的 term 必然大于 m 的 term。既然 m 已经 commit 了,创建了 p 的那一任 leader 必然在 log 中包含了 m,所以 x 也必然包含 m,产生矛盾。

      时机和可用性

      在 leader 选举中,时机(Timing)是很关键的,如果 RPC 用时过长,那么 candidate 在超时之前将无法赢得选举。Raft 需要满足以下时机约束:$broadcastTime ≪ electionTimeout ≪ MTBF$,broadcastTime 是平均一次 RPC 的时间,electionTimeout 是选举超时时间,MTBF(Mean Time Between Failures)是发生两次 server 故障的平均间隔时间。

      集群成员变更

      这里指的是增减 server。虽然我们总是能够通过下线整个集群、更新配置、上线集群的方式来增减 server,但这样就减少了可用性。为了使配置变更是安全的,必须保证同一 term 不可能有两个 leader 被选举出来。然而,任何直接由旧配置转变为新配置的做法都是不安全的,如下图所示:

      image-20230928111923370

      为了保证安全性,Raft 采取了一种两阶段的策略,集群会切换到一个过渡的配置,称为 joint consensus。论文没有给出这个过程的具体实现,我对这部分也理解得不是很清楚。

      log 压缩(compaction)

      如果没有压缩,log 的大小会无限增长,并且在 server 故障重启后重新应用 log 的时间会非常长,这显然是不划算的。

      快照(snapshot)是最简单的压缩方式。在一次快照中,当前的系统状态被写入到非易失存储中,然后到这个时刻的所有 log 将被丢弃。

      image-20230928141311077

      然而,快照带来了新的复杂度。如果一个 leader 的 log 如上图所示,在一次快照之后 log 中的第一个 entry 为 6,但是如果一个 follower 的 log 还停留在最后一个 entry 为 4,那么这个 follower 永远无法获得 entry 5 了。Raft 的做法是引入了一个新的 InstallSnapshot RPC,用于把 leader 的当前 snapshot 发送给 follower。

      snapshot 的创建过程依赖于上层应用,因为 Raft 对于状态机里的状态知之甚少。但是何时创建 snapshot 由 Raft 决定,一个简单的策略是当 log 达到固定大小时创建。

      每个 server 能够独立创建 snapshots,因而其偏离了 Raft 的强 leader 原则,但是由于在创建 snapshot 时共识已经达成了,所以这不会有问题。如果只有 leader 能创建 snapshot,那么有两个缺点 a) 每次发送 snapshot 都会浪费网络带宽 b) leader 的实现会变得复杂,因为它必须同时发送 snapshot 和新的 entries。

      客户端交互

      client 只会和 leader 交互,在 client 刚启动时,它可能会连接到一个随机的 server,若该 server 不为 leader,则 server 会拒绝这个请求并提供 leader 的信息。

      Raft 的目标是提供可线性化(linearizable)的语义,即每个操作会在调用和回复之间的某个时间立刻执行执行一次(exactly once)。然而,Raft 可能会执行一个操作多次,如果 leader 在 commit log entry 之后回复 client 之前崩溃了,client 会重试请求,导致一个操作执行了两次。这个问题的解决方案是每个 client 为对每个操作分配一个递增的序列号,leader 维护每个 client 最新的序列号。如果 leader 收到了一个请求包含已经被执行的序列号,那么它就不会再次执行这个请求。

      我们能够不向 log 写任何东西就处理读请求(Read-only operations),然而如果没有额外的措施,读请求可能会读到过时的(stale)数据。如由于网络分区当前 leader 已经被新的 leader 取代了,但是当前 leader 并不知道这一点。为了保证不会读到过时的数据,在 leader 刚当选时它必须发送一个 no-op entry,以便找出哪些 entry 已经 commit 了。其次 leader 必须检查它是否被新的 leader 取代了,这可以通过发送心跳包来检查是否有多数 server 回应来实现。

      Notes

      • Raft 是强一致性的协议,因为 commit 过的 log 总是一致的,如果出现网络分区则只有多数区能够达成共识,所以其符合了 CAP 原则中的 CP。另一种应对 split-brain 的方式是允许 split-brain,在网络恢复之后通过一些机制来调谐分叉的状态(reconcile the diverging state),通常这被称为最终一致性。

      • Q:一个 Raft 集群通常位于一个物理位置还是说可以部署在地理上不同的数据中心里?

        A:典型的部署方式是一个数据中心。我们会在之后看到一些运行 Paxos 的系统(如 Spanner)能够跨数据中心,其通常是一种无 leader 的设计,以便一个 client 能够同一个本地 server 通信。

        Google 的 Chubby 论文报告了它们的 Chubby 部署通常是在一个数据中心里,除了 root Chubby 是跨数据中心的(chubby 并未使用 raft,而是用了基于 Paxos 的一个内部库)

      可线性化(Linearizability)

      并发和不确定性的存在,导致分布式系统很容易存在一些微妙的 bug。除非使用形式化方法,否则我们只能依赖测试来验证系统的正确性。

      通过 Linearizability 这样一种一致性模型,我们可以基于一种顺序的规格形式地确定并发操作的正确性。在一个 linearizable 的系统中,每个操作看起来是在调用和回复之间的某个时间点原子、瞬间执行。可线性化可以被视作强一致性的同义词。

      Definition of linearizability:

      Execution history is linearizable if there exists a total order of the operations in the history that matches real time for non-concurrent requests. Each read sees the value of the most recent write in order.

      下图所示的执行历史是可线性化的:

      image-20231001151607479

      而下图则不是:

      image-20231001151618755

      我的理解是 Linearizability 是从外部观测获得的一种性质,它并没有说明具体的实现。

      参考


      最后修改于 2024-06-02

计划

我和 Moo 一开始说的是去新马泰,后来改为日本,真正开始做攻略已经是三月下旬了。日本只支持旅行社送签,我在 3/26 申请了签证并提交了材料,4/3 出签,花了六个工作日,还是挺快的。

image-20240424004023270

订机票和酒店的时候差不多离出发还有半个月,这时候的价格已经挺贵了,可能提前两三个月订会更便宜。初步的计划是东京->京都->大阪,详细的计划写在 hackmd 上。实际上我们的计划并不详细,只是提前订了 Tokyo Skytree、Shibuya sky 的票,规定了一下在东京的那几天每天在哪个区域活动,而在京都和大阪的行程则完全没有计划,全靠临时起意。

我在出发前半个月用多邻国速成了两周日语,非常失败,五十音都没认全,还是只能用英语交流。结论为不如用 Anki 认五十音。

流水账

4/13

终于踏上了去东京的旅程,虽然很怕在途中突然被要求改论文/交材料,but be it,放心玩,越玩越稳。

我是先到杭州东,坐垃圾桶去上海南,再坐地铁去浦东机场,这套流程也太长了,我上午十点出发,三点半才到浦东机场。

(来点经典小红书照片)

Moo 买的是西安->上海浦东->东京成田的中转航班,第二段和我是同一个航班,本来的计划是浦东机场会合。然而不幸的是当天西安有雷雨天气,第一段航班延误了大概两个小时,导致赶不上第二段航班,只能改签。日本的手机卡是 Moo 买的,由于 Moo 延误了,一个问题就是我在日本的第一天没有网。下飞机了发现我的电信卡没有国际漫游。有的话用一天 25,其实可以接受,但是我的卡已经没信号收不了短信了,只能微信喊我爸问了下客服,说是要换卡才能开通?这是什么操作。于是只能去便利店高价(880 日元的卡费 + 1300 日元三天的套餐费)买了张卡。

出关的时候排队排了好久,大概有半个多小时,很多人可能是填的纸质材料比较慢,我填了 Visit Japan Web 感觉就半分钟就办完了。本来我们在线买的两张 Skyliner 票,只要凭兑换二维码兑换车票就行,然而现在就我一个人,和 staff 交流了半天,最后的办法是退我们网上买的票,然后重新分别买。本打算顺便买交通卡的,但是卖卡的地方(不知道有没有别的地方,我问到的应该是在 Skyliner & Keisai Information Center)十点关门了,于是没买到。

到达第一天的青旅的时候已经过十一点了,被加收了 1000 日元 late check-in fee。入住的时候我说这是我第一次来日本,前台小哥特地说了一句“Welcome to Japan”。住的是十四人间(!),但是大家都挺安静的,只是晚上和早上在 lobby 都没见着什么人,没有交流。青旅建在铁路正下方,列车驶过的声音还挺大的。不过这地段是相当好啊,就在天空树旁边。

image-20240424015124861

(Wise Owl Hostels 就是我住的青旅,上面就是铁路)

4/14

Moo 中午才到,于是我上午按计划去了上野。到了上野先看国立西洋美术馆的特展,主题是 Does the Future Sleep Here? 每个章节配有中文文字说明,国际化做得不错。我没想到这个展有那么多内容,到十二点半了才看完。还有一段脱衣舞影片,我看不懂,但我大受震撼.jpg

看完去吃饭,Google Maps 搜了个蛋包饭,发现在 JR 上野站付费区里,于是买了张最便宜的票(150 日元)进去了,发现这家店要排队,于是换了家店,一碗非常普通的面要 900 日元。吃完出站,发现在自动售货机退不了款,于是亏了 150。 -吃完饭继续看美术馆的常设展,看完 Moo 到了。

image-20240424010133135

(这张图很符合我对美术馆的印象)

然后我们在上野公园和周边走了走,其实那是应该走到东京大学的,但是当时没想到。然后去秋叶原,逛了几个模型店,还有个卖动漫周边的,Moo 买了个二次元周边。晚饭在秋叶原随机挑了家店解决,tabelog 3 分,我只能给一分,牛肉煎得非常老,两个人 5000 日元,也不划算。

这天和之后的两天住的是一个在中野区的民宿,一个非常小的两层小楼。但是居住体验相当不错。位置也很好,离地铁站步行一分钟。

4/15

早上先去买交通卡,跑到 seibu-shinjuku 站买 pasmo 卡,被告知因为缺货无法出售,并建议我们去旁边的 JR East 旅客服务中心买 suica,我还担心没带护照能不能买到(welcome suica 只卖给外国人)。到了发现门口贴了张本店不出售 welcome suica 卡,但是到都到了就进去问问呗,一问发现居然卖普通 suica 卡,于是购入两张。

买完在 shinjuku 街头闲逛,喝了杯咖啡,中午去台场,逛了一下一个商场,然后吃了个炸牛排饭,比昨天晚上的那个要好不少。吃完看了看门口的高达,然后在海滨公园走了走,公园里有人工沙滩,没什么人。本来是打算乘船去上野的,走过了一个没什么人的码头,后来才发现最后一班船的时间已经过了,于是只能乘地铁去上野。

去上野看浅草寺,门口那个商店街和国内的景区差不多,花 100 日元求了个签。然后去天空树,看东京的夜景。

image-20240424224740647

(Tokyo Tower!)

晚饭吃マクドナルド(我已经改不回来了.jpg)。吃完饭大概九点多,还早,于是去 shinjuku 逛了逛,去的是歌舞伎町,看到了非常多的“无料案内所”,应该是拉皮条的,具体是不是真的嫖娼就不知道了。Moo 拍了一下某家店的招牌,就有个人上来搭话,问 “Are you looking for massages?",事后想想这应该就是皮条客了,按摩估计也不是正经按摩。还看到了一个非常乱的广场,垃圾随地乱扔,许多人躺在广场上,很难想象这是东京。小红书上说这里很多是不良少年。这个广场令人害怕,赶紧走了。

4/16

早上去 Shibuya Sky,其实就是楼顶的一个平台,昨天看了夜景今天看日景。小红书建议买黄昏的票看日落,然而我们当时买的时候已经没有黄昏的票了。

image-20240424224520667

(经典打卡地 Shibuya Scramble Crossing,这时人不是很多,后来我们在晚上再路过的时候人是真的多)

中午吃了乌冬面,那家店能够俯瞰 Shibuya 的街道,位置非常好。然后去逛了逛唐吉柯德,我反正是没啥购物的欲望。整个日本之旅只买了六张明信片。晚上仍然在 Shibuya,Google Maps 找了一家评分高一点的店,吃了个鲑鱼定食。日本的所有定食(套餐)都会附赠味增汤,然而味增汤并不好喝。

吃完饭去了家卖唱片的店,很大,有七八层。如今卖 CD 和黑胶还能开这么大的店真是不多见了。

4/17

Moo 要去丰乡小学圣地巡礼(傻逼二次元),所以今天的我们先乘坐东海道新干线从东京站到米原站。先在东京站吃了个午饭,汉堡肉拌饭,听着比较黑暗吃着还行。我们买的票是非指定席当日票,车上的人并不是很多,我们这个车厢可能就坐满了三分之一。

新干线的票价由里程票+特急券组成,刚买到的时候看到这两部分还以为买错了。

到米原后换乘近江铁道前往丰乡站,这车是我坐过的最晃的火车了,两节车厢,全车就一个司机,还是手工检票,非常落后。

image-20240424223442375

(车看着还挺新的)

米原是始发站,可能因为这天是工作日,在始发站上车时车上就我们两个人,让人非常怀疑这家公司什么时候倒闭。好在过了几站之后人就变多了,这时是三点多已经有高中生放学了,真早啊!Moo 还搭讪了一个女生,问她是不是已经放学了。我们都不会日语,怎么问呢?当然是用 Google 翻译了!打字翻译成日语然后给她看。她说今天放学有点早,同样用 Google 翻译打字给我们看。可惜 Moo 就搭讪了这一句,没有然后了。

image-20240424223637578

(空无一人的车厢)

image-20240427000708227

(还有车头视角)

差不多五点我们离开丰乡,回米原站拿寄存的行李,然后去京都。丰乡站没有工作人员,也没有售票口,怎么买票呢?问了一个黑人老哥,老哥非常热情,详细地解释了买票的流程,英语也非常标准,几乎没有口音。他说他在车站等他的妻子,在我们上车时他还给我们指了取票机的位置。这列车买票的方式是上车时取一张乘车券(写有当前的站名),然后看车上的显示屏根据上下车站计算车费,下车时把车费和乘车券交给乘务员,太原始了!

到了米原是晚饭时间了,于是又 Google Maps 找了家店,然后发现这家店只有日语菜单!只能全凭 Google 翻译加上意会了。吃了天妇罗,一碗豆腐加牛肉,炸猪排,还有个很黑暗的大葱天妇罗。

到了京都 check-in 青旅然后睡觉。

4/18

早上八点起床吃早饭,青旅提供免费早餐,但是只有面包。

本来想买个京都 one day pass 的,发现能租自行车,于是租了一辆,京都和东京一样全城禁止停车,只能停在自行车停车场。京都确实很小,感觉自行车能够一个小时绕一圈。

(这自行车还是光头窄胎,非常好骑)

瞎骑了一通后前往清水寺,找纸质地图上标的停车点没找到,绕了一大圈。看了一下租车时发的 rules 那张纸,发现有个 Google Maps 自制停车地图(就是在 Google Maps 上标点),发现之前其实已经快到了。停好车已经十二点了,吃了个炸鸡当午饭。

清水寺人非常多,仿佛回到了国内。有好多中学生,看着像在春游。买票走了一圈,求了个签,很怀疑都是吉。看挂的写心愿的牌牌的时候看到了一个希望 ETH 和 BTC 大涨的,太抽象了,你应该去求赛博佛祖.jpg

出去之后买个了抹茶冰激凌,确实味道不太一样,很好吃。

然后前往任天堂总部,发现就是两座楼,没啥好看的。然后前往京都铁道博物馆,我们到的时候已经四点了,五点闭馆,博物馆里有很多真的火车,科普也做得非常好,比如演示铁轨的原理。可惜只有一个小时来不及看。

image-20240427012131252

(这好像是初代新干线)

自行车五点半还车,日本人下班真早.jpg 晚上去吃一个米其林餐厅,去了一看给了个号预计七点二十吃上,于是去找了个咖啡厅吃了点小吃。这家米其林餐厅卖的是拉面,味道不错,但也没有特别惊艳。在大阪的时候我居然在一家书店的米其林指南里找到了这家店,很妙。

回到青旅后大厅有一桌子人在聊天,桌子上有一个德国男生,毕业工作了一年半后辞职出来旅行,中途还去面了个试;一个以色列男生,好像是在法国还是哪里上学,Gap 出来旅行;一个东京女生;两个加拿大人,岁数感觉有四十多了;一个台湾退休大爷;一个墨西哥人。我:阿巴阿巴。我很想加入进去但是口语太差,只能大部分时间听他们聊。台湾大爷讲了好多个笑话,简直是笑话大师。话题基本围绕着语言、每个人去过的地方和家乡展开。

4/19

早上在青旅吃早饭的时候碰到了昨天的台湾大爷,还有他的妻子。他们已经退休了,然后就到处旅游,这次来日本是报了一个语言学校学习日语,台湾可以免签逗留 90 天,所以他们的学习时长是两个月。他们很能聊,瞎扯了一堆。说自己的经历,问我的专业,说互联网公司的就业,说大陆的房价太高了,夸赞蒋经国搞好了台湾经济,又批评民进党把两岸关系搞得很差,又说现在台湾的年轻人已经对大陆没有感情了。那个阿姨的爷爷是苏州人在解放前迁往台湾的,她还会说两句苏州话“铜田”、“困觉”,非常妙。

吃完饭 checkout 后前往大阪,乘坐 Hankyu-Kyoto Line,在 Awaji Station 换乘 Hankyu-Senri Line,实际上我并没有看到 Hankyu-Senri Line,只要是开往天下茶屋(Tengachaya)方向的列车都可以坐,虽然 Google Maps 上标了两条线但是不用换乘。

(注意 “Continue on the same vehicle”)

相比于京都和东京,大阪简直是自由美利坚,地上随处可见烟头,人们在禁烟标志下吸烟,特别是在道顿堀那块,垃圾随处可见,太自由了。我们先去酒店放了行李,中午在 7-11 吃盒饭。

下午去中之岛,有几栋比较有特色的建筑,但也没有什么特别的看点。走到一个美术馆,看到有莫奈的展,于是进去看了一波。我的学生证居然还能买学生票。之前在国立西洋美术馆就买了两张莫奈的明信片,到这里又买了两张。这个展的国际化一般,很多画旁边只有日语写的说明,我只能借助 Google 翻译了。

(The Manneporte near Etretat)

然后去天王寺,在天王寺公园的草地上躺了会,看了会日落。吃旁边商场买的中华料理——饺子。

image-20240427003130229

晚上去道顿堀,感觉并不好看。路上看到了许许多多的居酒屋,大阪人是真爱喝酒。

4/20

今天没啥意思。商场感觉都一个样,本来想去坐坐阪堺电车的,感觉 Moo 不是很感兴趣,也没坐成。早上睡得比较晚,起床十一点了,就在 7-11 吃了早饭,然后去梅田,梅田感觉和道顿堀一样,都是商店街。Moo 在 7-11 买到了一个面包夹炒面,感觉非常黑暗。晚饭吃了个肯德基。

晚上去看了场脱衣舞(全裸!),门票 4500 日元,观众全是老大爷,还有一些游客。感觉也没啥意思,看得我都快睡着了。形式大概是表演一段,然后和观众打个招呼,收收小费。观众们给小费的挺多的,这些 stripper 感觉都很懂,面向一个方向的观众打招呼的时候都会特意露一下。

总体评价今天还不如在酒店看东京爱情故事。

image-20240427004442952

(酒店的电视)

4/21

今天踏上归途了,实际上今天和 4/13 都没有玩的部分,所以日本之旅去头去尾也就七天。今天是起床就出发去机场了。去机场坐的是 Nankai-Limited Express,Google Maps 上写的是坐一列特急的班次。站台上有个特急券售票机,我还在想需不需要买特急券。恰好正在买票的一个女生也是中国人(因为她选的语言是中文),正好问了一下,结果是需要的。但是可能太临近发车时间了,并没有买到(这辆车是全列指定席)。于是只能坐了下一班急行的列车。

机场里在过了海关和安检后有一堆免税店,据我在登机口的观察一大半的人都在免税店买了东西。免税店卖烟酒、零食、化妆品。然而 Moo 查了下发现化妆品其实并不比天猫国际更便宜。中华烟倒是比国内便宜,很神奇,如果买了是不是一种出口转内销呢。

image-20240427004646676

下午两点顺利返程。出机场的时候居然还看到了火山引擎的的巨幅广告,现在的云厂商都已经这么卷了吗。

铁路

日本的铁路系统和国内很不一样,首先它的地铁和铁路没有严格的区分,很多铁路都是横排座位,也就是地铁座位的常见布局。即使是纵排座位的列车,也大多是非指定席,我只见到东海道新干线的某几节车厢和大阪南海电铁特急列车是指定席。这就导致了比如在东京,我们的地铁通票(Tokyo Metro + Toei Subway)实际上并不能覆盖所有的公共交通,很多时候 Google Maps 会让我们坐 JR 山手线或者某个私铁。

其次铁路和车站非常密集,几乎随处可见铁轨、道口和轰隆隆行驶的列车。道口(日本称踏切)特有的铛铛-铛铛的提示声非常有感觉。

image-20240424215305194

(大阪天王寺附近的一个道口)

image-20240427020407790

(东京台场附近的一列车)

image-20240427021228647

(大阪梅田附近的一条铁路)

第一次坐 toei subway 的线路时感觉非常迷惑,地铁居然还分车的类型?还开往不同的终点站?一列车还能同时运行在两条线路上?到后来就习惯了。

image-20240424014310226

日本每条线路都会有一个或两个英文字母的编号,每个站有个数字编号,所以看 Google Maps 的时候只需要看比如乘坐 T(Tozai Line) T02->T03 换乘 JY(Yamanote Line) 到 JY20,而不需要去记站名了。但是 Google Maps 只对东京市内铁路有这个提示,其他铁路只会写线路编号,而没有站名编号了。

常开式的闸机也是好文明!就在不久前,上海地铁也开始试点这种模式了,真就学习国外先进经验了。

image-20240424014502291

(浅草附近的一个车站)

在山手线上还看到了一起事故,地铁飞人.jpg 网上查了查好像京王线的事故数量挺多的..

(山手线列车的显示屏)

另外每个站几乎都有列车的时刻表,而且基本都很准时,守时的日本人.jpg 每个站也基本都有各站的楼梯、卫生间等设施图。

在日本,你几乎能完全依靠公共交通,而且基本都是铁路。我们没有坐过公交车和出租车。在东京,基本上方圆一公里之内必有一个车站(当然,这可能只是市区的情况)。

其他感受

  • 日本人真有礼貌,动不动就すみません(私密马赛)。

  • 水很贵,一瓶 100-140 日元不等,但是 7-11 里 2L 的水也是 100 日元,就很奇怪。见到的最便宜的饮料是 80 日元的罐装咖啡,比水都便宜。

  • 语言方面,碰到的大部分人会简单的英语。也有英语比较好的,比如成田机场 skyliner 售票处、JR East 旅客服务中心和那位黑人老哥。东京第一晚青旅的前台小哥英文也很好,不过他看上去不是日本人。餐厅点餐、便利店买东西用英文进行简单的交流基本都没问题,当然还有另一种选择,那就是 Google 翻译。

  • 这次旅行基本都在看城市,下次还有机会去日本的话想去看看国家公园之类的地方,爬一爬富士山。七天的时间还是有点赶的,像在青旅的那桌人,基本都是一个月或者好几个月出来旅游。而在中国这种感觉不太现实,毕业成为社畜之后更难有大段的空闲时间了。

  • 这次旅行共计花费 9k 不到一点,机票占了 30%,酒店也差不多,剩下的是吃饭和景点门票。

    出发前我换了 4w 日元,到了日本后 Moo 又换了 4w 日元。几乎任何一个便利店都会有个 ATM 机以供取钱,在 7-11 取钱的时候好像会收十几块的手续费,走 JCB 的渠道购汇。剩下的消费靠信用卡。

    Suica 或者其他交通卡非常通用,几乎哪都能用。只是少数地方是 cash-only,不过我们在大阪见到的很多自动售货机都是 cash-only,比较落后。支付宝微信的普及度也很高,基本上能刷卡的地方就能用支付宝微信。

    image-20240427023828844

    (7-11 门口贴的支付方式一览)

    在米原吃饭的时候,Moo 想用微信支付结账,店员还操作了好一会(他们那块牌牌上写了支持微信支付),显然是第一次用(有种县城小饭店刷信用卡的感觉)。


最后修改于 2024-04-27