加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

如何解决分布式系统中的跨时区问题[实例篇]

发布时间:2020-12-16 09:05:03 所属栏目:asp.Net 来源:网络整理
导读:关于如何解决分布式系统中的跨时区问题,上一篇详细介绍了解决方案的实现原理,在这一篇中我们通过一个完整的例子来对这个问题进行深入探讨。尽管《原理篇》中介绍了那么多,解决方案的本质就是:在进行服务调用过程中将客户端的时区信息作为上下文传入服务

关于如何解决分布式系统中的跨时区问题,上一篇详细介绍了解决方案的实现原理,在这一篇中我们通过一个完整的例子来对这个问题进行深入探讨。尽管《原理篇》中介绍了那么多,解决方案的本质就是:在进行服务调用过程中将客户端的时区信息作为上下文传入服务端,并以此作为时间转换的依据。我们首先定一个具体的类型来定义包含时区信息的上下文类型,我们将这个类型起名为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:     }
  
   2: {
   4:     { 
   6:         {
   8:         }
  10:     }
  12:     static DateTime ConvertTimeFromUtc(DateTime dateTime)
  14:         if (dateTime.Kind == DateTimeKind.Utc)
  16:             return dateTime;
  18:         return TimeZoneInfo.ConvertTimeFromUtc(dateTime,ApplicationContext.Current.TimeZone);
  20: }

三、通过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来模拟跨时区场景

到目前为止,所有基础性编程已经完成,我们现在创建一个具体的分布式应用来使用上面定义的类型。为此,我们模拟一个用户提醒服务(Alertor Service):我们为某个人创建相应的通知或者提醒,比如什么时候开会,什么时候见客户之类的。首先,所有的Alert条目被最终保存在数据库中,对应的表的结构如右图所示。四个字段分别表示Alert的Id、被通知的人、消息和被触发的时间。这里的表示时间的类型就是我们常用的datetime(不具有时区偏移量信息)。

与这个数据表结构相对应,一个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