MoreRSS

site iconluozhiyun修改

93年,非科班程序员,喜欢健身、读书、编程。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

luozhiyun的 RSS 预览

北大《区块链技术与应用》——ETH篇

2025-11-26 20:17:32

课程地址:https://www.bilibili.com/video/BV1Vt411X7JF?spm_id_from=333.788.videopod.episodes&vd_source=f482469b15d60c5c26eb4833c6698cd5&p=2

ETH发展史

2013年底: Vitalik Buterin(V神)发布了以太坊白皮书。他的核心理念是:比特币像一个功能单一的计算器(可编程货币),而世界需要一个更通用的平台,像一台“世界计算机”(World Computer)。所以他提出了智能合约的概念,指的是运行在区块链上的、图灵完备的程序。通过使用以太坊虚拟机(EVM)提供的这样的沙盒环境,用于执行智能合约。

2015年7月"Frontier"(前线)版本上线。这是以太坊的第一个“创世区块”,标志着网络的正式启动,这是时候还仅仅是一个测试版本。采用的是工作量证明(PoW)的共识机制。

2016年初"Homestead"(家园)版本发布,这是第一个稳定版本,标志着以太坊不再是“测试版”,开始吸引DApp(去中心化应用)构建者。The DAO这个项目诞生了,它是一个去中心化的风险投资基金,通过智能合约管理,它筹集了当时价值约1.5亿美元的ETH。

同时也意味着危机,2016年6月,The DAO 合约遭到“重入攻击”(Re-entrancy Attack),导致约1/3的资金被盗。

所以这个时候社区面临一个哲学困境,是要接受损失,还是进行通过修改协议规则来回滚交易,追回被盗资金。最后社区投票支持硬分叉,追回了资金,成为了今天的主流链,也就是今天的 ETH。另一派坚持不回滚,保留了原始链,就成了另一个币 ETC。

我们接着跳过几年不这么重要的发展期来到PoS时代

为什么要从 PoW 转向 PoS 共识证明呢?我们都知道 PoW 用“工作”来换取记账权,工作量越大,越值得信赖,这就有个问题,需要巨大的电力和硬件成本,这是极度不环保的。

而 PoS 的核心理念是用“抵押”来换取记账权,你抵押的(Stake)越多,越值得信赖。参与者不再需要购买昂贵的矿机,而是需要购买并质押(锁定)网络的原生代币,将这些代币作为“保证金”或“押金”锁在网络中。如果一个验证者试图作恶(例如,提议无效区块、双重签名),网络会自动销毁他质押的“保证金,ETH 就是这样降低了约 99.95% 的能耗。

下面说一下ETH是怎么做到的:

2020年12月, 信标链(Beacon Chain)上线,这是一条独立运行的、采用 PoS 共识的全新区块链,它唯一的任务就是让验证者质押 ETH 并就 PoS 共识达成一致。此时,ETH 质押是单向的(只能存入,不能取出)。

2022年9月15日以太坊团队将原有的 PoW 链(现在称为“执行层”)的“引擎”——即 PoW 共识——拔掉。然后,将“执行层”接入到“信标链”(现在称为“共识层”)的 PoS 引擎上。这正式标志着以太坊进入了“PoS 时代”

合并完成后,2023年4月 "Shapella"(上海 + Capella)发布启用了质押提款(EIP-4895)。验证者终于可以取出他们质押的 ETH 和奖励,这次升级引入了 withdrawalsRoot(提款树根)字段到区块头中。

当然 ETH 的迭代远没有结束,我看社区还在继续讨论新的提案出来。比如 Pectra 、The Verge 与 The Purge 等,感兴趣的可以自行取查阅一下。

账户

这里用 BTC 和 ETH 进行对比,首先在 BTC 中,并不存在一个叫做“我的余额”的变量。它是由 UTXO 算出来的,所以假设钱包里有 5 BTC。这 5 BTC 在区块链上可能并不是一个“5”,而是:

  • 一笔 2 BTC 的“现金”(UTXO 1)
  • 一笔 1.5 BTC 的“现金”(UTXO 2)
  • 一笔 1.5 BTC 的“现金”(UTXO 3)

总余额 = UTXO 1 + UTXO 2 + UTXO 3 = 5 BTC

如果想支付 3 BTC,钱包会选择“消耗”掉 UTXO 1 (2 BTC) 和 UTXO 2 (1.5 BTC),总共 3.5 BTC。然后产生两个新的 UTXO:

  1. 一个 3 BTC 的 UTXO 发送给您的朋友(支付)。
  2. 一个 0.5 BTC 的 UTXO 发送回给您自己(找零)。

旧的 UTXO 1 和 UTXO 2 就被标记为“已花费”,不能再用了。

这种模型的优点: 简单、安全、隐私性相对较好(因为找零地址可以是新地址)、易于并行处理交易。 缺点: 难以实现复杂的逻辑(例如智能合约),因为它很难跟踪一个“账户”的复杂状态。

ETH 的设计更像是传统的银行系统。每个地址都是一个独立的“账户”。如果地址有 5 ETH,那么在以太坊的“全局账本”上,地址旁边就明确写着 balance: 5

如果要支付 3 ETH,发起一笔交易,声明:“从账户A转 3 ETH 到账户B”,网络验证账户余额(5 ETH)是否足够支付 3 ETH(以及手续费 Gas)。验证通过后,以太坊网络会:

  1. 将账户A的 balance 减去 3 ETH。
  2. 将账户B的 balance 加上 3 ETH。

为了防止余额数字被直接篡改,账户里面有 nonce 用来记数,每次交易完毕之后加一,防止重放攻击。

ETH 有两种账户:

外部账户 externally owned account,个人用户钱包,由私钥控制,可以发起交易;

合约账户 smart contract account 由代码(智能合约)控制,没有私钥,它不能主动发起交易,只能在被 EOA 或其他合约“调用”(发送消息)时被动执行其代码。

ETH模型的优点:使得智能合约(复杂的应用程序)成为可能。模型更直观,易于开发 DApps。缺点: 交易必须按顺序处理(因为有 nonce 机制防止重放攻击),这可能导致网络拥堵。

数据结构

image-20251125210438372

Merkel Patricia Trie

账户地址到账户状态的映射 , 账户地址是 160 位。

以太坊 (ETH) 的核心数据结构是 Modified Merkle Patricia Trie, 简称 MPT。我们可以把它拆解成两个关键概念的组合来理解:Merkle Tree (默克尔树)Patricia Trie (帕特里夏·树,或称压缩前缀树)

Merkle Tree (默克尔树):它是一种哈希树。树底部的每个“叶子”是数据块的哈希值。相邻的哈希值两两组合再哈希,层层向上,最终汇聚成一个“根哈希” (Root Hash),如下图:

这种树有一个特点是只要树中的任何一个数据发生(哪怕是 1 bit 的)改变,最终的“根哈希”都会变得完全不同。这使得节点只需比较这一个根哈希,就能快速验证彼此是否拥有完全相同的海量数据。

                                                 +-----------------+
                         |   Merkle Root   |  <- 最终的"指纹" (H_ABCD)
                         | (H_AB + H_CD)   |
                         +-----------------+
                                / \
                               /   \
                +---------------+   +---------------+
                |   Hash_AB     |   |   Hash_CD     |  <- 中间节点
                | (H(T1)+H(T2)) |   | (H(T3)+H(T4)) |
                +---------------+   +---------------+
                      / \                 / \
                     /   \               /   \
            +-------+ +-------+     +-------+ +-------+
            | H(T1) | | H(T2) |     | H(T3) | | H(T4) |  <- 叶子节点
            +-------+ +-------+     +-------+ +-------+
                |         |             |         |
            +-------+ +-------+     +-------+ +-------+
            |  T1   | |  T2   |     |  T3   | |  T4   |  <- 原始数据
            +-------+ +-------+     +-------+ +-------+

比如我们上图有四个数据块 T1, T2, T3, T4,然后计算哈希H(T1), H(T2), H(T3), H(T4)构成叶子节点,然后他们的父节点分别由他们拼接起来再哈希获得。如果有人把 T3 改成了 T3*,那么 H(T3) 会变,Hash_CD 也会变,最终 Merkle Root 会变得完全不同

Patricia Trie (称压缩前缀树)

它其实是 Trie 进化而来的,可以高效地存储和查找键值对 (Key-Value),特别是当“键”(Key) 有相同前缀时,它能极大压缩存储空间。举个例子,比如我们要存储以下几个键值对(以单词为例):

  • "romane": (值 1)
  • "romanus": (值 2)
  • "romulus": (值 3)
  • "rubens": (值 4)
  • "ruber": (值 5)
                                            (Root)
                        |
                      "r"
                      / \
                     /   \
                 "om"     "ub"
                 / \         / \
                /   \       /   \
              "an"  "ulus"  "e"   "ens"  (值 4)
              / \     |       |
             /   \  (值 3)    "r"
           "e"   "us"           |
            |     |           (值 5)
          (值 1) (值 2)

我们可以看到这个树基本上和 Trie 类似,唯一的区别就是对路径进行了压缩,对比普通前缀树会是 R -> O -> M -> A -> N -> E。看,R->OO->M 都是“单行道”,只有一个子节点。Patricia 的压缩它会把这些单行道合并。R 后面有两个分叉 ("o", "u"),所以 "r" 节点保留。但 "r" 之后的 "o" 和 "m" 都是单行道,所以它们被压缩成了 "om" 节点。同理,"rub" 被压缩成了 "ub" 节点。

使用 Patricia Trie结构对 ETH 来说主要有几点好处:

  1. ETH 需要跟踪数以亿计的账户,每个账户都有自己的状态(余额、nonce、合约代码等)。Patricia Trie 可以方便的用来将这些数据组织成 key value 对,比如 key 存的是账户地址 (0x...),value存的是该账户的状态信息。

  2. 并且以太坊的“键”(如账户地址)非常长(160位或更长)。如果使用标准的前缀树,从根节点到每个叶子节点都会有非常多的层级,而Patricia Trie它会把所有“没有分叉的单行道”路径压缩合并成一个节点,从而节省空间。

  3. Trie 树的最终形状和根哈希只取决于它所包含的“键值对”数据,而与插入这些数据的顺序无关。以太坊是一个全球分布式的系统。不同的节点在构建区块时,可能会以不同的顺序处理(本地缓存或插入)状态数据,所以这一点也是至关重要的。

  4. 最后就是 Patricia Trie 允许高效的状态更新,当一笔交易发生时(例如,A 转账给 B),通常只有极少数的“值”被改变了(A的余额减少,B的余额增加),种树形结构允许只更新从被修改的叶子节点到根节点的那条路径上的节点。

在 ETH 结构中,有三棵树都是使用的Patricia Trie结构:交易树 (Transaction Trie)、收据树 (Receipts Trie) 、状态树(State Trie),所以我们来看看 ETH 的 Merkel Patricia Trie 是怎么做的。

image-20251107212146532

ETH 它把键视为 16 进制的 nibble(半字节)序列,用三种节点(Branch / Extension / Leaf)压缩表示键空间,并对每个节点做序列化后取哈希,最后得到整棵树的根哈希,如上图所示。

nibble (半字节)序列简单说就是将输入的 字节流 (byte stream) 转换为 16 进制字符流。因为一个字节 (Byte) 包含 8-bit,而一个半字节 (Nibble) 包含 4-bit,所以每一个字节都会被精确地拆分成两个半字节 (nibbles)。一个 8-bit 的字节,比如 0x7A,就会被拆分成了两个 nibbles:[7, a]

下面我们看看三种节点(Branch / Extension / Leaf):

  • Branch 节点(branch) — 有 16 个子指针 + 一个可选值槽(用于恰好在此处结束的键):

    BranchNode:
    +----------------------------+
    | v0 | v1 | v2 | ... | v15 | value |
    +----------------------------+
    • v0..v15 共 16 个子指针是因为 Key 被拆分成了半字节 (nibbles) ,用十六进制表示,指向下一层节点(分别对应 nibble 0..15),如果没有对应路径则为 null(或空);
    • value 字段用于当某个键恰好在该节点结束(即键完全耗尽)时保存对应值。
  • Extension 节点(extension) — 用于把一段共享前缀聚合成一条边:

    ExtensionNode:
    +----------------------+    指向下一级节点
    | path: [nibble数组]   |  --->  子节点
    +----------------------+      (Branch/Leaf/Extension)
    • path 是一段 nibble(十六进制半字节)序列;extension 不包含值,只是压缩中间相同前缀。
  • Leaf 节点(leaf) — 存储键的剩余部分(从分支到末尾)和对应值:

    LeafNode:
    +---------------------------+
    | path: [剩余 nibble数组]   |
    | value: bytes              |
    +---------------------------+
    • 当一个键在 trie 中一个分支走到底时,用 leaf 存储剩下的 nibble 和最终值。

比如我们把把三个键插入到空的MPT中:

  • Key A:[a, b, c, d] -> 值 V1
  • Key B:[a, b, c, e] -> 值 V2
  • Key C:[a, b, f] -> 值 V3
root = Extension([a,b]) -> Branch
                         /   |   ...
                        c    f
                        |      \
                 Extension([c])  Leaf([ ] -> V3)
                    |
                   Branch
                   /   \
                 d      e
                 |      |
               Leaf   Leaf
               (V1)   (V2)

