如何解決分布式系統(tǒng)中的跨時(shí)區(qū)問題[實(shí)例篇]_第1頁
如何解決分布式系統(tǒng)中的跨時(shí)區(qū)問題[實(shí)例篇]_第2頁
如何解決分布式系統(tǒng)中的跨時(shí)區(qū)問題[實(shí)例篇]_第3頁
如何解決分布式系統(tǒng)中的跨時(shí)區(qū)問題[實(shí)例篇]_第4頁
如何解決分布式系統(tǒng)中的跨時(shí)區(qū)問題[實(shí)例篇]_第5頁
已閱讀5頁,還剩4頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

1、如何解決分布式系統(tǒng)中的跨時(shí)區(qū)問題實(shí)例篇關(guān)于如何解決分布式系統(tǒng)中的跨時(shí)區(qū)問題,上一篇詳細(xì)介紹了解決方案的實(shí)現(xiàn)原理,在這一篇中我們通過一個(gè)完整的例子來對這個(gè)問題進(jìn)行深入探討。盡管原理篇中介紹了那么多,解決方案的本質(zhì)就是:在進(jìn)行服務(wù)調(diào)用過程中將客戶端的時(shí)區(qū)信息作為上下文傳入服務(wù)端,并以此作為時(shí)間轉(zhuǎn)換的依據(jù)。我們首先定一個(gè)具體的類型來定義包含時(shí)區(qū)信息的上下文類型,我們將這個(gè)類型起名為ApplicationContext。一、通過CallContext實(shí)現(xiàn)ApplicationContext在通過WCF擴(kuò)展實(shí)現(xiàn)Context信息的傳遞一文中,我通過HttpSessionState和CallContext

2、實(shí)現(xiàn)了一個(gè)ApplicationContext類,為ASP.NET和其他類型的應(yīng)用提供上下文信息的容器。在這里進(jìn)行了簡化,僅僅實(shí)現(xiàn)了基于CallContext的部分。這樣一個(gè)ApplicationContext類型定義如下: 1: CollectionDataContract(Namespace=" 2: public class ApplicationContext:Dictionary<string, object> 3: 4: internal const string contextHeaderName = "ApplicationContext&quo

3、t; 5: internal const string contextHeaderNamespace = " 6: 7: private ApplicationContext() 8: public static ApplicationContext Current 9: 10: get 11: 12: if (null = CallContext.GetData(typeof(ApplicationContext).FullName) 13: 14: lock (typeof(ApplicationContext) 15: 16: if (null = CallContext.Ge

4、tData(typeof(ApplicationContext).FullName) 17: 18: var context = new ApplicationContext(); 19: context.TimeZone = TimeZoneInfo.Local; 20: CallContext.SetData(typeof(ApplicationContext).FullName, context); 21: 22: 23: 24: 25: return (ApplicationContext)CallContext.GetData(typeof(ApplicationContext).F

5、ullName); 26: 27: set 28: 29: CallContext.SetData(typeof(ApplicationContext).FullName, value); 30: 31: 32: public TimeZoneInfo TimeZone 33: 34: get 35: 36: return TimeZoneInfo.FromSerializedString(string)this"_TimeZone"); 37: 38: set 39: 40: this"_TimeZone" = value.ToSerializedSt

6、ring(); 41: 42: 43: 44: public static void Clear() 45: 46: CallContext.FreeNamedDataSlot(typeof(ApplicationContext).FullName); 47: 48: ApplicationContext繼承自Dictionary<string,object>類型,并被定義成集合數(shù)據(jù)契約。我們采用Singleton的方式來定義ApplicationContext,當(dāng)前上下文通過靜態(tài)方法Current獲取。而Current屬性返回的是通過CallContext的GetData方法獲取

7、,并且Key為類型的全名。便是當(dāng)前時(shí)區(qū)的TimeZone屬性的類型為TimeZoneInfo,通過序列化和反序列對當(dāng)前時(shí)區(qū)進(jìn)行設(shè)置和獲取。Clear則將整個(gè)ApplicationContext對象從CallContext中移除。二、創(chuàng)建一個(gè)用于時(shí)間轉(zhuǎn)化的DateTimeConverter服務(wù)端需要進(jìn)行兩種方式的時(shí)間轉(zhuǎn)化,其一是將可戶端傳入的時(shí)間轉(zhuǎn)換成UTC時(shí)間,其二就是將從數(shù)據(jù)庫獲取的UTC時(shí)間轉(zhuǎn)化成基于當(dāng)前時(shí)區(qū)上下文的Local時(shí)間。為此我定義了如下一個(gè)靜態(tài)的幫助類DateTimeConverter專門進(jìn)行這兩方面的時(shí)間轉(zhuǎn)換,而時(shí)間轉(zhuǎn)換依據(jù)的時(shí)區(qū)來源于當(dāng)前ApplicationContext

