资讯动态

混合逻辑时钟:融合物理与逻辑的分布式时间戳方案

发布时间:2026/10/24 1:18:59 来源:尧图企业网站定制
1. 为什么需要混合逻辑时钟再纠结物理时钟和逻辑时钟都搞不定1.1 物理时钟你以为的同一时刻其实未必是同一时刻分布式系统里最让人头疼的不是网络分区也不是节点宕机而是给一堆并发事件排队。做存储或者数据库的朋友应该都遇到过两台机器各写了一条记录你说谁先谁后看墙上时钟两块表可能差了500毫秒看逻辑时钟又完全不知道这条记录大概发生在几点。后来有人把这两种思路缝在一起就有了“混合逻辑时钟”Hybrid Logical ClockHLC。它解决的核心问题是在分布式的节点之间既要一个不违背因果关系的排序又希望这个排序的时间戳看起来接近真实物理时间。先聊物理时钟的坑。大多数程序里用的时间戳来自墙上时钟也就是“日历时间”。墙上时钟靠NTP之类的时间同步协议去对齐各节点可NTP本身不是魔法局域网里同步精度能到毫秒级互联网上几十毫秒甚至几百毫秒的偏差都很常见。更麻烦的是NTP同步过程中可能发生跳变管理员手动调时间、闰秒、虚拟机时钟漂移等情况都会让同一个时刻在不同节点上呈现出不同的读数。我曾经在模拟项目X里靠物理时间戳做 last-write-wins 冲突合并客户端A先给节点1写了一个值客户端B后给节点2写了一个值。理论上B应该覆盖A但节点1的时钟比节点2快了1秒A的时间戳反而比B大最终旧值覆盖了新值数据直接丢了。这个问题不是概率事件而是只要节点时钟没有严格同步就会偶尔出现。另一个容易被忽略的点是“单调时钟”——操作系统确实提供了本机不会回调的单调时钟但它只能保证单机内的时间递增不能跨节点比较。两个节点的单调时钟根本没有统一原点你拿它们做跨节点的先后判断是没意义的。1.2 Lamport时钟能排序但把“时间”丢了既然物理时钟不可靠计算机科学家很早就发明了逻辑时钟最经典的是Lamport时钟。它的思路非常简洁每个进程维护一个整数计数器本地事件发生时计数器加1发送消息时带上当前计数器接收方收到消息后取本地计数器和消息计数器的最大值再加1。这样做的结果是如果事件A在事件B之前因果发生那么A的Lamport时间戳一定小于B的。这个性质叫“happened-before”偏序保证听起来已经很完美了。但Lamport时钟有个非常实际的问题它把“时间”本身丢了。一个事件的时间戳可能是一个很大的整数但它和几点几分没有任何关系。你想查“这个日志大概是下午3点之前还是之后产生的”Lamport时钟答不上来。而且Lamport时钟允许不同节点上的事件拥有相同的值最终得靠节点ID强行打破平局这个全序是人为定的不是真实发生的顺序。更别扭的是Lamport时间戳不满足“如果C(e)C(f)那么e一定因果发生在f之前”。所以它只能用来给出一个不自然的全序。在分布式数据库里如果你拿Lamport时间戳当MVCC版本号用户执行“扫描最近5分钟的数据”这种查询基本不可能实现因为版本号不具备物理时间的语义。1.3 向量时钟因果信息齐全但代价不低Lamport之后出现的是向量时钟。每个节点维护一个长度为N的向量第i位代表节点i看到的事件数。发送或接收消息时把对应维度的计数更新为所有已知值的最大值再把自己的维度加1。向量时钟有一个好处它能精确判断两个事件是否并发。如果event1的向量逐项小于event2的向量那么event1因果早于event2如果两个向量互有小大就说明它们是并发的。坏处也很明显空间复杂度是O(N)N是节点数。节点越多向量越长每次消息都要携带完整向量带宽消耗大节点动态加入或离开时向量长度还要动态调整。更关键的是向量时钟依然不携带真实物理时间。它只能告诉你“谁先谁后”不能告诉你“大概发生在什么时候”。所以就有了混合逻辑时钟。HLC把Lamport那种轻量的逻辑计数和物理时钟融合在一起状态量始终只有两个整数却能同时做到三件事时间戳接近真实物理时间、因果相关的事件时间戳严格递增、空间开销O(1)。它不是物理时钟和逻辑时钟的简单拼凑而是一种更聪明的构造。接下来我详细拆一下它的算法和落地方式。2. 混合逻辑时钟算法拆解三元组、比较规则和三条更新路径2.1 先认识HLC的“三元组”HLC的每个时间戳是一个结构化数值常用三元组来表示(l, c, pt)。其中l代表“当前节点已知的最大物理时间”也就是这个节点自己物理钟读数以及所有从消息里见过的远端物理时间中最大的那个。c是在l保持不变时用来区分同一物理时间刻度内多个事件的发生顺序的计数器。pt是本地的物理时钟它在发送和接收时都会被读取但pt本身不作为最终时间戳的一部分真正用于比较的是(l, c)。为什么不直接用物理时间做排序因为物理时间可能回退、可能偏慢。l之所以叫“已知最大物理时间”是因为它会吸收所有来自其他节点的时间戳。某个节点的物理钟偏慢没有关系只要它收到一条携带着更大l的消息它的l就会被推到那个更大的值。这个过程让所有参与通信的节点在物理时间维度上逐渐“对齐”。比较两个HLC时间戳的规则也很直观先比较l谁的l大谁的时间戳就大如果l相等再比较c谁的c大谁就大。如果l和c都相等两个时间戳就是相同的。为了让全序唯一通常会在最后加上节点ID作为tie-break比如(l, c, nodeID)。不过要记住加上节点ID后得到的全序仍然不等同于真实发生顺序只是人为约定了一个稳定顺序。2.2 本地事件和发送消息时先看物理时间是否已经超过已知最大时间HLC的核心动作有三个本地事件发生、发送消息、接收消息。先看最常用的一条路径即“本地事件或发送消息前获取一个HLC时间戳”。如果用Go风格伪代码来表达大概是这样的type HLC struct { mu sync.Mutex l int64 // 已知最大物理时间纳秒为单位 c uint64 // 同一 l 下的事件计数 pt PhysicalClock } type Timestamp struct { L int64 C uint64 } func (h *HLC) Now() Timestamp { h.mu.Lock() defer h.mu.Unlock() now : h.pt.Now() if h.l now { // 本地物理时间已经超过了之前见过的所有时间戳 // 说明该事件的“物理时间”可以更新了。 h.l now h.c 0 } else { // 本地物理时间还在已知最大时间范围内 // 只能在同一个 l 下递增计数保证单调性。 h.c } return Timestamp{L: h.l, C: h.c} }这段逻辑非常关键。每次事件发生时系统先读一次本地物理时间now。如果now大于当前l说明物理时钟向前走了应该把l更新为now同时把c重置为0。为什么重置c因为l已经变了新的(now,0)在字典序上一定大于旧的(oldL, oldC)不需要计数来帮忙区分顺序。反过来如果now没有超过l说明最近一段时间的所有事件都落在这个物理周期内为了区分它们只能把c加1。这也是HLC“混合”二字的来源物理时间负责让它看起来像真实时间逻辑计数负责让它不会重复也不会出现两个因果事件时间戳完全相等。2.3 接收消息时三个分支想清楚就理解了全部算法接收消息是HLC最容易写错的地方。一条消息到达时里面携带发送方生成的时间戳(m.l, m.c)。接收方要做的是把本地HLC状态“推进”到一个至少大于发送方时间戳的位置同时尽量贴合本地的物理时间。接收算法的核心思路如下func (h *HLC) Recv(ts Timestamp) { h.mu.Lock() defer h.mu.Unlock() now : h.pt.Now() if h.l ts.L { // 远端物理时间领先于本地已知的最大时间 // 取远端 l再用远端的 c1 作为本地计数。 h.l ts.L h.c ts.C 1 } else if h.l now { // 本地物理时间已经领先于本地已知最大时间也领先于远端 l // 直接跳转到本地 now并清空计数。 h.l now h.c 0 } else { // 本地的 l 并没有落后仍然等于或大于远端 l。 // 此时为了保证严格大于所有已见过的时间戳 // 要把 c 推进到 max(本地 c, 远端 c) 1。 if h.c ts.C { h.c ts.C } h.c } }第一个分支对应“远端的时间戳比我能看到的物理时间还要大”的情况。比如A节点的物理钟快发送了一条(105, 0)的消息B节点本地l还是102收到消息后如果不更新l之后B生成的时间戳就会比A这条消息小因果顺序就乱了。所以这里必须把l推到105并把c推到011这样接收事件(105,1)严格大于发送事件(105,0)。第二个分支对应“虽然本地l不比远端小但本地物理钟已经跑到了一个新的、更大的时刻”。这种情况说明物理时间已经前进了所有旧的计数都可以作废直接用(now,0)作为接收事件时间戳既接近真实时间又比发送方时间戳大。第三个分支对应“本地l还是最大的物理钟也没跑过它”。这意味着在一个物理时间刻度内发生了多次因果相关的事件需要靠计数器不断累加。这时候不能简单c1还要考虑如果远端的c比自己大就必须先取更大的那个基数再加1保证新时间戳大于远端时间戳。理解了这三个分支HLC算法基本就掌握了。很多开源实现里会把第一个分支和第三个分支合并处理用max函数替代分支判断但上面的写法更贴近原理也更容易排查问题。2.4 为什么说HLC的时间戳“接近物理时间”有人会问既然l可能被远端的“未来时间”推高它还能代表物理时间吗答案是“能但误差有界”。HLC算法的核心保证是事件时间戳的l不会偏离该事件发生时所有节点物理时钟的最大值太远。如果整个集群的时钟同步误差有界网络延迟有界那么HLC的l和真实物理时间之间的误差也有界。具体来说一个节点的事件时间戳l最多不会超过它所见过的最超前节点的物理时间。当一个节点没有与其他节点通信时它的l会跟着本地物理时间走所以不会一直停留在一个陈旧值上。只有当它收到来自更超前节点的消息时l才可能短暂跳到“未来”。这部分“未来时间”其实是其他节点物理钟偏快造成的从全局看仍然是合理的近似。所以HLC非常适合做“版本号时间查询”结合的场景。它的排序性质足够强能保证因果一致它的时间语义又足够好能直接转成用户可读的物理时间误差控制在同步误差范围内。3. 典型应用HLC在MVCC、事务排序和日志审计里的用法3.1 多版本并发控制与快照隔离MVCC是目前关系型数据库和分布式数据库里最常用的事务实现手段。它的核心思想是给每个数据保留多个历史版本每个版本带一个写入时间戳write_ts。事务启动时拿一个start_ts读取的时候只看那些write_ts start_ts的版本从而在无锁的情况下实现快照隔离。如果这个时间戳直接用物理时钟就会出现前面说的时钟漂移问题事务T1先提交时间戳却比事务T2大T2快照读就看不到T1写入的数据产生“明明已提交却读不到”的异常。如果时间戳用HLC事情就顺了提交时给事务分配commit_tsHLC后续事务启动时如果它与前一个事务有因果关系比如收到了前一个事务的响应消息那么它的start_ts一定会在接收响应时通过Recv被推到大于commit_ts的位置。这样快照一定能看到先提交的事务。我在模拟项目X里就是这样给分布式KV加快照读的。每个key后面挂一个版本链表版本号就是HLC。事务协调节点发start_ts给各分片分片拿到请求头里的时间戳后先Recv再处理查询最后返回版本列表。只改动几十行就把原来靠物理时间戳时的“读不到已提交数据”问题解决了。要注意的是HLC只保证因果可见性不保证实时性。两个完全并发的事务谁的时间戳更大取决于谁先拿到协调节点的时间戳这是可接受的。3.2 跨节点事务总排序另一个典型场景是给分布式事务做一个全局可比较的ID。多个节点同时处理事务最终要把所有操作记录按某种顺序归并。物理时钟会漂移Lamport时钟又缺少时间语义HLC的(l,c,nodeID)可以作为一个稳定的全局顺序。具体做法每个事务的标识符就是发起节点生成的HLC加上节点ID。事务A如果在事务B之前发生并且对B可见比如B所在的节点收到了A发来的请求或响应那么B的HLC一定大于A的HLC。因此任何涉及因果链的事务都不可能被排到前面去。对于完全没有因果关系的事务HLC的顺序是任意的但这不影响事务内部一致性。这套方案有一个重要限制它不能拿来做“严格的外部一致性”。所谓外部一致性是指如果客户端观察到事务T1在T2之前完成那么任何地方都必须认为T1早于T2。HLC做不到这个保证因为T1和T2如果发生在不同节点且它们之间没有消息传递HLC就无法感知对方的时间戳。如果你的业务对顺序有绝对要求比如金融界面的提交顺序必须与用户体验一致那需要更重的方案比如所有事务都通过同一个授时节点取时间戳或者使用带“时间不确定区间”的物理时钟方案。3.3 日志归集与审计排序在做分布式日志系统时最烦的不是日志太大而是日志的顺序对不上。两个节点各记一条日志A节点说是14:00:00.100B节点说是14:00:00.050实际上A节点时钟慢了。把它俩按墙上时间排序顺序就反了。用HLC给每条日志打点之后因果相关的日志天然有序比如一条日志是另一条日志的响应那么前者的时间戳一定更小。更难得的是日志记录里还保留了一个近似物理时间l做监控、告警、审计时直接按l过滤时间段是可行的。用户看到“这条错误发生在14:00:00.100”不会觉得离谱。排序时要小心不能只拿l排序。同一个l下面可能有多个事件必须用c辅助否则日志列表会出现“同时发生”的假象。我的做法是日志时间展示用l排序键用(l,c)再加个节点名做最终tie-break。3.4 替代方案对比什么时候不要选HLC为了帮助判断我把几种方案的特性放在一起对比方案时间戳是否接近物理时间是否保证因果顺序额外空间状态实现复杂度物理时钟是但可能漂移回退否O(1)低Lamport时钟否是偏序O(1)低向量时钟否是可判并发O(N)中混合逻辑时钟是误差有界是偏序O(1)中低如果你的系统只需要一个单机内唯一、递增、带时间信息的ID雪花算法这类方案更合适不需要引入HLC。如果你只需要“不同节点之间不重复”的版本号UUID加物理时间也够用。只有当你同时需要“因果排序稳定”和“时间戳可用作近似真实时间”时HLC的性价比才最高。4. 落地实操在一个分布式KV模拟项目中一步步实现HLC4.1 线程安全的数据结构锁比原子变量更直观HLC的实现中Now和Recv都需要读写共享的l和c所以线程安全是绕不开的问题。最直观的方式是用互斥锁这也是我推荐的第一版实现方式。在高并发场景下单把锁在单个节点上会成为瓶颈吗实测下来百万级QPS以下基本不用担心锁开销相对整个网络处理来说很小。如果你后面确实需要优化可以用原子操作加CAS循环替代但第一版千万别在这里过早优化。上面给出的HLC结构体里已经带了mu sync.Mutex。Now和Recv都先加锁再更新时间戳。这里有一个非常容易犯的错有人为了减少锁粒度先读一遍l和c不加锁就在外面判断然后回来更新。两个goroutine可能在同一个物理时刻读了同一对(l,c)最后生成完全相同的HLC时间戳导致版本号冲突。所以“判断和更新”必须是一个原子临界区。4.2 物理时钟封装不要直接用墙上时钟HLC高度依赖pt.Now()不能回退。如果你在Java里直接System.currentTimeMillis()在Go里直接time.Now().UnixNano()一旦NTP回调本地物理时间可能往前跳也可能往后跳。一跳HLC的l可能被错误的物理时间覆盖或者长时间停留在旧值因果逻辑不会崩但时间戳的“接近真实时间”这一特性就毁了。正确做法是用单调时钟锚定墙上时钟。启动时记录startWall wallNow()和startMono monoNow()之后每次获取物理时间都返回startWall (monoNow() - startMono)。因为monoNow()保证单调递增这个公式算出来的时间也不会回退。它和真实墙上时钟的偏差等于启动时记录的startWall偏差加上单调时钟漂移通常足够用。如果系统运行时间很长单调时钟可能与墙上时钟逐渐拉开差距。此时可以增加一个后台校正任务定期读取真实墙上时钟计算新的偏移。但注意不要直接把这个新偏移写进startWall否则瞬间回退。可以限速校正比如把偏移调整量分散到几分钟内或者只在重启时校准。这里的原则是宁可让HLC时间戳慢慢偏离真实时间也不能让它突然回跳。type MonotonicPhysicalClock struct { startWall int64 startMono int64 } func NewPhysicalClock(wallNow int64, monoNow int64) *MonotonicPhysicalClock { return MonotonicPhysicalClock{startWall: wallNow, startMono: monoNow} } func (c *MonotonicPhysicalClock) Now() int64 { return c.startWall (monoNow() - c.startMono) }看起来简单但这个封装是我排查过的很多“时间戳忽大忽小”问题的根因所在。NTP回调后直接依赖系统时间会让HLC的l被一个较小的now重复覆盖最终出现新写入版本号小于旧版本号的情况。4.3 请求响应都要携带HLC时间戳实现HLC绝不能只在写数据时生成一个时间戳就完事。真正让它起作用的是消息传播路径上每个节点都调用Recv。例如客户端向节点A发了一个写请求节点A处理完给客户端返回结果客户端接下来向节点B发另一个写请求。如果B没有从请求头里知道A的时间戳B生成的HLC可能小于A那么这两个事务在全局排序时就会违反因果顺序。所以请求消息和响应消息都应该带上时间戳字段。客户端发送请求前调用hlc.Now()把时间戳放进请求头服务端收到请求后第一件事是调用hlc.Recv(request.Ts)然后再处理业务服务端返回响应前再调用hlc.Now()把响应时间戳放进响应头客户端收到响应后同样调用hlc.Recv(response.Ts)。这样一进一出HLC状态就在所有通信节点之间流动了。这套机制不需要应用层业务代码感知放在RPC中间件里即可。我在模拟项目X中就是在统一的调用链中加了两个拦截器分别处理请求和响应。4.4 状态持久化和重启恢复别让时间戳倒流HLC状态是易失的。一旦节点重启内存里的l和c全部没了。如果不做恢复第一个新时间戳通常等于本地物理时间。这时问题就来了如果之前收到的远端时间戳比这个物理时间更大那么重启后生成的新版本号就可能小于集群中已经存在的数据版本号等于覆盖了“未来”的数据造成数据丢失。解决思路很简单周期性地或者每次更新后把(l,c)写入本地元数据文件或WAL。我更推荐写WAL时顺带记录因为这样至少保证HLC状态更新和业务日志落盘是同一个原子批次。如果是读多写少的场景每写一条全量HLC元数据的开销也完全可以接受。启动恢复时先尝试从持久化存储里读(l,c)从Recover(reader)恢复如果读不到就扫描本节点存储的数据版本取最大版本号的HLC作为初始值。实在取不到就用当前物理时间作为lc置0。后面一条很重要宁可把时间戳初始化得大一点也不要初始化小了再次倒流。func (h *HLC) Recover(r io.Reader) error { h.mu.Lock() defer h.mu.Unlock() return gob.NewDecoder(r).Decode(h.l, h.c) }4.5 在复制组和Leader切换场景下的使用要点如果你的系统用了Raft之类的复制组HLC通常由Leader生成写日志的时间戳。这里有一个很多人会漏掉的细节Follower在apply日志时也需要调用Recv(logEntry.Ts)更新自己的HLC。否则Follower本地的下一个写事务时间戳可能小于刚刚apply的历史日志时间戳导致后续快照读或冲突检测出错。Leader切换时更要注意。新Leader如果从旧状态恢复了HLC但旧HLC落后于多数副本已经apply的日志时间戳新Leader之后生成的时间戳可能小于旧日志。安全的做法是在选主后通过quorum读取所有节点已知的最大HLC取最大值作为新Leader的起始状态或者把HLC状态作为复制状态机的一部分随快照一起复制。总之原则只有一个任何新时间戳都不能小于本地已经持久化的最大版本号。5. 常见问题与排查技巧实录5.1 HLC时间戳超过本地物理时间太多正常吗正常但超过太多就不正常了。HLC的l本质上是一个“见过的最大物理时间”某节点收到一个物理钟偏快的远端节点时间戳后它的l确实会跳到“未来”。这是为了保证因果顺序付出的代价。可如果l - now持续增长比如一直大于几百毫秒说明集群内节点时钟漂移过大或者某节点的物理钟封装有问题直接用了墙上时钟。排查时在每个节点上暴露一个监控指标hlc_l、pt_now、hlc_l - pt_now。正常情况下差值应该被网络延迟和时钟同步误差限制在一个小范围内。如果长期超过阈值检查物理时钟封装、NTP状态、有没有节点长时间未通信后突然接入集群。这个监控还能辅助做故障转移如果某个节点的时钟严重超前它广播出的HLC会污染其他节点必要时要把该节点隔离出服务。5.2 c值一直在暴涨怎么处理c暴涨的常见原因有三个。第一物理时间读取粒度太大。比如用毫秒做单位一毫秒内如果有几千个事件l不会变c只能一直加。优先把单位改成微秒或纳秒。第二收到了一个“未来”时间戳本地物理钟一直没跑过它导致每次事件都进入c分支。这种情况只能等物理时间慢慢追上或者检查上游时钟。第三本地时间戳粒度正常但确实某个瞬间事件太多比如突发流量的每秒几百万次写入c增长很快。解决办法是把c的类型从uint32改成uint64别让计数溢出。需要特别提醒的是c爆涨不代表算法出错它只是在同一个l下给事件排队。但如果你发现c持续涨了好几十分钟说明l可能被严重污染了。可以加一个日切逻辑当l - now超过某个阈值时将l慢慢向now拉近同时把c置0。不过这个操作会打破因果排序必须在确认当前没有未决因果依赖后才能做。我一般只告警不做自动修复。5.3 重启后新时间戳比旧数据还小怎么办这是最典型的HLC落地问题根因几乎都是没有持久化HLC状态。如果你的数据文件里已经保存了大量版本号重启后又从l0开始第一个新写请求的时间戳甚至可能比历史数据小新数据就会排在旧版本后面快照读会读到旧值。解决办法分两步。第一步在正常运行时把HLC状态持久化到WAL或者元数据文件。第二步启动恢复时先读取HLC状态如果读不到再扫数据文件里的最大版本号用max(最大版本, 当前物理时间)初始化。注意扫描范围要覆盖所有分片和所有数据文件不要只扫主键索引。如果你用共享存储其他节点上的最新HLC也得纳入考虑否则多副本之间仍然可能倒流。5.4 两个节点生成了完全相同的HLC时间戳怎么排序HLC只保证因果相关的事件时间戳严格递增不保证并发事件的时间戳全局唯一。两个节点在同一个l和同一个c0时各自发生本地事件就可能得到一模一样的(l,c)。如果你需要给每个事件一个绝对唯一的排序键就在HLC后面追加节点ID或随机唯一ID。排序时比较顺序是先比l再比c最后比节点ID。这个规则不会影响HLC的因果性质只是给并发事件一个确定的手工打破平局方式。但不要用“随机唯一ID”做持久化版本号因为版本号要求稳定可比较。节点ID是最合适的tie-break因为它不会变而且能告诉别人这个时间戳来自哪个节点。5.5 把HLC时间戳当成真实时间用在TTL和计费里踩坑最痛HLC的l只是“接近物理时间”不是真实物理时间。它可能因为接收了远端超前时间戳变得比当前真实时间大很多。如果拿它做倒计时、TTL过期判断比如给一条数据设置deadline hlc.l 60s这条数据可能在几秒内就被当作过期数据清理掉因为hlc.l本身就赊了一部分“未来”。我在模拟项目X里犯过这个错。数据写入时用HLC做版本号同时把过期时间也写成hlc.l ttl结果有一批数据提前被后台线程清掉了。正确做法是版本号、排序、MVCC可见性使用HLCTTL计算、租约续期、超时判断一律使用单调时钟或墙上时钟。这两种时间用途必须分开千万不要混在一起。5.6 消息重试会不会把HLC不断推高会。如果消息队列或RPC框架对同一个请求做了重试接收端可能收到同一条消息两遍每次调用Recv都会更新HLC。虽然不会破坏正确性但会让接收方的时间戳被重复推到发送方的时间戳之上甚至出现“处理重试消息”的事件比“处理首次消息”的事件时间戳大很多的情况。如果业务上对事件顺序特别敏感最好在消息处理层做幂等去重或者在重试请求里标记一个MessageId同一个MessageId只允许第一次触发Recv。这也说明HLC时间戳本身不是幂等的它只是一个随消息流动的排序状态而不是消息的唯一标识。唯一标识必须另用独立字段。最后说一点我个人的体会。HLC真正值钱的地方不是算法本身有多难而是它改变了你看待分布式时间戳的方式时间戳首先是用来排序的其次才是用来读表的。你需要在同一个系统里既保证“看起来像真实时间”又保证“因果顺序不出错”时HLC是性价比很高的解法。但它也不是银弹真到了必须严格保证实时提交顺序的业务场景还是得靠共识协议和更精确的时间源别指望一个轻量的逻辑时钟解决所有问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