探討系統時間與應用程式的影響:網路協定、誤差、以及正確使用方法

·

1 min read

上一集我們討論了MySQL Timestamp 的精準度,提到了不同主機之間很有可能有時間差異,那這個時間差會有多大?

在電腦和許多微控器裡面都有一個 RTC 晶片,利用石英振盪器提供系統時間。這個RTC在一班桌上型電腦上就是裝在主機板上,和BIOS一起用水銀電池供電。所以如果那個電池如果沒電了,BIOS設定都會跑掉,時間也會被重設。

關於石英振盪器,這裡有一篇Analog Devices的文章解釋石英振盪器的原理以及誤差。

在 Linux 上,我們可以利用 hwclock獲取時間。但是這件事情要經過IO操作,所以只有每次開機的時候讀取。開機之後會透過Timer interrupt計算時間。Timer Interrupt 是一個由系統的定時硬體在規律時間間隔內產生的中斷,它是核心使用的機制來追蹤時間間隔。每當 timer interrupt 發生,系統時間就會前進。所以從這邊我們知道,系統時間很有可能因為各種原因偏移,不只是RTC本身的誤差,還有系統處理中斷的誤差等等。

這於這個誤差有多大?如果只看RTC的誤差,根據上面Analog Devices的那篇文章,一天大概可以誤差正負1.6秒。透過NTP,Linux系統可以和網路伺服器校正時間。 NTP服務是一種網路協議,用於同步多台電腦的時鐘,以確保它們顯示相同的時間。這邊有一篇文章仔細介紹 NTP 的原理與算法

那進到正題,我很好奇時間差到底會不會影響到我們的應用。依照上次的範例,我有以下資料表和應用:

以下的定義就沒有明確定義,所以是到秒。本文的範例是以這個為主。

CREATE TABLE `services` (  
`id` bigint(20) NOT NULL AUTO_INCREMENT,  
`start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  
`end_time` timestamp,  
`name` varchar(255) NOT NULL,  
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  
PRIMARY KEY (`id`)  
)
public class Service {

    private Long id;
    private String name;
    private LocalDateTime startTime;
    private LocalDateTime endTime;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    public boolean isEnabled() {
        LocalDateTime now = LocalDateTime.now();
        if (startTime.isAfter(now)) {
            // Current time is prior to startTime
            return false;
        } else if (endTime == null || endTime.isAfter(now)) {
            // Current time is between startTime and endTime, or endTime is null
            return true;
        } else {
            // Current time is after endTime
            return false;
        }
    }
}

那我在寫入一個 endTime 之後馬上讀,在多長時間內有可能會被影響? 假設設定 endTime 當下的時間是 T1 ,而讀的那個時間是 T2 。那可以知道,當 T2 >= T1 的時候就沒問題。T2 和 T1 之間至少會差一個 MySQL 的 latency,設為 L (L>0) 。另外還有兩個時間的誤差設為 D (這裡方便設定D>0)

如果要讓 T2 >= T1 ,假設T2的環境的時間較T1的快,則變成T2 = T1+L+D+t (t是commit過後經過的時間) L+D+t >= 0,表示 t >=0 時都沒問題。實際上t一定會>=L。

另外如果反過來T2的環境比較慢,則變成T2 = T1+L-D+t L-D+t>=0,t>=D-L才會沒問題。也就是,如果這個時間誤差比MySQL的Latency要長,就有可能就算MySQL已經更新了,還會有D-L這段時間有錯誤結果。

在我這邊監控上發現,Aurora MySQL的Latency大概是在數毫秒。這裡有一篇文章講延Aurora統計數據。所以很有可能時間差大的時候造成影響。

但在AWS上,根據這個文件每16秒就會對時一次。EC2使用一個新的NTP實作Chrony,用chronyc tracking看了上面的紀錄,時間差是幾個微秒等級。

但是當我們有 Rounding 問題的時候,事情就變得嚴重了。雖然造成問題的機率是一樣的(因為四捨五入的期望值造成誤差期望值是0),但剛剛那個式子就要加上一個 T2 = T1+L-D+t +R。這個 R 是 0~500ms,所以可以直接忽略其他因素。只要當初設定的 endTime 是在一秒鐘的後半,500ms之內存取都會出問題。

最後我的解決方法是存的時候捨掉秒數以下,讀取時進位到秒,這樣就不會被影響了。

PS 這篇標題是 ChatGPT取的