8、的TimeZone屬性。 1: public static class DateTimeConverter 2: 3: public static DateTime ConvertTimeToUtc(DateTime dateTime) 4: 5: if(dateTime.Kind = DateTimeKind.Utc) 6: 7: return dateTime; 8: 9: return TimeZoneInfo.ConvertTimeToUtc(dateTime, ApplicationContext.Current.TimeZone); 10: 11: 12: public stati

9、c DateTime ConvertTimeFromUtc(DateTime dateTime) 13: 14: if (dateTime.Kind = DateTimeKind.Utc) 15: 16: return dateTime; 17: 18: return TimeZoneInfo.ConvertTimeFromUtc(dateTime, ApplicationContext.Current.TimeZone); 19: 20: 三、通過WCF擴(kuò)展實(shí)現(xiàn)ApplicationContext的傳播讓當(dāng)前的ApplicationContext在每次服務(wù)調(diào)用時(shí)自動(dòng)傳遞到服務(wù)端,并作為服務(wù)端

10、當(dāng)前的ApplicationContext,整個(gè)過程通過兩個(gè)步驟來實(shí)現(xiàn):其一是客戶端將當(dāng)前ApplicationContext對象進(jìn)行序列化,并置于出棧消息的報(bào)頭(SOAP Header);其二是服務(wù)在接收到請求消息時(shí)從入棧消息中提取該報(bào)頭并進(jìn)行反序列化,最終將生成的對象作為服務(wù)端當(dāng)前的ApplicationContext??蛻舳藢Ξ?dāng)前ApplicationContext輸出可以通過WCF的MessageInspector對象來完成。為此,我們實(shí)現(xiàn)了IClientMessageInspector接口定義了如下一個(gè)自定義的MessageInspector:ContextMessageInspec

11、tor。在BeforeSendRquest方法中,基于當(dāng)前ApplicationContext創(chuàng)建了一個(gè)MessageHeader,并將其插入出棧消息的報(bào)頭集合中。該消息報(bào)頭對應(yīng)的命名空間和名稱為定義在ApplicationContext中的兩個(gè)常量。 1: public class ContextMessageInspector:IClientMessageInspector 2: 3: public void AfterReceiveReply(ref Message reply, object correlationState) 4: public object BeforeSendRe

12、quest(ref Message request, IClientChannel channel) 5: 6: MessageHeader<ApplicationContext> header = new MessageHeader<ApplicationContext>(ApplicationContext.Current); 7: request.Headers.Add(header.GetUntypedHeader(ApplicationContext.contextHeaderName, ApplicationContext.contextHeaderName

13、space); 8: return null; 9: 10: 相應(yīng)地,服務(wù)端對ApplicationContext的接收和設(shè)置可以通過WCF的CallContextInitializer來實(shí)現(xiàn)。為此,我們實(shí)現(xiàn)了ICallContextInitializer接口定義了如下一個(gè)自定義的CallContextInitializer:ContextCallContextInitializer。在BeforeInvoke方法中,通過相同的命名空間和名稱從入棧消息中提取ApplicationConntext作為當(dāng)前的ApplicationContext。為了避免當(dāng)前ApplicationContext用在

14、下一次服務(wù)請求處理中 (ApplicationContext保存在當(dāng)前線程的TLS中,而WCF采用線程池的機(jī)制處理客戶請求),我們在AfterInvoke方法中調(diào)用Clear方法將當(dāng)前ApplicationContext清除。 1: public class ContextCallContextInitializer: ICallContextInitializer 2: 3: public void AfterInvoke(object correlationState) 4: 5: ApplicationContext.Clear(); 6: 7: public object Before

15、Invoke(InstanceContext instanceContext, IClientChannel channel, Message message) 8: 9: var index = message.Headers.FindHeader(ApplicationContext.contextHeaderName, ApplicationContext.contextHeaderNamespace); 10: if (index >= 0) 11: 12: ApplicationContext.Current = message.Headers.GetHeader<App

16、licationContext>(index); 13: 14: return null; 15: 16: 用于ApplicationContext發(fā)送的ContextMessageInspector,和用于ApplicationContext接收的ContextCallContextInitializer,最終我們通過一個(gè)EndpointBehavior被應(yīng)用到WCF運(yùn)行時(shí)框架中。為此我們定義了如下一個(gè)自定義的EndpointBehavior:ContextBehavior。 1: public class ContextBehavior : IEndpointBehavior 2:

17、3: public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) 4: public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) 5: 6: clientRuntime.MessageInspectors.Add(new ContextMessageInspector(); 7: 8: public void ApplyDispat

18、chBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) 9: 10: foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations) 11: 12: operation.CallContextInitializers.Add(new ContextCallContextInitializer(); 13: 14: 15: public void Validate(ServiceEndpoin

19、t endpoint) 16: 由于ContextBehavior這個(gè)終結(jié)點(diǎn)行為需要通過培植的方式來使用,我們需要定義它的BehaviorExtensionElement(本質(zhì)上是一個(gè)配置元素): 1: public class ContextBehaviorElement : BehaviorExtensionElement 2: 3: public override Type BehaviorType 4: 5: get return typeof(ContextBehavior); 6: 7: protected override object CreateBehavior() 8: 9

20、: return new ContextBehavior(); 10: 11: 四、建立一個(gè)Alertor Service來模擬跨時(shí)區(qū)場景image 到目前為止,所有基礎(chǔ)性編程已經(jīng)完成,我們現(xiàn)在創(chuàng)建一個(gè)具體的分布式應(yīng)用來使用上面定義的類型。為此,我們模擬一個(gè)用戶提醒服務(wù)(Alertor Service):我們?yōu)槟硞€(gè)人創(chuàng)建相應(yīng)的通知或者提醒,比如什么時(shí)候開會(huì),什么時(shí)候見客戶之類的。首先,所有的Alert條目被最終保存在數(shù)據(jù)庫中,對應(yīng)的表的結(jié)構(gòu)如右圖所示。四個(gè)字段分別表示Alert的Id、被通知的人、消息和被觸發(fā)的時(shí)間。這里的表示時(shí)間的類型就是我們常用的datetime(不具有時(shí)區(qū)偏移量信息)。與

