10分钟搞懂的混合逻辑时钟HLC

本文简单介绍一种分布式授时机制,用纯软件思路实现了一套去中心化,多点授时的机制。cockroachDB就用这套机制来保证事务的ACID特性。下面我用一个简单的FAQ带大家快速熟悉HLC:

FAQ

Q:为什么要用混合逻辑时钟?

A:分布式系统保证事件顺序。

Q:为什么不用物理时钟?

A:物理时钟要么误差大的感人,要么贵的感人,硬件搞不定就需要软件来搞定。

Q:混合是谁和谁混合?

A:物理时钟和逻辑时钟混合。物理时钟误差大,逻辑时钟来辅助。减少分布式系统的不确定性。

Q:逻辑时钟遵循什么逻辑?

A:(核心思想)每当节点收到外部事件时,它会根据收到的时间戳调整自己的物理时钟。因为发来消息的时间肯定小于自己收到消息的时间,如果误差导致此时本地物理时钟小于该消息的时间,那么就不看物理时钟,而是用消息时钟更新本地时钟!

Q:看起来这么麻烦,为什么不用中心授时?

A:因为分布式的终极目标就是去中心化啊,如果每个节点以来一个中心,那么伸缩性和灵活性就会下降很多,还会增加很多网络通信成本。

Q:说了那么多,HLC有什么坏处吗?

A:当然是有的,既然没有一个中心作为绝对公平的裁判,所以总有节点之间误差大到逻辑时钟都解决不了的时候,物理时钟失效之后,数据一致性被破坏的风险显著增加。

例子

pt是本地时钟,L是高位时钟也就是做物理时钟,C是低位时钟也就是逻辑时钟。如果节点生成一个新的事件,它会用物理时钟与逻辑时钟的组合来表示该事件的时间。

有一个消息A→B→C→D→B,每次收发消息都会会修改每个节点的HLC,B2时刻B发现本地物理时间落后于另一个节点的物理时间,就用那个物理时间10作为高位,同时B2作为接收方,A1作为发送方,那么B2时间戳肯定要比A1大。

如果B2没有低位逻辑时钟,而是直接把高位物理时钟改成11,那肯定不合适,因为这是物理时钟,随便+1(单位都不能统一)那是脱离物理世界会出事的,所以为了表明A1和B2的顺序,又添加了一个低位逻辑位,表明A1和B2逻辑上的先后关系。

所以每个节点的时间顺序就一目了然了,可以看到除了A节点,BCD的本地物理时钟都失效了,被逻辑时钟替换了,而之后这些节点的时间戳增长完全脱离了本地物理时钟

在这里插入图片描述

code

代码比较复杂,需要考虑本地事件更新时间和接受其他节点消息更新时间两种情况,只需记住如果事件时间戳太大导致本地物理时间失效,则不管物理位置用+1逻辑位如果事件没有影响本地物理时间,则HLC还是按照物理时间增加,逻辑时间就置0

#include <iostream>
#include <algorithm>

class HybridLogicalClock {
public:
    int localTime;    // 本地物理时钟(物理时间戳)
    int localCounter; // 本地逻辑时钟(计数器)
    
    // 构造函数:初始化时钟值为0
    HybridLogicalClock() : localTime(0), localCounter(0) {}

    // 处理本地事件
    void handleLocalEvent() {
        // 记录本地事件发生前的物理时钟值
        int previousLocalTime = localTime;

        // 更新物理时钟,确保本地时钟不回退
        localTime = std::max(localTime, getCurrentTime()); 

        // 根据物理时钟是否更新,调整逻辑时钟
        if (localTime == previousLocalTime) {
            localCounter++; // 物理时钟没有回退,逻辑时钟递增
        } else {
            localCounter = 0; // 物理时钟发生了回退,重置逻辑时钟
        }

        // 输出事件的时间戳(物理时钟和逻辑时钟)
        std::cout << "Local event timestamp: (" << localTime << ", " << localCounter << ")\n";
    }

    // 处理接收到的外部消息
    void handleReceivedEvent(int receivedTime, int receivedCounter) {
        // 记录接收前的物理时钟值
        int previousLocalTime = localTime;

        // 更新物理时钟,确保不回退
        localTime = std::max({localTime, receivedTime, getCurrentTime()});

        // 根据物理时钟的变化来调整逻辑时钟
        if (localTime == previousLocalTime && receivedTime == previousLocalTime) {
            localCounter = std::max(localCounter, receivedCounter) + 1; // 不同事件顺序,但物理时钟一致
        } else if (localTime == previousLocalTime) {
            localCounter++; // 本地时钟没有变化,递增逻辑时钟
        } else if (localTime == receivedTime) {
            localCounter = receivedCounter + 1; // 本地时钟更新,接受的消息时间戳更新
        } else {
            localCounter = 0; // 时钟回退,重置逻辑时钟
        }

        // 输出接收到的消息的时间戳(物理时钟和逻辑时钟)
        std::cout << "Received event timestamp: (" << localTime << ", " << localCounter << ")\n";
    }

private:
    // 获取当前本地时间戳(模拟物理时钟)
    int getCurrentTime() {
        return localTime + 1; // 每次调用返回本地时间的递增值
    }
};

int main() {
    HybridLogicalClock clock;

    // 处理本地事件
    clock.handleLocalEvent(); // 输出:Local event timestamp: (1, 1)

    // 处理接收到的事件(例如,来自其他节点的消息)
    clock.handleReceivedEvent(3, 5); // 输出:Received event timestamp: (3, 6)

    // 再次处理本地事件
    clock.handleLocalEvent(); // 输出:Local event timestamp: (4, 1)

    // 再次接收消息,模拟时钟的回退情况
    clock.handleReceivedEvent(2, 3); // 输出:Received event timestamp: (3, 4)

    return 0;
}

最后打个广告,了解开源时序数据库TDengine 更多内容欢迎访问:TDengine

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值