路径 [a,b] 是共有前缀,[c](成为 extension/直接连接到 branch 继续分叉指向 leaf v1 和 leaf V2,子槽 f 指向 leaf 值是 V3。

Block Header 四颗状态树

在 Block Header 里面有四棵树,状态树 (State Trie)、交易树 (Transactions Trie)、收据树 (Receipts Trie)、提款树 (Withdrawals Trie) 都是用 MPT 来构建的。

  • 状态树 State Trie它记录了所有账户的全局状态(余额、nonce、合约代码、合约存储)。需要注意的是 这是唯一一棵持久化的树。它不只是记录这个区块发生的事,而是记录了在执行完这个区块的所有交易之后,以太坊全世界所有账户(包括智能合约)的最终状态。每个新区块都会在旧状态树的基础上进行“更新”,产生一个新的 stateRoot,其他不变的账号状态还是用原来的节点。

    image-20251107213815983

    主要包含:

    • 每个人的 ETH 余额。
    • 每个账户的 nonce(交易计数)。
    • 每个智能合约的 codeHash(代码)。
    • 每个智能合约的 storageRoot(指向它自己的存储树)。
  • 交易树 (Transactions Trie)里面包含当前区块中的所有交易。它的唯一目的就是按顺序存储仅属于这个区块的所有交易。

    主要包含:

    • 交易 0, 交易 1, 交易 2…
  • 收据树 (Receipts Trie)包含当前区块中所有交易的执行回执(Receipts)。 这棵树对于 DApp 和钱包至关重要。当你想知道“我的交易成功了吗?”或者“某个智能合约是否触发了某个事件(Event)?”,你就是在这棵树里查找(或验证)这个“收据”。

    主要包含:

    • 交易 0 的结果:status: success, gasUsed: 21000, logs: [...]

    • 交易 1 的结果:status: failure, gasUsed: 50000, logs: []

  • 提款树 (Withdrawals Trie)这是“上海/Shapella”升级后新增的,专门用于处理从信标链(共识层)提款到执行层的操作,它为质押提款提供了可验证的记录。

    主要包含:

    • 提款 0:验证者 A 提取 X ETH 到地址 B。

    • 提款 1:验证者 C 提取 Y ETH 到地址 D。

logsBloom 日志布隆过滤器

在 ETH 的区块头还有一个logsBloom 字段,它使用布隆过滤器(Bloom Filter)来实现的,目的是为了做“快速索引”“摘要”。

在以太坊上,智能合约通过触发“事件”(Events/Logs)来与外界(DApp 前端、钱包)通信。例如,一个 ERC-20 代币合约在转账时会触发一个 Transfer 事件。假设你的钱包想显示你所有的 ERC-20 代币转账记录。它该如何找到这些记录?

如果没有 logsBloom ,那么钱包必须下载整条链(几 TB 的数据),然后遍历每一个区块里的每一笔交易每一条收据(Receipt),逐一检查其 logs 字段,看看是不是你想要的 Transfer 事件。这对于轻客户端(如手机钱包)或 DApp 前端来说是绝对不可能的。

logsBloom 巧妙的使用布隆过滤器(Bloom Filter)来构建区块中的交易触发的所有事件索引 log,当一个合约触发一个事件时,例如 Transfer(address indexed from, address indexed to, uint256 value),以太坊会把触发事件的合约地址所有被 indexed (索引) 标记的参数(比如 fromto 的地址)“添加”到这个布隆过滤器中。

布隆过滤器(Bloom Filter)是一种概率型数据结构,它非常节省空间,专门用来回答一个问题:“某个东西可能 在这个集合里吗?

它的回答只有两种:

  1. “绝对没有” (False): 如果它说“没有”,那这个东西 100% 不在集合里。
  2. “可能有” (True): 如果它说“有”,那这个东西有很大概率在集合里。(注意:它有很低的概率是“假阳性”,即它以为有,但其实没有)。

对 布隆过滤器(Bloom Filter)感兴趣的,可以去看我这篇文章:Go语言实现布谷鸟过滤器

那么有了这个 Log 我们就可以:

  1. 你的钱包想查找所有“发送到你地址 0xABC...”的 Transfer 事件。
  2. 不需要下载整个区块。它只需要下载区块头(非常小)。
  3. 它检查区块头里的 logsBloom 字段。
  4. 它向这个 logsBloom 提问:“你这里面可能包含 0xABC... 这个地址吗?”

logsBloom 回答 “绝对没有”那么钱包100% 确定这个区块里没有任何一笔交易触发了与 0xABC... 相关的事件。logsBloom 回答 “可能有”钱包才会去下载这个区块的完整数据,来精确找到它要的 Transfer 事件。

工作量证明 & 权益证明

为什么要有共识机制

我们先来说一下为什么要有PoW (工作量证明)PoS (权益证明) 这种共识机制,它们都是为了解决一个在计算机科学中极其古老且棘手的问题,尤其是在一个“去中心化”和“无需信任”的环境中,用来确保即使网络中充满了互不信任的陌生人(甚至有坏人),整个系统也能安全、一致地运行。

它们具体解决了以下三个关键问题

  1. 防止"女巫攻击" (Sybil Attack) —— 谁有资格记账?

    在一个开放的网络中,一个坏人几乎可以零成本地创建一百万个“假身份”(节点)。如果“记账权”是靠“一人一票”来决定的,那么这个坏人就能轻易地用他的“百万大军”投票控制整个网络。

    因为在 web2 中,是有一个去中心化的节点来控制的,所以一般是通过认证与授权 (Authentication & Authorization)来实现的,但是 web3 中,是去中心化的,所以需要设计这样的共识机制,用它增加记账的门槛,防止坏人可以低成本的记账,对网络产生影响。

  2. 防止"双花" (Double-Spending) —— 如何确保账本不可篡改?

    "双花"是数字货币的“原罪”。坏人张三有 10 ETH,他先发一笔交易给李四,同时(或之后)又发一笔交易把同样的 10 ETH 发给王五。网络必须决定哪一笔交易是“唯一真实”的。

    在 web2 中,是通过 一个中心化的数据库 (Single Source of Truth) 来实现的,在web3中是一个分布式的账本,我如何确保所有人都同意“张三的 10 ETH 是先给了李四,而不是先给了王五”?

    那么web3中就可以共识机制就可以设计一些有成本的操作,让“作恶成本”提高,来确保账本的唯一性和不可篡改性。

  3. 激励机制 (Incentives) —— 为什么有人愿意来记账?

    既然保护网络这么昂贵(要买矿机或锁定 ETH),为什么会有人愿意做这件事?

    在 Web2 中,不需要通过激励“陌生人”来进行记账,银行只需要商业模式 (Business Model) 和 雇佣 (Salary)机制来保障,不需要别人来记账。

    但在web3中需要奖励机制来鼓励诚实者来进行记账,诚实节点地遵循规则、打包区块、验证交易,系统就会奖励你新发行的代币(例如 ETH)和用户支付的交易费。这样ETH 越有价值,你作为奖励收到的 ETH 就越值钱,你也就越有动力去保护它。

所以这也是为什么在 web3 中需要PoW (工作量证明)PoS (权益证明) 这种共识机制。下面我们来看看这两种共识机制有什么区别。

PoW (工作量证明)其实就是需要矿工投入巨额的硬件成本电费(物理工作)来解题。第一个解出题的就拥有的记账权。但是 PoW 有个极大的问题就是它不环保,浪费了大量的电来做这个事情。

那么就有人提议,其实PoW为了能去挖矿是需要投入巨量的硬件成本电费,最后就是谁投入的钱多,谁就拥有这个记账权,既然如此,可以不可以直接点,直接用金钱来做抵押,那么这就是PoS (权益证明)基本理念,在 PoS 机制下,网络的安全不再依赖于消耗能源,而是依赖于经济激励惩罚

我们下面来详细看看 PoS 是怎么做的。

PoS (权益证明)

在 PoS 中验证者取代了 PoW 中的“矿工”。他们是运行特定软件的节点,负责处理数据、执行交易,并将它们打包成新的区块添加到链上。要成为一个验证者,你需要向一个特殊的智能合约中质押 32 个 ETH

质押就是将 32 ETH 锁定,这部分的资金会在惩罚的时候用到,一旦发现作恶,作恶者的一部分质押金(最多 32 ETH)将被销毁(永久消失),并且该验证者将被强制踢出网络。比如在同一个时隙提议两个不同的区块(试图分叉),抑或是提交自相矛盾的投票(例如试图支持两条不同的链),这些都是作恶的行为。

在 ETH 中,是按时间来组织打包区块的,每个 固定的 12 秒时间段被称为 Slot,理论上每个 Slot 都会产生一个新区块。

每个 Slot,系统会随机选择一个验证者作为“区块提议者”(Proposer), 32 个Slot 组成一个Epoch(约 6.4 分钟)。

ETH 的 PoS 架构分为两层:

  1. 共识层 (Consensus Layer, CL):这是 PoS 的“大脑”。它不处理交易,只负责协调所有验证者、随机抽签、分发选票、统计投票,并就区块的顺序和有效性达成共识。
  2. 执行层 (Execution Layer, EL):这是“引擎”。它负责执行智能合约、处理交易、更新我们之前讨论的“状态树”(State Trie) 和其他三棵树。

具体步骤:

  1. 在每个 12 秒的 Slot 开始时,共识层会从所有验证者中随机抽选一个验证者,作为这个 Slot 的“提议者”(Proposer)。

  2. 提议者打包区块,被选中的 proposer:

    1. 从“执行层”的交易池 (Mempool) 中抓取一批交易。
    2. 打包这些交易,创建一个新的区块
    3. 对这个新区块签名,并将其广播到整个网络。
  3. 共识层会为同一个 Slot 随机抽选一组(一个“委员会”/Committee),大约 100~几百个验证者组成的 Attestation 委员会(Committee)

    委员会的工作:

    1. 它们会收到“提议者”广播的新区块。
    2. 它们验证这个区块的有效性(签名是否正确?交易是否合法?)。
    3. 如果有效,它们会投出“赞成票”(Attestation)。
    4. 广播 attestation 给全网

    这些“赞成票”会汇集到共识层。

上面的讲述中,为了防止节点作恶,PoS 设计了一套安全机制。

如果诚实地参与提议和投票,验证者会获得两种奖励:

  1. 共识层奖励: 少量新发行的 ETH,作为维护网络安全的“工资”。
  2. 执行层奖励: 用户支付的“小费”(Priority Fees)。

如果节点作恶,那么就会执行相应的惩罚,这里的惩罚分为几种:

  1. 轻微惩罚 (Inactivity Leak):节点掉线了(例如停电、断网),没能及时投票,这样会损失掉本应获得的小额奖励
  2. 严厉惩罚(Slashing):比如双重提议 (Double Proposing),在同一个 Slot 提议了两个不同的区块;双重投票 (Double Voting),同一个 Epoch 里投票给了两个竞争的区块(试图制造分叉)。这样做会将质押的 ETH 进行一定的销毁,并强制踢出验证者队列,永久失去参与共识的资格。

LMD-GHOST机制

还有就是真的就是有作恶者进行了分叉选择 (Fork-Choice),提议了两个不同的区块,或者网络延迟导致出现了两个竞争的区块(分叉),怎么办?

在 ETH 中是通过 LMD-GHOST 机制来保障。节点根据“所有验证者最近一次投票(attestation)”来决定哪条分支最“重”,从而选出链头(head)。

当你的以太坊节点需要确定“主链的头部是哪个区块”时,它会执行以下操作:

  1. 从一个已“最终确定” (Finalized) 的区块开始作为 root,Finalized 的区块一般是上个 Epoch 锁定的。
  2. 查看它“观测到”的、来自所有验证者的“最新”投票。
  3. 使用“贪婪”算法,从 root 出发,在每一个分叉路口,都选择那条其子树累计获得了最多“质押权重”投票的路径。
  4. 一直走到这条“最重”路径的末端(叶子区块),这个叶子区块就是当前的主链头部(Head)。

Casper FFG机制(FFG)

Casper FFG 就是 ETH 的最终性 Finality 的机制,用来决定哪些区块被不可逆地锁定。

因为LMD-GHOST 选出的“头部”是可以改变的。如果网络延迟很严重,或者有攻击者在故意制造分叉,LMD-GHOST 选出的“最重链”可能会在 A 链和 B 链之间“摇摆不定”。这样用户没法确定这笔存款是不是100%到账了。

在 BTC 中我们知道它是通过“6 个区块确认”来确保交易的理论安全,而 FFG 是通过 Epoch 来确定。

ETH 在每个 Epoch 的第一个区块设立了Checkpoint (检查点),FFG 会通常跨越两个 Epoch(约 13 分钟)来实现最终确定。

举个例子,假设我们现在处于 Epoch 100

第一步:标记Justification (合理化)

  • 所有验证者(委员会)会一起投票,连接“上一个检查点”和“当前检查点”,即投票支持 C(99) -> C(100) 这条链;
  • 如果超过 2/3 的总质押 ETH 都投票给了这个连接;
  • C(100) 这个检查点区块被标记为 "Justified" (合理化),但是 Justified只是意味着“这个区块看起来非常棒,全网大部分人都同意它在主链上”,但它还不是最终的。

第二部:Finalization (最终确定)

  • 时间进入到了下一个 Epoch,Epoch 101
  • 验证者们再次投票,支持 C(100) -> C(101)
  • 如果 C(101) 也获得了超过 2/3 的投票,它自己也变成了 "Justified"
  • 此时,协议会回头看 C(101) 的“来源”——C(100),因为 C(100) 本身是 "Justified" 的,所以协议在这一刻C(100) 的状态升级为 "Finalized" (最终确定)

小结

所以我们可以从上面看的出来 ETH 的 PoS 机制依靠验证者(质押 32 ETH)而非矿工来保护网络。其安全不靠算力,靠押金:诚实有奖,作恶(如双重签名)则被罚没 (Slashing)

这套系统由两个协同工作的机制驱动:

  1. LMD-GHOST (选头部): 一个快速、灵活的规则,根据验证者的最新投票来选择“当前”的主链。
  2. Casper FFG (定最终): 一个缓慢、严谨的规则,通过“两步确认”将历史区块永久锁定(Finalized),使其不可逆转。

智能合约

简单来说:智能合约就是运行在以太坊区块链上的一段“自动执行的代码”。我们可以把它比喻成一台全自动贩卖机。

  • 输入:你投币(转账 ETH)并选择商品(调用函数)。
  • 逻辑:机器内部验证金额是否足够(代码逻辑判断)。
  • 输出:吐出饮料(分发 Token 或 NFT)并找零。
  • 特点:无需店员(去中心化),一旦设定无法随意更改价格(不可篡改)。

智能合约有这几个特点:

  1. 自动执行:一旦条件触发,就一定会执行,没人能阻止(连开发者本人也不行)。
  2. 不可篡改:部署到以太坊后,代码永远不能改(除非你一开始就写好升级机制)。
  3. 完全透明:全世界都能看到代码是什么(在Etherscan上点开任何合约都能看源码)。
  4. 去中心化:不靠任何公司或服务器,运行在全球几万台节点上,只要以太坊活着,它就活着。
  5. 用Gas付费:每次调用智能合约都要花一点ETH作为“燃料费”(Gas),防止有人写无限循环攻击网络。

一个最简单的“银行”合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleBank {
    // 1. 状态变量 (State Variable)
    // 这些数据会永久写入区块链,类似于数据库中的表
    mapping(address => uint256) public balances;

    // 2. 事件 (Event)
    // 类似于日志系统,用于前端监听
    event Deposit(address indexed user, uint256 amount);

    // 3. 函数:存款
    // payable 关键字表示该函数可以接收 ETH
    function deposit() public payable {
        require(msg.value > 0, "Deposit amount must be greater than 0");

        // msg.sender 是调用者的地址
        balances[msg.sender] += msg.value;

        emit Deposit(msg.sender, msg.value);
    }

    // 4. 函数:提款
    function withdraw(uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;

        // 将 ETH 转回给用户
        payable(msg.sender).transfer(amount);
    }
}
  • mapping 就是 Key-Value 存储(类似于 Redis),address 是 Key,uint256 是 Value。

  • msg.sendermsg.value 是全局注入的上下文变量(Context),无法伪造。

  • require 类似于 Assert 或中间件校验,如果不通过,整个事务回滚。

原子性

需要注意的是,智能合约的每次执行具有原子性,比如像上面这个银行的例子,如果失败了,就整个操作进行回滚,不存在中间状态;如果所有步骤都成功,所有的状态变更(State Changes)会被一起写入区块,永久生效。其实这就有点像数据库的事务。

熟悉数据库的朋友我可以这么解释:

在 EVM(以太坊虚拟机)中,每一笔交易天然就是一个隐式的 START TRANSACTION ... COMMIT。你不需要显式地写 Commit,但任何未捕获的错误都会触发自动 Rollback。

跨合约组合(Composability)

原子性不单单局限于单个合约内部,而是可以跨越多个合约的调用链。

假设一笔交易的调用链是这样的:

用户 -> 合约 A -> 合约 B -> 合约 C

如果在 合约 C 的执行中出错了:

  1. 合约 C 的状态回滚。
  2. 合约 B 的状态回滚。
  3. 合约 A 的状态回滚。
  4. 用户发起的这笔交易被标记为“失败”

这里就有一个问题,执行失败会收 Gas 费吗?我们先看看什么是 Gas 费

Gas 费

Gas 费本质上等于:工作量(Gas Units)X 单价(Gas Price)。

Gas Units(工作量/计量单位)

EVM(以太坊虚拟机)执行的每一个操作码(Opcode)都有一个固定的 Gas 消耗值,操作越复杂,消耗越高。

比如下面这些操作:

计算操作(便宜)

  • ADD (加法): 3 Gas
  • MUL (乘法): 5 Gas
  • KECCAK256 (哈希计算): 30 Gas + 动态数据费用

存储操作(极贵)

  • SSTORE (写入/修改状态变量): 20,000 Gas (这是最贵的操作之一)
  • SLOAD (读取状态变量): 2,100 Gas
  • LOG (生成日志): 375 Gas

基础费用

  • 发起一笔最简单的转账交易(不调用合约):固定 21,000 Gas

Gas Price(单价/市场汇率)

这是由市场供需决定的变量。Gas Units 是“你需要多少升油”,Gas Price 就是“今天加油站一升油卖多少钱”。

Gas 的单位是 Gwei:

  • 1 ETH = 10^18 Wei
  • 1 Gwei = 10 ^9 Wei (即 0.000000001 ETH)

动态定价

  • 网络拥堵时:大家都在排队提交交易,为了插队,你必须出高价(比如 100 Gwei)。这就像滴滴打车的“高峰溢价”。
  • 网络空闲时:价格下降(比如 10 Gwei)。

最终计算公式

现在的以太坊(EIP-1559 升级后)计费变得稍微复杂了一点点,分为两部分:

Tx Fee = Gas Used X (Base Fee + Priority Fee)
  1. Base Fee (基础费)
    • 系统自动定。根据上一个区块满了没,自动调整。
    • 这部分钱会被“销毁”(Burned),也就是说这部分 ETH 直接从流通总量中消失了(通缩机制)。
  2. Priority Fee (小费/优先费)
    • 你给矿工/验证者的红包
    • 如果你想让交易快点确认,就多给点小费,验证者会优先打包小费高的交易。

举个具体的例子:

假设你要在这个拥堵的周五晚上,调用一个合约函数 buyItem()

A. 代码层面(Gas Units) EVM 跑完你的代码,发现你做了一次加法,写了一次数据库,总共消耗了 50,000 Gas

B. 市场层面(Gas Price) 当前的 Base Fee 是 50 Gwei,为了快点成交,你给了 2 Gwei 的小费。 总单价 = 52 Gwei。

C. 你的账单

花费 = 50000(Units)X 52(Gwei) = 2,600,000 Gwei

换算成 ETH 就是 0.0026 ETH。 假设 ETH 现价 $3000,这笔操作就要花你 $7.8 美金

操作失败会收 gas 费吗?

两种失败场景的区别:

在 EVM 中,交易失败主要分两种情况,扣费逻辑略有不同:

  • 场景 A:Gas 耗尽(Out of Gas)

    Gas Limit 是预算上限,假如设置了100,000Gas ,意思是“我这笔交易最多允许烧掉 100,000 Gas,再多我就不付了”。

    如果代码跑到 100,000 还没跑完(Out of Gas),EVM 强制停机,这 100,000 的钱全部扣掉不退,且交易回滚。

  • 场景 B:逻辑错误(Revert / Require 失败)

    比如转账余额不足、权限不够。比如代码运行到第 10 行,触发 require(false)。这样只扣除前 10 行代码消耗的 Gas。剩余未使用的 Gas(即 Gas Limit - Gas Used)会退回到你的钱包。

为什么要这么设计?

对于去中心化网络,这是为了防止 DDoS 攻击(拒绝服务攻击)。 如果失败不收费,黑客可以写一个无限循环的恶意合约:

while(true) { i++; }

然后向网络发送几百万笔交易来调用它。如果不收费,全网节点的 CPU 就会被免费占用,导致网络瘫痪。

小结

我们可以看出ETH的智能合约,把区块链从“账本”升级为了“通用计算平台”。没有智能合约,区块链就只能炒币(Store of Value);有了智能合约,区块链才有了应用层(Application Layer),多了更多的活力和玩法。

比如:

  • Uniswap(去中心化交易所 – AMM),它没有传统股票交易所的“订单薄”(Order Book),没有挂单和吃单的概念;
  • Aave / Compound (去中心化借贷),资金池模式。存款人把钱扔进池子拿利息,借款人抵押资产从池子借钱付利息;
  • MakerDAO (去中心化稳定币 – DAI),相当于是以太坊上的“美联储”,通过合约抵押的方式,生成一个永远锚定 1 美元的币(DAI);

还有很多有意思的玩法,我这里就不一一列举了,如果要深入学习智能合约的话,可以看这几个教程:

https://cryptozombies.io/ 通过建一个“僵尸养成”区块链游戏,一步步教你写Solidity智能合约。

https://docs.soliditylang.org/ Solidity官方英文文档

https://www.wtf.academy/zh/course/solidity101 WTF Solidity极简教程

如何在ETH链上发币?

ETH 社区定义了 ERC-20 标准的 API 接口规范,任何一个智能合约,只要实现了这套规定的 API(方法和事件),它就是一个 ERC-20 代币。

我们可以把代币想象成存储在一个巨大的 Map 里面,比如这样的一个结构:

// 这是一个简化的核心存储结构
contract ERC20 {
    // 1. 账本:记录 "地址 -> 余额"
    mapping(address => uint256) private _balances;

    // 2. 授权表:记录 "我 -> 授权给谁 -> 多少钱"
    mapping(address => mapping(address => uint256)) private _allowances;

    // 3. 代币总供应量
    uint256 private _totalSupply;
}

当你发起一笔转账(例如 A 转给 B 100个币)时,EVM(以太坊虚拟机)到底做了什么?

  1. 交易发起:用户 A 向该代币合约地址发起一笔调用 transfer(B, 100) 的交易。
  2. 余额检查:EVM 检查 _balances[A] 是否大于等于 100。
  3. 数据重写
    • _balances[A] = _balances[A] - 100
    • _balances[B] = _balances[B] + 100
  4. 持久化:这些修改后的数据被写入以太坊的状态树(Merkle Patricia Trie),并打包进新的区块中,永久不可篡改。

数据结构

image-20251125210438372

我这边再引用一下上面的图,合约其实在 ETH 里面也是一个账户对象,当你通过地址找到这个账户(比如那个 ERC-20 代币合约地址)时,你会得到一个包含四个字段的结构体:

Nonce: 交易计数器。

Balance: 这里存的是 ETH 的余额(比如 0.5 ETH),不是代币余额

CodeHash: 智能合约的代码哈希(如果是普通账户则为空)。

StorageRoot (存储根): 这是一个哈希值,它指向了另一棵 MPT 树的根节点。这棵树专门属于这个合约,用来存储它所有的变量数据。

StorageRoot (存储根)

这棵树本质上是一个巨大的、持久化的 Key-Value 映射,其实也是一颗 MPT 树。

  • Key: 32 字节(256位)的存储槽位置 (Slot Index)。
  • Value: 32 字节(256位)的数据内容。

比如这样一个代币合约:

contract MyToken {
    // Slot 0: 假如这里有个 owner 变量
    address owner; 

    // Slot 1: 假如这里是总供应量
    uint256 totalSupply; 

    // Slot 2: 这里就是代币余额的 Mapping
    mapping(address => uint256) private _balances;
}

当 EVM 运行到 _balances 时,它并不会把整个 Mapping 存在 Slot 2 里面(因为 Mapping 大小是不确定的)。 它是通过哈希算法计算出具体的存储位置。

如果你想查 0xUserA 这个人的余额,数据存储在 Storage Trie 中的 Key 是这样算出来的:

image-20251126145250636

  1. 0xUserA: 用户的钱包地址(补齐到 32 字节)。
  2. SlotIndexOfMapping: _balances 变量在代码中声明的位置(假设是 Slot 2)。
  3. keccak256: 对这两个拼接后的数据进行哈希运算。

运算得出的结果(一个乱码一样的哈希值),就是该用户余额在底层的物理存储地址。 而这个位置对应的 Value,就是 uint256 类型的余额数值。

所以如果要查询代币的值,整个查找链条是这样的:

  1. Block Header -> 拿到 StateRoot

  2. World State Trie -> 用 TokenContractAddress 查找 -> 拿到 Account Object

  3. Account Object -> 拿到 StorageRoot (进入该合约的私有数据库)。

  4. Storage Trie -> 计算 keccak256(UserAddress + SlotIndex) 作为 Key -> 拿到余额数据

北大《区块链技术与应用》——ETH篇最先出现在luozhiyun`s Blog

泡温泉&amp;跑步(别府篇)

2025-11-02 20:37:17

最近有点时间,想找个地方休息一下,本来只是想去熊本看看高达的,别府只是顺路去一下,没想到这个小地方还是挺惊艳到。

别府有着很多温泉资源,拥有近3,000个温泉源头,温泉涌出量位居日本第一,在全球也仅次于美国的黄石国家公园。

温泉水多到什么程度呢,别府站旁边就有个池子可以用温泉水洗手,没错,就是下面这个雕像的右边,顺带一提,这个雕像的衣服会经常换,各位如果也来别府,看看他会穿什么。

image-20251102201329540

走在路上你甚至可以在街上的下水道里面看到有冒着白雾,也就是说这些下水道的水都是温泉水哦。

image-20251102201710144

泡温泉&跑步

在别府有很多温泉,大都集中在铁轮温泉区,其中最出名的就是这个温泉,葫芦温泉,是一家百年的老店。

image-20251102201933253

这个温泉固然是很好的,但是我不只是想讲这个温泉体验怎样。在泡温泉的时候,我就想泡温泉的时候就和跑步好像:

  • 不能玩手机;
  • 身体在承受一定的痛苦;

不能玩手机代表你必须要聚焦于自我的思绪当中,不受外界的信息干扰,表示你有更多的精力让你的思想放空,可以想到不一样的事情。很多时候,我都是在跑步的时候,放下手机的时候突然想起要做某件事情,然后去做,这篇文章也是一样,在泡温泉的时候,想起可以去写一篇这样的文章。

身体在承受一定的痛苦意味着身体会时不时提醒你有多久没关注过当下的生活了,你在吃什么,身边在发生什么事,经过了什么样的景色,遇到了什么样的人,似乎我们都错过了好多。但是泡温泉和跑步的时候,身体的痛苦会把你的思绪拉回来,让你记住当下的生活。

比如今天我泡温泉就记住了在一个池子里面有个老人长的挺帅的,可能有六七十岁了,戴着眼镜,有点像电影里面的老人,相信他穿着打扮一下,肯定气质不凡。

在露天温泉里有几个年轻人在我左前方一直在讲话,让我想起,要是国内也能这么方便的可以泡温泉就好了,一般我们都喜欢边吃饭边聊天,其实我们也可以把场景换一下,边泡温泉,大家赤诚相见,边泡温泉边聊天,其实也是不错的。

泡温泉和跑步有一点是不一样的,跑步都是越快越好,大家都在追求速度,但是温泉不一样,强制大家慢下来。在日本泡温泉的时候,会有告示牌告诉你,别这么急着进去泡,先慢慢洗一下身子。洗完身子之后,要缓步走到池子里,因为都是水,急的话就容易摔倒。进到池子的时候,也是要慢慢进去,因为水温很烫,身体一下钻进去肯定会把你烫出来。

我在国内生活很多年,几乎所有人都在说快一点,快一点。要快点学,赶紧考个好大学;要快点工作,好赚多点钱养家;要快点结婚生子,好繁衍子嗣;要快点工作完,好完成当下的kpi。那么什么时候慢下来呢?是必须要这么快吗?

说会到葫芦温泉,这里的水煮蛋,应该是我在日本看过最便宜的水煮蛋,只要80日元。

image-20251102204852686

在别府这里还有一个温泉,别府鉄輪蒸し湯,也是一家百年的老店,体验也还不错。

image-20251102205206851

这个温泉的特点仪式感很重,首先会让你进去先用温泉水淋一下,对,就是单纯的淋一下,然后穿上浴衣之后进入到蒸汽房里面,躺在草堆上,用药草蒸10分钟,到8分钟的时候门外会有人问一下你是否要提前出去,出来之后是感觉确实不同,很舒服。蒸完之后把浴衣换下之后就清洗一下身子开始泡温泉了。

汗蒸这个东西,其实很多温泉店都会有,但是这家温泉会强制顾客一定要去汗蒸一下,确实不太一样。

日本其实是一个很重仪式感的地方,它深刻地根植于日本文化的各个层面。比如泡温泉的时候,进玄关的时候会让顾客脱鞋,让顾客穿他们的浴衣,礼物本身可能不贵重,但包装一定要精美,店里面的店员一定会对顾客鞠躬问好等等。

其实我是一个不怎么喜欢仪式感的人,比如我一般在逢年过节的时候不会特意的去买各种节日食品,月饼,汤圆之类的,过生日也不喜欢搞什么生日聚会,不会买蛋糕,也不会去买礼物。

但是我的观念最近在慢慢的改变,我觉得仪式感可能没什么不好。生活本质上是充满不确定性和混乱的。仪式感通过固定的程序和可预测的步骤,为我们创造了一个“可控”的心理空间,其实是一种对抗不确定性的方式。并且仪式感可以将平凡的日常行为转变为特殊且有意义的时刻,其实也蛮有意思。

比如同样是吃饭,用你最喜欢的餐具、摆放整齐、关掉电视专注地吃,这顿饭的体验和价值感就远高于边看手机边草草了事。我们大部分时间都处在“自动驾驶”模式,脑子里想着过去或未来,想着各种事情。而仪式迫使你将注意力拉回到“此时此刻”的身体和感官上,这也是仪式的意义。

喜欢在干净的路上逛街

在日本比起大城市,其实我更喜欢乡下,因为没什么人,并且很干净。有时候看着这种没人的干净街道,就想一直逛下去,即使没什么好看的,也可以安安静静地走一天。

image-20251102212001736

11月了草还挺绿。

image-20251102212522976

我还是很喜欢海边散步的。

image-20251102212535038

晚霞

image-20251102212650725

写于 2025年11月2日晚,别府三日游明早就要走咯。

泡温泉&跑步(别府篇)最先出现在luozhiyun`s Blog

北大《区块链技术与应用》——BTC篇

2025-10-30 23:08:27

课程地址:https://www.bilibili.com/video/BV1Vt411X7JF?spm_id_from=333.788.videopod.episodes&vd_source=f482469b15d60c5c26eb4833c6698cd5&p=2

什么是加密货币

加密货币(Cryptocurrency)是一种运用密码学原理来确保交易安全并控制新单位创造的数字交易介质 。

根据 Jan Lansky 所述,加密货币是满足六个条件的系统:

  1. 去中心化:该系统无需中心机构,也就是不需要央行,靠共识维持。
  2. 所有权记录:系统能够清晰地记录每一单位加密货币及其当前的所有权归属。
  3. 发行机制:该系统定义能否产生新的加密货币。如果可以,则系统需定义新币的来源,并定义如何确定这些新币的所有者。
  4. 密码学所有权:对加密货币的所有权只能通过密码学手段(即私钥)来证明和行使。
  5. 所有权转移:该系统允许通过交易来改变加密货币的所有权。交易仅可从能证明加密货币当前所有权的实体发布。
  6. 双重支付防范:如果同一时间产生了两个改变相同加密货币所有权的指令,该系统最多只能执行其中一个。

这些条件共同定义了一种革命性的资产形式,其价值和安全性不依赖于任何单一机构的信用背书。有些人简单的将它归结为“数字化”,其实是不对的,因为支付宝、信用卡支付等都是数字话的,但是这些传统数字金融系统的核心是建立在对中心化中介机构(如银行、支付网关)的“信任”之上,由它们来维护账本、验证交易的合法性 。加密货币的根本性突破在于,它通过密码学、分布式共识等一系列技术手段,构建了一个“去信任化”(Trustless)的系统。

区块链的核心数据结构

所谓区块链,其实是由一系列按时间顺序连接的数据单元构成的,这些单元被称为“区块”(Block)。每一个区块包含以下内容:

  • 每个区块的交易数据;
  • 指向前一个区块的加密引用;

链条的起点是一个特殊的区块,被称为“创世区块”(Genesis Block),因此没有指向“前一个区块”的引用。

那么在实现上,区块是如何保存交易数据和引用的呢?数据结构是怎样的?每个区块在数据结构上,包含了区块头(Block Header)和区块体(Block Body):

  • 区块头:每个区块起始处的一个紧凑的、固定大小的部分。在btc协议中,其大小为80字节 。头中不含交易数据,仅包含了元数据(metadata)。元数据包括指向前一个区块的链接、时间戳以及对区块体内所有交易数据的加密摘要。
  • 区块体:这是一个可变大小的部分,其主要内容是该区块所包含的经过验证的、详细的交易记录列表 。

区块头

我们以btc为例,在btc协议中,区块头大小为80字节 。根据功能,这80字节可以被划分为六个独立的字段,共同构成了区块的元数据 。

  • 版本(Version) – 4字节

  • 前一区块哈希(Previous Block Hash) – 32字节。这是区块链数据结构中最关键的连接元素,它就是哈希指针。该字段存储的是其父区块(即链上的前一个区块)的区块头的SHA256(SHA256())双重哈希值 。通过这个字段,每个区块都牢固地指向其前驱,从而将独立的区块编织成一条不可分割的、按时间顺序排列的链。

    image-20250615162240044

  • Merkle Root – 32字节。btc 中使用的 merkle tree 的形式存储了区块体内所有交易,所以在头里面,还存储了区块中 merkle tree 的头节点,也就是 merkle root;

  • 时间戳(Timestamp) – 4字节,记录了该区块被矿工创建的大致时间。

  • 难度目标/比特(Difficulty Target / Bits) – 4字节。表示了当前区块挖矿的难度目标 ,也就是用来挖矿的。

  • 随机数(Nonce) – 4字节。这是一个由矿工在挖矿过程中不断改变的计数器 。矿工将版本、前一区块哈希、默克尔根、时间戳、难度目标和这个随机数拼接在一起,形成80字节的区块头,然后对其进行哈希计算。如果结果不满足难度目标,矿工就将随机数加一,然后再次尝试。这个暴力枚举的过程就是工作量证明的核心。

区块体

区块体则承载了该区块内所有经过验证的交易信息,所以可以把它理解为账本。以btc为例,它的区块体的结构由两部分构成:

  • 交易计数器 (Transaction Counter):记录该区块中包含的交易总数。
  • 交易列表 (Transactions):连续存放的、该区块内所有交易的原始数据。

那么如何记录一笔交易呢?比如,我们现实生活中进行转账,A 要给 B 转账,那么对于这笔记录首先我们需要知道这个币从哪里来的,这个叫做输入(Inputs);然后需要知道这个币是转给了谁,这个叫做输出(Outputs)。在转账的过程中,由于互联网上是基于互不信任的原则,所以这笔转账的过程还需要密钥加密,这叫数字签名(Digital Signatures),数字签名由资金所有者使用其私钥生成。

A转了5个币给B,给了 5个币给 C,这个过程中不停的交易,形成的这个链就是账本。比如下图,可以看成是交易账本的简化形式。

image-20250615171338943

那么交易账本的形式有了,那么如何构建安全的交易呢?首先,我们要了解一下什么是签名,上面我们也说了,在交易的过程中,A 转帐给 B,A 需要给这笔转账用 A 的私钥加密,这其实就是签名。

整个签名,其实就是非对称加密的过程,在btc钱包中,公钥相当于银行账号,私钥相当于银行密码。比如说A 要给 B 转 1 个 BTC,当 A 发起这笔交易时,他的钱包软件会做以下事情:

  1. 首先要回答 A 的“钱包”里有什么?

    假设 A 的钱包里并不是一个写着“我有 1.2 BTC”的数字。实际上,他的钱包知道他拥有两笔“未花费的钱”(Unspent Transaction Output,简称 UTXO),比如:

    • UTXO-1:价值 0.5 BTC(来自之前别人付给 A 的一笔钱)
    • UTXO-2:价值 0.7 BTC(来自更早之前别人付给 A 的另一笔钱)
  2. A 要给 B 转 1 个 BTC

    当 A 发起这笔交易时,他的钱包软件会做以下事情:

    • 输入 (Input):交易的“输入”必须明确指出它要花费哪几笔 UTXO。在这个例子里,钱包会选择 UTXO-1 (0.5 BTC) 和 UTXO-2 (0.7 BTC) 作为输入。总输入金额为 1.2 BTC。
    • 指定输出 (Output)
      • 输出1:向 B 的地址支付 1 BTC。这会为 B 创建一个价值 1 BTC 的新 UTXO。
      • 输出2 (找零):将多余的 0.2 BTC(1.2 – 1.0 = 0.2)转回给 A 自己的一个新地址。这会为 A 自己创建一个价值 0.2 BTC 的新 UTXO。
    • 签名:A 用自己的私钥对这笔完整的交易(包括输入和输出)进行数字签名。
  3. 最后就是验证这笔交易如何验证,在 BTC 中整笔交易要达成共识,入链才算完成。

    这笔交易被广播到btc网络后,每一个收到它的节点(矿工)都会进行严格的验证:

    • 验证签名:首先,用 A 的公钥验证交易签名是否有效。
    • 验证资金来源(最关键的一步!):节点会追溯整个区块链历史,去检查 A 在交易输入中声称要花费的 UTXO-1 和 UTXO-2 是否真的存在,并且是否真的是“未花费”的状态。
    • 检查结果
      • 如果节点在过去的区块里找到了这两笔 UTXO,确认它们属于 A,并且之前从未被花掉过,那么节点就确认 A 确实拥有这笔钱。
      • 如果 A 试图花费一个不存在的、或者已经被花掉的 UTXO(这就是“双花攻击”),全网的节点在查账时会立刻发现这个输入是无效的,从而拒绝这笔交易。
    • 一旦验证通过,矿工就会把这笔交易打包进一个新的区块。

好了,到这里,一笔交易内容有什么,以及如何保证安全已经说明了,那么如何在不下载全部数据的情况下,高效地验证某一个“小数据”是否属于这个“大数据集合”?就比如,我的手机钱包,如果验证 1 笔交易,而不用下载整个区块?

这就要提到 Merkle Tree,它是交易列表构成的一个树形结构,Merkle Tree 的叶子节点存的是每一笔交易的哈希值(Transaction Hash,也叫 TXID),可以看成是下图这样的结构:

image-20250615163517554

merkle tree 本质上是一棵哈希二叉树

  • 树的叶子节点是原始数据块(在区块链中就是一笔笔的交易)的哈希值。

  • 树的非叶子节点(树枝和树干)是它下面两个子节点哈希值拼接后再计算出的哈希值。

  • 这个过程不断重复,层层向上,直到只剩下一个最终的、位于最顶端的节点,这个节点被称为 merkle root。

merkle tree被发明出来主要有两个目的:

  1. 保证数据完整性(防篡改)

    merkle root 是对区块内所有交易的最终摘要。任何一笔交易哪怕被改动一个字节,其对应的叶子哈希就会改变,这个改变会像多米诺骨牌一样,层层向上传导,最终导致计算出的merkle root 完全不同。

  2. 极高的验证效率(轻量级验证)

    它允许在不下载整个区块数据的情况下,就能快速验证某笔交易是否存在于该区块中。

    比如手机钱包知道自己的交易哈希 H3,钱包从网络上下载了该区块的区块头(只有80字节,非常小),并从中获取了正确的merkle root ,那么对于 H3来说,它的验证路径是 H3 -> H34 -> Merkle Root。也就是只需要,它的直接兄弟 H4,它的上一层节点的兄弟 H12,然后就可以通过计算 hash 进行对比验证,这个过程就叫Merkle Proof。

    整个过程,手机钱包只需要下载几十字节的区块头和几十字节的 Merkle Proof,就能完成验证,而无需下载整个区块(可能好几MB)的所有交易数据,极大得节省了资源。

所以通过 merkle tree 就可以实现高效的“存在性证明”(Merkle Proof)以及保证数据完整性(防篡改)。

Hash 在区块链的作用?

其中在区块链中使用 Hash 算法有其关键的作用:

  1. collision resistance:在btc中使用的是SHA256(SHA256())双重哈希值,几乎不可能出现hash碰撞,因为如果可以轻易找到“碰撞”(两个不同输入得到相同输出),那么恶意攻击者就可能用一笔伪造的交易来替代合法的交易,从而破坏整个系统的信任;

  2. Hiding:由于算法的不可逆,所以无法由hash值推导出原值。这个特性对于保护数据隐私至关重要。在密码学中,这也被称为抗原像性 (Pre-image Resistance)。;

  3. puzzle friendly:如果想要找到某个特定的hash值对应的输入是什么,只能挨个去尝试,没有其他任何途径可以找到符合条件的hash值。

区块链中,由于下一个的指针是指向前一个,如果某个块的hash发生了改变,那么后续的也要改变,也就是牵一发而动全身,比如一个Merkle tree,叶子节点变了,其他节点也要变,因为其他节点是根据叶子节点算出来的。

                        [ Merkle Root ]
                              /     \
                             /       \
                    [ Hash_ABCD ]   [ Hash_EFGH ]
                       /     \         /     \
                      /       \       /       \
                [ Hash_AB ] [ Hash_CD ] [ Hash_EF ] [ Hash_GH ]
                   /   \       /   \       /   \       /   \
                 H(A) H(B)   H(C) H(D)   H(E) H(F)   H(G) H(H)

所以btc 中某个本地节点可以只保存最近的某些节点,如果需要前面的其他的节点可以问别人要,并且可以通过hash计算的方式来保证别人给的区块一定是正确的。就比如上图,只有几个节点,但是可以通过后面的节点计算别人给过来的前面的节点是否正确。

举例:

  1. 全节点发给你三样东西:
  • TX_C(交易 C 本身)
  • H(D) (你的“兄弟”哈希)
  • Hash_AB (你“叔叔”辈的哈希)
  • Hash_EFGH (你“伯父”辈的哈希)
  1. 轻钱包开始自己计算:
  • 第 1 步: 先把 TX_C 自己哈希一次,得到 H(C)
  • 第 2 步: 用自己算出的 H(C) 和全节点给你的 H(D) 组合起来哈希:
    • H( H(C) + H(D) ) 算出了 Hash_CD
  • 第 3 步: 用全节点给你的 Hash_AB 和上一步算出的 Hash_CD 组合起来哈希:
    • H( Hash_AB + Hash_CD ) 算出了 Hash_ABCD
  • 第 4 步: 用上一步算出的 Hash_ABCD 和全节点给你的 Hash_EFGH 组合起来哈希:
    • H( Hash_ABCD + Hash_EFGH ) 算出了一个“最终的 Root”
  1. 算出来的“最终的 Root”从区块头里拿到的那个“标准答案 Merkle Root”进行对比。

因为哈希的collision resistance特性。如果“别人”的 TX_C 是假的,或者 H(D)Hash_AB 中任何一个是假的,最终算出来的 Root 都不可能与“标准答案”一致。

分布式共识 distributed consensus

一个交易要被认可要取得分布式共识。那么什么什么是分布式共识?

想象一下,一群好朋友(比如 100 个人)共同记一本账本,记录着大家之间谁欠谁钱。他们没有一个中心记账员(比如银行),而是每个人手上都有一本一模一样的账本。

当其中一位朋友 A 要转 100 元给朋友 B 时,他会向所有人大喊:“我要从我的账上转 100 元给 B!”。

这时候问题来了:

  • 怎么确保 A 真的有这 100 元?
  • 怎么确保 A 没有同时跟别人说“我要把这 100 元转给 C”?(也就是所谓的“双花攻击”)
  • 最重要的是,在没有中心决策者的情况下,如何让这 100 个人全都同意在自己的账本上写下“A 转给 B 100 元”这同一笔记录,并且确保之后没有人可以反悔或篡改?

分布式共识 就是为了解决这个问题而设计的一套规则和方法。它的目标是:让一个分布式系统中的所有参与者(节点),在没有中央指挥的情况下,最终能够对某个状态或数值达成一致的协议。

如何达成分布式共识

上面我们也提到了,如果想要 A 转给 B 1 个 BTC,在 BTC 中整笔交易要达成共识,入链才算完成。这个共识算法就是工作量证明(Proof of Work, PoW)。

在 A 转 1 个 BTC 给 B 的时候,会用私钥对一笔交易进行数字签名,然后钱包会将这笔签好名的交易广播到整个区块链网络中,附近的节点会接收到这笔交易。

之后:

  1. 节点验证与传播

    • 初步验证:

      接收到交易的节点(我们称之为“矿工节点”)会立即进行验证,首先用A的公钥检查数字签名是否正确,再来就是追溯A的交易历史,确保您确实拥有足够的资金来支付这笔交易;

    • 验证通过后,这笔交易会被放入该节点的“内存池(Mempool)”中,这是一个等待被打包的交易的临时集合;

    • 该节点会将这笔验证过的交易继续传播给与它相连的其他节点,直到这笔交易遍布全网。

  2. 竞争记账权(挖矿)

    • 所有矿工节点会从自己的交易池中挑选一批交易,将选中的交易和上一个区块的哈希值等信息打包成一个“候选区块”
    • 矿工们开始进行疯狂的哈希计算,不断变换候选区块头中的一个随机数(Nonce),试图找到一个满足特定难度目标的哈希值(例如,开头有非常多个零);
  3. 达成共识与全网同步

    • 假设矿工 M 率先找到了正确的哈希值,他立刻向全网广播他创建的、包含您交易的新区块;
    • 网络上其他矿工收到这个新区块后,会停止自己的计算,并立即验证这个区块的有效性(包括验证其中的每一笔交易和那个“幸运哈希值”是否符合规则);
    • 验证非常快速。一旦确认无误,其他节点就会承认这个新区块是合法的,并将其添加到自己的区块链副本的末端。
    • 这就代表,全网对“这个新区块以及其中包含的所有交易是有效的”这件事达成了共识

由于网络问题产生分叉怎么办?比如即同时有两个矿工挖出区块,这个时候为了确保交易不可逆转,通常需要等待更多的区块在此基础上继续生成。

一般来说,在btc网络中,等待 6 个区块确认(大约 1 小时)后,该笔交易就被认为是完全被承认且不可篡改的了。

为什么矿工要帮忙做工作量证明

上面的工作量证明看起来实际上需要很大的计算量,需要很多计算机的算力,所以矿工做这些事情也会获得相应的报酬去激励他们继续保护和运行整个区块链网络。矿工主要有两部分收益:

  1. 区块奖励(Coinbase Reward)

    这是系统凭空创造出来、作为对矿工维护网络安全奖励的新币。这部分奖励是btc(或其他加密货币)通货膨胀的主要来源。对于btc来说这个奖励的数额是协议预先规定好的,并且会定期“减半”(Halving)。例如,btc最初每个区块奖励50个btc,现在(2024年减半后)是3.125个btc;

  2. 交易手续费(Transaction Fees)

    这是该矿工从他打包的那个区块中,所有交易的发起者支付的手续费的总和。用户为了让自己的交易能被矿工尽快打包,会附加一笔手续费。矿工自然会优先选择手续费高的交易来打包。这部分收益的数额是不固定的,取决于当时网络的拥堵情况和用户愿意支付的费用。

所以矿工的总收益 = 区块奖励 + 该区块内所有交易的手续费总和。

但是在 btc 中随着时间的推移,区块奖励会越来越少,直到最终变为零(预计在2140年左右)。到那时,矿工维护网络的唯一动力就将完全来自于交易手续费。这个设计确保了即使在所有币都发行完毕后,依然有经济激励促使矿工继续保护和运行整个区块链网络。

挖矿

上面我们提到了,挖矿的过程其实就是改变block header 里面的 Nonce 字段,计算出一个有效的“哈希值”,计算出的哈希值必须小于或等于当前网络设定的“目标值” (Target)。

举个例子,假设目标是: 00000000000000000005a3f6d8a4c1d8d3f6a8b3c5d1e7f9a2b4c6d8e。那么,任何计算出来的哈希值,只要在数值上比上面这个小,就是有效的。比如:00000000000000000001b8d3c5d1e7f9a2b4c6d8e4a3f6d8a4c1d8 (这个值更小,所以是有效的)。

但是Nonce 只是一个 32 位的字段,目前来说矿机每秒可以执行数百亿亿次哈希运算(TH/s)。一台高端ASIC矿机(例如140 TH/s)可以在微秒级别的时间内遍历整个Nonce空间。

所以仅靠Nonce 是找不到对应难度目标(Target)的值,当然Timestamp也可以有一定的调整空间,但是比较有限,后面就演变成使用 ExtraNonce 来扩展搜索空间。ExtraNonce 指的是矿工放置并修改在铸币交易(Coinbase Transaction)的scriptSig字段(也被称为coinbase data)中的任意数据,它可以进行修改 。

scriptSig 字段通常是用来数字签名来证明所有权用的,但是Coinbase交易是凭空创造新币的,它不消耗任何已存在的UTXO。因此,其输入中的scriptSig字段无需包含任何解锁脚本或数字签名。根据btc协议,这部分空间可以由矿工自定义填充,长度限制在2到100字节之间。

那么挖矿算法的大致流程就会变成:

  1. 构建区块模板:
    • 从内存池(mempool)中选择交易,通常按费率(fee rate)高低排序。
    • 构建铸币交易,包含区块奖励、交易费,并设置一个初始的ExtraNonce值(如0)。
    • 基于此交易集合计算出hashMerkleRoot
    • 组装候选区块头,填入VersionhashPrevBlockhashMerkleRoot、当前的TimeBits
  2. 内层循环(Nonce迭代):
    • FOR nonce FROM 0 TO 2^32:
      • 在区块头中设置当前的nonce值。
      • 计算 hash = SHA256(SHA256(header))
      • IF hash <= Target:
      • 找到解。广播完整的区块。
      • 返回第一步,开始构建下一个区块的模板。
  3. 外层循环(ExtraNonce迭代):
    • IF 内层循环完成仍未找到解:
      • 在铸币交易中递增ExtraNonce的值。
      • 重新计算hashMerkleRoot
      • 更新区块头中的hashMerkleRoot字段。
      • (可选)如果时间变化足够大,更新Time字段。
      • 返回第二步,使用新的区块头重新开始Nonce的迭代.

并且为了控制 btc 的产量,在btc网络诞生之初,矿工每成功打包一个区块,可以获得 50 BTC 的奖励。并且btc的协议规定,每产生210,000个区块,区块奖励就会减少一半(减半)。由于比特币网络的目标是平均每10分钟产生一个区块,210,000个区块大致相当于4年的时间(10分钟×210,000≈4年)。

我们可以用一个等比数列求和的公式来表示这个过程:

总供应量 = 210000×(50+25+12.5+6.25+…) = 210000×50×(1+0.5+0.25+0.125+…)=210000×50×2=2100w

image-20250705234838859

由于区块奖励不断减半,奖励金额会变得越来越小。大约在第33次减半(约2140年左右)之后,区块奖励将变得微不足道(小于1聪,即比特币的最小单位)。届时,可以说几乎所有的比特币都已被挖出,矿工的收入将完全依赖于交易手续费。

这种通缩模型的设计,使得比特币具有了稀缺性,从而避免了像传统法定货币那样因无限增发而导致的通货膨胀问题。

为什么早期仅遍历Nonce就可以,现在却不行?

主要是因为btc它会自动的调整难度,早期的时候参与者少,算力低在只有几 MH/s 的算力下,要遍历完这43亿种可能性需要很长时间(几百秒甚至更久)。当时网络的目标是大约10分钟产生一个区块,因此,仅仅通过不断尝试和改变Nonce,就有非常大的概率能在这10分钟内找到一个符合条件的哈希值。

现在对于一台算力为 100 TH/s(1014 次哈希/秒)的现代ASIC矿机来说,遍历完43亿的Nonce仅需要0.000043 秒,如果还是这个难度,估计用不了几秒就要被挖光了。

btc的难度调整是其协议中最优雅的设计之一,它确保了无论全网算力如何变化,新区块的产生速度都能稳定在平均10分钟一个。

btc的难度目标(Target)是个以一个256位的数字。挖矿的本质就是找到一个区块头的哈希值,使其小于或等于这个目标值。目标值越低,挖矿越难。因为一个更低的目标值意味着哈希结果的开头必须有更多的“0”,符合条件的哈希值就越少,找到它所需要的计算次数就越多。

所以为了实现大约10分钟产生一个区块这个目标,btc会动态的调整难度,难度调整的具体机制如下:

  1. 比特币网络中的每个全节点都会每2016个区块自动进行一次难度调整。节点会计算生成这最近的2016个区块所花费时间,即 2016区块 × 10分钟/区块 = 14天;
  2. 节点会根据实际时间与期望时间的偏差来调整下一个周期的目标值,公式New Target = Old Target * (Actual Time / Expected Time)
    • 挖得太快了。如果Actual Time小于20160分钟(比如只用了12天),说明全网算力增强了。此时 (Actual Time / Expected Time) 这个比率小于1,New Target就会变小,从而增加挖矿难度
    • 挖得太慢了。如果Actual Time大于20160分钟(比如用了16天),说明全网算力下降了。此时这个比率大于1,New Target就会变大,从而降低挖矿难度
  3. 为了防止网络因算力剧烈波动而产生过大的难度变化,单次调整的幅度被限制在一个4倍的范围内。即,调整系数(Actual Time / Expected Time)最大不会超过4,最小不会低于1/4。

我们也可以看到下面图的难度曲线的设置,是越来越陡峭的,跟价格几乎是成正比:

image-20250720180550478

并且比特币矿机也不再是以前的 GPU 时代了,而是专门的专用矿卡进行挖矿。比特币矿机的演变是一场追求极致算力和能效比的“军备竞赛”:最初人们用个人电脑(CPU) 就能挖矿,很快被游戏显卡(GPU) 的高并行算力所取代;接着,更省电的FPGA(半定制芯片) 短暂出现,但最终被ASIC(专用定制芯片) 以绝对的算力和能效优势彻底统治,使挖矿从此进入了专业的工业化时代。

Progress-Free性质

Progress-Free 指的是“无记忆性”(Memoryless),也就是在任何给定时刻,矿工找到下一个区块的概率与他们过去已经付出的努力无关。

在btc挖矿中,矿工们不断地进行哈希运算,尝试找到一个小于当前网络目标难度的哈希值。每一次哈希运算都是一个独立的、随机的尝试。也就是说包含两个特性:

  • 独立的尝试: 每一次哈希运算都使用一个略有不同的输入值(通过改变一个称为“nonce”的随机数)。因此,前一次哈希运算的结果对后一次完全没有影响。就像抛硬币一样,无论您已经连续掷出了多少次反面,下一次掷出正面的概率永远是50%,和过去的努力无关。
  • 成功的偶然性: 能否找到有效的哈希值,完全取决于运气。

由于这两个特性,表示过去的努力不会累积,每时每刻都是一个全新的开始,确保了挖矿的公平性,即便是算力较低的矿工,理论上也有机会在任何时刻找到区块。

挖矿攻击

Boycott Attack

指的是网络中掌握显著比例算力(或权益)的参与者(通常是大型矿池或PoS验证者)联合起来,故意“抵制”或“排斥”网络中的某些特定元素。

在这种攻击中最常见的就是一个或多个拥有大量算力的矿池故意拒绝将某些合法的交易打包到他们挖出的区块中。比如,某个矿池为了遵守其所在国的法规(如美国的 OFAC 制裁名单),宣布将“抵制”所有与黑名单地址相关的交易。

这种攻击的严重程度取决于“抵制联盟”掌握的算力。如果抵制联盟的算力低于 51%,被抵制的交易仍然可以被确认,但它们必须等待那些“不参与抵制”的矿池(例如只占 30% 算力)来挖到区块。

如果抵制联盟的算力超过 51%。他们不仅自己不打包这些交易,他们还会故意孤立(orphaning)任何包含了这些交易的诚实区块(因为他们总能挖出更长的链)。结果就是被抵制的交易将永远无法被打包上链。这等同于将某个用户或应用永久地“踢出”了网络

这种攻击并不像“双花”那样直接窃取资产,但它直接攻击了区块链最核心的价值主张之一:抗审查性(Censorship Resistance)和中立性(Neutrality)

51% Attack

当单个矿工或矿池掌握了全网超过 50% 的算力时,他们就有能力制造出一条比诚实网络更长的“分叉链”。攻击者可以在主链上将 BTC 发送给商家(例如,换取法币或商品),然后在自己的分叉链上构造一笔交易将同样的 BTC 发送回自己的地址。当他的分叉链长度超过主链并被网络接受时,之前给商家的交易就被“撤销”了,从而实现Double Spending。

但是这样的攻击需要天价的硬件和电力成本,而且成功攻击会摧毁人们对 BTC 的信心,导致币价暴跌,攻击者自身的收益也会大打折扣,,因此在经济上是不理智的。

虽然在BTC上不太可能这么做,但是这一点在那些算力较低的山寨币(altcoins)上体现得淋漓尽致。攻击者可以按小时租用强大的算力(OPEX)。对于一个小币种来说,攻击者可能只需租用比特币网络总算力的一小部分,就能轻松达到该小币种网络51%以上的算力。

Selfish Mining

攻击者,不需要 51% 的算力(理论上超过25%就有可能获利)。攻击者挖到区块后并不立即广播,而是选择不发布,并基于这个秘密区块继续挖下一个。

当诚实矿工挖到区块A时,自私矿工如果已经秘密挖到了区块A’和B’(比诚实链长),他就会立刻广播自己的A’和B’。网络会接受更长的链,导致诚实矿工的区块A作废,他们的算力被浪费。

为了防范这种攻击,在比特币中一笔交易通常需要等待6个区块的“确认”(Confirmations)后才被认为是最终、不可逆转的,这大约需要一个小时的时间。因为每增加一个确认,攻击者就需要付出更多的算力和时间来追赶并超越诚实的区块链。

中本聪的计算表明,如果一个攻击者掌握了10%的网络算力,那么在6个区块确认之后,他成功实现双花攻击的概率已经下降到了0.1%以下。这个概率被认为足够低,可以保障绝大多数商业交易的安全。“6个确认”因此成为了一个在安全性与用户体验之间取得平衡的行业“黄金标准”。

分叉

硬分叉

“硬”分叉 (Hard Fork) 是一种永久性的、不向后兼容的“规则升级”。当硬分叉发生时,旧版本的软件(节点)将不再接受新版本软件(节点)创建的区块,导致区块链永久性地分裂成两条不同的链

它强制要求所有参与者(矿工、节点、交易所、钱包)必须升级到新软件。如果你不升级,你就会被“留在”旧的、即将被淘汰的链上。

硬分叉在 BTC 最著名的例子就是 Bitcoin Cash (BCH),它在 2017 年 8 月从 BTC 硬分叉出去。

  • 分歧点: 如何解决比特币的“拥堵”(扩容)问题。们认为 1MB 的上限(以及 SegWit 的 4M WU)太小了,于是他们通过硬分叉,直接把区块大小上限改到了 8MB,后来又改到 32MB。
  • BTC 阵营(旧规则): 选择“隔离见证”(SegWit),这是一种复杂的“软分叉”升级(可以理解为优化道路,让每辆车坐更多人)。
  • BCH 阵营(新规则): 认为 SegWit 太复杂,主张简单粗暴的“硬分叉”——直接把区块大小上限(1MB)提高到 8MB(可以理解为直接把两车道公路扩建成八车道)。
  • 结果:
    1. BCH 修改了规则代码,并在特定区块高度激活。
    2. 坚持旧规则的节点继续留在 BTC 链上。
    3. 运行新规则的节点分裂出去,形成了 Bitcoin Cash 链。
    4. 新币诞生: 在分叉的那一刻,如果你持有 1 个 BTC,你现在会同时拥有 1 个 BTC(在原始链上)和 1 个 BCH(在新链上)。

这里有个有意思点,如果是在硬分叉之前拥有了某个加密货币,那么在分叉之后将同时拥有“原始货币”和所有“新分叉出来的货币”。

软分叉

软分叉 (Soft Fork) 是一种向后兼容的升级,可以认为是把旧规则变得更严格。

比如,以前的规则是“区块大小不能超过 1MB”。软分叉的新规则是“区块大小不能超过 1MB,并且里面必须包含 A 数据”。旧节点(只懂旧规则)看到新区块时,会觉得:“它没超过 1MB,合法。”(它看不懂 A 数据,但不影响)。新节点会严格执行新规则。

这样只要大多数矿工升级,网络就会被“拉”到新规则上,而不会像硬分叉那样导致区块链分裂

SegWit (Segregated Witness,隔离见证)就是btc链上一次最重大技术升级实现的软分叉。在 SegWit 之前,比特币网络面临两个主要问题:

  1. 在比特币交易被确认(打包进区块)之前,任何人都可以轻微地修改这笔交易的数字签名(scriptSig),而不会使交易失效,这种修改会导致交易ID(txid)发生变化。如果这个txid在交易确认前可以被篡改,就会引发严重问题,比如双花攻击的变种,或者让依赖 txid 的复杂合约(如闪电网络)变得极难实现。
  2. 比特币的区块大小被限制在 1MB,数字签名(见证数据)通常会占据一笔交易 60% 甚至更多的空间。这些数据都挤在 1MB 的空间里,大大限制了每个区块能容纳的交易数量。

SegWit 通过改变交易和区块的数据结构,重新定义了一笔交易的结构。它把交易分为两个部分:

  1. 核心交易数据 (Base Transaction Data)
    • 包含:发送方地址、接收方地址、金额等。
    • 不包含: 数字签名和解锁脚本。
    • 关键点: 交易ID(txid只根据这部分数据来计算。
  2. 见证数据 (Witness Data)
    • 包含:所有的数字签名和解锁脚本(即 scriptSigscriptWitness)。
    • 这部分数据被“隔离”出来,存放在交易的一个新字段中。

由于 txid 现在只根据核心交易数据计算,而签名(scriptSig)这个唯一可以被延展(篡改)的部分已经被移到了见证数据中,不再参与 txid 的计算。因此,一旦交易发出,其 txid 就被永久固定,交易延展性问题被彻底解决。

再来就是SegWit 并没有直接把 1MB 的区块大小限制(Block Size)改掉,而是引入了一个全新的概念:区块权重 (Block Weight)

  • 旧规则: 区块大小上限为 1MB
  • 新规则: 区块“权重”上限为 4,000,000 WU (Weight Units)

这个权重的计算方式是:

  • 1 字节核心交易数据 = 4 WU
  • 1 字节见证数据 = 1 WU

那么:

  1. 如果一个区块全是老式交易(数据和签名混在一起),所有数据都按 4 WU/字节 计算,那么 4,000,000 WU/ 4 = 1,000,000 字节,区块大小仍然是 1MB
  2. 如果一个区块全是SegWit交易(签名被隔离),大部分数据(签名)都按 1 WU/字节 计算。这使得区块可以塞下更多的交易数据。

在理想的(全是SegWit交易的)情况下,一个区块的实际物理大小可以达到接近 4MB,但其“权重”仍然是 400 万 WU。

北大《区块链技术与应用》——BTC篇最先出现在luozhiyun`s Blog

对腾讯5年职业生涯的总结

2025-10-24 11:11:58

先摆出我的四个知识奖的奖牌镇楼,哈哈哈。该是时候总结一下自己这五年多来做了什么事情了,希望对各位能有所启发。

IMG_7331

其实我每年都有发自己的总结文章,附上以前的一些总结:

2024年总结:沉寂积蓄新的力量

2023年总结:保持心情愉悦&积极向上

2022年总结:保持&缓行

2021年总结

2020年总结

文章

对于我自己来说是比较喜欢研究技术,写文章,所以这五年确实沉淀了很多我以前研究的技术文章。发布的文章承蒙各位喜爱,在腾讯内网也上过很多次头条和推荐,这里我再总结一下,希望这些文章能带给大家一些启发或者帮助。

Frame 2

AI

合集

AI 相关的文章合集是当时因为图片生成爆火,然后自己顺着AI的名义给自己买了一张 4090 显卡,图片玩过几次除了生成一堆涩图以外想着是不是可以用来干点正事,于是就想着能不能学点AI 相关的知识,于是就有了这个系列的文章。

[长文]写给开发同学AI强化学习入门指南

这篇文章应该是我写的最满意的文章之一了,首先里面有真实的学习步骤是怎样的,里面讲了我是怎么学习的,以及弄了一些 demo 来跑我们的模型,总而言之入门很合适了。

如何用 PPO 算法让 AI 学会玩 FlappyBird

后面我还用强化学习,根据图片来学习怎么玩 FlappyBird 这个也很有意思。

合集里面剩下的几篇AI文章就是讲的我怎么在生活中使用 AI 的也希望能给大家一点启发。

Golang

合集:https://www.luozhiyun.com/archives/category/%E5%90%8E%E7%AB%AF/go

go相关的技术文章是写了真的不少,因为当时自己对这一块比较感兴趣,工作中也常用,所以写了很多源码研究之类的文章,很多文章即使现在来看都写的不错,诸如:

[长文]从《100 Go Mistakes》我总结了什么?

这篇文章有时候没事我会去看看的,因为细节太多,很多时候一不留神写的代码容易出错,里面的错误范例对于工作中还是很实用。

如何编译调试Go runtime源码

调试源码这个事情,一般是不会去做,但是如果对源码感兴趣是可以参考一下怎么去调试。

下面几篇我也觉得写的不错,就是比较底层,读起来最好自己跟着看看代码,否则会比较费劲。

Go语言GC实现原理及源码分析

从源码剖析Go语言基于信号抢占式调度

从栈上理解 Go语言函数调用

一文教你搞懂 Go 中栈操作

C++

合集:https://www.luozhiyun.com/archives/tag/cpp

C++ 相关的文章也是写了不少,写这些文章的时候基本上也是边写边学,写文章的同时顺带把我的疑惑点也给解答了。

计算机基础

合集:https://www.luozhiyun.com/archives/tag/%e8%ae%a1%e7%ae%97%e6%9c%ba%e5%9f%ba%e7%a1%80

这方面也是我自己慢慢想着要整理一下自己现有的知识体系,然后写的一些文章。

作为开发需要了解 SSD 的一切

这篇文章可能在生产中还是有点用,算是科普一下SSD原理,以及如何更高效的使用。

CPU 是如何与内存交互的?

这一篇文章也是我对知识体系的一个梳理,但是很多知识点也只是起了一个抛砖引玉的效果,更多的知识感兴趣的同学可以去看看 《深入理解计算机系统》 、《深入浅出计算机组成原理》 等书。

云原生网络

合集:https://www.luozhiyun.com/archives/category/%e7%bd%91%e7%bb%9c

这里面的文章还是有点意思的,写这些文章也是因为我当时在研究 k8s 相关的技术的时候,对书里面的网络感觉很多地方都不懂,然后就自己专门又找了点资料自己研究了一下。

其他

这里就是再列举一些不知道分成什么类的文章,但是我觉得写的也不错。

深入 RocksDB 高性能的技术关键

这篇文章对于 RocksDB 是怎么实现的做了很详尽的分析,我觉得写的还是不错的,里面的图也画的很精美(我其他文章图也画的很精美)。

Protobuf 编码&避坑指南

这篇文章里面讲了一下 protobuf编码原理 & 最佳实践,我觉得在工作中还是很实用的,里面的图也画的很精美。

构建属于自己的云游戏服务器

其实我是很喜欢玩游戏的,但是不能在公司流畅的玩我想玩的游戏这点让我很难受,所以我决定自己来搞。

Yolov5物体识别与应用

当时沉迷于 DNFM 无法自拔,但是我又不想每天花2个小时搬砖,所以就想有什么办法可以让电脑来自动化这件事情。

最后

虽然我的鹅厂生涯结束了,但是我对这个世界的探索不会结束,感兴趣的可以继续关注我的公众号 & 微信 & 博客(文章最后放出)。

这五年来的一些改变

更健康的饮食

最近一两年我开始自己做饭,然后中午带饭去公司吃。我一开始觉得做饭是一种浪费时间的行为,但是后面觉得自己做饭一方面可以更健康的生活,另一方面做饭的时候我喜欢自己一边做饭一边听点播客,我基本什么都听,其实也算是了解其他行业的渠道。

其实就目前来看,我们的饮食里面其实配比是很不正常的,所以我会在做饭的时候会有这几点要求:

  1. 蔬菜的量尽量多点;
  2. 不要吃太多的碳水;
  3. 少摄入油盐;
  4. 戒糖;

控糖

其实我们生活中很大的疲倦感除了是真正的劳累造成的以外,血糖的波动也会带来疲倦感。血糖不稳定,就像让我们的身体坐上一辆失控的“过山车”,时而冲上顶峰(高血糖),时而跌入谷底(低血糖):

吃完饭血糖飙升 → 感到困倦、没精神(高血糖疲劳) → 胰岛素过量分泌 → 血糖骤降 → 感到虚弱、无力、发慌(低血糖疲劳) → 赶紧吃东西(尤其是高糖食物) → 再次飙升……

由于我们传统的饮食结构的影响,我们食物中大多是米饭,以及面条为主食,他们都含有大量的淀粉,淀粉会消化分解为葡萄糖,这些葡萄糖分子被小肠吸收到血液中,“血糖”指的就是血液中的葡萄糖浓度。因此,大量葡萄糖涌入血液,血糖水平就会迅速飙升。

当然,我要控糖也和我的尿酸偏高有关系。果糖(Fructose),对尿酸的影响非常显著,甚至可以说是独立于“高嘌呤饮食”之外的另一个主要诱因。糖对尿酸的影响是:

  • 一方面(开源):果糖在肝脏代谢,直接加速了嘌呤的分解,导致尿酸产量增加
  • 另一方面(节流):高糖引起的胰岛素抵抗,导致肾脏对尿酸的排泄减少

我们日常吃的白砂糖(蔗糖)在体内会分解成一半葡萄糖和一半果糖。而奶茶、可乐等甜饮料中添加的“高果糖玉米糖浆”,其果糖含量更高。

所以,控糖基本上就包含了两种:

一种是少吃各种碳水,米饭,面条等等,之类的含有高淀粉的食物,用糙米、黑米、藜麦、燕麦米等全谷物来部分或全部替代白米饭;

另一方面是含有甜味的食物,如奶茶,可乐等含有果糖的食物尽量不要去吃,顺带一提,很多水果也是高果糖的,也不能多吃;

蔬菜

然后再来提一下蔬菜,其实我们平时的蔬菜摄入实在是太少了,根据《中国居民膳食指南(2022)》成年人每日应摄入300至500克的蔬菜,但是很多时候蔬菜摄入实在太少了,比如一份点外卖,里面可能就几根蔬菜。所以我现在即使是早上都是粗粮比较多,比如会早上吃玉米,一份蔬菜,三个鸡蛋,然后才是两个小笼包。

蔬菜的好处有很多啦,我就不重复了,我说一点我比较关注的,就是可以帮助我们控制和稳定血糖的强大“盟友”。

蔬菜中的膳食纤维,特别是可溶性膳食纤维,扮演着“血糖缓冲器”的角色。可溶性膳’便纤维在遇水后会形成一种凝胶状物质,包裹住食物。这会显著减慢胃排空的速度和食物在肠道中的消化过程,从而使碳水化合物(糖)的吸收变得更加平缓,避免餐后血糖像坐过山车一样急剧飙升。并且纤维能够提供强烈的饱腹感,有助于控制食量。

运动

健身

其实我到现在为止健身超过 8 年了,从我大学时候就开始了,至今为止也练的不错,以前锻炼是想要练成super man 这样的肌肉形状,后面自己尝试过之后发现没有上科技根本不可能,现在也就保持这样的体型已经很久了,也就是其实和我的职业生涯一样,进入了“平台期”很久了。

Frame 1

而且随着年纪的慢慢增大,继续激烈的力量训练是很容易受伤的,近3年我就分别受伤了3次,腰部、肩膀、手腕都受伤过。手腕是最近一次去年 11月受伤,直到现在都没怎么恢复好,所以各位运动的时候一定要注意安全。

开始跑步

我从今年年初开始跑步,跑步真的给我很大的帮助,每次都竭尽全力完成每次计划的跑量真的很爽。然后看着自己的跑速,心肺,各项指标都在稳定的提高真的很开心,要知道人到中年能稳定提升的能力不多了

image-20251019115853678

跑步和健身很不一样,一方便跑步的时候没有组间休息,另一方面就是一旦开始了就不能停。现在我一旦心情不舒畅就会大跑一场,跑完之后你会发现自己无比淡定,再糟心的事情也不过如此,没有什么是过不去的。

一般的情况下我会选一个播客来听,这样起码让我的跑步过程不会很无聊,我一般喜欢听下面几个播客:

  • 知行小酒馆:听的最多的应该是这个,讲如何生活,讲投资。但是有些嘉宾请的也不是很好,比如 Anker 那期,翻开评论你可以发现全是骂的;

  • 半拿铁:里面会讲很多商业漫谈,以及商业史,但是我最喜欢的还是最喜欢他们的西游篇,把西游记从头到尾讲了一遍;

  • 面基:这个博主主要讲投资相关的东西,有时候看到感兴趣的主题会看看;

  • 起朱楼宴宾客:也是主要讲经济,商业,投资相关的,还不错;

  • The Wanderers 流浪者:这个是我最近喜欢听的,他们会对最近的实事进行总结,讲讲他们的投资理念,主理人是三个专业的投资人,讲解的话题覆盖了A股,港股,美股,加密货币;

image-20251019155331023

投资

我其实前几年开始就在想存钱,投资,然后可以产生复利,这样就可以通过投资反哺我的生活,让我有一定的被动收入,所以我也一直没买房买车,奢侈类的消费基本不参与,看准自己的目标,并为之而努力。

我的收入其实现在是分为几个部分:

  1. 现金以应对不确定性,在我没工作的时候可以给我提供支撑,或者市场大跌的时候可以有资金可以入场;
  2. 低波动资产,如纳指ETF、标普ETF、恒生指数ETF、货币基金等;
  3. 股票资产,主要以美股为主,港股为辅;

1和2大概占了我总资产的三分之一,3占了三分之二。我至今为止,其实盈利还行,主要还是选股选择的都是一些可以拿的住的一些股票,大多持股超过了1年,即使在波动比较大或者出现黑天鹅的时候,我也没有选择卖出,反而进行了加仓,这也是我投资的理念,除非公司大的方向发生了转变,否则我应该还会继续持有。

我也不是交易大师,但是我觉得这些交易策略是可以通用的:

  1. 不要追高,这点很重要,当然这是肯定要承受踏空的失落感,但同时起码能让你不亏;
  2. 做好交易笔记,保证是经过充分思考而买入,而不是一拍脑袋,然后就投入了你的血汗钱,买个手机都会货比三家,更何况是股票;
  3. 不要看短期热点炒股,热点这个东西有点虚无缥缈,我们是很难抓住的,通常消息传到我们耳朵里的时候,已经快过时了;

杂谈

人生是旷野

就在昨天跑步的时候,我都在想什么是财富自由?财富自由是表示自己不工作也不会饿死吗?表示自己可以一直躺平吗?我觉得不是,我觉得应该是无需为了生计而出卖自己的时间,可以自由选择自己想要的生活方式。也就是说我可以选择我的时间应该花在哪里,我可以将时间和精力投入到自己真正热爱的事业、兴趣爱好、家庭或社会贡献中,而不用被动地接受工作的束缚。

如果接受了这条假设,那么就表示代表人生不是只有一条路,而是充满了各种各样的选择和不确定性,就如同旷野一样。我可以去探索、去尝试,去定义自己的成功和幸福,而不是遵循他人的标准。当然我也需要自己去开辟道路,面对困难和迷茫。这既是挑战,也是成长的过程。

关于 AI 带给我的思考

最近我用 Claude Code 越多,我越发现它在平时的 coding 中能发挥的影响力就越大,我用它不限于:

  1. 对于陌生的项目,我让它帮我先看一下这个项目是做什么的,核心逻辑是啥,如何使用;
  2. 对于一些重复的代码,我会整理好模版和需求之后让它帮我快速实现,几乎100%没有问题;
  3. 对于一些自己也拿不准的需求,可以让它帮忙先出一个方案其实是一个挺好的探索;
  4. 现在用 Claude Code 做页面效果已经很好了,我不会写页面,但是也用它弄了一些网页,效果还不错;
  5. 很多时候写代码的时候,都会想要用它来帮我优化一版;

基于这几年对 ai 工具的持续使用,感觉现在真的已经慢慢的渗透到我们的日常工作中了,虽然现在很多时候仍需要手动接管,但是已经可以节省很多时间了,可以遇见在不远的未来,coding 的工作被取代也是情理之中的事情。

所以在这之前我一直在想,我能做什么?

我能做什么

对于我来说,我一直在审视自己的能力边界,以及后面可以持续发力的点,我不是个可以随时躺平的人,也不喜欢躺平,能找到一份热爱的事情,并持之以恒的做下去可以说是我人生的一直以来的追求。

那么对于我来说,可以一直做下去的事情莫过于有以下几种:

  1. 真的找到了可以一直深耕的业务,并且这个业务是需要人来做的,AI无法取代人,比如涉及钱的金融类的核心工作,至少在 AI 的幻觉被解决之前,操作钱的事情,交给人来做稳定性要更高一点,并且它具有一定的业务复杂度,并且发展很快;
  2. 找到真正热爱的事情,就像巴菲特一样,每天可以跳着踢踏舞去上班,每天上班都是带着激情去,而不是疲倦,不过这个事情是可遇不可求的;
  3. 一人公司,现在 ai 能力越来越强,我是真的觉得可能会有这个趋势,以前是一个人扎的越深,可能越值钱,但是未来我觉得在这一行广度也同样重要。只要有个很好的想法,ai 就可以帮我实现不再是一句空话,我自己而言就落地了几个产品,其实还不错;

所以对于我来说,我现在年纪不是特别大,对生活也有激情,钱其实也不是那么缺,在我了解到我既不可能在这里一直呆到退休,也不可能直接呆到财富自由,那么我一直在对自己发出灵魂拷问:

为什么不把剩余的时间投资到更有意义的地方呢?难道一定要等自己老了,尝啥都没味道了,逛任何地方都没有意思了,眼睛里面没有光了,才能开始做自己的事情吗?

IMG_7266 1

扫码_搜索联合传播样式-白色版 1

对腾讯5年职业生涯的总结最先出现在luozhiyun`s Blog

回顾《Scaling Memcache at Facebook》论文

2025-05-18 17:28:35

facebook 现在业务特点奠定了 Memcache 设计的原则:首先读要比写要多,这很明显,因为FB中浏览人数肯定大于发表的;其次在FB中查询的数据源会来源不同系统,如 MySQL、HDFS等,这种异构性要求一种灵活的缓存策略,能够存储来自不同来源的数据。

image-20250420175751676

如上图所示,memcache 读数据的时候先尝试从 memcache 中读数据,若读取失败则从DB中获取数据填充到 memcache 中;写数据时,先更新数据库,然后将 memcache 中相应的数据删除。

这里其实涉及到缓存一致性的问题,更新缓存的策略其实可以演变出4种策略:

更新数据库后更新缓存、更新数据库前更新缓存、更新数据库后删除缓存、更新数据库前删除缓存。
大体上,采取先更新数据库再删除缓存的策略是没有问题的,但是真实场景下,还是会有一个情况存在不一致的可能性,这个场景是读线程发现缓存不存在,于是读写并发时,读线程回写进去老值。并发情况如下:

时间 线程A(写请求) 线程B(读请求–缓存不存在场景) 潜在问题
T1 查询缓存,缓存缺失,查询数据库得到当前值100
T2 更新主库 X = 99(原值 X = 100)
T3 删除缓存
T4 将100写入缓存 此时缓存的值被显式更新为100,但是实际上数据库的值已经是99了

这里其实 FB 引入了 leases 解决这个问题,后面再说。

如何降低延迟

在FB中,用户的一个简单页面浏览可能会产生上百个 memcache 请求,并且 memcache 是分布式部署的,并通过一致性哈希算法进行路由,所以web服务器通常需要与多台 memcache 服务器通信才能完成用户请求,这种 all-to-all 的请求方式会造成两个问题:

  1. Incast Congestion:当许多 Web 服务器(发送方)同时同一个 memcache 服务器(接收方)发送请求时,会瞬间产生大量的网络流量涌向该 memcache 服务器或其连接的网络交换机端口。这会超出交换机缓冲区或 memcache 服务器网卡的处理能力,导致数据包丢失、重传和延迟急剧增加;
  2. Single Server Bottleneck:某个 memcache 服务器因为持有特别热门的数据("hot spot"),或者自身处理能力稍弱,或者暂时出现性能波动,那么它也可能成为瓶颈;

FB 主要在每个 Web 服务器上的 memcache 客户端 来解决延迟问题。解决之道主要有两个:

  • Parallel requests and batching(并行请求和批量处理):这主要是优化 RTT 次数,将可以一起取的数据通过一次 RTT 一并取出,减少时延;
  • Client-server communication(客户端-服务器通信):这个优化主要将控制逻辑集中到 memcache client。memcache client 分成两部分:sdk 与 proxy,proxy 也叫做 mcrouter,它用来桥接 web server 与 memcached server。

Client 的请求还进一步优化,将查询请求优化为 UDP 请求,写请求为 TCP 请求。因为 FB 的业务中是读多写少的,且读数据对错误的容忍度高,所以查询的时候通过使用 UDP 可以让 client 直接与memcached服务器通信,无需经过 mcrouter 中转,从而可以降低大量的开销。

在 FB 的实践中,会丢弃掉由于 UDP 数据乱序或者延迟产生的错误数据,运行中大约有 0.25% 的请求被丢弃的,其中 80% 都是由于延迟或丢包导致的,剩余是由于顺序错乱所致。

处理写请求时,为了稳定性所以使用 TCP 来进行处理,但是 TCP 是有连接的,为了减少 client 与 memcache的连接,加了一层中间层 mcrouter ,client 只需要与 mcrouter 建立连接,然后由 mcrouter 与 memcache 集群建立连接,这也叫做 connection coalescing (合并连接),通过这种方式可以降低网络、CPU、内存的开销。

我们通过 FB 的统计数据可知,使用 UDP 大概节省了 20% 左右的延迟。

image-20250426173646439

为了解决Incast Congestion问题,FB 在 client 中使用了滑动窗口机制来做流量限制,滑动窗口的大小和 TCP 的拥塞机制做的有点类似,滑动窗口的大小会在请求成功的时候缓慢增大窗口,在请求没有回应的时候缩小窗口。

image-20250426180000094

当窗口较小时,应用必须串行分发更多批次的memcache请求,从而延长了网络请求的处理时长。而窗口过大时,同时发起的memcache请求会引发网络拥塞,在这两种极端情况之间存在着最佳平衡点,既可避免不必要的延迟,又能最大限度减少网络拥塞。

降低负载

租期 Leases

FB 使用 Leases 主要为了解决两个问题:

  • 陈旧写入(Stale Set)
  • 惊群效应(Thundering Herd)

Stale Sets(陈旧写入)

当多个客户端同时更新同一键值时,若网络延迟或系统调度导致写入操作乱序到达,后提交的更新可能覆盖先前已生效的更新,后续读取可能将旧数据重新写入缓存,形成持久性污染。

虑以下操作序列:

  1. 客户端B发送DELETE K指令清除缓存
  2. 客户端A由于网络延迟,稍后发送SET K=V1
  3. 客户端C在此时读取K,将获得已过时的V1值

这种时序错乱会导致缓存系统与持久化存储之间的数据不一致,进而引发业务逻辑错误。

惊群效应(Thundering Herd)

惊群效应(Thundering Herd)特指当某个热点缓存项失效时,大量并发请求同时穿透缓存层,直接冲击后端数据库的现象。这种场景类似于自然界的"踩踏事件",在毫秒级时间窗口内形成请求洪峰。

假设某热门内容(如病毒式传播的视频元数据)缓存过期:

  1. 缓存服务器标记键K为失效状态
  2. 在接下来的100ms内,10,000个客户端同时请求K
  3. 所有请求均未命中缓存,触发数据库查询
  4. 数据库瞬间承受超过日常峰值10倍的查询压力

这种突发负载可能导致数据库响应延迟激增,严重时引发级联故障。

综上,基于这几个问题,FB 使用 Leases 机制,通过 令牌仲裁速率限制 同时解决上述两大问题:

Stale Sets 的解决路径:

  1. 令牌绑定:每个缓存未命中事件生成唯一租约令牌,客户端需携带令牌进行写入;
  2. 无效化仲裁:若键值在租约有效期内被删除(如数据库更新),后续携带该令牌的写入将被拒绝;
  3. 单调性保证:通过令牌版本号确保写入操作的时序一致性,消除乱序覆盖;

Thundering Herds 的抑制策略

  1. 令牌发放速率限制:每键每 10 秒 仅发放一个有效租约,强制后续请求等待或重试;
  2. 渐进式回填:首个获得租约的客户端负责从数据库加载数据并回填缓存,其他客户端在短暂等待后可直接读取缓存;

伪代码如下:

// 伪代码示例:租约校验逻辑
void handle_get_request(key) {
    if (cache_miss(key)) {
        if (lease_rate_limit_exceeded(key)) {
            return WAIT_RESPONSE;
        }
        lease_token = generate_lease(key);
        return lease_token;
    }
}

void handle_set_request(key, value, lease_token) {
    if (validate_lease(key, lease_token)) {
        cache_set(key, value);
    }
}

进一步的,FB 还设置了 Stale values 机制,可以让业务来自行选择是否使用略微过期的数据来降低请求等待的时间。当一个 key 被删除时,这个 key 的值被短暂的存储到一个过期数据的地方,但是这个时候memcache里面还没被写入新的值,这个时候如果业务不想等待,那么可以直接取走老的数据,从而加速响应时间。

Memcache Pools 缓存池

memcache因为是一个基础应用,但是需要应对不同应用,那么不同业务的工作负载可能会对 memcache 缓存命中产生负面干扰,导致命中率下降。所以为了应对不同的业务,FB 在 memcache 中按照业务的特点来划分了不同的缓存进行隔离。

FB 将缓存集群划分为:

  1. Wildcard Pool:存储常规数据,采用标准淘汰策略
  2. Small Pool:存放高频访问但缺失代价低的元数据
  3. Large Pool:存储低频访问但重建成本高的批量数据

因为当高低频数据共存于同一池时,会产生 缓存驱逐风暴,高频数据持续写入导致低频数据的 LRU 链提前被截断,低频数据被迫频繁回填,引发数据库查询洪峰。

Replication Within Pools 池内复制

其核心目标是通过数据冗余换取吞吐量提升,适用批量读取密集型场景,比如应用需要单次请求获取大量关联键值。

举个例子,如用户动态页需加载100+社交关系数据:

  • 分片方案:将100键分片到2节点,每节点处理50键请求总吞吐量维持1M QPS,单节点负载500K QPS;

  • 复制方案:2副本各存储完整100键总吞吐量提升至2M QPS,单节点负载500K QPS。

虽然两种方案的单节点负载相同,但复制方案通过减少网络交互次数实现了端到端延迟优化。

Gutter 机制

Gutter 被定义为临时故障接管池,当一个常规的 memcache 服务器发生故障,客户端在访问该服务器上的数据时会超时或失败。此时,客户端会将请求重定向到Gutter服务器。如果Gutter服务器中也没有这份数据(缓存未命中),客户端会从后端数据库获取数据,并将这份数据写入到Gutter服务器中。这样,Gutter服务器就临时接管了故障服务器的缓存职责,吸收了原本会直接冲击数据库的请求。

如果没有 Gutter 服务器,少量memcache 服务器的故障会导致大量请求直接打到后端数据库。Gutter通过提供一个临时的缓存层,有效地缓冲了这种冲击。并且 Gutter中的条目通常设置了较短的过期时间。当故障的Memcache服务器被修复或替换后,系统会逐渐恢复正常,Gutter中的数据也会因为过期而自动清理,避免了复杂的失效通知机制。同时,Gutter的存在使得在少量服务器故障期间,系统仍然能够保持较高的缓存命中率。

也就是说 Gutter 起到了故障转移与负载吸收、防止雪崩等作用。在实践中,该Gutter 将客户端可见的故障率降低了99%,并且每天将10%-25%的缓存失败转换为缓存命中。如果memcache 服务器完全失败,Gutter 的命中率通常在4分钟内超过35%,并且通常接近50%,有效的吸收了负载。

Regional 按区域复制

在单个集群内扩展 memcache 虽然在一定程度上有效,但在延迟、负载管理和容错方面会遇到限制 。随着 Web 服务器数量和请求量的增加,单个 memcache 集群可能会成为瓶颈。并且管理和维护如此庞大的单体集群也带来了运营挑战 。对于频繁访问(“热”)的键,简单地将数据分区到更多服务器上(分片)并不能减轻持有这些键的特定服务器上的负载 。

所以 FB 把 memcache 转向了按区域复制,通过按区域部署分散负载并提高故障隔离能力。FB 将位于同一地理区域内部署多个前端集群,每个前端集群由一组处理用户请求的 Web 服务器和 memcache 服务器组成 ,但是同一区域内的所有这些前端集群共享同一个底层存储集群。

前端缓存层与后端持久存储的分离提供了更高的灵活性,FB 能够流量情况扩展前端的 memcache 实例数量,而数据库基础设施可以根据写入量和数据存储需求独立扩展。

Regional Invalidations 区域失效机制

失效机制主要由一个名为 mcsqueal 的守护进程驱动。存储集群,特别是 MySQL 提交日志 ,是数据修改的真实来源 。mcsqueal 守护进程在每个数据库服务器上运行,并监视已提交的 SQL 语句中是否有修改数据的操作。

检测到相关的数据库修改后,mcsqueal 会提取需要失效的相应 memcache 键 。为了最大限度地减少网络开销,这些失效命令(通常是删除操作)被批量处理成更少的包 。然后,这些批处理的失效消息被发送到位于每个前端集群中的专用 mcrouter 服务器 。mcrouter 服务器解包单个删除命令,并将它们路由到其本地集群中相应的 memcache 服务器 。

image-20250510180752441

Regional Pools 区域池

如果不管数据大小和key的访问热度,而盲目进行复制数据可能导致内存效率低下,尤其是对于大型的、很少访问的 key,所以 FB 使用区域池来管理这部分数据。区域池是同一区域内所有前端集群都可以访问的共享 memcache 服务器集合 。区域池中只存储一个副本,应用程序逻辑负责确定哪些键应该驻留在区域池中。

区域池提供了一种通过维护较少访问数据的单个区域副本,从而减少整体缓存层内存占用量的方法。

Cold Cluster Warmup 冷集群预热策略

当一个新的前端集群上线时,其 memcache 服务器最初是空的,导致缓存未命中率很高 。这种后端数据库请求的突然增加可能导致性能下降和潜在的过载。为了缓解这个问题,FB 采用了一种称为冷集群预热的机制 。

在预热过程中,它会将请求转发到同一区域中一个已经运行并具有良好缓存命中率的集群 。然后暖集群将数据提供给冷集群中的客户端,并且冷集群也会用此数据填充其自身的缓存。这使得新集群能够逐步构建其缓存,其中包含频繁访问的项目,而不会直接压垮数据库 。

Across Regions: Consistency 跨区域一致性

FB 通常会指定一个区域作为数据库的主区域用于写,而其他区域则包含只读的副本 。数据不一致的主要原因是主数据库和位于不同区域的副本数据库之间存在复制延迟

来自主区域的写入:mcsqueal 运行在主数据库上,负责提取与数据修改相对应的删除语句。然后,mcsqueal 将这些失效通知广播到主区域内所有前端集群的 memcache 部署。

来自非主区域的写入:FB 为了避免数据不一致的情况,引入了“远程标记”(remote marker)机制 。

当 Web 服务器想要更新键 k 的数据时,会执行以下步骤 :

  • 在区域池(该区域内多个前端集群共享的 Memcache 池)中设置一个远程标记 rk
  • 将写入请求发送到主区域,并在请求中包含 krk,以便在写入复制时失效 。
  • 从非主区域的本地 Memcache 集群中删除键 k

随后,当非主区域再次请求键 k 时,会发生以下情况 :

  • Web 服务器在本地 Memcache 中找不到 k(因为之前已被删除)。
  • 然后,它会检查区域池中是否存在远程标记 rk
  • 如果 rk 存在,则表明本地副本数据库可能仍然是过时的,因此该查询将被重定向到主区域以获取最新数据
  • 如果 rk 不存在,则表明来自主区域的复制可能已经完成,可以从本地副本数据库提供读取服务。

需要注意的是,当存在远程标记时,由于请求需要发送到主区域,这种机制会在缓存未命中时引入额外的延迟 。

Single Server Improvements 单服务器改进

这里的改进主要从数据结构、锁的粒度、协议:

  • 哈希表扩展:自动扩展哈希表以避免O(n)查找时间,确保高效的数据访问;

  • 细粒度锁定:最初引入多线程使用全局锁,随后改进为细粒度锁定;

  • UDP端口分配:为每个线程分配自己的UDP端口,以减少争用并分散中断处理开销;

  • 使用UDP替代TCP:单次获取提高13%,10键多获取提高8%;

总结

可以看到整片论文其实是围绕着下面几点优化进行的:

  • 传输协议优化:UDP用于查询请求,TCP用于写的请求,这样既可以降低查询请求延迟,也可以保证数据的一致性;
  • 租约机制:租约机制防止陈旧集(stale sets)和惊群效应(Thundering Herd)保证分布式集群下的数据一致性;
  • 池化技术的合理利用:系统分为默认通配符池、小池(频繁但缓存未命中成本低)和大池(不频繁但未命中成本高),使用缓存池来剥离不同的业务场景;并对于频繁访问的数据,复制到多个服务器,减少单服务器负载,优化资源使用;
  • 处理故障:使用 Gutter 接管故障服务器,客户端在无响应时重试到排水池,防止雪崩;
  • 跨区域一致性:主区域处理所有写入,并使用 mcsqueal 同步数据库的修改操作到 memcache 前端集群避免竞争条件;
  • 优化单服务器:最后还要提升单服务器的性能,比如使用细粒度锁定减少锁占用,使用UDP替代TCP等;

Reference

https://read.engineerscodex.com/p/how-facebook-scaled-memcached

https://tech.ipalfish.com/blog/2020/04/07/fb-memcache/

回顾《Scaling Memcache at Facebook》论文最先出现在luozhiyun`s Blog

2024年总结:沉寂积蓄新的力量

2025-01-26 17:08:29

今年本来元旦的时候可以抽时间写一下这篇文章,但是临近元旦竟然生病了,然后在床上躺了两天。竟然已经过了,那就不急了,慢慢写了。

新学习了啥?

文章

毫无疑问,我觉得现在的生活节奏是越来越快了,特别是在AI的加持之下,掌握AI并利用它进行终生学习已经是必然趋势了,所以我今年没有像往年一样输出很多技术类的文章,因为我觉得没什么必要了,很多知识直接问一下 AI 再结合自己过往的经验很快就能掌握。话虽然这么说,但是还是写了一些文章:

作为开发需要了解 SSD 的一切

透过ClickHouse学习列式存储数据库

深入 RocksDB 高性能的技术关键

构建属于自己的云游戏服务器

Yolov5物体识别与应用

C++ 中到底什么是”&&“ ?

相比以往动则十几篇,量已经少了很多,不出意外的话明年会更少(拼多多财报式发言,笑~)。

英语

从去年开始要学习突破英语,已经过去了一年,我其实还是蛮想去考个雅思,告诉大家自己的英语成绩有哪些突破,自己的方法有多么牛逼。

但是很可惜的是,我没有这么做,因为去年一年因为兴趣的原因,不太想花很多时间在应付考试上面,做的更多的是口语练习以及泛听磨耳朵训练,所以到后面写作以及雅思的考试范式练习我是一样没做。

不过还是讲一下,过去一年我是怎么优化学习英语的:

口语

首先是随便找一篇文章,或者一个感兴趣的事件,比如我经常不知道找什么话题,就会去 https://engoo.com/app/daily-news 上面找一篇文章看完,然后用对着 AI 复述一遍,让 AI 帮我找出我说的句子或语法有什么问题,或者在复述的时候,就发现了很多内容不会表达,全部都记下来,句子我会手写出来,单词我会记到 flomo 里面作为卡片,方便背诵:

Frame 1

然后,每次在开始每天的英语学习之前,把自己的笔记拿出来,不熟的可以多背背。

到了周末,有比较多的时间,我会和外教上一节自由交流课,其实大多时候没有任何主题,随便聊50分钟。但即使这样,很多时候也可以检验自己的英语到底怎样,因为在说的时候,你会发现自己对有些主题很熟悉,对有些主题很陌生。比如我对工作学习相关的主题就比较熟悉,对美食相关的就不怎么会说,这是因为各种食材,酱料之类的名词记的比较少。

听力

我把听力分为泛听和精听两部份:

我现在洗澡做饭,包括早上健身的时候都会经常泛听一些英语材料,泛听就比较发散了,随便什么都听,下面推荐几个感觉还不错的:

Round Table China:里面有几个人应该是中国人,会用英语和老外聊一些中国的实事,非常好懂,我是比较强烈推荐的;

Think Fast, Talk Smart Podcast:这个播客是斯坦福大学运营的一档节目,优点是发音比较清晰,主题偏向商务,工作,学习等;

Vinh Giang:他的播客主要聚焦于教人怎么表达,随便听听还行,因为主题关系,听多了有点单一;

Marques Brownlee:油管最大的电子区up主,发音清晰,用词也比较简单,主要是讲数码,讲电子产品功能啥的,没事可以听听;

Ariannita la Gringa:她的节目比较简单,多以生活场景为主,人也很上镜,学起来会比较愉快;

EnglishAnyone:这个人的英语会让你听起来很有信心,因为他会用简单的句子和单词告诉你该用什么方式学英语,但是场景比较单一;

Long Beach City College的LBCC Study Skills 课程还挺好听的,可以精听和泛听,这个课程讲的一点都不枯燥,强烈推荐!

对于精听,我一般需要抽一整块时间来听,需要配合插件 《Netflix和YouTube-AFL语言学习》一句句听了:

Tom Bilyeu:专门会找一些领域的大佬来访谈,我蛮喜欢的,比如最近找了 Ray Dalio ,Michael Saylor,

Answer in Progress:我非常喜欢他们的节目,各种小东西的科普,探究其原理,社会现象,每个视频十几分钟,但是完全不枯燥,强烈推荐。

AI带给我的学习上的改变

以前我学习效率都是比较低的,很多时候看到一本书的知识点遇到不懂的地方,也只能网上搜一下,但是目前网上不是没有知识,而是太多了,运气好的话,可以很快得出结果,运气不好得看很多篇同样的的文章才找到自己想要的。

但是现在有了AI之后学习方式可以说完全发生了改变,有些章节可以先问问AI,有个大概的了解再读书这样可以加速理解。对于不懂的可以问AI,然后让AI step by step的告诉你这是为什么,甚至可以给你举一反三,可以说AI成为了世上最好的老师。

还有就是 AI 帮我们做了知识的收口,现在互联网的知识太多了,随便查一个东西不是苦于没有,而是苦于不知道该看哪个,但是 AI 可以只吐给我们有效的知识,帮我过滤无用东西。

所以可以看到,合理的使用AI,是可以加速我们掌握新的技能,从另一个角度上讲,AI可能会让人们在未来更方便的转型,从A跨越到B不会太困难,所以我觉得在未来依旧充满了各种机会。

健康 & 健身

过去一年,我还是一样保持了高频的健身习惯,和过往一样,基本上每周至少 4次力量训练和至少一次的有氧训练,天气不冷的时候有氧训练会去游泳,天气太冷了就会去跑步。

Frame 2

近些年来,我一直保持了良好的健身习惯和饮食,要说今年有什么变化,就是在10月体检报告出来之后发现尿酸超标了一些,虽然没有超标很多,但是还是引起了很我重视。其实我本人也不胖,也不抽烟不喝酒,每天差不多11点就睡了,睡眠时间大概在7.5小时以上,之所以会有高尿酸究其原因我觉得应该是我常年以来的力量训练加上高蛋白饮食导致的。因为无氧训练和高蛋白饮食是容易对肾脏产生不小的压力的,再加上慢慢的年纪也大了,肾脏功能衰退也是意料之中的事情。

为了应付高尿酸,我主要做的一件事情就是利用控制变量观察我的尿酸变化情况,以及平时在饮食中减少对高嘌呤食物,含果糖食物的摄入。后面经过为期两个月的观察,发现其实还是喝水最能解决我的问题,因为人体中的尿酸基本是通过体液排出,所以按理来说只要喝水就能解决。然后我通过控制变量观察到基本上只要一天喝够4000ml的水我的尿酸就能稳定在380左右,其实问题就还好。

最后就是在12月某天训练的时候竟然扭伤了手腕,这也导致了12月我基本上就是腿部训练以及跑步为主,运动量没少,应该还大了,毕竟练腿是很累的 ;)。所以截止到1月我的手腕还没好,连我这个训练老手都会受伤,各位在训练的时候一定要注意安全。

Frame 3

学会如何投资

上面讲的几part都是自我价值的投资,这一part主要是想要分享一下我在股市中学到什么。

为什么要进行投资

对于我来说,这也是尝试一个新方向的机会,我想着把我以前学习的能力看看能不能迁移到做投资上。再来就是现在消息面实在是太多了,什么牛鬼蛇神都跑出来教人做投资,如果自己在这方面没有认知,那很容易吃亏的,所以与其被人带着跑,不如自己领跑。

10月的教训

10月那时候,天真的以为中概股会在沉寂几年之后会迎来新的机会,那时候我也受了很多情绪上面的感染,然后入股了一些中概,结果肯定是在不断追高之下输的很惨。后面也让我意识到了,在下行市场上,基本面没有变化的情况下,怎么挣扎力度是有限的。

后面我也去看了《巨债危机》,学习了一些周期方面的知识,让我体会到什么是周期。里面提到一点让我很受用,就是在经济下行的时候,政府会通过一些政策进行经济刺激,每次的刺激都会带来一些上涨,但是总体上来说是跌的,《巨债危机》里面例举了2008年美国的大萧条中,股市有六次大幅上涨,但是总共下跌了 89%。

image-20250126163244520

所以不要跟风,不要抛开基本面进行投资,其实大多数亏损都是在牛市中产生的。

不要被资讯所干扰

现在我们所处的世界,是信息太多了,连巴菲特也这么认为,所以他没有把公司设立在华尔街,他认为远离华尔街的喧嚣能够帮助他避免受到短期市场情绪和集体思维的干扰。在华尔街,投资者很容易陷入信息过载和盲从心理。

举个例子就是,我记得当时,我有个朋友在tesla发布了robotaxi之后很激动,以为这就是future,结果特斯拉第二天股价直接跳水十个点,并且持续低迷了一段时间。后来加上他又看了一些新闻,认为川普可能会选不上,所以就把tesla割肉抛掉了。

image-20250126164518304

这个事情也告诉我,不要被资讯所要干扰,如果当时真是相信马斯克的故事,认为他真的能做成,那就一直持有,否则就不要买入。

其实不只是股市,其他事情也是,现在社交媒体这么发达,很多时候看了一条新闻义愤填膺,然后过几天发现是 fake news,如果没有独立思考的能力就会被玩弄在鼓掌之中。

相信长期的力量

这件事情不只是反映在我对生活的态度,比如坚持写文章,坚持健身,学英语,到了投资这里也是一样的,巴菲特有句话说的好,如果你不愿意持有一只股票十年,那就不要考虑持有它十分钟。

一方面我们都是普通人,做短期很容易亏,看不准形式,并且短期中掺杂了很多噪音会影响判断,另一方面我也没这么多时间盯盘,整天看盘比较影响心情。

所以在过去一年我几乎所有的收益都是由长期持有的股票贡献的,反倒是因为一些小道消息跟风买的一些股票让我亏了一些。

弄了哪些有趣的玩意

游戏

其实整个24年让我印象深刻,并值得说的游戏并不多,玩的很多,但是能回忆起来的没有几个。

《寂静岭2》重制版:玩完《寂静岭2》真的给我一种淡淡的伤感,又是一个艺术与游戏结合的游戏,上次玩到这样的游戏还是 《最后生还者 1》。这个作品真的做到了久久的留存在我的脑海里,无论是背景音乐还是叙事手法,都趋近完美。

《暗黑4》:这游戏首发的时候我就发了600买了豪华版,玩了一阵之后感觉不太行就放下了。今年在第四赛季的时候风评好了不少,于是我又回归玩了一阵,总体来说还是不错的,但是暴雪总归是暴雪,在失望的路上从来不会让人失望,后面整出个300的DLC,我直接就没有太大的兴趣了,直接和暴雪say goodbye。

《流放之路2》:这游戏其实是免费游戏,只是现在还在内测,可以充30刀开启内测资格,还挺良心的,说实话,这游戏是真的做的不错,精良程度,游戏深度都做的不错;

买过哪些玩意

一加ACE3 pro

买这个手机是因为我很久没有体验过安卓机了,像搞个来看看目前主流安卓机有啥可玩的。

优点:

  1. 硬盘价格是真的便宜,16+512+8gen3只要3k多;
  2. 沙盒系统真好用,真的完全隔离,赖皮应用全丢到沙盒里,什么信息都获取不到;
  3. 侧滑返回真的爽,任何应用都可以左右侧滑返回,反观ios的垃圾返回操作万年不改;
  4. 小窗是很方便的,挂机打游戏,看视频回信息;
  5. 分屏很爽,有时候上下分两个屏处理信息快不少;
  6. 屏下指纹比faceid好用一万倍;
  7. 可玩性很高,可以玩各种 galgame ,switch模拟器,王国之泪也能在安卓机上玩;
  8. 100w充电真的很快,早上洗漱的时间,充好就可以拿走了;

缺点:

  1. 广告太多,虽然可以关,但是什么类似快应用这样的app仍然在后台收集数据;
  2. AI应用虽然好,但是打开后台应用的权限管理,可以看到AI的数据收集工作一刻不停,一天收集几百次,并且各种权限都要;
  3. 相册权限管理很差,适配的应用可以只给部分照片,没适配只能全给或者不给。像小红书这种,给了权限,几乎一天要读几十次相册

iPhone16P:没错,最后我还是换到了苹果,其实也没有什么特别的原因,感觉能满足我的需求,不需要折腾,没广告,偶尔去旅旅游录个视频效果也还挺好,那就足够了。没有买pro max是因为觉得太重了,太撑手了,现在市面上手机一个比一个宽,一个比一个大,感觉都要双手捧着才能用了;

iPad mini7:这个平板应该是我使用频率最高的电子产品之一,可以用来看电子书,看视频,玩游戏,串流,因为小巧轻便有时候还可以用笔记一点东西,由于很便携,我基本走哪里都带上;

macbook air m3:这台电脑是我五一的时候去冲绳买的,那时候日元汇率很不错,然后就入手了。它目前来看基本上能满足所有的日常使用需求,并且还非常的轻薄,后面出去玩再也不用带着我16寸的MacBook pro了,瞬间解放我的背包;

xiaomi buds5:这个产品刚使用的时候确实很惊艳,因为一个半入耳式的耳机竟然可以有不错的降噪效果,并且音效我听起来也很棒,感觉比我原来的 airpods2 好太多了。

去过哪些地方

北海道之行

平时雪都很少见的我,去小樽滑的第一场雪,我觉得是非常好玩的,一开始的时候我甚至都不能起来,后来还是放弃了,找了个教练,教练竟然是台湾人,带我练了几个小时后我基本上就可以自己入门了。

Frame 4

我去小樽的那天,他们也在搞灯节,还挺漂亮的,旁边有工作人员,可以100日元买个小杯子,里面有个小蜡烛,我写的是:for tomorrow

Frame 5

漫步在小樽的街上你可以感受到,他们的生活过的很悠闲,下午3点就没什么人了,以至于想找个吃饭的地方都没几个店。

Frame 6

很有意思的一点是,悠闲的逛公园的时候,你会发现很多牌子,告诉你小心乌鸦,因为一不小心你买的饼就没了。。。

Frame 7 (3)

冲绳

然后就是五一的时候去了冲绳,那边的沙滩还挺美的。

Frame 8

冲绳的海水真的好清澈,第一次看到玻璃一样的海水。

Frame 9

冲绳还有必去海洋馆。

Frame 10

总体来说,我觉得冲绳的旅游体验很好的,有点不太好的是交通不是很方便,经常需要打车。

亚庇

亚庇这个小城市,还挺有意思的,好几面墙上有巨大的壁画,可惜我去的是十月,天气不是很好,总是在下雨。

Frame 11

亚庇的主要旅游景点也是去各种小岛上面玩,但是我感觉其实体验很差,比如我去的这个环滩岛,据说是最好的岛之一了,但是各种服务还是很差,吃的也不好,洗浴间排队很长时间。所谓的游玩项目也就是浮潜一个多小时,然后回岛上吃个饭,然后就回去了。

并且浮潜如果没有自己带装备的话,他们提供的装备是真的差,并且漏水,然后一个一次性嘴塞10马币。

Frame 12

亚庇的消费其实挺高的,随便点个菜也要几十,并且当地的菜都是类似糊糊一样的东西,我是吃不太习惯,最好吃的感觉还是当地的华人菜馆。

然后就是亚庇的榴莲是真的好吃,我基本上每天都会去开两个榴莲吃,不必吃什么猫山王,就本地的白榴莲就可以了,一个就十几马币。

Frame 13

总结

总体来说,旅游体验来说:北海道>冲绳>亚庇,但是价格来说,也是北海道>冲绳>亚庇。

新的展望

技术上,我希望能学点不一样的东西,最近接触到了一点 ue 相关的开发,希望能在新的一年往这方面点一下技能树。

然后就是希望能更加深度挖掘一下已经掌握的技能,不是说学了就能放着不管,时间久了很慢慢遗忘,没什么想学的时候多问问自己哪些掌握了,哪些没有,比如:c++是真的熟悉了吗?c++17的特性有哪些?等等。

对于英语,还是希望能坚持,语言性的东西要学起来其实是需要漫长的过程,不是一朝一夕就可以练成,不过现在很多 AI 工具已经大大的简化了学习流程了,我觉得掌握起来不算太难,还是继续每天花至少半个小时多多练习比较重要。

对于健身,除了身形以外,更加希望是朝着健康的方向发展,更加需要注意怎样不让自己受伤的情况下,承受最大的重量,然后把身体锻炼好,适当的加入有氧运动,而不是全都是无氧运动,这样更能激活自己身体机能。

2024年总结:沉寂积蓄新的力量最先出现在luozhiyun`s Blog