如何解決分布式系統中的跨時區問題[原理篇]

作者: Artech  來源: 博客園  發布時間: 2010-09-12 13:11  閱讀: 1454 次  推薦: 0   原文鏈接   [收藏]  
摘要:《談談你最熟悉的System.DateTime[上篇][下篇]》從跨時區的角度對DateTime這個我們熟知的類型進行了深入探討,它們都是為這篇文章作的準備工作。在接下來的兩篇文章中,我們將完整的介紹如果在一個分布式系統中處理時區的問題。

  一、場景以及需求

   為了讓大家本文介紹的主題有一個比較直觀的認識,我們給出一個具體的應用場景。一個跨國公司開發一套統一的辦公系統,供遍布全球的所有分公司使用。客戶端的UI采用Smart Client (Windows Forms應用),而主要的業務邏輯均通過WCF服務的形式提供。我們將承載業務服務的服務器成為應用服務器,如右圖(點擊看大圖)所示,應用服務器部屬于中國境內(東8區)。主要的客戶端(分公司)分布于三個主要的國家和地區:北美、歐州和澳洲。

  不論客戶端和服務器之間,還是不同的客戶端之間所處的時區均不相同,在進行時間處理的時候就會遇到一些麻煩:某個客戶端通過服務調用獲取的時間值應該基于哪個時區?對于這個問題,不同的場景可能有不同的要求。在大部分情況下,我們希望獲取的時間值就是基于客戶端的本地時區。不過也有些場景我們希望獲取的時間值對應的時區是描述對象基于的那個時區。比如說,美國分公司于當地時間9月1號早8點舉行開業典禮,歐洲分公司員工讀取這條信息就沒有必要將時間轉換成基于本地時區的時間。

  不過,本文不考慮這種情況,我們的最終要求是:客戶端應用根本不用考慮時區問題,就像是一個單純的本地應用一樣。客戶端調用服務傳入的時間是DateTimeKind.Local時間或者DateTimeKind.Unspecified時間,同理通過服務調用返回的時間也應該是基于客戶端所在時區的時間。

  二、解決方案實現原理

  現在我們就來談談如何解決上面提出的問題。既然時區的處理不能在客戶端做,換言之就必須在服務端實現。我們的一個前提是:在數據庫中不存儲時區的任何信息。在這樣一個前提下實現上述的目標,需要解決兩個問題:時間的保存和時間獲取。

  在時間的保存方面,既然數據庫中能保存任何時區偏移之類的信息。在這種情況下,我們必須讓所有保存在數據庫中的時間都是基于同一個時區。我們可以選擇應用服務器所在的時區,也可以直接采用UTC時間。我們的方案采用后者,即數據庫所有時間保存為UTC時間 。

  時間在數據庫中的存儲形式確定了,現在又出現一個問題:客戶端傳來的時間為客戶端所在時區的當地時間,服務端接收到客戶端發送的時間后,需要基于客戶端相應時區轉換成UTC時間才能保存到數據庫。那么,服務端如何獲取客戶端所在的時區信息呢?將其作為服務操作的參數肯定是不可取的。

  如果你看過我之前的WCF系列文章,可能會記得我有一篇介紹如何通過WCF擴展實現在客戶端和服務端之間傳遞上下文的文章:《通過WCF Extension實現Context信息的傳遞》。在這篇文章中我通過WCF擴展實現了將可戶端的Culture和UICulture自動傳向了服務端,從而確保兩邊保存一樣的語言文化環境上下文。如果我們能夠將基于客戶端本地的TimeZoneInfo作為上下文進行傳遞,就能解決服務端對客戶端的時區識別問題了。

  關于保存時間的處理大體可以通過上面的序列圖(點擊看大圖)來描述。客戶端將基于本地時區的DateTimeKind.Local或者DateTimeKind.Unspecified時間作為輸入操作調用某個服務,與此同時,本地的TimeZoneInfo序列化后作為上下文傳遞到服務端。服務端接將接收到的時間,根據接收到TimeZoneInfo上下文轉換成DateTimeKind.Utc時間,并保存到數據庫中。

`當客戶端調用服務獲取某個時間的時候,本地的同樣作為上下文信息被傳遞到服務端。借助于這個TimeZoneInfo,服務端可以將數據庫中以UTC形式保存的時間轉換成基于客戶端時區的DateTimeKind.Local時間。下圖(點擊看大圖)所示的序列圖反映了這個過程。

  三、TimeZoneInfo的序列化問題

  在《談談你最熟悉的System.DateTime[上篇]》對TimeZoneInfo這個類進行介紹中,我說該類是可以被序列化的,序列化對于解決跨時區問題很重要。就是因為我們需要將TimeZoneInfo作為上下文在客戶端和服務端進行傳遞,換言之,就是將TimeZoneInfo對象進行序列化,將序列化后的內容放入出棧消息(Outgoing Message)的消息報頭(Message Header)中。

  不過關于TimeZoneInfo對象序列化,我們一般并不會真正地將整個TimeZoneInfo對象交給序列化器去做序列化,而是利用定義在TimeZoneInfo中的兩個特殊的方法來進行序列化和反序列化的工作。一個是實例方法ToSerializedString,將TimeZoneInfo轉換成序列化后的一個字符串;另一個則靜態方法FromSerializedString,對序列化后的字符轉進行反序列化生成TimeZoneInfo對象。這兩個方法的定義如下:

   1: [Serializable]
   2: public sealed class TimeZoneInfo
   3: {
   4:     //Others
   5:     public static TimeZoneInfo FromSerializedString(string source);
   6:     public string ToSerializedString();
   7: } 

  下面的代碼演示了通過上述的這兩個方法對TimeZoneInfo的序列化和反序列化的實現:

   1: string serializedString = TimeZoneInfo.Local.ToSerializedString();
   2: Console.WriteLine("SerializedString: {0}\n", serializedString);
   3: TimeZoneInfo deserializedTimeZone = TimeZoneInfo.FromSerializedString(serializedString);
   4: Console.WriteLine("deserializedTimeZone.Equals(TimeZoneInfo.Local) ? {0}", deserializedTimeZone.Equals(TimeZoneInfo.Local));
   5: Console.WriteLine("deserializedTimeZone == TimeZoneInfo.Local ? {0}", deserializedTimeZone == TimeZoneInfo.Local);

  下面是輸出結果,從中我們看出最終被序列化后的文本的內容。此外,輸出結果也反映兩個另一個信息:兩個包含時區信息的TimeZoneInfo對象,調用Equals方法和使用==操作符得到不一樣的結果。個人覺得這是微軟作得不太到位的地方。

   1: SerializedString: China Standard Time;480;(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi;China Standard Time;China Daylight Time;;
   2:  
   3: deserializedTimeZone.Equals(TimeZoneInfo.Local) ? True
   4: deserializedTimeZone == TimeZoneInfo.Local ? False

  關于這個分布式系統中跨時區問題的討論暫時就到這里,在下篇中我將給出一個完整的例子,相信會使你對本文給出的解決方案有一個深刻的認識。

   [相關閱讀]
0
0
 
標簽:分布式 時區
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()