通用架构设计归纳

介绍软件工程领域一些通用的设计方案。

Lease

通过 Lease 可以解决或者缓解下面的一些问题:

  1. 减少 invalidation 检查的开销
  2. 减少惊群带来的大量无效计算
    例如在选举模型中,在 lease 期间,voter 就不会发起投票驱逐 leader,即使它们暂时联系不上 leader。

Backoff

Cache

Cache miss

Cache miss 往往和随机读有关。

Flyweight Pattern

Amortize

Batching

Pipeline

Locality

抽象和反抽象

Parallel

这个策略包含了数据并行和模型并行。前者分割数据,每一个并行单元处理一部分数据;后者分割代码,每个并行单元处理一部分逻辑。

SIMD

例如,将 uint8_t 聚合成 uint128_t 比较。通常用来加速字符串的匹配。

机器学习中的模型并行

机器学习中的模型,其实也是一部分数据。例如神经网络中,它可能就是表示权值的矩阵。如果这个模型比较大,则它对应的矩阵可以被切分。
注意,这种情况下,子模型之间存在依赖关系,所以需要同步和通信。

Provision

这个思路会尝试预测资源的使用,从而提前分配,以一个较小的预测失误的代价,获取预测成功的大幅性能提升。

Prefetch

包含:

  1. prefetch 数据
  2. prefetch 代码,也就是 branch prefiction

Warm-up

Ordering

Partial Ordering

通常面临这样的场景,很多个命令构成了全序关系,但其实它们可以被拆成多组彼此 concurrent 的偏序关系:

  1. 在 NewSQL 的事务层+共识层的架构中,将 (start_ts, commit_ts) 的事务拆到多个共识组中,多个共识组之间可以并发 apply。对于共识组中不相交的事务,可以通过并行 apply 继续提高并发度。
  2. CPU 中的 superscalar 技术
    不相关的指令可以乱序执行。

Pipeline

Pipeline 可以作为一个 first-class 的优化类型了。但是我觉得它也是一个 Ordering 的优化。

Parallel

重放

GC

我们往往在异步线程中预处理一些对象,并最后将它们 link 到主干上,或者从主干上 unlink 对象,并最后 gc 掉。如果这中间发生重启,那么这些对象就会游离在存储中。如何区分被 unlink 但尚未被回收的对象,和刚被创建但还没有被 link 的对象呢?这里的通用思路是在重启后对比主干和存储中的对象,所有不出现在主干中的对象就需要被删掉。然后依赖重放来解决第一种情况。

Trade off

Trade off 的思路是权衡两个方案 A 或者 B,在它们各自的好处和坏处中取得一个妥协。

质量和速度

  1. 向量搜索中的 ANN
  2. 机器学习中的量化
  3. 图像处理中的有损压缩

熵(序)

  1. 存储系统中,如果追求低熵,那么就会产生资源消耗。例如 LSM 中的写放大是提升系统的 Ordering。

    The design space of LSM trees spans everything between a write-optimized log to a read-optimized sorted array.

读和写

读优化和写优化的 trade off 是非常常见的。以 RocksDB 为例,就可以列出:

  1. Leveled 和 Tiered

延迟和吞吐量

  • 攒批的方式能够提升吞吐量,但是可能会增加延迟

锁和非锁

锁意味着串行逻辑,通常用来避免写写冲突。
非锁的方案往往涉及多个版本:

  1. CAS 的方案本质上是异步完成新版本的构建,再原子替换上去。它并不是避免冲突,而是在冲突时退出。
  2. MVCC 的方案本质上是让读不会阻塞写,解决读写冲突。

类似的思想出现很多:

  1. TiDB 中使用乐观锁或者悲观锁处理写写冲突,通过 MVCC 处理读写冲突。
  2. Masstree 中使用锁解决写写冲突,引入版本号解决读写冲突。
  3. Snapshot Isolation 中,读不会被写影响。

存储和计算

例如,可以存一部分中间结果,从而减少重新计算的开销。包含:

  1. 动态规划类算法

对特定访问模式的优化

Eager vs Lazy

COW

C++ 中 COW 字符串最初被用来优化内存使用,以及减少不必要的复制。它也被认为是读多写少场景下的一种优化策略。但是它后续被废弃了,原因是它在多线程中的表现不佳。

转移代价

相比于 trade off,另一种思路是将代价转移。此时,接纳方案 A 和 B 的大部分的好处,承担第三个方案/操作 C 的代价,使得 A + B + C 的开销小于单独的 A 或者 B。

一些算法的例子

LRU cache

它使用了双向链表来支持的插入和删除,以及排序;然后又引入了 hash table 来支持快速的查询。它的操作 C 是去维护两个数据结构。而维护链表和 hash 表的代价要比单独使用一个,然后接受另一个的代价要低很多。

雪花算法

分离读优化和写优化

分离冷数据和热数据

Scale out