21、這個(gè)數(shù)據(jù)表結(jié)構(gòu)相對應(yīng),一個(gè)Alert類型被創(chuàng)建出來表示一個(gè)具體的Alert條目。Alert被定義成數(shù)據(jù)契約,下面的代碼給出了該類的具體定義。 1: DataContract 2: public class Alert 3: 4: DataMember 5: public string Id get; private set; 6: DataMember 7: public string Person get; private set; 8: DataMember 9: public string Message get; private set; 10: DataMember 11: publ

22、ic DateTime Time get; set; 12: public Alert(string persone, string message, DateTime time) 13: 14: this.Id = Guid.NewGuid().ToString(); 15: this.Person = persone; 16: this.Message = message; 17: this.Time = time; 18: 19: 然后我們定義服務(wù)契約:IAlert接口。該結(jié)構(gòu)定義了兩個(gè)操作成員,CreateNewAlert用于創(chuàng)建一個(gè)信息的Alert條目;而GetAlerts則用于獲取

23、某個(gè)人對應(yīng)的所有Alert列表。 1: ServiceContract(Namespace = " 2: public interface IAlertor 3: 4: OperationContract 5: void CreateNewAlert(Alert alert); 6: OperationContract 7: IEnumerable<Alert> GetAlerts(string person); 8: 下面是實(shí)現(xiàn)上面這個(gè)服務(wù)契約的具體服務(wù)的實(shí)現(xiàn):AlertorService。DbHelper是我創(chuàng)建的一個(gè)簡單的進(jìn)行數(shù)據(jù)操作的幫助類,AlertorServ

24、ice用它來執(zhí)行一段參數(shù)化的SQL語句,以及執(zhí)行一段SELECT語句返回一個(gè)DbDataReader。對此你無需過多關(guān)注沒,你需要關(guān)注的是在CreateNewAlert方法中,在進(jìn)行數(shù)據(jù)保存之前先調(diào)用了DateTimeConverter的ConvertTimeToUtc將基于客戶端時(shí)區(qū)的本地時(shí)間轉(zhuǎn)化成了UTC時(shí)間;而在GetAlerts方法中在將從數(shù)據(jù)庫中返回的Alert列表返回給客戶端的時(shí)候,調(diào)用了DateTimeConverter的ConvertTimeFromUtc將UTC時(shí)間轉(zhuǎn)化成了基于客戶端時(shí)區(qū)的本地時(shí)間。 1: public class AlertorService:IAlerto

25、r 2: 3: private DbHelper helper = new DbHelper("TestDb"); 4: public void CreateNewAlert(Alert alert) 5: 6: alert.Time = DateTimeConverter.ConvertTimeToUtc(alert.Time); 7: var parameters = new Dictionary<string, object>(); 8: parameters.Add("id", alert.Id); 9: parameters.Add

26、("person", alert.Person); 10: parameters.Add("message", alert.Message); 11: parameters.Add("time", alert.Time); 12: helper.ExecuteNoQuery("INSERT INTO dbo.Alert(Id, Person, Message, Time) VALUES(id,person,message,time)", parameters); 13: 14: public IEnumerable

27、<Alert> GetAlerts(string person) 15: 16: var parameters = new Dictionary<string, object>(); 17: parameters.Add("person", person); 18: using ( reader = helper.ExecuteReader("SELECT Person, Message, Time FROM dbo.Alert WHERE Person = person", parameters) 19: 20: while (

28、reader.Read() 21: 22: yield return new Alert(reader0.ToString(),reader1.ToString(),DateTimeConverter.ConvertTimeFromUtc( (DateTime)reader2); 23: 24: 25: 26: 在對上面的服務(wù)進(jìn)行寄宿的時(shí)候,采用了如下的配置,將上面創(chuàng)建的ContextBehavior終結(jié)點(diǎn)行為應(yīng)用到了相應(yīng)的終結(jié)點(diǎn)上。 1: <?xml version="1.0" encoding="utf-8" ?> 2: <conf

29、iguration> 3: <system.serviceModel> 4: <behaviors> 5: <endpointBehaviors> 6: <behavior name="contextBehavior"> 7: <contextPropagtion /> 8: </behavior> 9: </endpointBehaviors> 10: </behaviors> 11: <extensions> 12: <behaviorExtensio

30、ns> 13: <add name="contextPropagtion" type="Artech.TimeConversion.ContextBehaviorElement, Artech.TimeConversion.Lib, Version=, Culture=neutral, PublicKeyToken=null" /> 14: </behaviorExtensions> 15: </extensions> 16: <services> 17: <service nam

31、e="Artech.TimeConversion.Service.AlertorService"> 18: <endpoint address=":3721/alertservice" behaviorConfiguration="contextBehavior" 19: binding="ws2007HttpBinding" bindingConfiguration="" contract="Artech.TimeConversion.Ser

32、vice.Interface.IAlertor" /> 20: </service> 21: </services> 22: </system.serviceModel> 23: </configuration>客戶端在通過如下的配置將ContextBehavior應(yīng)用到用于服務(wù)調(diào)用的終結(jié)點(diǎn)上: 1: <?xml version="1.0" encoding="utf-8" ?> 2: <configuration> 3: <system.serviceMode

33、l> 4: <behaviors> 5: <endpointBehaviors> 6: <behavior name="contextBehavior"> 7: <contextPropagation /> 8: </behavior> 9: </endpointBehaviors> 10: </behaviors> 11: <client> 12: <endpoint address=":3721/alertservice&q

34、uot; behaviorConfiguration="contextBehavior" 13: binding="ws2007HttpBinding" bindingConfiguration="" contract="Artech.TimeConversion.Service.Interface.IAlertor" 14: name="alertservice" /> 15: </client> 16: <extensions> 17: <behavior

35、Extensions> 18: <add name="contextPropagation" type="Artech.TimeConversion.ContextBehaviorElement, Artech.TimeConversion.Lib, Version=, Culture=neutral, PublicKeyToken=null" /> 19: </behaviorExtensions> 20: </extensions> 21: </system.serviceModel>

36、; 22: </configuration>而下面的代碼代表了客戶端程序:我們?yōu)槟硞€(gè)人(Foo)創(chuàng)建了三個(gè)Alert,主要這里指定的時(shí)間的DateTimeKind為默認(rèn)的DateTimeKind.Unspecified。然后調(diào)用服務(wù)或者這三條Alert對象,并將消息的時(shí)間打印出來。 1: public class Program 2: 3: static void Main(string args) 4: 5: CreateAlert("Foo", "Weekly Meeting with Testing Team", new DateTim

37、e(2010, 9, 1, 8, 0, 0); 6: CreateAlert("Foo", "Architecture and Design Training", new DateTime(2010, 9, 2, 8, 0, 0); 7: CreateAlert("Foo", "New Stuff Orientaion", new DateTime(2010, 9, 3, 8, 0, 0); 8: 9: foreach (var alert in GetAlerts("Foo") 10: 11: Console.WriteLine("Alert:t0", alert.Message); 12: Console.WriteLine("Time:t0n", ale

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論