<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>周刊 on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/%E5%91%A8%E5%88%8A/</link>
    <description>Recent content in 周刊 on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Sun, 04 Sep 2022 09:10:31 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/%E5%91%A8%E5%88%8A/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>周刊（第24期）：sqlite并发读写的演进之路</title>
      <link>https://www.codedump.info/zh/post/20220904-weekly-24/</link>
      <pubDate>Sun, 04 Sep 2022 09:10:31 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220904-weekly-24/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本文梳理sqlite并发读写方案的演进之路。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;sqlite并发读写的演进之路&#34;&gt;&#xA;  sqlite并发读写的演进之路&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#sqlite%e5%b9%b6%e5%8f%91%e8%af%bb%e5%86%99%e7%9a%84%e6%bc%94%e8%bf%9b%e4%b9%8b%e8%b7%af&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;概论&#34;&gt;&#xA;  概论&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%ae%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;sqlite底层的存储基于B-tree，B-Tree对底层存储的基本读写单位是页面，而每个页面都由全局唯一的页面编号与之对应，一般来说页面编号从1开始递增。&lt;/p&gt;&#xA;&lt;p&gt;类B-Tree的存储引擎修改数据的流程如下图所示：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;b-tree&#34; src=&#34;https://www.codedump.info/media/imgs/20220904-weekly-24/b-tree.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; b-tree &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;从上图中，需要区分B-Tree类的存储引擎几个核心的模块：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;B-Tree算法模块：从页面管理器中读取页面到内存，进行逻辑的修改，修改完毕之后标记该页面为脏页面，这样页面管理器就知道哪些页面被修改，后续需要进行落盘。&lt;/li&gt;&#xA;&lt;li&gt;页面管理器：负责向B-Tree算法模块提供根据页面编号读、写页面的接口。&lt;/li&gt;&#xA;&lt;li&gt;数据库文件：这其实不是一个模块，泛指在磁盘上的数据库相关文件，任何的修改最终都要落到数据库文件。在sqlite中，数据库文件是单一文件，在其他存储引擎里可能是一组相关的文件。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;最上层的B-Tree算法模块，在进行写事务的时候，是首先向页面管理器发起读页面到内存中的请求，注意到B-Tree模块并不会直接跟数据库文件打交道，而是经过页面管理器模块（下面会展开说），修改了页面之后标记为“脏页面”，页面管理器最终负责将脏页面落盘到数据库文件中。&lt;/p&gt;&#xA;&lt;p&gt;现在来谈谈“页面管理器”模块的具体工作，也有的实现称为“缓存管理器（buffer manager）”。这个模块负责：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在内存中管理页面，这涉及到两部分内容：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如果页面当前不在内存中，需要根据页面编号到磁盘上加载页面。&lt;/li&gt;&#xA;&lt;li&gt;页面也并不是每一次读写时都要到磁盘上加载，有些时候页面已经在缓存中存在了，这种情况下不需要到磁盘上加载页面数据。于是，“页面管理器”模块还需要负责维护这些内存中的页面缓存，何时淘汰这些页面、淘汰哪些内存中的页面、何时真正从磁盘上加载，都是这个模块的工作。&lt;/li&gt;&#xA;&lt;li&gt;对外部而言（这里的外部更多的是B-Tree算法模块），其实不需要也看不到页面缓存的细节，页面管理器对外提供根据页面编号读、写页面接口即可。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;错误的恢复、事务的管理。比如：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一次事务要修改N个页面，修改到中间的时候，进程崩溃了，这时候重新启动时需要恢复到这个事务之前的数据成功启动，即需要提供回滚事务的功能。&lt;/li&gt;&#xA;&lt;li&gt;同样的一个事务要修改N个页面，在事务还未提交的时候，如果事务级别不是read uncommitted， 那么前面的修改效果不能被其他事务可见，这也是页面管理器需要做的事情，毕竟它对外提供了读、写页面的接口，同一个页面编号的页面什么时候的内容可见都由它来决定。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;有了这些基础的了解，我们来看看sqlite在并发读写方面的演进之路。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;journal&#34;&gt;&#xA;  Journal&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#journal&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;最早的页面管理器实现是基于Journal文件的，这个文件存储页面在修改之前的内容：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;journal&#34; src=&#34;https://www.codedump.info/media/imgs/20220904-weekly-24/journal.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; journal &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;可以看到的是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Journal文件存储了一个事务所要修改的页面在修改之前的内容，这个定义有点拗口，姑且称为“旧页面内容”。&lt;/li&gt;&#xA;&lt;li&gt;每次一个事务提交之后，意味着这个事务所有队页面的修改都已经落到了数据库文件中，这时候Journal文件里保存的旧页面内容就不再需要了，可以被删除了。&lt;/li&gt;&#xA;&lt;li&gt;由于每次事务修改都要落盘到数据库文件，这些落盘操作涉及到多次磁盘寻道，即一次事务多次随机磁盘寻道，这样代价其实是很大的。&lt;/li&gt;&#xA;&lt;li&gt;当需要事务回滚的功能时，页面管理器就可以从Journal文件中读出来旧页面内容覆盖回去。&lt;/li&gt;&#xA;&lt;li&gt;虽然这个算法很简单，但是缺陷也明显：它没有任何的读写并发支持。每次开始一个写事务，从开始写事务，到这个写事务提交完成的过程中间，其他的读写事务都不能开始，可以说是“一写全卡住”。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;wal&#34;&gt;&#xA;  WAL&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#wal&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;从上面的分析可以看出，以Journal文件的机制，每次写事务：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;需要把内容修改全部落盘到数据库文件才能算完成。&lt;/li&gt;&#xA;&lt;li&gt;这个过程中间，不能同时存在其他并发的读、写操作。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;从sqlite3.7.0版本开始（&lt;a href=&#34;https://www.sqlite.org/releaselog/3_7_0.html&#34;&gt;SQLite Release 3.7.0 On 2010-07-21&lt;/a&gt;），sqlite引入了更常见的WAL机制来解决页面的读写并发问题，WAL的原理如下图所示：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;wal&#34; src=&#34;https://www.codedump.info/media/imgs/20220904-weekly-24/wal.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; wal &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;WAL机制中，事务对页面的修改：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;并没有马上落到数据库文件里，而是首先写入WAL文件中。这样有两个好处：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;WAL文件是append-only的文件，在文件结尾处添加新内容，对写磁盘文件这种操作而言是更快的，因为少了很多磁盘寻道的流程。&lt;/li&gt;&#xA;&lt;li&gt;由于事务的修改并没有马上落盘到数据库文件，所以就并不可见，后续如果需要回滚事务的修改也更容易：不要这个事务修改的那部分WAL内容即可。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;由于修改有时候还未落盘，需要维护一个wal中页面的索引，用于根据页面编号定位到WAL中的页面。由于wal索引可以控制哪些wal文件内容“可见”，于是就能控制未提交的事务修改对读操作并不可见了。&lt;/li&gt;&#xA;&lt;li&gt;WAL文件不能一直增长下去，需要定期把WAL文件中已经提交的事务修改内容落盘到数据库文件，这个流程被称为“checkpoint”。在“checkpoint”之后，wal索引就可以修改了。虽然checkpoint过程将WAL文件中的内容落盘到数据库文件，仍然是针对数据库文件的随机写流程，有很多磁盘寻道操作，但是由于一次checkpoint累计了多次写事务一次性落盘，代价小了一些。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;有了WAL之后，读写并发有了一些改善：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;虽然同一时间仍然只能有一个写事务在进行，但是读事务同时存在多个。其核心原因是因为修改并没有马上直接落盘到数据库文件中，这样修改的可见性就可以由wal索引来控制，即：写事务尽管写，读事务尽管读，只要控制这些写事务的修改不在wal索引中可见即可。&lt;/li&gt;&#xA;&lt;li&gt;WAL虽然支持“一写多读”，而不是Journal文件那样的“一写全卡住”，但是还有一个问题没有解决：在做checkpoint操作的时候，连写事务也不能进行了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;两个可能的优化方案&#34;&gt;&#xA;  两个可能的优化方案&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%a4%e4%b8%aa%e5%8f%af%e8%83%bd%e7%9a%84%e4%bc%98%e5%8c%96%e6%96%b9%e6%a1%88&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;以下介绍sqlite目前在讨论的两个优化方案，之所以说是“可能”，因为看这部分代码还并没有合并到主干中，目前暂时还在分支里，参见：https://github.com/sqlite/sqlite/tree/begin-concurrent-pnu-wal2。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;wal-2&#34;&gt;&#xA;  WAL-2&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#wal-2&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;为了解决“checkpoint时无法进行写事务”的痛点，sqlite目前在尝试新的WAL-2机制。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;wal-2&#34; src=&#34;https://www.codedump.info/media/imgs/20220904-weekly-24/wal-2.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; wal-2 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;引入WAL-2之后，同时有两个WAL文件，这样可以：checkpoint其中一个WAL文件时，继续写另一个WAL文件，下一次再进行checkpoint时进行切换，这样checkpoint就不会阻塞住写操作。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;begin-concurrent&#34;&gt;&#xA;  BEGIN CONCURRENT&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#begin-concurrent&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;目前的WAL机制，都只能支持同一时间一个写事务，&lt;code&gt;BEGIN CONCURRENT&lt;/code&gt;机制可以实现多个写并发，这篇&lt;a href=&#34;https://www.sqlite.org/cgi/src/doc/begin-concurrent/doc/begin_concurrent.md&#34;&gt;SQLite: Begin Concurrent&lt;/a&gt;文档中，大概描述了一下这个优化的思路：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第23期）：图解Blink-Tree：B&#43;Tree的一种并发优化结构和算法</title>
      <link>https://www.codedump.info/zh/post/20220807-weekly-23/</link>
      <pubDate>Sun, 07 Aug 2022 10:33:38 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220807-weekly-23/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：&lt;a href=&#34;https://www.csd.uoc.gr/~hy460/pdf/p650-lehman.pdf&#34;&gt;《Efficient Locking for Concurrent Operations on B-Trees 》&lt;/a&gt;论文中提出了一种称为“Blink-Tree”的数据结构，这个数据结构提供了B+Tree并发访问的一些优化方式，本文对这篇论文进行解读。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概论&#34;&gt;&#xA;  概论&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%ae%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;由于Blink-Tree本质上是B+Tree的一种优化，所以要理解它首先要对B+Tree有一些了解，在这以前介绍过B+Tree，就不在这里阐述了，可以参考：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;B树、B+树索引算法原理（上） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200615-btree-2/&#34;&gt;B树、B+树索引算法原理（下） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我们来看如果同时存在两个读写操作并发访问一颗B+Tree，会出现什么问题，见下图：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;b&amp;#43;tree-1&#34; src=&#34;https://www.codedump.info/media/imgs/20220807-weekly-23/b&amp;#43;tree-1.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; b+tree-1 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;进程P1查询数据15，而进程P2写入数据9，当P2写入数据完毕时，树结构变成了下图这样：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;b&amp;#43;tree-2&#34; src=&#34;https://www.codedump.info/media/imgs/20220807-weekly-23/b&amp;#43;tree-2.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; b+tree-2 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;由于原先的叶子节点要满足B+Tree的性质，所以分成了两个叶子节点，而这时P1进程对此并没有感知，还停留在旧的节点上，于是就导致了查询数据15失败。&lt;/p&gt;&#xA;&lt;p&gt;一种最直观的优化方式是读、写的时候加全局锁，但是这样做的效率不高。Blink-Tree就是为了高效解决这类并发访问问题引入的一种结构和算法。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;数据结构&#34;&gt;&#xA;  数据结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Blink-Tree本质上还是一颗B+Tree，即数据存储在叶子节点上的B-Tree。&lt;/p&gt;&#xA;&lt;p&gt;对于一颗&lt;code&gt;k-degree&lt;/code&gt;的Blink-Tree而言，它有如下的性质：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;所有叶子节点是同一高度的，即从根节点到每个叶子节点都是同一长度。（Each path from the root to any leaf has the same length, h.）&lt;/li&gt;&#xA;&lt;li&gt;对于每个内部节点而言，除非是根节点，否则都至少有&lt;code&gt;k+1&lt;/code&gt;子节点。（Each node except the root and the leaves has at least k + 1 sons.）&lt;/li&gt;&#xA;&lt;li&gt;根节点要么是叶子节点，否则至少有两个子节点。（The root is a leaf or has at least two sons.）&lt;/li&gt;&#xA;&lt;li&gt;内部节点最多有&lt;code&gt;2k+1&lt;/code&gt;个子节点（Each node has at most 2k + 1 sons），结合上面的内容即内部节点的子节点数量在&lt;code&gt;[k+1,2k+1]&lt;/code&gt;之间。&lt;/li&gt;&#xA;&lt;li&gt;数据都存储在叶子节点上。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以看到，上面的性质和B+Tree很相似，在此基础上Blink-Tree还增加了以下数据：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第22期）：图解一致性模型</title>
      <link>https://www.codedump.info/zh/post/20220710-weekly-22/</link>
      <pubDate>Sun, 10 Jul 2022 14:41:24 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220710-weekly-22/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本文使用大量的图例，同时没有难懂的公式，意图解释清楚一致性模型要解决什么问题，以及三种一致性模型：顺序一致性、线性一致性、因果一致性。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;图解一致性模型&#34;&gt;&#xA;  图解一致性模型&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9b%be%e8%a7%a3%e4%b8%80%e8%87%b4%e6%80%a7%e6%a8%a1%e5%9e%8b&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;解决什么问题&#34;&gt;&#xA;  解决什么问题？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%a7%a3%e5%86%b3%e4%bb%80%e4%b9%88%e9%97%ae%e9%a2%98&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;分布式系统要保证系统的可用性，就需要对数据提供一定的冗余度：一份数据，要存储在多个服务器上，才能认为保存成功，至于这里要保存的冗余数，有&lt;code&gt;Majority&lt;/code&gt;和&lt;code&gt;Quorum&lt;/code&gt;之说，可以参考之前的文章：&lt;a href=&#34;https://www.codedump.info/post/20220528-weekly-17/&#34;&gt;周刊（第17期）：Read-Write Quorum System及在Raft中的实践&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;p&gt;同一份数据保存在多个机器上提供冗余度，也被称为&lt;code&gt;副本(replica)策略&lt;/code&gt;，这个做法带来下面的好处：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;容错性：即便分布式系统中几台机器不能工作，系统还能照常对外提供服务。&lt;/li&gt;&#xA;&lt;li&gt;提升吞吐量：既然同一份数据存储在多个机器上，对该数据的请求（至少是读请求）能够分担到多个副本上，这样整个系统可以线性扩容增加更多的机器以应付请求量的增加。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;同时，副本策略也有自己需要解决的问题，其中最重要的问题就是一致性问题：在系统中的一个机器写入的数据，是否在系统中其他机器看来也是一样的？&lt;/p&gt;&#xA;&lt;p&gt;很显然，即便在一切都正常工作的条件下，在系统中的一个机器成功写入了数据，因为广播这个修改到系统中的其他机器还需要时间，那么系统的其他机器看到这个修改的结果也还是需要时间的。换言之，中间的这个&lt;code&gt;时间差&lt;/code&gt;可能出现短暂的数据不一致的情况。&lt;/p&gt;&#xA;&lt;p&gt;可以看到，由于这个&lt;code&gt;时间差&lt;/code&gt;的客观存在，并不存在一个&lt;code&gt;绝对&lt;/code&gt;意义上的数据一致性。换言之，&lt;code&gt;数据一致性&lt;/code&gt;有其实现的严格范围，越严格的数据一致，要付出的成本、代价就越大。&lt;/p&gt;&#xA;&lt;p&gt;为了解决一致性问题，需要首先定义一致性模型，在维基的页面上，&lt;code&gt;一致性模型（Consistency model）&lt;/code&gt;的定义如下：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;In computer science, a consistency model specifies a contract between the programmer and a system, wherein the system guarantees that if the programmer follows the rules for operations on memory, memory will be consistent and the results of reading, writing, or updating memory will be predictable.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;我们举一个日常生活中常见的问题来解释&lt;code&gt;一致性模型&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;wechat&#34; src=&#34;https://www.codedump.info/media/imgs/20220710-weekly-22/wechat.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; wechat &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第21期）：Lamport时钟介绍</title>
      <link>https://www.codedump.info/zh/post/20220703-weekly-21/</link>
      <pubDate>Sun, 03 Jul 2022 10:59:09 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220703-weekly-21/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：在分布式系统中，由于有多个机器（进程）在一起协调工作，于是如何定义分布式系统中事件的先后顺序就成了难题，本文介绍论文 &lt;a href=&#34;https://lamport.azurewebsites.net/pubs/time-clocks.pdf&#34;&gt;《Time, Clocks, and the Ordering of Events in a Distributed System》&lt;/a&gt;中提到的Lamport时钟。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;lamport时钟介绍&#34;&gt;&#xA;  Lamport时钟介绍&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#lamport%e6%97%b6%e9%92%9f%e4%bb%8b%e7%bb%8d&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;概论&#34;&gt;&#xA;  概论&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%ae%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在分布式系统中，由于有多个机器（进程）在一起协调工作，于是如何定义分布式系统中事件的先后顺序就成了难题，本文介绍论文 &lt;a href=&#34;https://lamport.azurewebsites.net/pubs/time-clocks.pdf&#34;&gt;《Time, Clocks, and the Ordering of Events in a Distributed System》&lt;/a&gt;中提到的Lamport时钟。&lt;/p&gt;&#xA;&lt;p&gt;内容以如下的顺序展开：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;物理时钟的问题在哪里？（解决了什么问题）&lt;/li&gt;&#xA;&lt;li&gt;全序和偏序关系。（数学基础）&lt;/li&gt;&#xA;&lt;li&gt;Lamport时钟的原理介绍、&lt;code&gt;happen-before&lt;/code&gt;关系介绍。（原理介绍）&lt;/li&gt;&#xA;&lt;li&gt;分布式一致性的基础。（更远的影响）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;物理时钟的问题&#34;&gt;&#xA;  物理时钟的问题&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%89%a9%e7%90%86%e6%97%b6%e9%92%9f%e7%9a%84%e9%97%ae%e9%a2%98&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;分布式系统中定义一个事件的先后顺序是一个难点，下意识的第一反应是：给每个事件加上一个物理的时间戳，不就可以比较不同事件的时间戳来决定其顺序了吗？&lt;/p&gt;&#xA;&lt;p&gt;这样做的问题在于：在分布式系统中，由多个机器组合起来协调工作，而每个机器上的物理时间也不尽相同，所以“物理时间戳”本质上是一个机器属性，并不一定系统中所有机器都满足同一个时间度量。&lt;/p&gt;&#xA;&lt;p&gt;以下图为例：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;&#34; src=&#34;https://sookocheff.com/post/time/lamport-clock/assets/clock-adjustment-error.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;（引用自&lt;a href=&#34;https://sookocheff.com/post/time/lamport-clock/&#34;&gt;Lamport Clocks - Kevin Sookocheff&lt;/a&gt;）&lt;/p&gt;&#xA;&lt;p&gt;上图中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;server A在发出事件A时，打上了本机的时间戳1点。&lt;/li&gt;&#xA;&lt;li&gt;同理，server B给事件B打上了本机的时间戳12:59。&lt;/li&gt;&#xA;&lt;li&gt;可以看到这两个事件都以本地时间为准，当观察者进程收到这两个事件的时候，先后顺序与事件上所带的时间戳并不一致：先收到了时间戳更大的事件A。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这个例子说明：在分布式系统中，以“物理时间”来衡量事件的先后顺序，并不可行。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;全序和偏序&#34;&gt;&#xA;  全序和偏序&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%85%a8%e5%ba%8f%e5%92%8c%e5%81%8f%e5%ba%8f&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在继续讲解之前，还需要了解两个数学上的概念：全序（total ordering）和偏序（partial ordering）关系。&lt;/p&gt;&#xA;&lt;p&gt;我们首先来定义集合上的几种关系，对一个集合${\displaystyle X}$中的${\displaystyle a,b}$和${\displaystyle c}$ 而言，有以下这些关系：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;自反性：∀a∈S，有a≤a。&lt;/li&gt;&#xA;&lt;li&gt;反对称性：若 $ {\displaystyle a\leq b}$且$ {\displaystyle b\leq a} $ 则 $ {\displaystyle a=b} $。&lt;/li&gt;&#xA;&lt;li&gt;传递性：若${\displaystyle a\leq b} $ 且 $ {\displaystyle b\leq c} $ 则 $ {\displaystyle a\leq c} $。&lt;/li&gt;&#xA;&lt;li&gt;完全性：$ {\displaystyle a\leq b} $ 或 $ {\displaystyle b\leq a} $。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;有了这几种关系之后，就可以看看全序和偏序关系分别满足以上的哪些关系了：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第20期）：Rust并发安全相关的几个概念(下)</title>
      <link>https://www.codedump.info/zh/post/20220625-weekly-20/</link>
      <pubDate>Sat, 25 Jun 2022 10:00:49 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220625-weekly-20/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本文介绍Rust并发安全相关的几个概念：&lt;code&gt;Send&lt;/code&gt;、&lt;code&gt;Sync&lt;/code&gt;、&lt;code&gt;Arc&lt;/code&gt;，&lt;code&gt;Mutex&lt;/code&gt;、&lt;code&gt;RwLock&lt;/code&gt;等之间的联系。这是其中的下篇，主要介绍&lt;code&gt;Arc&lt;/code&gt;，&lt;code&gt;Mutex&lt;/code&gt;、&lt;code&gt;RwLock&lt;/code&gt;这几个线程安全相关的类型。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;rust并发安全相关的几个概念下&#34;&gt;&#xA;  Rust并发安全相关的几个概念（下）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#rust%e5%b9%b6%e5%8f%91%e5%ae%89%e5%85%a8%e7%9b%b8%e5%85%b3%e7%9a%84%e5%87%a0%e4%b8%aa%e6%a6%82%e5%bf%b5%e4%b8%8b&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在&lt;a href=&#34;https://www.codedump.info/post/20220619-weekly-19/&#34;&gt;上一节&lt;/a&gt;中，讲解了&lt;code&gt;Send&lt;/code&gt;和&lt;code&gt;Sync&lt;/code&gt;这两个线程安全相关的&lt;code&gt;trait&lt;/code&gt;，在此基础上展开其它相关类型的讲解。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;rc&#34;&gt;&#xA;  Rc&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#rc&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;Rc&lt;/code&gt;是&lt;code&gt;Reference Counted（引用计数）&lt;/code&gt;的简写，在Rust中，这个数据结构用于实现单线程安全的对指针的引用计数。之所以这个数据结构只是单线程安全，是因为在定义中显式声明了并不实现&lt;code&gt;Send&lt;/code&gt;和&lt;code&gt;Sync&lt;/code&gt;这两个&lt;code&gt;trait&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#[stable(feature = &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;rust1&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;, since = &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;impl&lt;/span&gt;&amp;lt;T: ?&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Sized&lt;/span&gt;&amp;gt; !marker::&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Send&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;for&lt;/span&gt; Rc&amp;lt;T&amp;gt; {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#[stable(feature = &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;rust1&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;, since = &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;impl&lt;/span&gt;&amp;lt;T: ?&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Sized&lt;/span&gt;&amp;gt; !marker::&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Sync&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;for&lt;/span&gt; Rc&amp;lt;T&amp;gt; {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;个中原因，是因为&lt;code&gt;Rc&lt;/code&gt;内部的实现中，使用了非原子的引用计数（non-atomic reference counting），因此就不能满足线程安全的条件了。如果要在多线程中使用引用计数，就要使用&lt;code&gt;Arc&lt;/code&gt;这个类型：&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;arc&#34;&gt;&#xA;  Arc&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#arc&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;与&lt;code&gt;Rc&lt;/code&gt;不同的是，&lt;code&gt;Arc&lt;/code&gt;内部使用了原子操作来实现其引用计数，因此&lt;code&gt;Arc&lt;/code&gt;是&lt;code&gt;Atomically Reference Counted（原子引用计数）&lt;/code&gt;的简写，能被使用在多线程环境中，缺陷是原子操作的性能消耗会更大一些。&lt;/p&gt;&#xA;&lt;p&gt;虽然&lt;code&gt;Arc&lt;/code&gt;能被用在多线程环境中，并不意味着&lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt;天然就实现了&lt;code&gt;Send&lt;/code&gt;和&lt;code&gt;Sync&lt;/code&gt;，来看看这两部分的声明：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#[stable(feature = &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;rust1&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;, since = &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;unsafe&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;impl&lt;/span&gt;&amp;lt;T: ?&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Sized&lt;/span&gt; + &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Sync&lt;/span&gt; + &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Send&lt;/span&gt;&amp;gt; &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Send&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;for&lt;/span&gt; Arc&amp;lt;T&amp;gt; {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#[stable(feature = &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;rust1&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;, since = &lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;unsafe&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;impl&lt;/span&gt;&amp;lt;T: ?&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Sized&lt;/span&gt; + &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Sync&lt;/span&gt; + &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Send&lt;/span&gt;&amp;gt; &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Sync&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;for&lt;/span&gt; Arc&amp;lt;T&amp;gt; {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从声明可以看出：一个&lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt;类型，当且仅当包裹（wrap）的类型&lt;code&gt;T&lt;/code&gt;满足&lt;code&gt;Sync&lt;/code&gt;和&lt;code&gt;Send&lt;/code&gt;时才能被认为是满足&lt;code&gt;Send&lt;/code&gt;和&lt;code&gt;Sync&lt;/code&gt;的类型。来做一个实验：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#![feature(negative_impls)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;use&lt;/span&gt; std::sync::Arc;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#[derive(Debug)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Foo&lt;/span&gt; {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;impl&lt;/span&gt; !&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;Send&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;for&lt;/span&gt; Foo {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;main&lt;/span&gt;() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;font-style:italic;text-decoration:underline&#34;&gt;let&lt;/span&gt; foo = Arc::new(Foo {});&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    std::thread::spawn(&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;move&lt;/span&gt; || {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        dbg!(foo);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在以上的代码中，由于在第8行显示声明了&lt;code&gt;Foo&lt;/code&gt;这个类型不满足&lt;code&gt;Sync&lt;/code&gt;，所以这段代码编译不过，报错信息如下：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第19期）：Rust并发安全相关的几个概念(上)</title>
      <link>https://www.codedump.info/zh/post/20220619-weekly-19/</link>
      <pubDate>Sun, 19 Jun 2022 10:42:40 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220619-weekly-19/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本文介绍Rust并发安全相关的几个概念：&lt;code&gt;Send&lt;/code&gt;、&lt;code&gt;Sync&lt;/code&gt;、&lt;code&gt;Arc&lt;/code&gt;，&lt;code&gt;Mutex&lt;/code&gt;、&lt;code&gt;RwLock&lt;/code&gt;等之间的联系。这是其中的上篇，主要介绍&lt;code&gt;Send&lt;/code&gt;、&lt;code&gt;Sync&lt;/code&gt;这两个&lt;code&gt;trait&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;rust并发安全相关的几个概念上&#34;&gt;&#xA;  Rust并发安全相关的几个概念（上）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#rust%e5%b9%b6%e5%8f%91%e5%ae%89%e5%85%a8%e7%9b%b8%e5%85%b3%e7%9a%84%e5%87%a0%e4%b8%aa%e6%a6%82%e5%bf%b5%e4%b8%8a&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;rust的所有权概念&#34;&gt;&#xA;  Rust的所有权概念&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#rust%e7%9a%84%e6%89%80%e6%9c%89%e6%9d%83%e6%a6%82%e5%bf%b5&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在展开介绍并发相关的几个概念之前，有必要先了解一下Rust的所有权概念，Rust对值（value）的所有权有明确的限制：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一个值只能有一个owner。&lt;/li&gt;&#xA;&lt;li&gt;可以同时存在同一个值的多个共享的非可变引用（immutable reference）。&lt;/li&gt;&#xA;&lt;li&gt;但是只能存在一个值的可变引用（mutable reference）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;比如下面这段代码，user在创建线程之后，被移动（move）到两个不同的线程中：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;fn main() {&#xA;    let user = User { name: &amp;#34;drogus&amp;#34;.to_string() };&#xA;&#xA;    let t1 = spawn(move || {&#xA;        println!(&amp;#34;Hello from the first thread {}&amp;#34;, user.name);&#xA;    });&#xA;&#xA;    let t2 = spawn(move || {&#xA;        println!(&amp;#34;Hello from the second thread {}&amp;#34;, user.name);&#xA;    });&#xA;&#xA;    t1.join().unwrap();&#xA;    t2.join().unwrap();&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;由于&lt;code&gt;一个值只能有一个owner&lt;/code&gt;，所以编译器报错，报错信息如下：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;error[E0382]: use of moved value: `user.name`&#xA;  --&amp;gt; src/main.rs:15:20&#xA;   |&#xA;11 |     let t1 = spawn(move || {&#xA;   |                    ------- value moved into closure here&#xA;12 |         println!(&amp;#34;Hello from the first thread {}&amp;#34;, user.name);&#xA;   |                                                    --------- variable moved due to use in closure&#xA;...&#xA;15 |     let t2 = spawn(move || {&#xA;   |                    ^^^^^^^ value used here after move&#xA;16 |         println!(&amp;#34;Hello from the second thread {}&amp;#34;, user.name);&#xA;   |                                                    --------- use occurs due to use in closure&#xA;   |&#xA;   = note: move occurs because `user.name` has type `String`, which does not implement the `Copy` trait&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 class=&#34;heading&#34; id=&#34;send和sync的约束作用&#34;&gt;&#xA;  Send和Sync的约束作用&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#send%e5%92%8csync%e7%9a%84%e7%ba%a6%e6%9d%9f%e4%bd%9c%e7%94%a8&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;于是，如果一个类型会被多个线程所使用，是需要明确说明其共享属性的。&lt;code&gt;Send&lt;/code&gt;和&lt;code&gt;Sync&lt;/code&gt;这两个&lt;code&gt;trait&lt;/code&gt;作用就在于此，注意到这两个&lt;code&gt;trait&lt;/code&gt;都是&lt;code&gt;std::marker&lt;/code&gt;，实现这两个&lt;code&gt;trait&lt;/code&gt;并不需要对应实现什么方法，可以理解为这两个&lt;code&gt;trait&lt;/code&gt;是类型的&lt;code&gt;约束&lt;/code&gt;，编译器通过这些&lt;code&gt;约束&lt;/code&gt;在编译时对类型进行检查。到目前为止，暂时不展开对两个概念的理解，先来看看两者是如何在类型检查中起&lt;code&gt;约束&lt;/code&gt;作用的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第18期）：网状的思考，线性的写作</title>
      <link>https://www.codedump.info/zh/post/20220612-weekly-18/</link>
      <pubDate>Sun, 12 Jun 2022 10:19:47 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220612-weekly-18/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本文介绍我理解的“卡片式笔记法”，以及我的笔记实践、工具等。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;网状的思考线性的写作&#34;&gt;&#xA;  网状的思考，线性的写作&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%bd%91%e7%8a%b6%e7%9a%84%e6%80%9d%e8%80%83%e7%ba%bf%e6%80%a7%e7%9a%84%e5%86%99%e4%bd%9c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;现实世界中的思考&#34;&gt;&#xA;  现实世界中的思考&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%8e%b0%e5%ae%9e%e4%b8%96%e7%95%8c%e4%b8%ad%e7%9a%84%e6%80%9d%e8%80%83&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;我们的大脑，每时每刻都在进行着一些思考：走在路上、做饭、看书看电影时，等等。&lt;/p&gt;&#xA;&lt;p&gt;而在物理的时空上，肉身在任意时刻只能身处在一个物理意义上的空间里，在时间上也只能处于一个时间点上。&lt;/p&gt;&#xA;&lt;p&gt;可见，思考产生的念头，和时空的限制对比起来，是发散的、不确定的。如下图所示：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;time-thinking&#34; src=&#34;https://www.codedump.info/media/imgs/20220612-weekly-18/time-thinking.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; time-thinking &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;很多时候，思考并不是凭空而生，可能是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;前几天有过一次对某问题的思考。&lt;/li&gt;&#xA;&lt;li&gt;过了几天，想起来这个问题，又有了另外的考虑和补充。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;当需要记录下来这些想法为笔记时，单篇笔记本身是线型的，只能算是一个一维的创作，这是因为从物理角度来理解一篇“笔记”的话，它在物理上只能存储在一个地方（比如只能是一个文件）。如果按照“一维”的视角，去记录写作的想法，可能就会这样做：某天产生了一个想法，把它记录下来；隔几天有了对这个想法的补充，要么找到前几天的想法记录的地方，继续在这上面补充，也或者另开一篇再记录一次今天的想法。如果这样做，就会得到类似上图那样的效果：不同时空的思考之间，缺少了联系。&lt;/p&gt;&#xA;&lt;p&gt;从这个意义上来说，用只有一个物理维度的“笔记”，来试图存储维度不限的想法、念头，并不适合。这种做法相当于把多维度的想法、念头给降维了。什么时候更适用于这类“降维”的写作行为？我认为是在思考已经很清楚，能够把想法、念头整合在一篇完整的文章时才适合，这时候这篇输出的文章是经过整理、且有完整的观点的。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;article&#34; src=&#34;https://www.codedump.info/media/imgs/20220612-weekly-18/article.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; article &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在更多的时候人是在不同的时空进行不同的思考，这时候就需要另外的工具来整合这些念头，“卡片式笔记法”就适用于这种场景。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;卡片式笔记法&#34;&gt;&#xA;  卡片式笔记法&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%8d%a1%e7%89%87%e5%bc%8f%e7%ac%94%e8%ae%b0%e6%b3%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;“卡片式笔记法”中的“卡片”，对应的是前述场景中不同时空下产生的想法、念头。与传统意义上的“笔记”不同的是，“卡片式笔记法”中的记录粒度更细，可以任意想法就能记录在它所谓的“卡片”上。同时，在每个“卡片”赋予一个逻辑上的“地址”，这个&lt;code&gt;逻辑地址&lt;/code&gt;类似于编程中的IP地址、超链接等概念。当有了另外的和这个想法有关联的其他想法时，可以再创建另外的卡片，不同的卡片之间通过&lt;code&gt;逻辑地址&lt;/code&gt;进行关联。同时，为了更好的查找同类的想法，可以使用&lt;code&gt;tag&lt;/code&gt;等方式打上标签，便于搜索、归类。&lt;/p&gt;&#xA;&lt;p&gt;下图是一个典型的“卡片笔记”组成示意图：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Unique Identity：这篇笔记的唯一ID，也就是上述的&lt;code&gt;逻辑地址&lt;/code&gt;，其它笔记可以通过这个唯一的&lt;code&gt;逻辑地址&lt;/code&gt;和这篇笔记发生关联。&lt;/li&gt;&#xA;&lt;li&gt;Tags：这篇笔记的标签。笔记的“物理地址”只能有一个（比如存储在哪个目录的哪个文件里），但是逻辑上可以位于多个标签下，在标签这个维度上可以无限制。这样，多个相同标签的笔记就能发生联系。&lt;/li&gt;&#xA;&lt;li&gt;Links：与这篇笔记相关联的、之前写过的笔记的&lt;code&gt;逻辑地址&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;complete-zettel&#34; src=&#34;https://www.codedump.info/media/imgs/20220612-weekly-18/complete-zettel.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; complete-zettel &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;（出自&lt;a href=&#34;https://zettelkasten.de/introduction/zh/&#34;&gt;卢曼卡片盒笔记法介绍 (Introduction to the Zettelkasten Method) • Zettelkasten Method&lt;/a&gt;）&lt;/p&gt;&#xA;&lt;p&gt;可以看到，“卡片式笔记”与传统笔记相比：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;笔记可以更细粒度，只记录某个时刻的某个具体念头即可，更强调笔记的原子化（Atomicity）。当产生了新的念头之后，并不需要去补充到之前的笔记中，因为笔记是&lt;code&gt;原子化&lt;/code&gt;的，只记录一个瞬间的想法，有了新的念头之后，只需要新建笔记与之前的笔记产生关联。&lt;/li&gt;&#xA;&lt;li&gt;笔记有唯一的&lt;code&gt;逻辑地址&lt;/code&gt;，可以打上不同的tags。&lt;/li&gt;&#xA;&lt;li&gt;笔记与笔记之间，可以通过&lt;code&gt;逻辑地址&lt;/code&gt;、&lt;code&gt;tag&lt;/code&gt;来发生关联。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;有了“卡片式笔记”之后，之前在单一时空维度的想法念头，可能就演变成这样的了：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;connected-thinking&#34; src=&#34;https://www.codedump.info/media/imgs/20220612-weekly-18/connected-thinking.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; connected-thinking &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;可以看到，在“卡片式笔记”的视角下，对某个事情有了思考之后，针对这件事情的思考可能发生在不同的时空里，想法和想法之间互相联系、互为补充，不需要再把它们局限、降维仅仅记录在单篇物理意义上的“笔记”里了。在上图中，时间点C产生的想法C，和想法B、A产生了关联，于是想法C加上对这两条笔记的链接，这样并不需要回头对原有的笔记A、B进行补充，只需要新增笔记C，并且增加链接即可。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;我的笔记实践&#34;&gt;&#xA;  我的笔记实践&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%88%91%e7%9a%84%e7%ac%94%e8%ae%b0%e5%ae%9e%e8%b7%b5&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;最后聊一聊我自己怎么记录笔记的，我的笔记分为两个类型：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;碎片化的想法。&lt;/li&gt;&#xA;&lt;li&gt;相对正式的记录。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;碎片化的想法&#34;&gt;&#xA;  碎片化的想法&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%a2%8e%e7%89%87%e5%8c%96%e7%9a%84%e6%83%b3%e6%b3%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;一类是前述提到的一些碎片化的想法，这部分记录在&lt;code&gt;flomo&lt;/code&gt;上。&lt;/p&gt;&#xA;&lt;p&gt;在&lt;code&gt;flomo&lt;/code&gt;上，可以对笔记打标签、还可以通过它所谓的&lt;a href=&#34;https://help.flomoapp.com/advance/thread.html&#34;&gt;🔗 批注连接&lt;/a&gt; 功能给之前的想法做补充，其实这里的&lt;code&gt;批注&lt;/code&gt;就是前面提到的在不同的笔记之间发生关联。&lt;/p&gt;&#xA;&lt;p&gt;使用&lt;code&gt;flomo&lt;/code&gt;记录这些零星想法的好处是：心智负担很低，随手就能记录，不需要在乎格式、是否美观等等，一个工具使用起来的心智负担越低，越能提升做这类事情的频率。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;相对正式的记录&#34;&gt;&#xA;  相对正式的记录&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%9b%b8%e5%af%b9%e6%ad%a3%e5%bc%8f%e7%9a%84%e8%ae%b0%e5%bd%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;另一类是相对更正式的一些记录，比如每天的日志、阅读某些内容之后的笔记等，这部分使用Markdown方式记录在本地的文件中，同时还按照前面的规则，给需要打标签分类的记录打上标签，在需要笔记之间发生关联的时候，切换到&lt;code&gt;obsidian &lt;/code&gt;下面使用&lt;code&gt;[[]]&lt;/code&gt;的方式关联笔记。对&lt;code&gt;obsidian&lt;/code&gt;使用感兴趣的可以看参考资料中推荐的&lt;code&gt;Obsidian&lt;/code&gt;的使用介绍文章。&lt;/p&gt;&#xA;&lt;p&gt;在这篇演示&lt;code&gt;Obsidian&lt;/code&gt;双链效果的文章&lt;a href=&#34;https://publish.obsidian.md/chinesehelp/01&amp;#43;2021%E6%96%B0%E6%95%99%E7%A8%8B/%E5%8F%8C%E9%93%BE%E8%BD%AF%E4%BB%B6&#34;&gt;双链软件 - Obsidian中文教程&lt;/a&gt;中，演示了&lt;code&gt;Obsidian&lt;/code&gt;下双链的效果。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;why-not-logseq&#34;&gt;&#xA;  Why not Logseq？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#why-not-logseq&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;除了&lt;code&gt;Obdisian&lt;/code&gt;之外，国人的作品&lt;code&gt;Logseq&lt;/code&gt;也是一款很好的笔记软件，但是我并没有把它做为自己的主力软件。之前尝试用&lt;code&gt;Logseq&lt;/code&gt;好几次，并不是很习惯，我回想起来，可能更多的原因是：&lt;code&gt;Logseq&lt;/code&gt;对于使用者来说，更淡化了本地文件的存在，而我做为一个更倾向于自托管Markdown本地文件来记录笔记的人，更希望能够清楚知道我的每个笔记存储在哪个位置。所以现在，我的主力Markdown编辑器是&lt;code&gt;Typora&lt;/code&gt;，只有在需要查看双链接、tags的时候才用&lt;code&gt;Obsidian&lt;/code&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第17期）：Read-Write Quorum System及在Raft中的实践</title>
      <link>https://www.codedump.info/zh/post/20220528-weekly-17/</link>
      <pubDate>Sat, 28 May 2022 16:16:57 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220528-weekly-17/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：在Paxos、Raft这类一致性算法的描述里，经常会看到&lt;code&gt;Majority&lt;/code&gt;、&lt;code&gt;Quorum&lt;/code&gt;这两个词，在以前我以为都是表达“半数以上”的含义，最近才发现两者有不小的区别。本文介绍这两者的区别，以及在Raft中实践中的问题。有了&lt;code&gt;Quorum&lt;/code&gt;的视角，能更好得理解一致性算法。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;read-write-quorum-system&#34;&gt;&#xA;  Read-Write Quorum System&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#read-write-quorum-system&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;首先来在数学上给出&lt;code&gt;Read-Write Quorum System&lt;/code&gt;的定义。&lt;/p&gt;&#xA;&lt;p&gt;一个&lt;code&gt;Read-Write Quorum System（读写法定系统）&lt;/code&gt;是两个集合组成的元组，即&lt;code&gt;Q=(R,W)&lt;/code&gt;，其中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;集合&lt;code&gt;R&lt;/code&gt;被称为&lt;code&gt;Read Quorum（读法定集合）&lt;/code&gt;，即可以认为读操作都是读的集合&lt;code&gt;R&lt;/code&gt;中的元素；&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;集合&lt;code&gt;W&lt;/code&gt;被称为&lt;code&gt;Write Quorum（写法定集合）&lt;/code&gt;，即可以认为写操作都是写入到集合&lt;code&gt;W&lt;/code&gt;中的元素。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;$r∈R,  w∈W,r∩w≠0 $，即任从读集合中取一个成员r，以及任从写集合中取一个成员w，这两个集合一定有交集。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;都知道在分布式系统中，一个写入操作要达成一致，读写操作一定要有一定的冗余度，即：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;写入多份数据成功才能认为写入成功，&lt;/li&gt;&#xA;&lt;li&gt;从多个节点读到同一份数据才认为读取成功。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在&lt;code&gt;Majority&lt;/code&gt;系统中，这个冗余度就是系统内半数以上节点。因为根据&lt;a href=&#34;https://baike.baidu.com/item/%E6%8A%BD%E5%B1%89%E5%8E%9F%E7%90%86/233776&#34;&gt;抽屉原理&lt;/a&gt;，当写入到至少半数以上节点时，读操作与写操作一定有重合的节点。&lt;/p&gt;&#xA;&lt;p&gt;但是在一个&lt;code&gt;Read-Write Quorum System&lt;/code&gt;中，这个条件变的更宽泛了，在这类系统中，只需要满足以下条件即可认为读写成功：&lt;/p&gt;&#xA;&lt;p&gt;$r∈R,  w∈W,r∩w≠0 $&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;用直观的大白话来说：在&lt;code&gt;Read-Write Quorum System&lt;/code&gt;中，只要读、写集合中的任意元素有重合即可。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;我们来详细看看&lt;code&gt;Majority&lt;/code&gt;和&lt;code&gt;Read-Write Quorum System&lt;/code&gt;这两个系统的区别在哪里。&lt;/p&gt;&#xA;&lt;p&gt;首先，&lt;code&gt;Majority&lt;/code&gt;系统并没有区分读、写两类不同的集合，因为在它的视角里，读和写操作都要到半数以上节点才能达到一致。但是在&lt;code&gt;Read-Write Quorum System&lt;/code&gt;系统里，是严格区分了读、写集合的，尽管可能在很多时候，这两类集合是一样的。&lt;/p&gt;&#xA;&lt;p&gt;再次，有了前面严格区分的读、写集合之后，以这个视角来看分布式系统中，一个数据达成一致的大前提是“读、写操作一定有重合的节点”，这样就能保证：写入一个数据到写集合中，最终会被读集合读到。在&lt;code&gt;Majority&lt;/code&gt;系统里，读、写集合都必须是半数以上节点的要求当然能够满足这个条件，但是这个条件太&lt;code&gt;强&lt;/code&gt;了。如果只考虑&lt;code&gt;读、写集合有重合&lt;/code&gt;这个条件，是可以适当放宽而且还不影响系统的一致性的。&lt;/p&gt;&#xA;&lt;p&gt;从以上的讨论，可以得到下面的结论：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;分布式系统中，只要读、写集合有重合，就能保证数据的一致性了。&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;Majority&lt;/code&gt;系统是对上述条件的一个强实现，但是存在比这个实现更弱一些的实现，同样能保证数据的一致性。&lt;/li&gt;&#xA;&lt;li&gt;以&lt;code&gt;Read-Write Quorum System&lt;/code&gt;的定义和视角来看，&lt;code&gt;Majority&lt;/code&gt;系统相当于在这两方面强化了&lt;code&gt;Read-Write Quorum System&lt;/code&gt;系统的要求：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;读、写集合完全一样，&lt;/li&gt;&#xA;&lt;li&gt;且都是半数以上节点集合的&lt;code&gt;Read-Write Quorum System&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;即可以认为&lt;code&gt;Majority&lt;/code&gt;系统，只是&lt;code&gt;Read-Write Quorum System&lt;/code&gt;的一个子集。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;quorum&#34; src=&#34;https://www.codedump.info/media/imgs/20220528-weekly-17/quorum.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; quorum &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;讲了这么多，来看一个非&lt;code&gt;Majoiry&lt;/code&gt;的 &lt;code&gt;Read-Write Quorum System&lt;/code&gt;，下面的集合&lt;code&gt;{a,b,c,d,e,f}&lt;/code&gt;组成的网格（grid）被划分成了横竖两个读、写集合：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;grid&#34; src=&#34;https://www.codedump.info/media/imgs/20220528-weekly-17/grid.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; grid &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在上图中，定义了一个&lt;code&gt;Read-Write Quorum System&lt;/code&gt;，&lt;code&gt;Q={{abc}∪{def},{ab}∪{bc}∪{ac}}&lt;/code&gt;，其中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;读集合为&lt;code&gt;{abc}∪{def}&lt;/code&gt;，即横着的两个集合&lt;code&gt;{abc}&lt;/code&gt;和&lt;code&gt;{def}&lt;/code&gt;组成了读集合。&lt;/li&gt;&#xA;&lt;li&gt;写集合为&lt;code&gt;{ad}∪{be}∪{cf}&lt;/code&gt;，即竖着的三个集合&lt;code&gt;{ad}&lt;/code&gt;、&lt;code&gt;{be}&lt;/code&gt;、&lt;code&gt;{cf}&lt;/code&gt;组成了写集合。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;显然这个划分是能够满足前面的条件：$r∈R,  w∈W,r∩w≠0 $ 的，因为任选一个读集合中的集合如&lt;code&gt;{abc}&lt;/code&gt;，写集合中任选的一个集合如&lt;code&gt;{ad}&lt;/code&gt;，这两个集合中的元素都会有重合。&lt;/p&gt;&#xA;&lt;p&gt;假设是这样构成的一个分布式系统，那么写操作只需要写入写集合中的任意一个集合即可认为成功，可以看到一个写集合最小可以只有两个节点构成，这个数量是小于&lt;code&gt;Majority&lt;/code&gt;的。&lt;/p&gt;&#xA;&lt;p&gt;有了对&lt;code&gt;Read-Write Quorum System&lt;/code&gt;系统及与&lt;code&gt;Majority&lt;/code&gt;的区分和联系，以这个视角来看看raft的成员变更算法。&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第16期）：图解ARIES论文（下）</title>
      <link>https://www.codedump.info/zh/post/20220521-weekly-16/</link>
      <pubDate>Sat, 21 May 2022 11:46:44 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220521-weekly-16/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：ARIES(Algorithm for Recovery and Isolation Exploiting Semantics的简称）是论文&lt;a href=&#34;https://cs.stanford.edu/people/chrismre/cs345/rl/aries.pdf&#34;&gt;《ARIES: A Transaction Recovery Method Supporting Fine-Franularity Locking and Partial Rollbacks Using Write-Ahead Logging》&lt;/a&gt;中提到的一种存储引擎中数据恢复的算法。这篇论文可以说是存储引擎数据恢复领域必读的一篇论文，这两期的周刊就是对这篇论文的图解，这是其中的下篇。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;图解aries论文下&#34;&gt;&#xA;  图解ARIES论文（下）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9b%be%e8%a7%a3aries%e8%ae%ba%e6%96%87%e4%b8%8b&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;前情回顾&#34;&gt;&#xA;  前情回顾&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%89%8d%e6%83%85%e5%9b%9e%e9%a1%be&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在&lt;a href=&#34;https://www.codedump.info/post/20220514-weekly-15/&#34;&gt;周刊（第15期）：图解ARIES论文（上）&lt;/a&gt;中，讨论了存储引擎面临的问题，如果存储引擎宕机重启，将要进行以下两个操作：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;撤销（Undo）：未完成或者由于各种原因发生回滚（rollback）、中断（abort）的事务，其修改需要被撤销，即回滚为事务之前的旧值。&lt;/li&gt;&#xA;&lt;li&gt;重做（Redo）：已经提交的事务，其修改操作的效果需要体现为新值。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;为了这两个操作，存储引擎就需要回答这两个问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;“是否允许未提交事务的修改在持久化存储上生效”（Whether the DBMS allows an uncommitted txn to overwrite the most recent committed value of an object in non-volatile storage），被称为&lt;code&gt;Steal policy&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;一个事务在提交之前是否需要将所有修改同步到持久化存储上（Whether the DBMS requires that all updates made by a txn are reflected on non-volatile storage before the txn is allowed to commit.），称为&lt;code&gt;force policy&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;两个问题合并起来一共有四种组合：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第15期）：图解ARIES论文（上）</title>
      <link>https://www.codedump.info/zh/post/20220514-weekly-15/</link>
      <pubDate>Sat, 14 May 2022 06:33:26 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220514-weekly-15/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：ARIES(Algorithm for Recovery and Isolation Exploiting Semantics的简称）是论文&lt;a href=&#34;https://cs.stanford.edu/people/chrismre/cs345/rl/aries.pdf&#34;&gt;《ARIES: A Transaction Recovery Method Supporting Fine-Franularity Locking and Partial Rollbacks Using Write-Ahead Logging》&lt;/a&gt;中提到的一种存储引擎中数据恢复的算法。这篇论文可以说是存储引擎数据恢复领域必读的一篇论文，这两期的周刊就是对这篇论文的图解，这是其中的上篇。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;图解aries论文上&#34;&gt;&#xA;  图解ARIES论文（上）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9b%be%e8%a7%a3aries%e8%ae%ba%e6%96%87%e4%b8%8a&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;在展开解释ARIES算法原理之前，需要对&lt;a href=&#34;https://www.codedump.info/post/20220410-weekly-12/&#34;&gt;Page oriented类存储引擎&lt;/a&gt;的日志系统有一定的了解，才能继续解释基于这个日志系统之上做的恢复算法。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;问题&#34;&gt;&#xA;  问题&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%97%ae%e9%a2%98&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在一个存储系统中，出错是非常常见的情况的，这就涉及到出错了之后系统恢复时还需要能继续工作，即数据不能发生破坏导致整个系统跑不起来。&lt;/p&gt;&#xA;&lt;p&gt;于是，当系统出错需要重启恢复时，就涉及到以下两个动作：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;撤销（Undo）：未完成或者由于各种原因发生回滚（rollback）、中断（abort）的事务，其修改需要被撤销，即回滚为事务之前的旧值。&lt;/li&gt;&#xA;&lt;li&gt;重做（Redo）：已经提交的事务，其修改操作的效果需要体现为新值。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;来看下图中提出的问题：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;bufferpool&#34; src=&#34;https://www.codedump.info/media/imgs/20220514-weekly-15/bufferpool.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; bufferpool &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在上图中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;存在事务T1和T2在同时执行：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;事务T1：修改A值为3，但是在事务还未提交前，事务T2开始执行。&lt;/li&gt;&#xA;&lt;li&gt;事务T2：修改B值为8，并且成功提交。&lt;/li&gt;&#xA;&lt;li&gt;事务T1终止：在事务T2成功提交之后，事务T1终止。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这个事务调度的执行顺序引发了以下几个问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;回滚未提交的事务T1需要做什么？&lt;/li&gt;&#xA;&lt;li&gt;对于未提交的事务T1，是否允许其修改操作在持久化存储上生效（即将A修改为3）？&lt;/li&gt;&#xA;&lt;li&gt;在磁盘的数据库文件中，已成功提交的事务T2，其修改操作是否应该立即落盘（即从buffer pool中同步修改的内容到硬盘）。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;第一个问题当前暂且放到一边，来看后面两个问题。&lt;/p&gt;&#xA;&lt;p&gt;“是否允许未提交事务的修改在持久化存储上生效”（Whether the DBMS allows an uncommitted txn to overwrite the most recent committed value of an object in non-volatile storage），被称为&lt;code&gt;Steal policy&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;steal：允许未提交事务的修改持久化存储上生效。&lt;/li&gt;&#xA;&lt;li&gt;no steal：反之。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;一个事务在提交之前是否需要将所有修改同步到持久化存储上（Whether the DBMS requires that all updates made by a txn are reflected on non-volatile storage before the txn is allowed to commit.），也有两种策略：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第14期）：重读Raft论文中的集群成员变更算法（二）：实践篇</title>
      <link>https://www.codedump.info/zh/post/20220507-weekly-14/</link>
      <pubDate>Sat, 07 May 2022 17:57:08 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220507-weekly-14/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：以前阅读Raft大论文的时候，对“集群变更”这部分内容似懂非懂。于是最近又重读了大论文这部分的内容，以下是重读时做的一些记录。这部分内容打算分为两篇文章，上篇讲解成员变更流程的理论基础，下篇讲解实践中存在的问题。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;重读raft论文中的集群成员变更算法二实践篇&#34;&gt;&#xA;  重读Raft论文中的集群成员变更算法（二）：实践篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%87%8d%e8%af%bbraft%e8%ae%ba%e6%96%87%e4%b8%ad%e7%9a%84%e9%9b%86%e7%be%a4%e6%88%90%e5%91%98%e5%8f%98%e6%9b%b4%e7%ae%97%e6%b3%95%e4%ba%8c%e5%ae%9e%e8%b7%b5%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;单步成员变更存在的问题&#34;&gt;&#xA;  单步成员变更存在的问题&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%8d%95%e6%ad%a5%e6%88%90%e5%91%98%e5%8f%98%e6%9b%b4%e5%ad%98%e5%9c%a8%e7%9a%84%e9%97%ae%e9%a2%98&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;正确性问题&#34;&gt;&#xA;  正确性问题&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%ad%a3%e7%a1%ae%e6%80%a7%e9%97%ae%e9%a2%98&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;单步变更成员时，可能出现正确性问题。如下面的例子所示，最开始时，系统的成员是&lt;code&gt;{a,b,c,d}&lt;/code&gt;这四个节点的集合，要将节点&lt;code&gt;u&lt;/code&gt;和&lt;code&gt;v&lt;/code&gt;加入集群，按照单步变更成员的做法，依次会经历：&lt;code&gt;{a,b,c,d}&lt;/code&gt;-&amp;gt;&lt;code&gt;{a,b,c,d,u}&lt;/code&gt;-&amp;gt;&lt;code&gt;{a,b,c,d,u,v}&lt;/code&gt;的变化，每次将一个节点加入到集群里。&lt;/p&gt;&#xA;&lt;p&gt;上面的步骤看起来很美好，但是考虑下面的例子，在变更过程中leader节点发生了变化的情况：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;C₀ = {a, b, c, d}&#xA;Cᵤ = C₁ ∪ {u}&#xA;Cᵥ = C₁ ∪ {v}&#xA;&#xA;Lᵢ: Leader in term `i`&#xA;Fᵢ: Follower in term `i`&#xA;☒ : crash&#xA;&#xA;    |&#xA; u  |         Cᵤ                  F₂  Cᵤ&#xA;--- | ----------------------------------&#xA; a  | C₀  L₀  Cᵤ  ☒               L₂  Cᵤ&#xA; b  | C₀  F₀          F₁          F₂  Cᵤ&#xA; c  | C₀  F₀          F₁  Cᵥ          Cᵤ&#xA; d  | C₀              L₁  Cᵥ  ☒       Cᵤ&#xA;--- | ----------------------------------&#xA; v  |                     Cᵥ                  time&#xA;    +--------------------------------------------&amp;gt;&#xA;          t₁  t₂  t₃  t₄  t₅  t₆  t₇  t₈&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;（引用自&lt;a href=&#34;https://blog.openacid.com/distributed/raft-bug/&#34;&gt;TiDB 在 Raft 成员变更上踩的坑 - OpenACID Blog&lt;/a&gt;）&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第13期）：重读Raft论文中的集群成员变更算法（一）：理论篇</title>
      <link>https://www.codedump.info/zh/post/20220417-weekly-13/</link>
      <pubDate>Sun, 17 Apr 2022 15:16:30 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220417-weekly-13/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：以前阅读Raft大论文的时候，对“集群变更”这部分内容似懂非懂。于是最近又重读了大论文这部分的内容，以下是重读时做的一些记录。这部分内容打算分为两篇文章，上篇讲解成员变更流程的理论基础，下篇讲解实践中存在的问题。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;重读raft论文中的集群成员变更算法一理论篇&#34;&gt;&#xA;  重读Raft论文中的集群成员变更算法（一）：理论篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%87%8d%e8%af%bbraft%e8%ae%ba%e6%96%87%e4%b8%ad%e7%9a%84%e9%9b%86%e7%be%a4%e6%88%90%e5%91%98%e5%8f%98%e6%9b%b4%e7%ae%97%e6%b3%95%e4%b8%80%e7%90%86%e8%ae%ba%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;“集群成员变更（cluster membership change）”意指一个集群内节点的增、删操作，这在一个分布式系统中是必不可少的操作，因为并不能保证一个集群的所有节点都一直能工作的很好。Raft大论文《&lt;a href=&#34;https://web.stanford.edu/~ouster/cgi-bin/papers/OngaroPhD.pdf&#34;&gt;Consensus: Bridging Theory and Practice&lt;/a&gt;》中有专门的一节来讲解这部分内容。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;安全性&#34;&gt;&#xA;  安全性&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%ae%89%e5%85%a8%e6%80%a7&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;首先，Raft算法中要求所有操作都需要保证安全性（safety），即：任何时候都不能在集群中同时存在两个leader节点。“集群成员变更”算法也必须保证安全性这个大前提不能被破坏，于是论文中阐述了为什么直接变更多个节点是不被允许的：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;4.2&#34; src=&#34;https://www.codedump.info/media/imgs/20220417-weekly-13/4.2.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 4.2 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在上图的图示中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;旧集群有1、2、3这三个节点，而需要将这个三节点的集群新增节点4、5变更到5节点集群去。&lt;/li&gt;&#xA;&lt;li&gt;如果直接如图中这样变更，由于每个节点的时间窗口并不一致，可能就会出现这种情况：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在某一时刻，节点1、2还使用的是旧集群（只含有{1,2,3}）的成员配置，而3、4、5已经是新集群（含有{1,2,3,4,5}）的成员配置了。&lt;/li&gt;&#xA;&lt;li&gt;这样就可能出现还使用旧集群节点配置的1、2选出了一个leader，以及已经使用了新集群配置的节点3、4、5选出了另一个leader的情况，于是违反了上面阐述的“安全性”要求。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;需要说明的是，在上面这个错误的示例中，是由于有两类行为同时出现才导致的错误：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一次性变更多个节点。在例子中，就是一次性把4、5两个节点加入到集群中。&lt;/li&gt;&#xA;&lt;li&gt;直接（directly）变更。直接变更由于集群中不同节点的步子不一样，而不一样的节点如果出现了两个不同的集群，那么就可能导致选出两个不同的leader。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;cluster-membership-change&#34; src=&#34;https://www.codedump.info/media/imgs/20220417-weekly-13/cluster-membership-change.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; cluster-membership-change &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;于是，由于这两个错误操作是一起发生才会导致错误，论文中给出了两种方案：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;要么一次性严格限制只变更一个节点。&lt;/li&gt;&#xA;&lt;li&gt;如果实在想一次变更多个节点，那就不能直接变更，需要经过一个中间状态的过渡之后才能完成同时变更多个节点的操作。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以下分别来阐述这两种不同的实现。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;一次变更单个节点&#34;&gt;&#xA;  一次变更单个节点&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%80%e6%ac%a1%e5%8f%98%e6%9b%b4%e5%8d%95%e4%b8%aa%e8%8a%82%e7%82%b9&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;如果限制每次只变更一个节点，那么就能保证“新、旧集合的quorum集合是有重合的”，由于有重合，这样就能保证新旧两个集群的集合不会选出不同的leader，就能间接保证安全性。&lt;/p&gt;&#xA;&lt;p&gt;论文中以下面几个例子来说明这样操作的正确性：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;4.3&#34; src=&#34;https://www.codedump.info/media/imgs/20220417-weekly-13/4.3.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 4.3 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;这几个图，是在两个维度上做示范的：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;增、删操作。&lt;/li&gt;&#xA;&lt;li&gt;原集群节点数量是奇数还是偶数。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;两个维度的组合一共就是上面的4中情况，但是无论哪一种情况，由于都保证了“新、旧集合的quorum集合是有重合的”这个条件，于是不会选出不一样的leader来。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;一次变更多个节点&#34;&gt;&#xA;  一次变更多个节点&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%80%e6%ac%a1%e5%8f%98%e6%9b%b4%e5%a4%9a%e4%b8%aa%e8%8a%82%e7%82%b9&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;从上面的例子中可以看到：只要能保证一次只变更一个节点，是可以直接（directly）变更的。即：无需中间状态，直接从A集合变更到A+1集合，因为这两个集合的quorum肯定有重合。&lt;/p&gt;&#xA;&lt;p&gt;但是，在一次需要变更多个节点的情况下，就不能这样直接变更，因为会出现最开始示例的那样同时选出两个leader的情况。于是，为了解决这个问题，需要引入一个中间状态：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;假设原先的集群节点集合为C_Old，新的集群节点集合为C_New，那么首先变更配置到{C_Old,C_New}，也就是新旧集群节点集合的并集。&lt;/li&gt;&#xA;&lt;li&gt;上面这次变更提交之后，再向集群变更配置到C_New。在这次变更提交之后，那些不在C_New节点集合中的节点，收到这个变更时，自动下线退出集群。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以证明：上面两个步骤中，都不会出现“同时存在两个leader”的情况。&lt;/p&gt;&#xA;&lt;p&gt;从本质上来说，这种变更算法，属于一种两阶段的成员变更算法，Raft大论文中称之为“Joint Consensus（联合共识）”算法。下图中演示了Joint Consensus算法这两个阶段的流程：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;4.8&#34; src=&#34;https://www.codedump.info/media/imgs/20220417-weekly-13/4.8.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 4.8 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;failover&#34;&gt;&#xA;  Failover&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#failover&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;我们来看看Joint Consensus算法，在变更过程中如果出错，是如何failover选出新leader的。&lt;/p&gt;&#xA;&lt;p&gt;第一阶段，这时候选出来的leader只有可能有两种情况，还是旧的C_Old节点集合，或者已经收到了{C_Old,C_New}节点集合：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;只有C_Old节点集合的节点：由于这时候这个leader并没有第一阶段提交的{C_Old,C_New}节点集合变更，因此那些已有{C_Old,C_New}节点集合的follower这部分的日志将被截断，成员变更失败，回退回C_Old集合。&lt;/li&gt;&#xA;&lt;li&gt;有{C_Old,C_New}节点集合的节点：这意味这个leader已经有第一阶段提交的{C_Old,C_New}节点集合变更，可以继续将未完成的成员变更流程走完。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;类似的，也可以去推导一下在第二阶段出现leader宕机时，选出来的leader只可能具备两种情况，但是这两种情况都不可能选出多个leader。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;集群变更何时生效&#34;&gt;&#xA;  集群变更何时生效？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%9b%86%e7%be%a4%e5%8f%98%e6%9b%b4%e4%bd%95%e6%97%b6%e7%94%9f%e6%95%88&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;以上讲解完毕两种不同的集群变更方式，下面来聊一聊集群变更何时生效。&lt;/p&gt;&#xA;&lt;p&gt;在Raft、Paxos这类状态机模型的一致性算法中，将任何变更操作都认为是一个命令（Command），命令的处理流程是这样的：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;状态机收到命令，首先在自己本地将命令持久化。&lt;/li&gt;&#xA;&lt;li&gt;然后广播给集群中的其他节点。&lt;/li&gt;&#xA;&lt;li&gt;当收到集群半数以上节点的应答时，认为命令是可以被提交（commit）的，于是可以生效将这些已经被提交的日志传给应用层的状态机使用了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以上流程可以看到：一条命令，只有在“提交（commit）”之后才能“生效（apply）”。&lt;/p&gt;&#xA;&lt;p&gt;在Raft中，“成员变更”这个操作，也是一类命令，即：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第12期）：Page oriented类存储引擎里可能同时存在多种结构</title>
      <link>https://www.codedump.info/zh/post/20220410-weekly-12/</link>
      <pubDate>Sun, 10 Apr 2022 11:38:16 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220410-weekly-12/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本期聊一聊Page oriented类存储引擎内的数据结构组织。在满足“向磁盘读写的基本单位是物理页面”这个大前提下，这类存储引擎的可能同时存在多种结构。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;page-oriented类存储引擎里可能同时存在多种树形结构&#34;&gt;&#xA;  page oriented类存储引擎里可能同时存在多种树形结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#page-oriented%e7%b1%bb%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e%e9%87%8c%e5%8f%af%e8%83%bd%e5%90%8c%e6%97%b6%e5%ad%98%e5%9c%a8%e5%a4%9a%e7%a7%8d%e6%a0%91%e5%bd%a2%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;存储引擎的分类&#34;&gt;&#xA;  存储引擎的分类&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e%e7%9a%84%e5%88%86%e7%b1%bb&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;目前接触到的存储引擎，以向磁盘读写方式来分类的话，大体可以分为两类：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;LSM-Tree结构。&lt;/li&gt;&#xA;&lt;li&gt;Page oriented类。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;LSM-Tree是“Log-Structured Merge-Tree”的简称，这类存储引擎写入一条数据的流程大体如下：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;向内存以及WAL日志中写入完成，即可认为写入成功。&lt;/li&gt;&#xA;&lt;li&gt;内存中的数据写满之后，将落盘到所谓的sstable中。&lt;/li&gt;&#xA;&lt;li&gt;sstable分为多层，随着写入进行，不同层次的sstable数据将进行合并。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;LSM&#34; src=&#34;https://www.codedump.info/media/imgs/20220410-weekly-12/LSM.jpeg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; LSM &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;（图片引用自&lt;a href=&#34;https://zhuanlan.zhihu.com/p/181498475&#34;&gt;LSM树详解 - 知乎&lt;/a&gt;)&lt;/p&gt;&#xA;&lt;p&gt;从上面简单的写入LSM的流程可以看到：无论是写入内存还是磁盘，这类存储引擎在写入新数据时（不是合并sstable流程），磁盘操作的单位是一条记录。而一条记录的长度，是不定长的。&lt;/p&gt;&#xA;&lt;p&gt;与LSM-Tree类的结构不同的是，Page oriented类的存储引擎，向磁盘发起读写操作的基本单位是页面（page），一个页面通常的大小是2的次方，最小一般是1024字节，比如sqlite的存储，其页面大小为4K（可以修改编译选项配置页面大小）。&lt;/p&gt;&#xA;&lt;p&gt;以一个物理页面为读写磁盘的基本单位，这也是这一类存储引擎之所以被称为”Page oriented类存储引擎“的原因。本文重点是介绍Page oriented类存储引擎的结构。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;page-oriented存储引擎的结构&#34;&gt;&#xA;  Page oriented存储引擎的结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#page-oriented%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e%e7%9a%84%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;还是以之前介绍过的sqlite的架构图来开头：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;btree架构&#34; src=&#34;https://www.codedump.info/media/imgs/20211217-sqlite-btree-0/btree-arch.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; btree架构 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;这个架构由下往上依次是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;系统层：提供不同系统级API的封装，比如文件读写、加解锁操作等。&lt;/li&gt;&#xA;&lt;li&gt;物理页面管理层：提供物理页面读写、缓存等功能。&lt;/li&gt;&#xA;&lt;li&gt;树形结构的实现：根据具体的树形算法，组织物理页面之间的逻辑关系（比如父子页面、兄弟页面），以及单个物理页面之内的数据的组织。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这里的重点是页面管理层和树形结构的实现这两部分：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;物理页面管理相当于是磁盘文件的”原材料供应商“，负责对它的客户也就是各种不同结构的实现提供物理页面这一”原材料“的读写、缓存管理，而它对这些材料被客户拿去做成了什么，一无所知。&lt;/li&gt;&#xA;&lt;li&gt;树形结构的实现，从页面管理器拿到了”物理页面“这个原材料之后，可以按照自己的算法和数据结构任意塑造成任何合理的结构。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;数据库文件的物理页面组织和逻辑页面结构&#34; src=&#34;https://www.codedump.info/media/imgs/20220201-sqlite-btree-5-btree/database-file.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 数据库文件的物理页面组织和逻辑页面结构 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;可以看到，Page oriented存储引擎，在满足“向磁盘读写的基本单位是物理页面”这个大前提下，这类存储引擎的可能同时存在多种结构：可能只有B-Tree，也可能只有B+Tree。还有另一种情况是：这类存储引擎内部同时存在多种结构。&lt;/p&gt;&#xA;&lt;p&gt;以sqlite为例，内部其实就存在两种结构：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;存储索引的index tree：结构为B-Tree，键为表索引，值为这一行数据的&lt;code&gt;rowid&lt;/code&gt;，其中&lt;code&gt;rowid&lt;/code&gt;为隐藏列，创建数据表时自动生成，这一列是自增整数。&lt;/li&gt;&#xA;&lt;li&gt;存储数据的table tree：结构为B+Tree，键为&lt;code&gt;rowid&lt;/code&gt;，值为一行数据。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这两类存储引擎，由于同属于“Page oriented类存储引擎”，因此可以共用同一个物理页面管理器。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;数据库文件的rowid全量数据表和索引表&#34; src=&#34;https://www.codedump.info/media/imgs/20220201-sqlite-btree-5-btree/btree-rowid.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 数据库文件的rowid全量数据表和索引表 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;下面，以sqlite中的一个表为例来解释上面这个流程。&lt;/p&gt;&#xA;&lt;p&gt;首先，创建一个表以及索引：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// &lt;span style=&#34;&#34;&gt;创建数据库&lt;/span&gt;COMPANY&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;TABLE&lt;/span&gt; COMPANY(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   ID             &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;INT&lt;/span&gt;      &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;NULL&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   NAME           &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;TEXT&lt;/span&gt;    &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;NULL&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   AGE            &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;INT&lt;/span&gt;     &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;NULL&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// &lt;span style=&#34;&#34;&gt;创建索引&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;INDEX&lt;/span&gt; id_index &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;ON&lt;/span&gt; COMPANY (id);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面这个建表以及创建索引之后，对应的在这个数据文件中就有了两个树形结构：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第11期）：mmap适用于存储引擎吗？</title>
      <link>https://www.codedump.info/zh/post/20220327-weekly-11/</link>
      <pubDate>Sun, 27 Mar 2022 15:06:38 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220327-weekly-11/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本期聊一聊mmap技术在存储引擎中的应用。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;mmap适用于存储引擎吗&#34;&gt;&#xA;  mmap适用于存储引擎吗？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#mmap%e9%80%82%e7%94%a8%e4%ba%8e%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e%e5%90%97&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;想写这篇文章，主要源于两篇文章（论文）中的对mmap在存储引擎中使用的两种截然不同的观点讨论：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;反方（mmap不应该用于存储引擎）：&lt;a href=&#34;https://db.cs.cmu.edu/mmap-cidr2022/&#34;&gt;Are You Sure You Want to Use MMAP in Your Database Management System? (CIDR 2022)&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;正方（mmap可以用于存储引擎）：&lt;a href=&#34;https://ayende.com/blog/196161-C/re-are-you-sure-you-want-to-use-mmap-in-your-database-management-system&#34;&gt;re: Are You Sure You Want to Use MMAP in Your Database Management System? - Ayende @ Rahien&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;由于刚好看过这两种方式的btree存储引擎：sqlite的btree实现以及boltdb，所以可以结合我的认知来聊一聊这个问题。这两个存储引擎的实现都已经整理成了系列博客，这两个系列的第一篇分别是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/&#34;&gt;sqlite3.36版本 btree实现（零）- 起步及概述 - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200625-boltdb-1/&#34;&gt;boltdb 1.3.0实现分析（一） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;先来看看一个存储引擎实现时的大体分层，以sqlite为例分为三层：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;btree架构&#34; src=&#34;https://www.codedump.info/media/imgs/20211217-sqlite-btree-0/btree-arch.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; btree架构 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;自下而上，这三个层次分别是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;os层：封装系统级API实现文件的读写等操作。&lt;/li&gt;&#xA;&lt;li&gt;页面管理层：提供以页面为单位的读、写、加载、缓存等操作。&lt;/li&gt;&#xA;&lt;li&gt;btree实现：btree以物理页面为单位向下一层的页面管理层来读写页面，而物理页面内部的逻辑组织（比如父子关系），以及页面内的数据组织（比如一个页面中管理的数据）由这一层负责。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以这样来简单区别理解“页面管理”模块和btree模块的功能：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;页面管理：顾名思义，页面管理模块的最基本单位是”页面“，页面的读、写、缓存、落盘、恢复、回滚等，都由页面模块负责。上一层依赖页面管理模块的btree模块，不需要关心一个页面何时缓存、何时落盘等细节。即：&lt;strong&gt;页面模块负责页面的物理级别的操作&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;btree：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;负责按照btree算法，来组织页面，即负责的是页面之间逻辑关系维护。&lt;/li&gt;&#xA;&lt;li&gt;除此以外，一个页面内部的数据的物理、逻辑组织，也是btree模块来负责的。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;即：&lt;strong&gt;btree负责维护页面间的逻辑关系，以及一个页面内数据的组织。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;以页面物理、逻辑关系的维护看模块划分&#34; src=&#34;https://www.codedump.info/media/imgs/20211217-sqlite-btree-0/page-module.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 以页面物理、逻辑关系的维护看模块划分 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在数据库文件中，通常按照页面为单位来划分文件，比如sqlite一般是4KB大小为一个物理页面，所以一个数据库文件可以看做是一个大的“物理页面数组”，这样的话每个物理页面都有一个对应的编号（从1开始），这个编号通常简称为PID（page id）。&lt;/p&gt;&#xA;&lt;p&gt;从上面的功能划分可以看到，“页面管理器（也被称为“buffer pool）”的功能是非常复杂的，这里列举几个最关键的：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;读页面：上层的btree要读一个数据库文件中的页面时，通常传入一个PID，由页面管理器去加载这个页面的数据。而页面数据并不是每次都会到数据库文件中一次磁盘IO读出来，也很可能在内存中，此时就不需要读磁盘操作了。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;写页面：当一个页面被修改后，就被称为“脏页面（dirty page）”，需要落盘；但并不是每一次修改了一个页面的内容之后就马上落盘，其原因在于：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一次写事务可能修改了不止一个页面，需要以事务为单位去落盘脏页面。&lt;/li&gt;&#xA;&lt;li&gt;即便是落盘脏页面，由于涉及到写磁盘操作，所以还会用其他方式减少写磁盘的次数。比如sqlite的wal备份文件机制中，脏页面的内容是首先写入wal文件的，由于写wal文件是一次append操作而不是随机写，所以效率会更高，如果一个脏页面的内容被写入wal文件的话，那么这部分页面内容是不急于马上写入数据库文件的。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;缓存页面：由于页面缓存的功能，所以还需要一个页面缓存管理的功能，主要负责：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第10期）：“忘记目标，专注于体系”</title>
      <link>https://www.codedump.info/zh/post/20220319-weekly-10/</link>
      <pubDate>Sat, 19 Mar 2022 13:50:46 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220319-weekly-10/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本期聊一聊《掌控习惯》这本书里提到的养成习惯的方法论。我读下来一个最深的感受是：越不需要“坚持”就能做下去的事情，才越能长久做下去。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;忘记目标专注于体系&#34;&gt;&#xA;  “忘记目标，专注于体系”&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%bf%98%e8%ae%b0%e7%9b%ae%e6%a0%87%e4%b8%93%e6%b3%a8%e4%ba%8e%e4%bd%93%e7%b3%bb&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;“忘记目标，专注于体系（Forget About Setting Goals, Focus on the system Instead）”是出自《&lt;a href=&#34;https://jamesclear.com/atomic-habits&#34;&gt;Atomic Habits&lt;/a&gt;》（中文名&lt;a href=&#34;https://book.douban.com/subject/34326931//&#34;&gt;《掌控习惯 》&lt;/a&gt;）一书的一句话，个人认为这是本书最重要的一个观点。&lt;/p&gt;&#xA;&lt;p&gt;要做成一件事情，只是依靠简单的设定目标，是远远不够的，更重要的是建立一套体系，促进事情的良性循环，最终让事情发生；反之，如果一件不好的事情，经常重蹈覆辙，其根本原因也是从未改变导致这种状况一再发生的体系。如果没有建立一套体系，所做的一切只是治标不治本。&lt;/p&gt;&#xA;&lt;p&gt;这里说的“体系”，在作者的话语中就是“习惯”，作者列出了培养良好习惯的四个步骤（提示、渴求、反应和奖励）：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;1、提示：让它显而易见。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;2、渴求：让它有吸引力。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;3、反应：让它简便易行。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;4、奖励：让它令人愉悦。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;作者将这四个步骤，总结在&lt;code&gt;习惯循环（habit-loop）&lt;/code&gt;里，如下图：&lt;/p&gt;&#xA;&lt;div align=&#34;center&#34;&gt;&#xA; &lt;img src=&#34;https://www.codedump.info/media/imgs/20220319-weekly-10/habit-cycle.png&#34; width=&#34;400&#34; height=&#34;200&#34; alt=&#34;习惯循环&#34; align=center /&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;这四个步骤缺一不可，任何一个环节做的不够的话，就难以形成习惯：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;消除提示，习惯形成循环永远也不会开始。&lt;/li&gt;&#xA;&lt;li&gt;降低渴求，就不会有足够的动力去行动。&lt;/li&gt;&#xA;&lt;li&gt;让行动变得困难，将无法付诸实施。&lt;/li&gt;&#xA;&lt;li&gt;如果奖励不能满足欲望，那么未来就没有理由再这样做了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;没有前三步，就不会有任何行为发生。没有这四个步骤，任何一种行为都不会得以重复。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;1提示让它显而易见&#34;&gt;&#xA;  1、提示：让它显而易见&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#1%e6%8f%90%e7%a4%ba%e8%ae%a9%e5%ae%83%e6%98%be%e8%80%8c%e6%98%93%e8%a7%81&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在日常行为中，“原动力经常被高估，而环境的作用往往被低估”，比如经常会认为树立一个远大的目标，坚持做下去就好；而现实的情况是，行为是环境中人的函数：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;B（行为）=f（函数）[P（人），E（环境）]&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;即有怎样的行为，除了和人有关系之外，还和环境有重要的关系。&lt;/p&gt;&#xA;&lt;p&gt;环境函数中，常见的两个提示是时间和地点。比如&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;同样摆放在超市的东西，更容易被接触到、更显眼的商品，被购买的概率会更高。&lt;/li&gt;&#xA;&lt;li&gt;据书中列举的越战士兵的统计显示，在离开越南战场之后，毒瘾戒除率大大提高，因为生活环境发生了彻底的改变。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这一定理给的提示是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;假如不需要经常自我克制的话，做起来就会更容易。所以，没错，毅力、勇气和意志力是取得成功的要素，但是增强这些品质的途径不是期望你自己成为一个自律的人，而是创造一个有纪律的环境。&lt;/li&gt;&#xA;&lt;li&gt;消除坏习惯的最实用的方法之一是避免接触引起它的提示，比如要少玩手机就想办法远离手机；反过来，想养成好习惯比如多喝水，就把水杯放在易接触的位置。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;2渴求让它有吸引力&#34;&gt;&#xA;  2、渴求：让它有吸引力&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#2%e6%b8%b4%e6%b1%82%e8%ae%a9%e5%ae%83%e6%9c%89%e5%90%b8%e5%bc%95%e5%8a%9b&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;习惯是多巴胺驱动的反馈回路。每一种极可能形成习惯的行为——吃垃圾食品、玩电子游戏、浏览社交媒体——都与较高浓度的多巴胺有关。每当你预测一个机会会有回报时，你体内的多巴胺浓度就会随着这种预期飙升。当你获得奖励时，大脑中激活的奖励系统，与你期待奖励时激活的系统是同一个。这就是对一种体验的期待往往比体验本身，更令人感到愉悦的原因之一。&lt;/p&gt;&#xA;&lt;p&gt;这就引出了“喜好绑定”的原理：把你需要做的事与愿意做的事绑定。比如想培养健身的习惯，那么可以让自己在健身的时候同时看喜欢看的视频做为奖励。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;3反应让它简便易行&#34;&gt;&#xA;  3、反应：让它简便易行&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#3%e5%8f%8d%e5%ba%94%e8%ae%a9%e5%ae%83%e7%ae%80%e4%be%bf%e6%98%93%e8%a1%8c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;《精益求精》一书里举过这样的例子：“日本公司强调为人所知的‘精益生产’理念，坚持不懈地努力寻求从生产流程中去除各种浪费，直至重新设计工作环境，使得工人们的身体不必转来转去，从而避免为拿工具而浪费时间。结果是日本工厂比美国工厂效率更高，产品更可靠。“&lt;/p&gt;&#xA;&lt;p&gt;作者将这样的策略称为”因减而加“：寻找生产线上的每一个阻力点，予以清除。这样减少了无用功，反过来就增加了效率。&lt;/p&gt;&#xA;&lt;p&gt;与之类似的，有”两分钟法则”：当你开始培养一种新习惯时，它所用时间不应超过两分钟。这样的策略也有另一个原因：它们强化着你想要建立的身份。如果你连续五天现身健身房，哪怕只在那里停留两分钟，你就是在为你的新身份投赞同票。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;4奖励让它令人愉悦&#34;&gt;&#xA;  4、奖励：让它令人愉悦&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#4%e5%a5%96%e5%8a%b1%e8%ae%a9%e5%ae%83%e4%bb%a4%e4%ba%ba%e6%84%89%e6%82%a6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;保持习惯的关键是要有成就感，哪怕只是细微的感受。成就感是一个信号，它表明你的习惯有了回报，你为此付出的努力是值得的。比如，把要做的事情列成一个todo列表，完成一件划掉一项，看到todo上的事情全部划掉就是一种“奖励”。&lt;/p&gt;&#xA;&lt;p&gt;取得进步令人满意，借助于视觉量度，如移动曲别针、发夹或弹珠，你能清晰地看到自己的进步。这样做的结果是，它们强化着你的行为，并为任何活动增加一些即时满足感。视觉量度有多种形式：食物日志、健身日志、打孔忠诚卡、软件下载进度条，甚至书籍中的页码等等。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;作者做的总结表格&#34;&gt;&#xA;  作者做的总结表格&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bd%9c%e8%80%85%e5%81%9a%e7%9a%84%e6%80%bb%e7%bb%93%e8%a1%a8%e6%a0%bc&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;作者将根据以上四个步骤如何养成好习惯以及戒除坏习惯的方法，总结在下面的表格里。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;怎样养成好习惯&#34;&gt;&#xA;  怎样养成好习惯&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%80%8e%e6%a0%b7%e5%85%bb%e6%88%90%e5%a5%bd%e4%b9%a0%e6%83%af&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;div align=&#34;center&#34;&gt;&#xA; &lt;img src=&#34;https://www.codedump.info/media/imgs/20220319-weekly-10/habit-table.png&#34; width=&#34;400&#34; height=&#34;200&#34; alt=&#34;养成好习惯&#34; align=center /&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;怎样戒除坏习惯&#34;&gt;&#xA;  怎样戒除坏习惯&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%80%8e%e6%a0%b7%e6%88%92%e9%99%a4%e5%9d%8f%e4%b9%a0%e6%83%af&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;div align=&#34;center&#34;&gt;&#xA; &lt;img src=&#34;https://www.codedump.info/media/imgs/20220319-weekly-10/drop-habit-table.png&#34; width=&#34;400&#34; height=&#34;200&#34; alt=&#34;戒掉坏习惯&#34; align=center /&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;我的实践&#34;&gt;&#xA;  我的实践&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%88%91%e7%9a%84%e5%ae%9e%e8%b7%b5&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;为了减少睡觉前看手机，以及睡醒之后看手机的时间，我的做法是睡觉前把手机放在远离床头的地方。（避免坏习惯就让它难以被接触）。&lt;/li&gt;&#xA;&lt;li&gt;跑步时使用&lt;a href=&#34;https://github.com/yihong0618/running_page&#34;&gt;running_page&lt;/a&gt;这个项目来记录、展示我的跑步数据，一目了然也会带来更多的成就感。（奖励习惯）。&lt;/li&gt;&#xA;&lt;li&gt;年纪越大，越发现需要“坚持”去做的事情，很难做得长久。因为一提到需要“坚持”，很大原因是因为其中有阻碍、没有正反馈等等，这些都可以在上面四个步骤里找到答案。越不需要“坚持”就能做下去的事情，才越能长久做下去。所以，以后再遇到事情发现做不下去时，第一反应不应该再是什么意志力不够等原因，而是要从上面几个步骤里找到是哪里出了问题。是不够显而易见？不够有吸引力？不够简便？奖励不够？不够愉悦？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;其他推荐&#34;&gt;&#xA;  其他推荐&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%85%b6%e4%bb%96%e6%8e%a8%e8%8d%90&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;有关sre和devops的两篇文章&#34;&gt;&#xA;  有关SRE和Devops的两篇文章&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%9c%89%e5%85%b3sre%e5%92%8cdevops%e7%9a%84%e4%b8%a4%e7%af%87%e6%96%87%e7%ab%a0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;推荐&lt;a href=&#34;https://twitter.com/laixintao&#34;&gt;@laixintao&lt;/a&gt;有关SRE和Devops的两篇文章：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第9期）：Mozilla rr使用简介</title>
      <link>https://www.codedump.info/zh/post/20220313-weekly-9/</link>
      <pubDate>Sun, 13 Mar 2022 11:20:59 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220313-weekly-9/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：在之前的&lt;a href=&#34;https://www.codedump.info/post/20220227-weekly-7/&#34;&gt;周刊（第7期）：一个C系程序员的Rust初体验&lt;/a&gt;中，简单提到过Mozilla rr这款调试工具，由于这个工具并不是太为人所知，所以本文对该工具做一个简介。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;mozilla-rr使用简介&#34;&gt;&#xA;  Mozilla rr使用简介&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#mozilla-rr%e4%bd%bf%e7%94%a8%e7%ae%80%e4%bb%8b&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://rr-project.org/&#34;&gt;rr&lt;/a&gt;是由Mozilla出品的一款调试工具，用官网的话来说：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;rr aspires to be your primary C/C++ debugging tool for Linux, replacing — well, enhancing — gdb. You record a failure once, then debug the recording, deterministically, as many times as you want. The same execution is replayed every time.&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;即它的特点是：可以记录下来程序运行时的上下文环境，包括线程、堆栈、寄存器等等，这样的好处有两个：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;“deterministically”：很多问题问题的产生，都与特定的环境相关，如：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;线程调度执行的顺序，先执行A线程再B线程，以及反之，可能得到的是不同的结果。&lt;/li&gt;&#xA;&lt;li&gt;环境参数，如输入不同的参数，尤其一些边界条件的触发就跟输入不同的参数有关。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;replay：记录下来程序执行的环境之后，rr除了支持gdb方式的调试之后，还能利用环境来不停的重放程序，甚至反向来执行程序。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以下对&lt;code&gt;rr&lt;/code&gt;的使用做一些简单的介绍。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;deterministically&#34;&gt;&#xA;  deterministically&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#deterministically&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;以下面一个最简单的多线程程序来解释何为&lt;code&gt;deterministically&lt;/code&gt;：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&amp;lt;pthread.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;void&lt;/span&gt; * &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;doPrint&lt;/span&gt;(&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;void&lt;/span&gt; *arg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;NULL&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;main&lt;/span&gt;() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;pthread_t&lt;/span&gt; pid;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;pthread_create&lt;/span&gt;(&amp;amp;pid, &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;NULL&lt;/span&gt;, doPrint, &lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;NULL&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;pid = %lu&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;&lt;/span&gt;, pid);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;return&lt;/span&gt; 0;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个程序很简单：创建一个线程之后，打印线程的pid。&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第8期）：技术配图的一些心得</title>
      <link>https://www.codedump.info/zh/post/20220304-weekly-8/</link>
      <pubDate>Fri, 04 Mar 2022 22:10:11 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220304-weekly-8/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：写过不少技术文章，以及给不少技术思路手绘示例配图之后，在这方面有了一些心得，本文权当个人的一些的总结，抛砖引玉。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;技术配图的一些心得&#34;&gt;&#xA;  技术配图的一些心得&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%8a%80%e6%9c%af%e9%85%8d%e5%9b%be%e7%9a%84%e4%b8%80%e4%ba%9b%e5%bf%83%e5%be%97&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;我觉得我们理工科出身的，对于可以量化的事情，总是很容易根据量化差异来做出判断，比如一个程序性能优化之后，能比优化之前快出多少，都能有一个量化的数字来说明。&lt;/p&gt;&#xA;&lt;p&gt;但是对于那些不能量化的东西，就很难说出具体好在哪里了。&lt;/p&gt;&#xA;&lt;p&gt;本文主题要讨论的“技术配图”就属于这种很难量化的领域，很难有一个标准来量化说明两幅图之间差别在哪里。我也是画了很多图，以及看了别人的很多配图之后，才慢慢有一些心得，本文权当个人的一些的总结，抛砖引玉。&lt;/p&gt;&#xA;&lt;p&gt;本文并不是一个画图工具的对比说明，尽管现在各种绘图工具已经很多，也各有自己的优缺点以及个人喜好，但是在这里并不讨论具体工具的使用，会把更多的文字放在配图的一些注意事项上。但是，也总有人问我文章的配图使用什么工具做的，在这里再回答一次：&lt;a href=&#34;https://www.omnigroup.com/omnigraffle&#34;&gt;OmniGraffle&lt;/a&gt;，一款目前仅有Mac版本的工具软件。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;一图胜千言&#34;&gt;&#xA;  一图胜千言&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%80%e5%9b%be%e8%83%9c%e5%8d%83%e8%a8%80&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在开始交代具体的配图注意事项之前，有必要先说说配图的重要性。&lt;/p&gt;&#xA;&lt;p&gt;绘图，某种程度也是辅助自己思考某个技术点的手段之一，以我个人的体会来说，有时候讲不清楚一个技术点的时候，就手绘图出来，比朴素的文字更容易说明问题。其中的原因，有可能是：图片可以有多维的信息，而文字通常只有一维，遇到文字表达能力不太好的人，这仅有的一维能力可能还不好发挥出来。&lt;/p&gt;&#xA;&lt;p&gt;所以，在交代技术细节、沟通交流的时候，尽量多画图。反向的，图画多了，也自然慢慢会找到感觉，如何更好的通过图示表达思路。&lt;/p&gt;&#xA;&lt;p&gt;顺便一提，还有比朴素的文字表达更差的技术沟通方式，就是简单粗暴的贴一大段代码上去。这种做法，其实更多时候是没有对作者的思路有太多个人的整理，想偷懒的方式，最后回头再看写过的文字，可能连自己都看不懂了。&lt;/p&gt;&#xA;&lt;p&gt;个人的一个体会：如果产出某些输出的时候，能假设自己未来就是这些输出的读者、维护者，那么输出起来会更“友善”一些。比如写的代码、文章、甚至于提交代码时候的信息，如果能考虑是写给未来的自己看的，会更清晰、尽可能留下更多的信息。我最开始要在文章里大量配图，也是为了将来自己回看的时候能看懂。&lt;/p&gt;&#xA;&lt;p&gt;扯远了，总之，尽可能多画图来表达技术思路。&lt;/p&gt;&#xA;&lt;p&gt;下面开始正题，以下会以简单的几个原则及示例来说明。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;区分联系组合&#34;&gt;&#xA;  区分、联系、组合&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%8c%ba%e5%88%86%e8%81%94%e7%b3%bb%e7%bb%84%e5%90%88&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;配图中，应该尽量将不同的模块、组件等区分开来，“区分”的方式有很多，常见的有：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;使用不同的颜色。&lt;/li&gt;&#xA;&lt;li&gt;使用不同的形状。&lt;/li&gt;&#xA;&lt;li&gt;使用箭头、曲线等表示数据的走向、趋势。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;等等，所有的这些手段，概括起来就是尽量在图中，将不同的元素区分开来，“有区分”意味着至少有一个维度的不同，这样能给读者更加清晰的感觉。可以结合下面的例子来理解区分、联系和组合的绘图表达。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;分组&#34;&gt;&#xA;  分组&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%88%86%e7%bb%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;一个模块里，可能由多个组件构成，可以把这些组件分组到一个更大的模块中。&lt;/p&gt;&#xA;&lt;p&gt;分组是非常常见的一种手段，这里多举几个例子。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;cpu&#34; src=&#34;https://www.codedump.info/media/imgs/20220304-weekly-8/cpu.jpeg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; cpu &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中，每个CPU Core中有L1、L2缓存，于是把这些组件合并在一起放在Core组件中，周围使用一个正方形包裹起来，同时这个正方形左上角有一个&lt;code&gt;Core&lt;/code&gt;的说明文字，这样一目了然：Core模块，由L1、L2缓存构成。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;meituan&#34; src=&#34;https://www.codedump.info/media/imgs/20220304-weekly-8/raft-log.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; meituan &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图出自Raft论文，整体上划分为了Client、Server这两大部分。而每个Server又有以下三部分组成：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一致性算法模块。&lt;/li&gt;&#xA;&lt;li&gt;状态机。&lt;/li&gt;&#xA;&lt;li&gt;持久化的日志。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;所以，图示中将这三部分合在一起放在同一个矩形里，表示一个Server有这三个组件。&lt;/p&gt;&#xA;&lt;p&gt;另外还需注意的是，一般这种分组中外围的矩形，有这样的讲究：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一般使用斜面矩形，即四个角是圆角的矩形，这样圆润一些的边角看起来会更舒服一些，如上图。&lt;/li&gt;&#xA;&lt;li&gt;如果这个组合，是一种逻辑上的组合，那么线的形状一般用虚线；否则就一般用的实线。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在分组时，有时候可以将相同类型的模块层叠起来，这样会更加简洁，如下图：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;raft&#34; src=&#34;https://www.codedump.info/media/imgs/20220304-weekly-8/raft.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; raft &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图是出自Raft论文中的状态机模型，其中想要表达的一个点是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;有多个client向server发起请求。&lt;/li&gt;&#xA;&lt;li&gt;server要达成一致，需要将日志在server之间同步。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;但是上图中，并没有把这些同类型的组件分开表达，而是巧妙的使用层叠的方式，简洁得表达了有多个client、多个server的情况。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;趋势&#34;&gt;&#xA;  趋势&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%b6%8b%e5%8a%bf&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;如果不同的组件之间，有不同的趋势，可以在图中使用类似箭头这样的符号表达出来。&lt;/p&gt;&#xA;&lt;p&gt;下图是描述不同层次存储的访问速度，于是用了两个方式来表达访问速度的变化趋势：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;左边的箭头表达速度和成本的变化。&lt;/li&gt;&#xA;&lt;li&gt;不同大小的多边形表达了这些存储空间的变化：越往上访问速度越快，但是对应的存储空间也更小。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;cache&#34; src=&#34;https://www.codedump.info/media/imgs/20220304-weekly-8/cache.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; cache &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;再比如，下图中，是说明sqlite中btree页面的数据组织的。其中的两部分内容，&lt;code&gt;Cell地址数组&lt;/code&gt;以及&lt;code&gt;Cell内容区&lt;/code&gt;为变长大小，前者从地址低位向高位生长，后者反之，于是在图中，就用箭头示例出地址的高低位区别，以及两者的增长方向：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;页面内数组的组织&#34; src=&#34;https://www.codedump.info/media/imgs/20220201-sqlite-btree-5-btree/page-format.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 页面内数组的组织 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;（出自&lt;a href=&#34;https://www.codedump.info/post/20220201-sqlite-btree-5-btree/&#34;&gt;sqlite3.36版本 btree实现（五）- Btree的实现 - codedump的网络日志&lt;/a&gt;）&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;联系&#34;&gt;&#xA;  联系&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%81%94%e7%b3%bb&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;用箭头等表示数据、状态等的走向，或者模块之间的联系。&lt;/p&gt;&#xA;&lt;p&gt;这在涉及：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;状态切换。&lt;/li&gt;&#xA;&lt;li&gt;数据流向。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;等场景下是非常常见的手段，比如经典的TCP状态机切换：&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第7期）：一个C系程序员的Rust初体验</title>
      <link>https://www.codedump.info/zh/post/20220227-weekly-7/</link>
      <pubDate>Sun, 27 Feb 2022 11:25:33 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220227-weekly-7/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：在工作里使用Rust已经有两个多月的时间了，谈谈我做为一名多年的C系（C、C++）程序员，对Rust的初体验。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;一个c系程序员的rust初体验&#34;&gt;&#xA;  一个C系程序员的Rust初体验&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%80%e4%b8%aac%e7%b3%bb%e7%a8%8b%e5%ba%8f%e5%91%98%e7%9a%84rust%e5%88%9d%e4%bd%93%e9%aa%8c&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;最近由于工作的原因，使用上了Rust语言，在此之前我有多年的C、C++编码经验（以下将C、C++简称C系语言）。&lt;/p&gt;&#xA;&lt;p&gt;使用C系语言编码时，最经常面对的问题就是内存问题，诸如：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;野指针（Wild Pointer）：使用了不可知的指针变量，如已经被释放、未初始化、随机，等等。&lt;/li&gt;&#xA;&lt;li&gt;内存地址由于访问越界等原因被覆盖（overflow），这不但是可能出错的问题，还有可能成为程序的内存漏洞被利用。&lt;/li&gt;&#xA;&lt;li&gt;内存分配后未回收。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;连Chrome的报告都指出，Chrome中大约70%的安全漏洞都是内存问题，见：&lt;a href=&#34;https://www.chromium.org/Home/chromium-security/memory-safety/&#34;&gt;Memory safety&lt;/a&gt;。（不仅如此，微软的文章也显示在微软的产品中70%的安全漏洞也是内存问题，见：&lt;a href=&#34;https://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/&#34;&gt;Microsoft: 70 percent of all security bugs are memory safety issues | ZDNet&lt;/a&gt;）&lt;/p&gt;&#xA;&lt;p&gt;C系语言发展到今天，已经有不少可以用于内存问题检测的利器了，其中最好用的莫过于&lt;a href=&#34;https://en.wikipedia.org/wiki/AddressSanitizer&#34;&gt;AddressSanitizer&lt;/a&gt;，它的原理是在编译时给程序加上一些信息，一旦发生内存越界访问、野指针等错误都会自动检测出来。&lt;/p&gt;&#xA;&lt;p&gt;但是即便有这些工具，内存问题也不好解决，其核心的原因在于：这些问题绝大部分都是运行时（Runtime）问题，即要在程序跑到特定场景的时候才会暴露出来，诸如上面提到的AddressSanitizer就是这样。&lt;/p&gt;&#xA;&lt;p&gt;都知道解决问题的第一步是能复现问题，而如果一个问题是运行时问题，这就意味着：复现问题可能会是一件很麻烦的事情，有时候还可能到生产环境去复现。&lt;/p&gt;&#xA;&lt;p&gt;以我之前经历的一个Bug来看这类工作的复杂度，见&lt;a href=&#34;https://www.codedump.info/post/20190413-problem-fix/&#34;&gt;线上存储服务崩溃问题分析记录 - codedump的网络日志&lt;/a&gt;，这是一个很典型的发生在生产环境上由于内存错误导致的崩溃问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;不好复现，因为跟特定的请求相关，还跟线程的调度有关；&lt;/li&gt;&#xA;&lt;li&gt;本质是由于使用了被释放的内存导致的错误。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这个线上问题，记得当时花了一周时间来复现问题解决。&lt;/p&gt;&#xA;&lt;p&gt;换言之，如果一个问题要等到运行时才能发现，那么可以预见的是：一旦出现问题，要复现问题可能要花费大量的精力，以及需要很多经验才行。如果一个问题还是在特定场景，或者用户现场才出现的，那就更麻烦了，C系程序员以往一般都是这样来保存“现场”：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;出现崩溃的时候保存core文件来查看调用堆栈、变量等信息。&lt;/li&gt;&#xA;&lt;li&gt;发明了各种复制流量重放的工具，比如&lt;a href=&#34;https://github.com/session-replay-tools/tcpcopy&#34;&gt;tcpcopy&lt;/a&gt;等。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;总而言之，运行时问题一旦出现是很麻烦的，而解决这类问题的时间是难以预期的。&lt;/p&gt;&#xA;&lt;p&gt;Rust给这类内存问题的解决提供了另一个解决思路：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;一个内存地址同时只能被一个变量使用。&lt;/li&gt;&#xA;&lt;li&gt;不能使用未初始化的变量。&lt;/li&gt;&#xA;&lt;li&gt;&amp;hellip;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;简而言之，凡是可能出现内存错误的地方，都在语言的语法层面给予禁止，换来的就是更多的编译时间，因为要做这么多检查嘛，而需要更多的编译时间反过来就需要更好的硬件。我想这也是Rust到了最近几年才开始慢慢流行开来的原因之一，毕竟即便是现在，一些大型的Rust项目普通的机器编译起来也还是很耗时。&lt;/p&gt;&#xA;&lt;p&gt;“编译时间（compile time）”是一个可以预期的固定时间，能通过增加硬件性能（比如买更好的机器来写Rust）来解决；而“运行时问题”一旦出现，查找起来的时间、精力、场景（比如出现在用户现场、几百万次才能重现一次等）不确定性可就很高了。&lt;/p&gt;&#xA;&lt;p&gt;两者权衡，我选择解决“编译时间”问题。而且，在我意识到有这样的工具能够在编译期解决大部分内存问题时，反过来再看使用C系语言的项目，几乎可以预期的是：只要代码和复杂度上了一定规模，那么这类项目都要花上相当的一段时间才能稳定下来。原因在于：类似内存问题这样的运行时问题，是需要场景去积累，才能暴露出来的，而场景的积累，就需要很多的小白鼠和运行时间了。&lt;/p&gt;&#xA;&lt;p&gt;总结一下我的观点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;C系语言最多的问题就是各类内存问题，而这些问题大多是运行时问题。即便现在已经有了各种工具，解决其运行时问题也很困难。&lt;/li&gt;&#xA;&lt;li&gt;Rust解决这类问题的思路，是在语法层面禁止一切可能出现内存问题的操作，换来的代价就是更多的编译时间。&lt;/li&gt;&#xA;&lt;li&gt;解决可预期的“编译时间”和难预期的“运行时问题”，我选择前者。人生苦短，浪费时间在解决运行时的各种内存问题太不值当了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;番外篇&#34;&gt;&#xA;  番外篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%95%aa%e5%a4%96%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;rr&#34;&gt;&#xA;  rr&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#rr&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://rr-project.org/&#34;&gt;rr: lightweight recording &amp;amp; deterministic debugging&lt;/a&gt;也是出自Mozilla的另一款调试C系程序的利器，&lt;code&gt;rr&lt;/code&gt;是&lt;code&gt;Record and Replay&lt;/code&gt;的简称，目的还是为了解决各种运行时问题，由于运行时问题中存在着各种不确定的因素，包括：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;变量值。&lt;/li&gt;&#xA;&lt;li&gt;进程、线程环境，比如不同的线程调度顺序可能导致了不同的结果。&lt;/li&gt;&#xA;&lt;li&gt;输入不同的数据，能得到不同的结果。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;于是，&lt;code&gt;rr&lt;/code&gt;要解决的核心问题，就是让一个程序在运行时有一个固定的环境，它可以抓取程序运行的环境保存下来。这样在出现问题之后，就能使用它可以记录下来程序运行时的环境，不停的重放来调试解决问题。&lt;/p&gt;&#xA;&lt;p&gt;但是，即便是这样，&lt;code&gt;rr&lt;/code&gt;可能更适合于明确知道问题的情况下去抓取环境，不可能在线上直接打开这个工具。所以又回到前面的结论了：调试运行时问题可能面对的困难，包括场景、时间、用户现场等等不确定因素。&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;rr&lt;/code&gt;和&lt;code&gt;Rust&lt;/code&gt;一样，都出自Mozilla，我想不是偶然的。Mozilla和chrome等一样，都是使用C++编码的超大型项目，而这里一定遇到了各种运行时问题，不止于内存问题，所以才要使用各种工具来辅助解决这类问题。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;吃上硬件升级的红利了吗&#34;&gt;&#xA;  吃上硬件升级的红利了吗？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%90%83%e4%b8%8a%e7%a1%ac%e4%bb%b6%e5%8d%87%e7%ba%a7%e7%9a%84%e7%ba%a2%e5%88%a9%e4%ba%86%e5%90%97&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;前面提到过，Rust目前较大的问题是编译时间过长，这可能是导致它最近几年才开始逐渐流行开来的原因。其实反过来说，在硬件升级之后，应该能尽量利用上硬件，在编译期尽量多检查出错误来，减少运行时发现问题的数量。这样，才能吃上硬件升级的红利，利用硬件来减少自己的犯错。&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第6期）：《sqlite 3.36 btree实现解析》番外篇</title>
      <link>https://www.codedump.info/zh/post/20220220-weekly-6/</link>
      <pubDate>Sun, 20 Feb 2022 10:53:41 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220220-weekly-6/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：从2021年9月份开始要探索生产级btree存储引擎的实现，到2022年2月整理完毕发布《sqlite 3.36 btree实现解析》的系列文章，我花费了小半年的时间，本期会聊聊整个过程下来我的一些想法。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;sqlite-336-btree实现解析番外篇&#34;&gt;&#xA;  《sqlite 3.36 btree实现解析》番外篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#sqlite-336-btree%e5%ae%9e%e7%8e%b0%e8%a7%a3%e6%9e%90%e7%95%aa%e5%a4%96%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;时间回到2021年9月份。彼时，因为工作的关系，要研究一下生产级btree存储引擎的实现，在此之前我大体对btree、b+tree的数据结构和算法有个了解，见：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200609-btree-1/&#34;&gt;B树、B+树索引算法原理（上） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200615-btree-2/&#34;&gt;B树、B+树索引算法原理（下） - codedump的网络日志&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;但是，一个生产级的产品，对比教科书的示范型代码，还是有很大的区别的，具体来说，我当时不明白以下这些生产级存储引擎的问题如何解决：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如何存储变长的数据？&lt;/li&gt;&#xA;&lt;li&gt;如何存储数据大小超过一个物理页面的数据？&lt;/li&gt;&#xA;&lt;li&gt;如何利用被回收的空间？&lt;/li&gt;&#xA;&lt;li&gt;如何处理崩溃恢复？&lt;/li&gt;&#xA;&lt;li&gt;读写并发如何处理？&lt;/li&gt;&#xA;&lt;li&gt;&amp;hellip;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;为了解答这些疑问，先后去翻阅InnodDB、WiredTiger、sqlite的文档，但是这些项目代码量都太大了，以我当时的程度，无法马上找到很具体的解答。&lt;/p&gt;&#xA;&lt;p&gt;事情的突破在从网上查找文章时看到的这一篇文章：&lt;a href=&#34;https://dzone.com/articles/database-btree-indexing-in-sqlite&#34;&gt;How Database B-Tree Indexing Works - DZone Database&lt;/a&gt;，这是一篇解释btree工作原理的文章，这篇文章同时还列出了一个项目：&lt;a href=&#34;https://github.com/madushadhanushka/simple-sqlite&#34;&gt;madushadhanushka/simple-sqlite: Code reading for sqlite backend&lt;/a&gt;，这个项目的作者，将sqlite2.5版本中btree的实现，单独抽取出来形成了一个独立的KV库，可以编译通过使用。&lt;/p&gt;&#xA;&lt;p&gt;看到这个项目的时候，我的感觉就是如获至宝，因为虽然只有几千行的代码量，但是解答了很多上面提到的疑问，“麻雀虽小五脏俱全”，我花了几天的时间整体阅读了解了原理，这个项目给我打开了研究生产级btree存储引擎的突破口。&lt;/p&gt;&#xA;&lt;p&gt;在这以后，考虑到2.5版本的sqlite已经是2002年的作品，距离现在时间太久了，还想接着了解后面做了那些改进，又接着阅读了3.6.10版本的实现，找这个版本的原因，是因为这是sqlite官方在github上同步的第一个版本，那时候仍然步子不敢迈得太大。&lt;/p&gt;&#xA;&lt;p&gt;又花了一个多月把这个版本的btree实现了解以后，我了解到在这之后的版本里，sqlite做了另一个重大的更新：在页面管理部分引入了WAL机制，加上前面两个版本阅读下来累积的信心，就接着找当时还是最新的3.36版本的实现来阅读，这又花了一个多月的时间。&lt;/p&gt;&#xA;&lt;p&gt;这以后，就是逐步将整理的笔记写成文档了，后续的事情不表，都在这几篇文档里。&lt;/p&gt;&#xA;&lt;p&gt;回头看这整个流程，我自己的感受是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;“问题驱动”可能是效率更高的学习方式，带着问题出发、找到自己疑问的答案，能更快的学习某个知识。&lt;/li&gt;&#xA;&lt;li&gt;生产级的实现和教科书的区别很大，后者更多的是讲解原理，而生产级实现考虑更多的是各种实际生产中的边际情况。如果只了解原理，而不去具体做实现，对事情的理解最后只能浮于表面。&lt;/li&gt;&#xA;&lt;li&gt;找到那个精简实现是这个过程里的“突破口”，原因在于：如果一上来看的很成熟的版本，而且你在这个领域积累的不深，那么很可能会导致丢失了很多“上下文（context）”情景，给阅读、理解带来很大困难。下次再遇到类似的问题，我会按照这次的经验，先尝试回退到之前的更简单的版本，看看在那里能不能跟上作者的思路，攻克简单的实现之后，再尝试最新的版本。&lt;/li&gt;&#xA;&lt;li&gt;除了数据库领域以外，有一些别的领域，在教学的时候会让学生参与实现一个简单的项目。这类型的项目虽然简单，但是五脏俱全，能够让学生了解这个领域的概貌，我把这种流程称为“破解神秘感”。如我最开始提到的那些疑问，如果在这之前做过数据库相关的作业，应该会有个大体的想法。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;这篇番外篇的番外篇&#34;&gt;&#xA;  这篇番外篇的番外篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%bf%99%e7%af%87%e7%95%aa%e5%a4%96%e7%af%87%e7%9a%84%e7%95%aa%e5%a4%96%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;sqlite的注释&#34;&gt;&#xA;  sqlite的注释&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#sqlite%e7%9a%84%e6%b3%a8%e9%87%8a&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;除了这些以外，sqlite的代码风格也很好，尤其是注释写的非常详尽。&lt;/p&gt;&#xA;&lt;p&gt;有一种说法，“好的代码都是自解释的，无需多做注释”。我对这句话有一些不太一样的看法，因为即便再好的代码，如果只看代码的话，对整个的架构、结构很难了解。这一点sqlite就做的很好，在代码中会写上类似文档一样的注释来解释结构，比如有这么一段解释btree内部结构的注释文档：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** This file implements an &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;external&lt;/span&gt; (disk-based) database using BTrees.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** For a detailed discussion of BTrees, refer to&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**     Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**     &lt;span style=&#34;color:#666;font-style:italic&#34;&gt;&amp;#34;Sorting And Searching&amp;#34;&lt;/span&gt;, pages 473-480. Addison-Wesley&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**     Publishing Company, Reading, Massachusetts.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** The basic idea is that each page of the file contains N database&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** entries and N+1 pointers to subpages.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**   ----------------------------------------------------------------&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**   |  &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Ptr&lt;/span&gt;(0) | &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Key&lt;/span&gt;(0) | &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Ptr&lt;/span&gt;(1) | &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Key&lt;/span&gt;(1) | ... | &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Key&lt;/span&gt;(N-1) | &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Ptr&lt;/span&gt;(N) |&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**   ----------------------------------------------------------------&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** All of the keys on the page that &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Ptr&lt;/span&gt;(0) points to have values less&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** than &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Key&lt;/span&gt;(0).  All of the keys on page &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Ptr&lt;/span&gt;(1) and its subpages have&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** values greater than &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Key&lt;/span&gt;(0) and less than &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Key&lt;/span&gt;(1).  All of the keys&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** on &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Ptr&lt;/span&gt;(N) and its subpages have values greater than &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;Key&lt;/span&gt;(N-1).  And&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;** so forth.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;&#34;&gt;*/&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果不写这些注释，读者想要理解作者的思路的话，仅凭代码是很困难的。&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第5期）：从存储模型聊一聊时序数据库的应用场景</title>
      <link>https://www.codedump.info/zh/post/20220211-weekly-5/</link>
      <pubDate>Fri, 11 Feb 2022 22:27:57 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220211-weekly-5/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：本期介绍时序数据库的存储模型，只有理解了时序数据的存储模型，才能更好的了解时序数据库的优缺点以及其适用场景。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;从存储模型聊一聊时序数据库的应用场景&#34;&gt;&#xA;  从存储模型聊一聊时序数据库的应用场景&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bb%8e%e5%ad%98%e5%82%a8%e6%a8%a1%e5%9e%8b%e8%81%8a%e4%b8%80%e8%81%8a%e6%97%b6%e5%ba%8f%e6%95%b0%e6%8d%ae%e5%ba%93%e7%9a%84%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;想写本文，是因为看到了知乎上的一篇文章：&lt;a href=&#34;https://zhuanlan.zhihu.com/p/453556881&#34;&gt;投资数据库领域：2021年总结（NoSQL、图、时序） - 知乎&lt;/a&gt;，里面谈到了时序数据库：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;但缺陷是实际的市场空间较小。跟通用型数据库，尤其是OLAP数据库相比，时序数据库最大的差异点在于对于时间维度建立了独特的索引与优化，而其他所谓schemaless等特性在OLAP数据库上都能做到，不存在技术障碍。这也就是为什么其实在公司做时序场景的数据库选型的时候会直接将时序数据库与一些OLAP数据库（比如ClickHouse）做比较。如果要把时序数据库往更宽的场景发展，那就是想好如何与那么多的通用型数据库做竞争了。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;由于之前有过短暂一段时间的时序数据库从业经历，所以想从我的理解聊聊时序数据库的应用场景。&lt;/p&gt;&#xA;&lt;p&gt;要了解应用场景，需要首先对时序数据库的存储模型有个大概的了解，在下文中我尽量不涉及到太艰深的技术术语来描述我的理解。由于我从业时序数据库的时间并不长，所以有可能理解会有偏差。&lt;/p&gt;&#xA;&lt;p&gt;何谓“时序数据（time-series data）”？就我个人粗浅的理解，就是任何一定会带上时间戳（timestamp）维度的数据。日常生活里，在微博、微信等社交媒体的发现就可以理解时序数据，因为它们肯定都有一个发言时间，所以有时候会把个人看到的微博等称为“时间线（timeline）”。对应到工业领域，比如一个电表每小时上报的用电量也是时序数据，比如服务器监控时每隔15分钟采集的性能数据也是时序数据。&lt;/p&gt;&#xA;&lt;p&gt;由于时序数据天然有“时间“这个维度，为了更好的优化其写入性能，通常专门存储时序数据的存储引擎会按照时间分块、按列来存储数据，如下图：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;数据库文件的物理页面组织和逻辑页面结构&#34; src=&#34;https://www.codedump.info/media/imgs/20220211-weekly-5/time-series-model.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 数据库文件的物理页面组织和逻辑页面结构 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中，演示用的数据格式有三列：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;时间戳。&lt;/li&gt;&#xA;&lt;li&gt;A指标。&lt;/li&gt;&#xA;&lt;li&gt;B指标。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;通常，时序数据库存储时，会按照时间来划分块（block）：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;块的大小固定。&lt;/li&gt;&#xA;&lt;li&gt;在同一个块时间区的数据，会存储到同一个块中。&lt;/li&gt;&#xA;&lt;li&gt;而块内部，将除了时间维度之外的其他的列，将其中相同列的数据存储在一起。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这样做的好处是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;由于时序数据的特点，写入的数据也是在时间上连续的，因此通常写入的时候按照上面的设计就能落在同一个块中。&lt;/li&gt;&#xA;&lt;li&gt;不同行但是同一列的数据，都是相同类型的，将相同类型的数据紧邻放在一起，更容易进行压缩。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;换言之，这样做换来的好处是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在时序数据的写入场景下，写入速度很快。&lt;/li&gt;&#xA;&lt;li&gt;由于同类型数据放在一起，压缩性能也很好。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这些都是相对于传统BTree类存储引擎而言的，因为这类型的数据写入更像append操作，这是必然会更快的。&lt;/p&gt;&#xA;&lt;p&gt;但是注意到没有，这样存储数据之后，最大的问题是：查询时只有时间这个维度做了索引，而除去时间维度之外的其他列都没有做索引。&lt;/p&gt;&#xA;&lt;p&gt;这样导致的问题是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;任何查询都要带上时间参数才能管用。比如：“请查询过去一个小时里哪五分钟的CPU最高”这样的查询是可以的，但是更多其他的查询是不知道时间维度，或者说查询者就是不知道具体时间才想来查询的，比如“我是什么时候达成了累计跑步100公里成就的？”这类探索型、且没有时间维度的查询。&lt;/li&gt;&#xA;&lt;li&gt;即便是带上了时间维度的查询可行，由于没有对其他维度做索引，所以查询时的处理，更多的是按照时间维度查询出数据、再进行聚合计算，比如上面的“请查询过去一个小时里哪五分钟的CPU最高”这个查询，只能先把过去一小时的CPU数据全部查出来，然后逐个计算才能算出哪5分钟的CPU最高了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;总结下来：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;时序数据库根据时序数据的特点设计和优化了时序数据库的存储模型，对比传统的关系数据库存储模型来说，优势是写入速度快、压缩比高。&lt;/li&gt;&#xA;&lt;li&gt;但这样的存储只有时间这个维度，换言之由于没有其他维度的索引数据，导致对不带有时间维度或者时间跨度大的查询支持的不够友好。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;回到最开始引用的文章，了解了时序数据库的存储特点，也就能解释为何作者认为纯粹的时序数据库场景不大了。&lt;/p&gt;&#xA;&lt;p&gt;好像大部分时候，事情也是这样的：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在一个维度优化到极致，可能其他维度就做的不够好，不存在各个维度都能做得很好的产品，因为不同维度之间也会彼此有制约，更多时候要看使用者自己的场景取舍，并不存在适用于一切场景的产品。&lt;/li&gt;&#xA;&lt;li&gt;所谓”优势“，在换了上下文和场景之后，也可能会变成劣势。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;相关推荐&#34;&gt;&#xA;  相关推荐&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%9b%b8%e5%85%b3%e6%8e%a8%e8%8d%90&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;writing-a-time-series-database-from-scratch--fabian-reinartz&#34;&gt;&#xA;  &lt;a href=&#34;https://fabxc.org/tsdb/&#34;&gt;Writing a Time Series Database from Scratch | Fabian Reinartz&lt;/a&gt;&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#writing-a-time-series-database-from-scratch--fabian-reinartz&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://prometheus.io/&#34;&gt;Prometheus&lt;/a&gt;项目的核心开发者写的文章，介绍了如何从头实现一个时序数据库的存储引擎，可做为上面文章更深入的补充。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;cmu在2017年时组织的时序数据库系列讲座&#34;&gt;&#xA;  CMU在2017年时组织的时序数据库系列讲座&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#cmu%e5%9c%a82017%e5%b9%b4%e6%97%b6%e7%bb%84%e7%bb%87%e7%9a%84%e6%97%b6%e5%ba%8f%e6%95%b0%e6%8d%ae%e5%ba%93%e7%b3%bb%e5%88%97%e8%ae%b2%e5%ba%a7&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;邀请了一系列业内时序数据库的从业人员来分享，见：&lt;a href=&#34;https://db.cs.cmu.edu/seminar2017/&#34;&gt;Time Series Database Lectures – Fall 2017&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;阿里云数据库内核月报分类整理&#34;&gt;&#xA;  &lt;a href=&#34;https://github.com/tangwz/db-monthly&#34;&gt;阿里云数据库内核月报分类整理&lt;/a&gt;&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e9%98%bf%e9%87%8c%e4%ba%91%e6%95%b0%e6%8d%ae%e5%ba%93%e5%86%85%e6%a0%b8%e6%9c%88%e6%8a%a5%e5%88%86%e7%b1%bb%e6%95%b4%e7%90%86&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;阿里云数据库内核团队的月报提供了很多学习数据库技术的资料、文章，这个github将每个月的月报进行了分类整理，推荐收藏。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;how-does-a-database-work--lets-build-a-simple-database&#34;&gt;&#xA;  &lt;a href=&#34;https://cstack.github.io/db_tutorial/&#34;&gt;How Does a Database Work? | Let’s Build a Simple Database&lt;/a&gt;&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#how-does-a-database-work--lets-build-a-simple-database&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;自己动手打造一个简单的数据库，应该有很多地方参考了&lt;a href=&#34;https://sqlite.org/index.html&#34;&gt;SQLite&lt;/a&gt;的实现，本博客也深度解析了sqlite的btree实现，见：&lt;a href=&#34;https://www.codedump.info/post/20211217-sqlite-btree-0/&#34;&gt;sqlite3.36版本 btree实现（零）- 起步及概述 - codedump的网络日志&lt;/a&gt;。&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第4期）：为什么我还在看中国足球</title>
      <link>https://www.codedump.info/zh/post/20220204-weekly-4/</link>
      <pubDate>Fri, 04 Feb 2022 21:55:23 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220204-weekly-4/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：虎年大年初一的晚上，一场脆败发生在世界杯亚洲区预选赛中国客场对越南队的比赛上。如今，“你居然还在看中国男足”，仿佛已成一句骂人的质问。本期从我角度来谈谈，我眼中的中国足球，以及说说我为什么还一直在关注这个领域。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;我为什么还在看中国足球&#34;&gt;&#xA;  我为什么还在看中国足球&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%88%91%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%98%e5%9c%a8%e7%9c%8b%e4%b8%ad%e5%9b%bd%e8%b6%b3%e7%90%83&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;我从94年开始看球，中国足球绝大部分的重要比赛都看了：94年亚运会决赛输给乌兹别克斯坦、97年大连金州被伊朗逆转、2002年世界杯出线&amp;hellip;太多了，数不过来，算是从我开始看球之后就一直有关注中国足球。&lt;/p&gt;&#xA;&lt;p&gt;在看国足比赛二十多年之后，慢慢地从一个参与者、评论者的角色，切换到了近似于第三方视角的观察者角色。切换到这个视角之后，让我能从里面各种情绪里抽离出来，当然高兴的时候也会像个普通球迷那样欢乐，比如2017年世界杯预选赛击败韩国这样的比赛。&lt;/p&gt;&#xA;&lt;p&gt;我国虽然在奥运会上取得了看似很好的成绩，金牌数总是保持前列，但是有一说一，并不算是体育大国，更别提强国了。&lt;/p&gt;&#xA;&lt;p&gt;只看我们占优势、能取得好成绩的项目，大多有这样的特点：小众、冷门，这样的特点直接导致这样的项目，实际是商业化程度很低的领域。这样的领域，国外参与的人不会太多，也因此可以继续沿用以前我们擅长的打法：集中力量办大事，换到体育这个领域，就是所谓的“举国体制”。&lt;/p&gt;&#xA;&lt;p&gt;这样做的好处是，能用较少的资源拿到不错的效果，因为大部分人只关注金银牌这些数字，并不关心你怎么拿到的。这个策略，用知乎上一个回答的话来说叫“田忌赛马”，见：&lt;a href=&#34;https://www.zhihu.com/question/414037344/answer/1713991758&#34;&gt;为什么中国的其他运动项目那么强，到了男足这里就不行呢？&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;在商业化、职业化很好的体育项目，比如足球、篮球、网球等等领域，我们的成绩就不这么好了，李娜、姚明、刘翔是少数在这些领域拿得出手的世界级运动员。（后面会专门谈谈女足）&lt;/p&gt;&#xA;&lt;p&gt;一言以蔽之：举国体制从目前的成绩来看，并不适合职业化、商业化很好的体育项目。&lt;/p&gt;&#xA;&lt;p&gt;“足球是体育工业化的集大成者”（见&lt;a href=&#34;https://www.zhihu.com/question/310636566/answer/1720809481&#34;&gt;(为什么整个中国都知道中国足球的问题，为什么还是没有办法解决？ - 知乎&lt;/a&gt;），所以它不像其他领域那样，需要长期的积累和基础。&lt;/p&gt;&#xA;&lt;p&gt;所以，男足的存在，在我看来更多像是一个“大型的社会实验”，我在这个实验中，看到不同的政策、体制、行政干预、市场行为等对这个运动的影响，看到各方参与者、评论者、媒体的所作所为，从中能看到某些我们社会的缩影。&lt;/p&gt;&#xA;&lt;p&gt;男足也是“客观规律”的具象化代言人（见&lt;a href=&#34;https://weibo.com/1573046985/Ldz5zvYdY&#34;&gt;中国男足挺好的，理直&amp;hellip; - @祝佳音的微博 - 微博&lt;/a&gt;）：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;男足不跟你讲这些虚的，不按科学规律办事就是不行。给钱诱惑也不行，立规矩骂人也不行，做思想工作说服不行，临时加班加练也不行。富贵不能淫威武不能屈，不行就是不行。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;相对其他很多领域，足球的成绩更透明、公开，足球运动一直在提醒我要尊重“客观规律”。&lt;/p&gt;&#xA;&lt;p&gt;足球在中国，属于“参与度很低，但是关注度高”的体育项目。在这样的领域：看起来中超火爆的时候现场能有几万球迷现场观赛，看起来花了很多钱、投入了很多人力，但是只要没有按照足球的“客观规律”办事，成绩马上就能打脸，公开透明。&lt;/p&gt;&#xA;&lt;p&gt;规则透明、结果公开，等等这些都是我一直还在关注这个“大型社会实验”的原因。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;番外&#34;&gt;&#xA;  番外&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%95%aa%e5%a4%96&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;番外篇里，试图简单科普关于中国足球相关的几个常见问题。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;14亿人里为什么就选不出11个能踢球的人&#34;&gt;&#xA;  “14亿人里为什么就选不出11个能踢球的人？”&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#14%e4%ba%bf%e4%ba%ba%e9%87%8c%e4%b8%ba%e4%bb%80%e4%b9%88%e5%b0%b1%e9%80%89%e4%b8%8d%e5%87%ba11%e4%b8%aa%e8%83%bd%e8%b8%a2%e7%90%83%e7%9a%84%e4%ba%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;这是最常见的问题了，用类比的方式试图回答一下，这就好比问：“这么大一块沙漠为什么就种不出几棵树来？”。显然很多人并没有意识到，我国在足球从业人员领域属于“沙漠”，只是看起来热闹，仅此而已。&lt;/p&gt;&#xA;&lt;p&gt;我们的选材不是从14亿里选，而是足球注册球员里面选（中国好像只有几万足球从业人口），这个基数对比足球发达国家差的很多。&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;怎么看待女足的成绩比男足好这么多&#34;&gt;&#xA;  怎么看待女足的成绩比男足好这么多？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%80%8e%e4%b9%88%e7%9c%8b%e5%be%85%e5%a5%b3%e8%b6%b3%e7%9a%84%e6%88%90%e7%bb%a9%e6%af%94%e7%94%b7%e8%b6%b3%e5%a5%bd%e8%bf%99%e4%b9%88%e5%a4%9a&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;女足有过比男足更光辉的历史：奥运会银牌、世界杯亚军。但是需要认识到，这些成绩的取得，已经年代久远，距离现在有20多年了。&lt;/p&gt;&#xA;&lt;p&gt;取得这些成绩很大的原因是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;由于宗教、妇女地位等原因，参与女足运动的国家并不多。&lt;/li&gt;&#xA;&lt;li&gt;当时职业化的女足并不多。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;比如在亚洲，男足要打世界杯预选赛、亚洲杯决赛这样的比赛，都要先进行一轮预选赛，因为有50多个国家参与；而女足对应的比赛，则无需预选赛，因为参与的队伍实在不多，比如一大批相对落后的国家派不出女足，比如阿拉伯国家也不让女足参赛（这次女足亚洲杯伊朗倒是参赛了）。&lt;/p&gt;&#xA;&lt;p&gt;另外，说到女足职业化。随着欧美女足职业联赛的发展，她们的水平提高了很多，此消彼长，这就是后来北京奥运会、东京奥运会大比分输球，以及世界排名一路滑到19名的原因。&lt;/p&gt;&#xA;&lt;p&gt;做一个可能不正确的类比：10个人参加的比赛里，取得第3名的成绩，确实比50个人参加的比赛里取得第10名，看起来好看一些。&lt;/p&gt;&#xA;&lt;p&gt;但是，即便抛开职业化、商业化、参赛队伍基数等因素，有一说一，女足的精气神确实比男足要高出一大截来，尤其在春节密集得看了几场男足、女足的比赛对比就更明显了。&lt;/p&gt;&#xA;&lt;p&gt;豆瓣上有人整理了本届女足亚洲杯现役国家队成员的介绍：&lt;a href=&#34;https://www.douban.com/group/topic/258215115/?_dtcc=1&amp;amp;_i=4158490DTua3Gc&#34;&gt;中国女足现役国家队队员介绍（亚洲杯来了）&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 class=&#34;heading&#34; id=&#34;是否一定要搞好足球&#34;&gt;&#xA;  是否一定要“搞好足球”？&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%98%af%e5%90%a6%e4%b8%80%e5%ae%9a%e8%a6%81%e6%90%9e%e5%a5%bd%e8%b6%b3%e7%90%83&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;这个问题见仁见智，我不认为一定有“搞好足球”的必要，毕竟比这个事情重要的事情还有很多，“足球”也并不能代表一个国家的综合国力。&lt;/p&gt;&#xA;&lt;p&gt;我比较同意知乎这个回答里的几段话：&lt;a href=&#34;https://www.zhihu.com/question/310636566/answer/1830192531&#34;&gt;为什么整个中国都知道中国足球的问题，为什么还是没有办法解决？ - 知乎&lt;/a&gt;&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;因为和整个国家要解决的问题来比，中国足球不重要。&lt;/p&gt;&#xA;&lt;p&gt;&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;有基建重要吗？有国防重要吗？有教育重要吗？有医疗重要吗？有扶贫攻坚重要吗？有抗击疫情重要吗？……三百六十行，至少得有三百行排在竞技体育前面好不好？&lt;/p&gt;&#xA;&lt;p&gt;中国人喜欢足球吗？中国人只喜欢看足球，而且是可有可无的那种喜欢。真喜欢踢，投身足球事业的不会只有这么点。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;但是不同意回答里的这句话：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;中国足球的问题不是没有办法解决，要解决中国足球的问题，集国家力量有一万种办法解决。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;毕竟我们还从来没有解决过，实事求是。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;总结&#34;&gt;&#xA;  总结&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%80%bb%e7%bb%93&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;总结一下我的观点：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;足球属于商业化、职业化程度很高的竞技体育项目，“举国体制”从目前来看不适用于这样的领域。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;足球在中国属于“参与度低、关注度高”的项目，看着很热闹，实际真正参与、从业的人并不多。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;因为早期我们只关注奥运会的金银牌，所以集中力量发展冷门项目更容易出成绩。大部分人只关注简单的数字、金银牌，不会关注背后的难易程度，“10个人参加的比赛里，取得第3名的成绩，确实比50个人参加的比赛里取得第10名，看起来好看一些。”&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;我并不认为，有一定要“搞好足球”的必要性，至少现在没有，因为还有更重要的事情需要做。&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第3期）：一个前游戏开发者眼中的游戏后端技术</title>
      <link>https://www.codedump.info/zh/post/20220129-weekly-3/</link>
      <pubDate>Sat, 29 Jan 2022 14:21:20 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220129-weekly-3/</guid>
      <description>&lt;hr&gt;&#xA;&lt;p&gt;引言：在我之前的工作里，因为各种原因，断续在游戏行业里有过总共大概四年左右的从业时间，今天想从我的视角聊聊游戏行业后端开发相关的技术，供那些想在这个行业从业，尤其是后端开发从业人员一些参考。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;一个前游戏开发者眼中的游戏后端技术&#34;&gt;&#xA;  一个前游戏开发者眼中的游戏后端技术&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%80%e4%b8%aa%e5%89%8d%e6%b8%b8%e6%88%8f%e5%bc%80%e5%8f%91%e8%80%85%e7%9c%bc%e4%b8%ad%e7%9a%84%e6%b8%b8%e6%88%8f%e5%90%8e%e7%ab%af%e6%8a%80%e6%9c%af&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;由于我从业的方向都是后端开发，所以这里仅谈论游戏开发后端的技术。&lt;/p&gt;&#xA;&lt;p&gt;游戏本质是个内容行业，所以游戏开发时相当一部分工作内容，就是不停的更新新的内容出来给玩家消费。这些内容包括但不限于：新的英雄、技能、玩法，等等。&lt;/p&gt;&#xA;&lt;p&gt;而要支撑这些玩法，相当一部分是策划的配表数据，即：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;程序员把玩法的框架、逻辑搭建好，留出读玩法数据的接口来。&lt;/li&gt;&#xA;&lt;li&gt;玩法数据由负责该玩法的策划来配表实现。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这其实就是很朴素的“表驱动”的编程方式。&lt;/p&gt;&#xA;&lt;p&gt;因为这个原因，所以游戏服务器启动时，要加载相当多的数据，主要有：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;玩家的数据，包括账号、角色、帮派、金钱等数据。&lt;/li&gt;&#xA;&lt;li&gt;玩法相关的策划配表数据。比如一个场景的坐标位置、NPC的坐标位置、任务，等等。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;由于需要在启动的时候要加载很多数据，游戏服务器才能完成初始化，所以“编译型”语言在游戏开发里并不适用来编写游戏玩法逻辑，试想下面的开发场景：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;策划提出了新的玩法需求。&lt;/li&gt;&#xA;&lt;li&gt;开发使用如C++这样的编译型语言编码实现玩法。&lt;/li&gt;&#xA;&lt;li&gt;编译新修改的代码：这里面有包括了编译、修改编译不通过时候的报错，等等。&lt;/li&gt;&#xA;&lt;li&gt;停止服务器，重启服务器来验证玩法的逻辑。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以看到，姑且不论其他因素，单是每次验证代码修改时需要停服、重启服务器的流程，而重启时又要加载一堆数据才能完成初始化，这个过程就严重影响开发效率。&lt;/p&gt;&#xA;&lt;p&gt;换言之，“编译型”语言并不适合于用来编码在游戏开发里需要经常变更的玩法逻辑。&lt;/p&gt;&#xA;&lt;p&gt;于是，一种新的开发架构出现了，见下图。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;游戏后端的架构分层&#34; src=&#34;https://www.codedump.info/media/imgs/20220129-weekly-3/game-server-arch.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; 游戏后端的架构分层 &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;在图中，将游戏服务器架构分为了两层：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;引擎层：这部分由C++编码，实现了游戏开发中与具体逻辑关系不大、且不太会变更的部分，如网络数据收发、数据库访问，等等。&lt;/li&gt;&#xA;&lt;li&gt;脚本层：这部分由Python、Lua这样的脚本语言实现，主要就是各种玩法。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;采用这样的架构最主要的优点，就是解决前面提到的开发效率问题。由于Python、Lua这样的脚本语言，支持热更新，即“不需要重启进程也能更新最新的代码”，这样开发模式就变成了：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;策划提出了新的玩法需求。&lt;/li&gt;&#xA;&lt;li&gt;开发使用如Python、Lua这样的脚本型语言编码实现玩法。&lt;/li&gt;&#xA;&lt;li&gt;热更新脚本代码，调试玩法。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;可以看到，由于支持“热更新”，省去了停服、重启、加载数据的步骤，一下子开发效率就提升了很多。&lt;/p&gt;&#xA;&lt;p&gt;多说一句，“热更新”还有一个优点：假如线上出问题时，总不可能停服下来修复，热更新不需要重启就能更新最新代码的特点在这里又发挥了作用。&lt;/p&gt;&#xA;&lt;p&gt;从这个开发架构里，也可以看到游戏服务器人员的构成：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;引擎层只有少数人能够去维护，要求稳定、高效。&lt;/li&gt;&#xA;&lt;li&gt;绝大部分人，都在脚本层用脚本语言来写各种玩法逻辑，类似于web开发中的CRUD。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这个架构已经很久没变化了，而且每个公司都有自己一套（甚至几套）维护得较为稳定的服务器引擎，这会有一个问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;技术演进慢。十几年前的架构到现在基本没变。&lt;/li&gt;&#xA;&lt;li&gt;由于演进慢，而且对稳定性的要求高，实际上新人也很难找到机会在引擎层发挥作用，引擎层的代码通常也不会让脚本层工作的人看到，这样就进入不到这一层的开发，而且引擎层绝大部分的问题已经被前人解决了，用现在的话来说，这部分技术“固化”了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;为了证实我的观点，我找来了去年刚出版的&lt;a href=&#34;https://book.douban.com/subject/35669430/&#34;&gt;《腾讯游戏开发精粹Ⅱ 》&lt;/a&gt;这本书，来看看书里关于游戏服务器都有哪些内容。全书一共21章，与游戏服务器相关的只有三章：&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;部分Ⅴ 服务端架构和技术&lt;/p&gt;&#xA;&lt;p&gt;第15章 面向游戏的高性能服务网格TbusppMesh 304&lt;/p&gt;&#xA;&lt;p&gt;15.1　TbusppMesh摘要 304&lt;/p&gt;&#xA;&lt;p&gt;15.2　TbusppMesh数据通信 305&lt;/p&gt;&#xA;&lt;p&gt;15.3　TbusppMesh组网策略 309&lt;/p&gt;&#xA;&lt;p&gt;15.4　TbusppMesh有状态服务 315&lt;/p&gt;&#xA;&lt;p&gt;15.5　总结 321&lt;/p&gt;&#xA;&lt;p&gt;第16章 游戏配置系统设计 322&lt;/p&gt;&#xA;&lt;p&gt;16.1　游戏配置系统概述 322&lt;/p&gt;&#xA;&lt;p&gt;16.2　游戏配置简介 322&lt;/p&gt;&#xA;&lt;p&gt;16.3　游戏配置系统 323&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊（第2期）：从笔记软件谈被体制化</title>
      <link>https://www.codedump.info/zh/post/20220123-weekly-2/</link>
      <pubDate>Sun, 23 Jan 2022 15:38:31 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220123-weekly-2/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;从笔记软件谈被体制化&#34;&gt;&#xA;  从笔记软件谈被体制化&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bb%8e%e7%ac%94%e8%ae%b0%e8%bd%af%e4%bb%b6%e8%b0%88%e8%a2%ab%e4%bd%93%e5%88%b6%e5%8c%96&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;前两周，一则收购消息，在偌大的中文互联网上，几乎没有掀起任何的讨论：&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://mp.weixin.qq.com/s/luImz3NFwN5zmNHsqvjmDg&#34;&gt;为知笔记并入 ONES，WizNote X 迎来新的征程&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;我是为知笔记的老用户了，从 2011 年就开始使用为知笔记，即便是现在不怎么使用的情况下，也已经把 VIP 会员续费到了 2024 年。在我看来，为知笔记是一个气质与众不同的互联网产品：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;在互联网上几乎没看到这款产品主动来宣传自己，都是靠用户的口碑传播，最开始我也是通过用户的介绍知道这款产品的。&lt;/li&gt;&#xA;&lt;li&gt;是最早支持 Markdown 的笔记本软件，这在我最开始了解 MD 并且开始用这个格式来记录笔记时起了很大的帮助。&lt;/li&gt;&#xA;&lt;li&gt;价格良心。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;所以，当看到为知笔记被收购的新闻，心情还是有些复杂的。我大体知道一些互联网产品的玩法，知道为知笔记应该生存的不太好，被收购了也许对团队是个好事。&lt;/p&gt;&#xA;&lt;p&gt;于是，我开始回想起来，到底是什么样的契机，让我开始慢慢抛弃了传统的笔记软件，以及我现在都用什么方式记录笔记。&lt;/p&gt;&#xA;&lt;p&gt;在 Evernote 刚出来的时候，多端可用、可以收藏文章、记录自己的笔记等等，我也大概是那时候开始记录笔记的，然后就是后来使用网易云笔记，再到主力使用为知笔记。&lt;/p&gt;&#xA;&lt;p&gt;让我逐渐意识到不应该把笔记托管到笔记软件上，有那么几个原因：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;笔记软件之间竞争激烈，可能你从 A 家换到 B 家的产品时，数据的迁移是个大问题，有时候就不得不丢掉一部分难迁移的数据。比如把数据从 Evernote 迁移出来就很麻烦，还好我之前放在这里的笔记也不太用得上了。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Markdown 格式开始流行以后。&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我大概是从2015年开始用MD格式开始写笔记的，这种格式马上就让我爱上了记录笔记：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;格式对比LaTex来说太简单了，只有常见的几种格式，易学易写。&lt;/li&gt;&#xA;&lt;li&gt;纯文本格式，这意味着用任何文本编辑器就能打开。&lt;/li&gt;&#xA;&lt;li&gt;搭配好用的编辑器，能获得”所见即所得“的体验。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;由此，我开始反思之前对笔记本软件的使用：以往都将数据的格式和具体的软件绑定了，这是后面一系列问题（迁移难、文本编辑器打不开）的核心原因。&lt;/p&gt;&#xA;&lt;p&gt;这样，我慢慢意识到，对记录笔记工具这件事情，应该是这样的：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;格式通用，且是纯文本格式。&lt;/li&gt;&#xA;&lt;li&gt;由于上面这个限制条件，于是就能将格式和写作软件解除绑定，我能在用不惯一款写作工具的时候，毫不费力的用另一款支持这个通用格式的软件继续写我的笔记。&lt;/li&gt;&#xA;&lt;li&gt;而为知笔记之类的笔记软件，即便后来支持了MD格式，也存储为自己的私有格式，导致了导出数据麻烦。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;我后来也是这样实践的：本地建一个仓库，在里面写MD格式文件，搭配顺手的MD编辑器，需要存储的时候将这个仓库同步到云端。&lt;/p&gt;&#xA;&lt;p&gt;现在看来，能让这个写笔记方式发生很大变化，有这么几个契机：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;MD格式开始流行。&lt;/li&gt;&#xA;&lt;li&gt;由于这个格式开始流行，雨后春笋般出来了很多相关编辑器。用现在的话来说，MD编辑器这个领域开始卷了起来，这样做为用户就有了很多选择，依赖关系反转。&lt;/li&gt;&#xA;&lt;li&gt;微软收购Github之后，创建私人仓库变得轻而易举。（有钱，真的能让用户为所欲为。）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;”笔记格式，应该和笔记软件解绑“，这样我就不会被绑定在一个具体的软件里面了，就不会被一个具体的软件所”体制化“。我怀疑这类想法，很大程度上受到最喜欢的《肖申克的救赎》这部电影的影响：电影里在监狱里生活了几十年的老布，已经被监狱”体制化“，这导致他出去监狱没多久就适应不了不被体制化的生活，选择了上吊自杀。&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;These walls are funny.First you hate them,then you get used to them;Enough time passes,gets so you depend on them.That&amp;rsquo;s institutionalized.&lt;/p&gt;</description>
    </item>
    <item>
      <title>周刊(第1期)：开刊，数字化生活数据</title>
      <link>https://www.codedump.info/zh/post/20220116-weekly-1/</link>
      <pubDate>Sun, 16 Jan 2022 10:00:54 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20220116-weekly-1/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;为什么会写周刊&#34;&gt;&#xA;  为什么会写周刊&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e5%86%99%e5%91%a8%e5%88%8a&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;我为写周刊这个事情思考了小一阵子。&lt;/p&gt;&#xA;&lt;p&gt;列举了好几个优点和缺点。比如，优点有能以输出来倒逼自己的输入，缺点有我的积累是否能够足以支撑我每周写一点东西，以及这种有明确时间限制的输出，是否会给我带来心智负担，等等。再比如，以往的博客内容都是很“硬”的技术内容，周刊不可能也不会每周都更新硬技术文章，会不会导致读者群有点分裂。&lt;/p&gt;&#xA;&lt;p&gt;思考到最后，我还是决定写周刊。&lt;/p&gt;&#xA;&lt;p&gt;从去年开始远程工作，大部分时间都在家里。由于我又很宅，网购也足够的发达，这又让我更宅了。然而，“物理”意义上的活动范围变小，并不意味着“逻辑”上能触达的范围也变小了，实际情况是，由于把通勤时间节省下来，我有更多时间看书、跑步了。所以，我需要一种方式，能输出我看到的一些东西，来与外界发生交流。&lt;/p&gt;&#xA;&lt;p&gt;我在微博上看到了这么两句话，也反向让我坚定了这么做的意义。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;（&lt;a href=&#34;https://weibo.com/1244214071/LagQ8fzm0&#34;&gt;宋一松SYS的微博&lt;/a&gt;）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;社交媒体在我看来最大的价值：它是最开放的peer review system&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://weibo.com/5339148412/L6LhDE7Zu&#34;&gt;硅谷王川的微博&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;让读书产生好处的最简单办法是，一旦有灵感和想法之后，马上写出来，公开发布在社交媒体上，即使不成熟也没关系。写的过程也是自己深度思考的一个步骤，外人的有价值评论可帮你不断推敲，或给你带来新的线索，积累多了自然会出深刻的洞见。一个人孤立封闭的傻读写笔记，很难迅速提高思考深度。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;周刊于我的意义，就是能定期把自己想到、看到的事情都公开出去，反向的让自己定期整理、输出，这是我突破“物理”界限和人交流的手段之一。内容将会以自己的一些想法、业界的动态、推荐、读书、影视等为准，与我写的其他技术文章相比，不会这么“硬”。&lt;/p&gt;&#xA;&lt;p&gt;另外，写周刊还有一个好处，之前有很多想法、念头会散落在各种社交平台这样的新媒体上，实际对整理、汇总、索引等也不好，把念头汇之于文章，用这种更老式的传播方式可以更好得记录下来。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;数字化生活数据&#34;&gt;&#xA;  数字化生活数据&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b0%e5%ad%97%e5%8c%96%e7%94%9f%e6%b4%bb%e6%95%b0%e6%8d%ae&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;既然谈到了周刊做为个人对外输出的一个手段，我就想起最近几年一直在做的事情：数字化各种生活数据。&lt;/p&gt;&#xA;&lt;p&gt;做过一阵子时序数据库的开发，有些人当时并不太了解什么叫做“时序数据”，于是我当时想了一下生活里的类比，其实“时序数据”就好比我们的“流水账”：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;9:00：起床，洗漱。&lt;/li&gt;&#xA;&lt;li&gt;9:30：工作。&lt;/li&gt;&#xA;&lt;li&gt;12:00：午餐。&lt;/li&gt;&#xA;&lt;li&gt;&amp;hellip;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;所谓“数字化生活数据”，也就是按照时间顺序把这些记录下来。“时序数据”，说白了就是时间强相关的数据，以时间为第一维度来存储、查询的数据。&lt;/p&gt;&#xA;&lt;p&gt;“数字化各种生活数据”，这么做的好处有什么？在我看来，有这些好处：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;形成上面的“时序数据”之后，可以方便进行聚合、统计、查询。&lt;/li&gt;&#xA;&lt;li&gt;定期还能依赖于各种工具来进行汇总、回顾等。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;比如，我每天都在用的&lt;code&gt;DayOne&lt;/code&gt; app，就有一个“每年今日”的功能，提醒我以往的这一天我都记录了什么，这就是基于这些时序数据的汇总；再比如，每到年底各种app都会自动给用户汇总生成这一年的用户行为统计数据，告诉你最喜欢的歌、和你爱好最匹配的人，等等。&lt;/p&gt;&#xA;&lt;p&gt;这些功能，都依赖于你之前上报过的“时序数据”。&lt;/p&gt;&#xA;&lt;p&gt;今天要推荐的两个相关的开源项目，作者都是&lt;a href=&#34;https://github.com/yihong0618&#34;&gt;yihong0618 (yihong)&lt;/a&gt;，他也是“数字化生活数据”的提倡者，可以在小宇宙里收听他的访问：&lt;a href=&#34;https://www.xiaoyuzhoufm.com/episode/619896e8138b51cbd78f3912&#34;&gt;S01E03 专访 YiHong，自学成为流行开源项目作者的点滴 - 开源面对面 | 小宇宙 - 听播客，上小宇宙&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;第一个项目是&lt;a href=&#34;https://github.com/yihong0618/running_page&#34;&gt;yihong0618/running_page: Make your own running home page&lt;/a&gt;，可以抓取主流的几个跑步app数据，生成好看的跑步数据展示页面：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;&#34; src=&#34;https://user-images.githubusercontent.com/15976103/98808834-c02f1d80-2457-11eb-9a7c-70e91faa5e30.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;第二个项目是&lt;a href=&#34;https://github.com/yihong0618/GitHubPoster&#34;&gt;yihong0618/GitHubPoster: Make everything a GitHub svg poster and Skyline!&lt;/a&gt;，可以将在各种app上（twitter、多邻国、扇贝等）上报的数据可视化：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;&#34; src=&#34;https://github.com/yihong0618/GitHubPoster/raw/main/examples/summary_2021.svg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;&#34; src=&#34;https://github.com/yihong0618/GitHubPoster/raw/main/examples/strava_circular.svg&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;可视化展示，会给人很直观的反馈。人的行为如果能得到即时的反馈，某种程度上会有正向作用。以我来说，去年9月份开始跑步，也是fork了这个项目每天生成跑步的可视化数据到我的网站，时不时会看一看数据，知道自己都做了哪些努力，潜移默化的会让我有一些成就感。&lt;/p&gt;&#xA;&lt;p&gt;一个想法：未来会不会有一天，可以拿到一个人所有的数字化数据，在这个人去世之后以这些数据重塑回一个“一样”的人？&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;推荐&#34;&gt;&#xA;  推荐&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%8e%a8%e8%8d%90&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;算法可视化交互动图&#34;&gt;&#xA;  算法可视化交互动图&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%ae%97%e6%b3%95%e5%8f%af%e8%a7%86%e5%8c%96%e4%ba%a4%e4%ba%92%e5%8a%a8%e5%9b%be&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;既然这一期讲到了数字化数据之后方便交互演示，就接着推荐旧金山大学制作的系列算法可视化交互动图，包括常见的堆、栈、队列等。学习算法数据结构的时候，如果能图示化的展现其变化过程，理解起来就会更顺畅，在学习B+Tree算法的时候，我就用过这里的演示来理解流程。&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.cs.usfca.edu/~galles/visualization/Algorithms.html&#34;&gt;Data Structure Visualization&lt;/a&gt;&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
