当前位置:首页 > 问答 > 正文

阿里数据库底层怎么用HLC搞分布式事务,细节和原理深挖

根据阿里云官方技术博客(如阿里云数据库ApsaraDB公众号、阿里云开发者社区)以及相关技术分享(如阿里资深工程师的技术沙龙内容),阿里在自研分布式数据库(如PolarDB-X、OceanBase等)中,为了解决分布式场景下的事务时序和一致性难题,深度应用并优化了混合逻辑时钟(HLC)机制。

核心问题:为什么需要HLC?

在单机数据库中,确定两个事务谁先谁后非常简单,数据库自身维护一个单调递增的事务ID或时间戳就足够了,但在分布式数据库中,数据分散在多台不同的服务器(称为节点)上,每个节点都有自己的本地物理时钟,这就带来了两个致命问题:

  1. 物理时钟不同步:尽管我们会使用NTP(网络时间协议)等工具来同步不同机器的时钟,但同步必然存在误差,可能是几毫秒,甚至更多,如果单纯依靠物理时钟来给事务分配时间戳,很可能出现“时间倒流”的诡异现象:即一个在物理时间上稍晚发生的事务,因为其所在节点的时钟更快,反而获得了一个更早的时间戳,这会破坏事务的序列化顺序,导致数据不一致。
  2. 并发事务的因果关系:即使物理时钟完全同步,也会有问题,假设事务A在节点1上提交,然后触发了在节点2上发起的事务B,从因果关系上看,A一定发生在B之前,但如果节点2的时钟在A提交的瞬间恰好“滴答”了一下,使得B获得的物理时间戳可能比A的还要早一点点,这同样破坏了因果律。

不能直接使用不可靠的物理时钟作为分布式事务的全局时序标准,HLC就是为了解决这个问题而被引入的。

HLC的原理:物理时钟 + 逻辑计数器

HLC的设计非常巧妙,它不是一个全新的时钟,而是对现有物理时钟的一个“增强补丁”,一个HLC时间戳通常是一个二元组 (pt, l)

  • pt:代表物理时间部分,它取自当前节点的本地物理时钟(通常是Unix毫秒时间戳)。
  • l:代表逻辑计数部分,是一个整数,用于在物理时间相同的情况下,进一步区分先后顺序。

HLC的核心运作规则可以概括为:

阿里数据库底层怎么用HLC搞分布式事务,细节和原理深挖

  1. 本地事件:当节点上发生一个本地事件(比如收到一个客户端请求)时,节点会先读取当前的物理时间 c_pt

    • c_pt 大于当前HLC的物理部分 h_pt,说明时间正常流逝了,那么就将HLC的物理部分更新为 c_pt,并将逻辑部分 l 重置为0,新的HLC时间戳就是 (c_pt, 0)
    • c_pt 小于或等于 h_pt,说明本地时钟没有进步(可能慢了或跳变了),此时不能后退物理部分,HLC会保持 h_pt 不变,但将逻辑部分 l 加1,新的HLC时间戳就是 (h_pt, l+1)
  2. 接收远程消息:这是HLC保证因果关系的关键,当一个节点从其他节点收到一条消息时,消息中会携带发送方在发送时刻的HLC时间戳 (m_pt, m_l)

    • 节点同样先读取当前的物理时间 c_pt
    • HLC会取三个值中的最大值:
      • 当前HLC的物理部分 h_pt
      • 消息携带的发送方HLC物理部分 m_pt
      • 当前的本地物理时间 c_pt 将最大值作为新的物理部分 new_pt
    • 如果新的 new_pt 来自于本地时钟或者当前HLC的进步,那么逻辑部分 l 重置为0。
    • 如果新的 new_pt 等于消息的 m_pt 且大于当前的 h_pt,那么逻辑部分取 m_l
    • 如果新的 new_pt 等于当前的 h_pt 且等于消息的 m_pt(即物理部分大家都没变),那么逻辑部分取 max(当前l, m_l) + 1

通过这套规则,HLC时间戳在任何情况下都只增不减,并且完美地捕捉了事件之间的因果关系:如果事件A发生在事件B之前,那么A的HLC时间戳一定小于B的HLC时间戳。

阿里数据库如何用HLC搞分布式事务?

阿里数据库底层怎么用HLC搞分布式事务,细节和原理深挖

在阿里的分布式数据库架构中,HLC扮演着“全局时序协调员”的角色。

  1. 分配全局快照:在支持快照隔离(SI)或更高级别隔离级别的事务中,每个事务在开始时都需要一个全局一致的快照版本号,用于决定它能看到哪些数据,事务管理器会为这个新事务分配一个当前的HLC时间戳作为其开始时间戳,由于HLC的时间戳是单调递增且因果一致的,所有节点在看到这个时间戳时,都认同这是一个“过去”的时间点,从而能够提供出一致的数据快照视图。

  2. 确定提交顺序:当事务执行完毕准备提交时,协调者节点会为其分配一个提交时间戳,这个时间戳同样来自HLC,这个提交时间戳就是该事务修改数据最终的版本号,所有节点在应用这个事务的修改时,都会用这个HLC时间戳来标记数据行,由于HLC的单调性,后提交的事务永远有更大的时间戳,这保证了数据的版本历史是线性增长的,不会出现混乱。

  3. 解决冲突与悲观锁:在悲观事务模型中,如果两个事务试图修改同一行数据,后发起的事务会被先到的事务阻塞,当先到的事务提交后,它会释放锁,并带上它的HLC提交时间戳,后到的事务在获取锁后,它的开始时间戳可能需要根据收到的这个HLC时间戳进行调整(通过HLC的消息接收规则),以确保它能看到前一个事务的修改,从而维护可序列化。

  4. 与TrueTime的结合:根据部分资料提及,阿里在一些场景下可能将HLC与更精确的时钟源(如通过硬件支持的精确时钟,类似于Google的Spanner使用的TrueTime)结合,HLC的物理部分 pt 可以不仅仅依赖于误差较大的NTP时钟,而是使用更可靠的时钟源,这样可以极大地减少逻辑部分 l 的使用频率,使得时间戳的“颗粒度”更细,性能更高。

总结来说,阿里数据库底层利用HLC,巧妙地规避了分布式系统物理时钟不可靠的缺陷,以一种轻量级的方式生成了全局单调递增、且保持因果关系的事务时间戳,这套机制是保障其分布式事务ACID特性(特别是隔离性I和一致性C)的基石之一,使得跨多个节点的数据操作能够像在单机上一样有序、一致地进行。