微服务中时间传递的最佳实践

2,521 阅读5分钟

先说结论:所有传递时间的地方都尽量使用13位精确到毫秒的数值类型的时间戳

前情概要

在软件开发过程中,时间类型是最常用的数据类型之一,经常出现在前后端接口调用的参数或返回值中、以及服务与中间件或其他服务交互的数据中,在这些交互中,如何传递时间类型的数据是一件十分重要的事情,一个好的传递方式可以减少交互中的错误,降低沟通成本,反之则会增加沟通成本以及错误发生的几率。

那到底什么样才算是一个好的传递方式呢,来看下面几个场景

常见场景

服务与前端之间的时间传递

这是最常见的场景,前端请求后端接口,传递数据、获取数据。时间传递发生在接口请求,传递参数以及接口响应返回数据。

先看接口请求,传递参数阶段
前端传递 时间类型的数据给 后端,一般有两种方式

  • 时间格式的字符串:例如 "yyyy-MM-dd HH:mm:ss" 但是这种传递方式,需要后端将字符串转换时间类型的字段
  • 时间戳(13位数值类型):前端传递时间戳给后端,后端直接使用数值类型接受,这种方式后端不需要转换操作。

因此,建议前端传递时间类型数据给后端时,优先考虑时间戳。

需要注意的是,时间戳是13位数值类型,精度为毫秒,但如果字段类型不需要精确到毫秒时,比如生日一般只到年月日,这时时间戳就应该将尾部的时分秒和毫秒值都置为0

接口返回数据阶段 返回时间类型的数据一般也是两种格式

  • 时间格式的字符串:例如 "yyyy-MM-dd HH" 这种方式最常见,但也固定了前端的展示格式,如果前端想换另一种格式展示,就需要转换
  • 时间戳(13位数值类型):前端可以根据展示需要,自己转换成需要的时间格式

建议后端返回数据类型给前端时也返回时间戳,前端想用什么格式自己去转换

服务与服务之前的时间传递

在微服务中不同服务使用的架构可能一致,A服务使用Spring,B服务使用node.js,无法使用某个特定语言的时间对象,比如 A 服务使用 Date 对象,想传递给B服务的话,必须进行序列化,B服务接收到序列化的值后,还需要进行解析,如果还使用特定语言的时间对象无疑会增加服务之间调用的复杂性,这个时候就应该统一使用13位数值类型的时间戳,这样各个服务都不需要转换,降低复杂性的同时,也减小了出错的概率。

服务与中间件之间的时间传递

常用的中间件通常分两类,消息队列(如 kafka、rabbitmq等)和 nosql 数据库(如 redis、mongodb等),既然是中间件,自然是多个服务之间共用的,既然是共用的,那么时间类的数据最好是统一的格式,方便其他服务使用。

一般有两种选择

  • 时间格式的字符串,例如 "yyyy-MM-dd HH:mm:ss"
  • 时间戳(13位数值类型)

显然这种情况下时间戳(13位数值类型)具有更好的兼容性,因为时间格式的字符串有可能是 "yyyy-MM-dd HH" ,有可能是 "yyyy-MM-dd HH:mm:ss" ,而时间戳长度都是固定的

总结

时间格式的字符串可读性最强,但是兼容性最差,需要转换后才能使用,而且还需要关心时区问题。而时间戳(13位数值类型)可读性最差,兼容性最好,而且不存在时区问题,因此建议

  • 时间传递时使用时间戳(13位数值类型)
  • 时间展示时(后端打log、前端展示等)使用时间格式的字符串

至于存储时一般还是使用数据库内置的时间类型,这样可以方便的使用内置时间函数

后记

上文中有提到使用时间戳(13位数值类型)表示出生日期时,需要把时分秒置为0。那像生日这种字段怎么表示呢,生日一般只有月日,这种类型的时间无法用时间戳表示。

像生日这种类型,不光无法用时间戳表示,几乎所有的中间件和数据库中也没有对应的时间类型,甚至很多语言都没有对应的时间类型,这种就只能使用时间格式字符串来表示了,如果这类字段还需要计算,可以考虑将时间拆分成数值存储,比如 生日,拆分成 月份和日期两字段

后记2

只是在 时间传递时 使用时间戳(13位数值类型),而程序内部依然使用时间对象的话,用起来会很不方便,每次接收传递的时间戳(13位数值类型)和传递时间戳时,都需要转换。

为了避免转换,我建议在全链路中都使用时间戳,除了以下情况

  • 需要展示人类可读的时间时 :比如打印日志、前端展示
  • 需要计算时