# TSO 分配: 时间窗口

__*TSO 时间窗口*__

![`smart`](./student-guide/diagram/TSOTimeWindow_zh_001_20220509.png)
-------------------------------------------------------------
单点授时首先要解决的肯定是单点故障问题。我们说过, `PD` 集群通过 `Raft` 可以保证在主节点宕机后马上选出新主, 在短时间内恢复授时服务。那问题来了, 如何保证新的 `leader` 产生的 `TSO` 一定大于旧的 `leader` 呢？

那就必须将旧 `leader` 的当前 `TSO` 存储起来, 存储也必须是高可靠的, 所以 `TiDB` 使用了 `etcd`。但是, 每产生一个时间戳都要保存吗？显然不行, 那样时间戳的产生速度直接与磁盘 `I/O` 能力相关, 会存在瓶颈的。`TiDB` 采用预申请时间窗口的方式解决性能问题, 这个过程如图所示。

当前 `PD leader`的 `TSO` 是 700 (为了便于讲解, 这是虚拟值), `PD` 向 `etcd` 申请了一个"可分配的时间窗口"。时间窗口的跨度是可以通过参数指定的, 系统的默认配置是 3 秒, 示例采用了默认配置, 所以这个窗口的起点是 `PD` 当前 `TSO` 700, 时间窗口的终点就在 `TSO` 703 处。写入 `etcd` 成功后, `PD` 将得到一个从 700 到 703 的"可分配时间窗口", 在这个时间窗口内 `PD` 可以使用系统的物理时间作为高位, 拼接自己在内存中累加的逻辑时间, 对外分配时间戳。上述设计意味着, 所有 `PD` 已分配时间戳的高位, 也就是物理时间, 永远小于 `etcd` 存储的最大值。那么, 如果 `PD leader` 宕机, 新的 `leader` 就可以读取 `etcd` 中存储的最大值, 在此基础上申请新的"可分配时间窗口", 这样新的 `leader` 分配的时间戳肯定会大于旧的 `leader` 了。此外, 为了降低通讯开销, 每个客户端一次可以申请多个时间戳, 时间戳数量作为参数, 由客户端传给 `PD`。但要注意的是, 一旦在客户端缓存, 多个客户端之间的时钟就不再是严格单调递增的。