如何解决分布式系统中的跨时区问题[实例篇]_第1页
如何解决分布式系统中的跨时区问题[实例篇]_第2页
如何解决分布式系统中的跨时区问题[实例篇]_第3页
如何解决分布式系统中的跨时区问题[实例篇]_第4页
如何解决分布式系统中的跨时区问题[实例篇]_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、如何解决分布式系统中的跨时区问题实例篇关于如何解决分布式系统中的跨时区问题,上一篇详细介绍了解决方案的实现原理,在这一篇中我们通过一个完整的例子来对这个问题进行深入探讨。尽管原理篇中介绍了那么多,解决方案的本质就是:在进行服务调用过程中将客户端的时区信息作为上下文传入服务端,并以此作为时间转换的依据。我们首先定一个具体的类型来定义包含时区信息的上下文类型,我们将这个类型起名为ApplicationContext。一、通过CallContext实现ApplicationContext在通过WCF扩展实现Context信息的传递一文中,我通过HttpSessionState和CallContext

2、实现了一个ApplicationContext类,为ASP.NET和其他类型的应用提供上下文信息的容器。在这里进行了简化,仅仅实现了基于CallContext的部分。这样一个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>类型,并被定义成集合数据契约。我们采用Singleton的方式来定义ApplicationContext,当前上下文通过静态方法Current获取。而Current属性返回的是通过CallContext的GetData方法获取

7、,并且Key为类型的全名。便是当前时区的TimeZone属性的类型为TimeZoneInfo,通过序列化和反序列对当前时区进行设置和获取。Clear则将整个ApplicationContext对象从CallContext中移除。二、创建一个用于时间转化的DateTimeConverter服务端需要进行两种方式的时间转化,其一是将可户端传入的时间转换成UTC时间,其二就是将从数据库获取的UTC时间转化成基于当前时区上下文的Local时间。为此我定义了如下一个静态的帮助类DateTimeConverter专门进行这两方面的时间转换,而时间转换依据的时区来源于当前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扩展实现ApplicationContext的传播让当前的ApplicationContext在每次服务调用时自动传递到服务端,并作为服务端

10、当前的ApplicationContext,整个过程通过两个步骤来实现:其一是客户端将当前ApplicationContext对象进行序列化,并置于出栈消息的报头(SOAP Header);其二是服务在接收到请求消息时从入栈消息中提取该报头并进行反序列化,最终将生成的对象作为服务端当前的ApplicationContext。客户端对当前ApplicationContext输出可以通过WCF的MessageInspector对象来完成。为此,我们实现了IClientMessageInspector接口定义了如下一个自定义的MessageInspector:ContextMessageInspec

11、tor。在BeforeSendRquest方法中,基于当前ApplicationContext创建了一个MessageHeader,并将其插入出栈消息的报头集合中。该消息报头对应的命名空间和名称为定义在ApplicationContext中的两个常量。 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: 相应地,服务端对ApplicationContext的接收和设置可以通过WCF的CallContextInitializer来实现。为此,我们实现了ICallContextInitializer接口定义了如下一个自定义的CallContextInitializer:ContextCallContextInitializer。在BeforeInvoke方法中,通过相同的命名空间和名称从入栈消息中提取ApplicationConntext作为当前的ApplicationContext。为了避免当前ApplicationContext用在

14、下一次服务请求处理中 (ApplicationContext保存在当前线程的TLS中,而WCF采用线程池的机制处理客户请求),我们在AfterInvoke方法中调用Clear方法将当前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发送的ContextMessageInspector,和用于ApplicationContext接收的ContextCallContextInitializer,最终我们通过一个EndpointBehavior被应用到WCF运行时框架中。为此我们定义了如下一个自定义的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这个终结点行为需要通过培植的方式来使用,我们需要定义它的BehaviorExtensionElement(本质上是一个配置元素): 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: 四、建立一个Alertor Service来模拟跨时区场景image 到目前为止,所有基础性编程已经完成,我们现在创建一个具体的分布式应用来使用上面定义的类型。为此,我们模拟一个用户提醒服务(Alertor Service):我们为某个人创建相应的通知或者提醒,比如什么时候开会,什么时候见客户之类的。首先,所有的Alert条目被最终保存在数据库中,对应的表的结构如右图所示。四个字段分别表示Alert的Id、被通知的人、消息和被触发的时间。这里的表示时间的类型就是我们常用的datetime(不具有时区偏移量信息)。与

21、这个数据表结构相对应,一个Alert类型被创建出来表示一个具体的Alert条目。Alert被定义成数据契约,下面的代码给出了该类的具体定义。 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: 然后我们定义服务契约:IAlert接口。该结构定义了两个操作成员,CreateNewAlert用于创建一个信息的Alert条目;而GetAlerts则用于获取

23、某个人对应的所有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: 下面是实现上面这个服务契约的具体服务的实现:AlertorService。DbHelper是我创建的一个简单的进行数据操作的帮助类,AlertorServ

24、ice用它来执行一段参数化的SQL语句,以及执行一段SELECT语句返回一个DbDataReader。对此你无需过多关注没,你需要关注的是在CreateNewAlert方法中,在进行数据保存之前先调用了DateTimeConverter的ConvertTimeToUtc将基于客户端时区的本地时间转化成了UTC时间;而在GetAlerts方法中在将从数据库中返回的Alert列表返回给客户端的时候,调用了DateTimeConverter的ConvertTimeFromUtc将UTC时间转化成了基于客户端时区的本地时间。 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: 在对上面的服务进行寄宿的时候,采用了如下的配置,将上面创建的ContextBehavior终结点行为应用到了相应的终结点上。 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应用到用于服务调用的终结点上: 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>而下面的代码代表了客户端程序:我们为某个人(Foo)创建了三个Alert,主要这里指定的时间的DateTimeKind为默认的DateTimeKind.Unspecified。然后调用服务或者这三条Alert对象,并将消息的时间打印出来。 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. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论