如何解决分布式系统中的跨时区问题[实例篇]
关于如何解决分布式系统中的跨时区问题,上一篇详细介绍了解决方案的实现原理,在这一篇中我们通过一个完整的例子来对这个问题进行深入探讨。尽管《原理篇》中介绍了那么多,解决方案的本质就是:在进行服务调用过程中将客户端的时区信息作为上下文传入服务端,并以此作为时间转换的依据。我们首先定一个具体的类型来定义包含时区信息的上下文类型,我们将这个类型起名为ApplicationContext。 一、通过CallContext实现ApplicationContext在《通过WCF扩展实现Context信息的传递》一文中,我通过HttpSessionState和CallContext实现了一个ApplicationContext类,为ASP.NET和其他类型的应用提供上下文信息的容器。在这里进行了简化,仅仅实现了基于CallContext的部分。这样一个ApplicationContext类型定义如下: 1: [CollectionDataContract(Namespace="http://www.artech.com/")] 3: { 5: string contextHeaderNamespace = "http://www.artech.com/"; 7: private ApplicationContext() { }
9: { 11: { 13: { 15: { 17: { 19: context.TimeZone = TimeZoneInfo.Local; 21: } 23: } 25: return (ApplicationContext)CallContext.GetData(typeof(ApplicationContext).FullName); 27: set 29: CallContext.SetData(value);
31: } 33: { 35: { 37: } 39: { 41: } 43:? 45: { 47: }
三、通过WCF扩展实现ApplicationContext的传播让当前的ApplicationContext在每次服务调用时自动传递到服务端,并作为服务端当前的ApplicationContext,整个过程通过两个步骤来实现:其一是客户端将当前ApplicationContext对象进行序列化,并置于出栈消息的报头(SOAP Header);其二是服务在接收到请求消息时从入栈消息中提取该报头并进行反序列化,最终将生成的对象作为服务端当前的ApplicationContext。 客户端对当前ApplicationContext输出可以通过WCF的MessageInspector对象来完成。为此,我们实现了IClientMessageInspector接口定义了如下一个自定义的MessageInspector:ContextMessageInspector。在BeforeSendRquest方法中,基于当前ApplicationContext创建了一个MessageHeader,并将其插入出栈消息的报头集合中。该消息报头对应的命名空间和名称为定义在ApplicationContext中的两个常量。 void AfterReceiveReply(ref Message reply,1)">object correlationState) { }
5: { 7: request.Headers.Add(header.GetUntypedHeader(ApplicationContext.contextHeaderName,ApplicationContext.contextHeaderNamespace)); 9: } class ContextCallContextInitializer: ICallContextInitializer 4: { 6: } 8: { 10: if (index >= 0)
13: } 16: } 用于ApplicationContext发送的ContextMessageInspector,和用于ApplicationContext接收的ContextCallContextInitializer,最终我们通过一个EndpointBehavior被应用到WCF运行时框架中。为此我们定义了如下一个自定义的EndpointBehavior:ContextBehavior。 void AddBindingParameters(ServiceEndpoint endpoint,BindingParameterCollection bindingParameters) { }
5: { 7: } foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
14: } 16: } 由于ContextBehavior这个终结点行为需要通过培植的方式来使用,我们需要定义它的BehaviorExtensionElement(本质上是一个配置元素): override Type BehaviorType
protected override object CreateBehavior() 11: } 四、建立一个Alertor Service来模拟跨时区场景与这个数据表结构相对应,一个Alert类型被创建出来表示一个具体的Alert条目。Alert被定义成数据契约,下面的代码给出了该类的具体定义。 class Alert
string Id { get; private set; }
string Person { get; 8: [DataMember]
11: public DateTime Time { get; set; }
this.Id = Guid.NewGuid().ToString(); 16: this.Message = message;
18: } 1: [ServiceContract(Namespace = interface IAlertor
void CreateNewAlert(Alert alert); 7: IEnumerable<Alert> GetAlerts(string person);
class AlertorService:IAlertor void CreateNewAlert(Alert alert) 7: var parameters = new Dictionary<object>(); 9: parameters.Add("@person",alert.Person);
11: parameters.Add("@time",alert.Time);
13: } 15: { 17: parameters.Add(using (var reader = helper.ExecuteReader("SELECT Person,Time FROM dbo.Alert WHERE Person = @person",parameters)) 20: while (reader.Read())
22: yield new Alert(reader[0].ToString(),reader[1].ToString(),DateTimeConverter.ConvertTimeFromUtc( (DateTime)reader[2])); 25: } <?xml version="1.0" encoding="utf-8" ?> system.serviceModel 4: behaviors 5: endpointBehaviors 6: behavior name="contextBehavior" 7: contextPropagtion /> 19: binding="ws2007HttpBinding" bindingConfiguration="" contract="Artech.TimeConversion.Service.Interface.IAlertor" service 21: 22: 23: > 客户端在通过如下的配置将ContextBehavior应用到用于服务调用的终结点上: 14: ="alertservice" />
class Program 5: CreateAlert("Foo",1)">"Weekly Meeting with Testing Team",1)">new DateTime(2010,9,1,8,0));
7: CreateAlert("New Stuff Orientaion",3,1)" id="lnum8"> 8:?
10: { 12: Console.WriteLine("Time:t{0}n",alert.Time);
15: Console.Read(); 17:? using (ChannelFactory<IAlertor> channelFactory = new ChannelFactory<IAlertor>("alertservice")) 22: IAlertor alertor = channelFactory.CreateChannel(); 24: { 26: } 28: } 30: { 32: 33: {
35: using (alert 36: { 38: } 40: } 1: Alert: New Stuff Orientaion 3:? 5: Time: 9/1/2010 8:00:00 AM 8: Time: 9/2/2010 8:00:00 AM 相关内容
推荐文章
站长推荐
热点阅读
|