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

简介:

《谈谈你最熟悉的System.DateTime[上篇][下篇]》从跨时区的角度对DateTime这个我们熟知的类型进行了深入探讨,它们都是为这篇文章作的准备工作。在接下来的两篇文章中,我们将完整的介绍如果在一个分布式系统中处理时区的问题。

一、场景以及需求

image 为了让大家本文介绍的主题有一个比较直观的认识,我们给出一个具体的应用场景。一个跨国公司开发一套统一的办公系统,供遍布全球的所有分公司使用。客户端的UI采用Smart Client (Windows Forms应用),而主要的业务逻辑均通过WCF服务的形式提供。我们将承载业务服务的服务器成为应用服务器,如右图(点击看大图)所示,应用服务器部属于中国境内(东8区)。主要的客户端(分公司)分布于三个主要的国家和地区:北美、欧州和澳洲。

不论客户端和服务器之间,还是不同的客户端之间所处的时区均不相同,在进行时间处理的时候就会遇到一些麻烦:某个客户端通过服务调用获取的时间值应该基于哪个时区?对于这个问题,不同的场景可能有不同的要求。在大部分情况下,我们希望获取的时间值就是基于客户端的本地时区。不过也有些场景我们希望获取的时间值对应的时区是描述对象基于的那个时区。比如说,美国分公司于当地时间9月1号早8点举行开业典礼,欧洲分公司员工读取这条信息就没有必要将时间转换成基于本地时区的时间。

不过,本文不考虑这种情况,我们的最终要求是:客户端应用根本不用考虑时区问题,就像是一个单纯的本地应用一样。客户端调用服务传入的时间是DateTimeKind.Local时间或者DateTimeKind.Unspecified时间,同理通过服务调用返回的时间也应该是基于客户端所在时区的时间。


二、解决方案实现原理

现在我们就来谈谈如何解决上面提出的问题。既然时区的处理不能在客户端做,换言之就必须在服务端实现。我们的一个前提是:在数据库中不存储时区的任何信息。在这样一个前提下实现上述的目标,需要解决两个问题:时间的保存和时间获取。

image在时间的保存方面,既然数据库中能保存任何时区偏移之类的信息。在这种情况下,我们必须让所有保存在数据库中的时间都是基于同一个时区。我们可以选择应用服务器所在的时区,也可以直接采用UTC时间。我们的方案采用后者,即数据库所有时间保存为UTC时间

时间在数据库中的存储形式确定了,现在又出现一个问题:客户端传来的时间为客户端所在时区的当地时间,服务端接收到客户端发送的时间后,需要基于客户端相应时区转换成UTC时间才能保存到数据库。那么,服务端如何获取客户端所在的时区信息呢?将其作为服务操作的参数肯定是不可取的。

如果你看过我之前的WCF系列文章,可能会记得我有一篇介绍如何通过WCF扩展实现在客户端和服务端之间传递上下文的文章:《通过WCF Extension实现Context信息的传递》。在这篇文章中我通过WCF扩展实现了将可户端的Culture和UICulture自动传向了服务端,从而确保两边保存一样的语言文化环境上下文。如果我们能够将基于客户端本地的TimeZoneInfo作为上下文进行传递,就能解决服务端对客户端的时区识别问题了

image关于保存时间的处理大体可以通过上面的序列图(点击看大图)来描述。客户端将基于本地时区的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

关于这个分布式系统中跨时区问题的讨论暂时就到这里,在下篇中我将给出一个完整的例子,相信会使你对本文给出的解决方案有一个深刻的认识。

[相关阅读]

作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
7月前
|
NoSQL 算法 安全
分布式锁—1.原理算法和使用建议
本文主要探讨了Redis分布式锁的八大问题,包括非原子操作、忘记释放锁、释放其他线程的锁、加锁失败处理、锁重入问题、锁竞争问题、锁超时失效及主从复制问题,并提供了相应的优化措施。接着分析了Redis的RedLock算法,讨论其优缺点以及分布式专家Martin对其的质疑。此外,文章对比了基于Redis和Zookeeper(zk)的分布式锁实现原理,包括获取与释放锁的具体流程。最后总结了两种分布式锁的适用场景及使用建议,指出Redis分布式锁虽有性能优势但模型不够健壮,而zk分布式锁更稳定但部署成本较高。实际应用中需根据业务需求权衡选择。
|
10月前
|
运维 NoSQL 算法
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
本文深入探讨了基于Redis实现分布式锁时遇到的细节问题及解决方案。首先,针对锁续期问题,提出了通过独立服务、获取锁进程自己续期和异步线程三种方式,并详细介绍了如何利用Lua脚本和守护线程实现自动续期。接着,解决了锁阻塞问题,引入了带超时时间的`tryLock`机制,确保在高并发场景下不会无限等待锁。最后,作为知识扩展,讲解了RedLock算法原理及其在实际业务中的局限性。文章强调,在并发量不高的场景中手写分布式锁可行,但推荐使用更成熟的Redisson框架来实现分布式锁,以保证系统的稳定性和可靠性。
636 0
【📕分布式锁通关指南 04】redis分布式锁的细节问题以及RedLock算法原理
|
存储 Dubbo Java
分布式 RPC 底层原理详解,看这篇就够了!
本文详解分布式RPC的底层原理与系统设计,大厂面试高频,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 RPC 底层原理详解,看这篇就够了!
|
机器学习/深度学习 存储 运维
分布式机器学习系统:设计原理、优化策略与实践经验
本文详细探讨了分布式机器学习系统的发展现状与挑战,重点分析了数据并行、模型并行等核心训练范式,以及参数服务器、优化器等关键组件的设计与实现。文章还深入讨论了混合精度训练、梯度累积、ZeRO优化器等高级特性,旨在提供一套全面的技术解决方案,以应对超大规模模型训练中的计算、存储及通信挑战。
699 4
|
分布式计算 Hadoop 网络安全
Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
203 1
|
存储 机器学习/深度学习 缓存
Hadoop-07-HDFS集群 基础知识 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
Hadoop-07-HDFS集群 基础知识 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
316 1
|
存储 缓存 数据处理
深度解析:Hologres分布式存储引擎设计原理及其优化策略
【10月更文挑战第9天】在大数据时代,数据的规模和复杂性不断增加,这对数据库系统提出了更高的要求。传统的单机数据库难以应对海量数据处理的需求,而分布式数据库通过水平扩展提供了更好的解决方案。阿里云推出的Hologres是一个实时交互式分析服务,它结合了OLAP(在线分析处理)与OLTP(在线事务处理)的优势,能够在大规模数据集上提供低延迟的数据查询能力。本文将深入探讨Hologres分布式存储引擎的设计原理,并介绍一些关键的优化策略。
719 0
|
网络协议 安全 Java
分布式(基础)-RMI的原理
分布式(基础)-RMI的原理
|
存储 供应链 安全
区块链技术原理及应用:深入探索分布式账本技术
【4月更文挑战第30天】区块链,从加密货币的底层技术延伸至多元领域,以其分布式账本、去中心化、不可篡改性及加密技术重塑数据存储与交易。核心组件包括区块、链和节点,应用涵盖加密货币、供应链管理、金融服务等。尽管面临扩展性等挑战,未来潜力无限。