當前位置:編程學習大全網 - 編程語言 - 如何解決分布式系統中的跨時區問題

如何解決分布式系統中的跨時區問題

從跨時區的角度對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

關於這個分布式系統中跨時區問題的討論暫時就到這裏,在下篇中我將給出壹個完整的例子,相信會使妳對本文給出的解決方案有壹個深刻的認識。轉載,僅供參考。

  • 上一篇:如何用C語言編程,使出現壹個界面幾秒後自動跳到另壹個界面;求高手解答。
  • 下一篇:計稅編程
  • copyright 2024編程學習大全網