平时开发监控系统时免不了与时序数据库的查询打交道,在查时序数据库时 时间范围 是必不可少的条件,所以在查询的UI展示上通常会将时间范围作为一个独立的组件来让用户交互。

时间范围通常会展示为两种形式:相对时间和绝对时间。对于监控系统来说,日常观察指标、建立看板基本都是使用相对时间,因为使用绝对时间的话一是不能及时更新,二是容易引发慢查询。而绝对时间的使用场景一般是定位具体问题。

在我们的监控前端里主要使用相对时间的地方有两个,一是adhoc查询,另一个是看板。在这两处需求里都需要对相对时间序列化,前者用来分享查询链接,后者用来保存看板配置。下面就谈谈如何序列化相对时间。

使用key来映射

这是一开始监控里使用的方式,就是通过一些预定义的key(yesterday, today, thisweek等)来保存相对时间范围,前端在展示时需要额外写死的 Label MapDuration Map

const LabelMap = {yesterday: '昨天',today: '今天',thisweek: '这周',// and so on..
};const DurationMap = {yesterday: () => [moment().subtract(1, 'day').startOf('day'), moment().subtract(1, 'day').endOf('day')],today: () => [moment().startOf('day'), moment().endOf('day')],thisweek: () => [moment().startOf('week'), moment().endOf('week')],// and so on..
}
复制代码

这种方式很简单但不灵活,如果需要一个新的时间段就必须改这两个Map才行。而且如果用户有一些特殊的相对时间的话,这种方案就行不通了。

使用结构化数据

为了灵活性考虑,我们可以使用对象来保存相对时间,这里我们需要先理解相对时间由什么组成。

相对时间的抽象

在项目里我们一般用的时间段都是由一个开始点和一个结束点构成,其中一个相对时间点是由一连串计算产生的,这里的计算我们可以分为两类:偏移和区间首尾。对应的moment方法为

// 偏移
moment().add(1, 'hour');
moment().subtract(1, 'day');// 区间首尾
moment().startOf('hour');
moment().endOf('day');
复制代码

实现

对应的数据结构如下

type Unit = 's' | 'm' | 'h' | 'd' | 'w' | 'M' | 'y';interface Offset {type: 'Offset';// 用来表示 add 或者 subtract,一般实际使用都是 subtract 所以可以省略// op: '+' | '-';number: number;unit: Unit;
}interface Period {type: 'Period';// 用来表示 startOf 或 endOf,实际使用时可以使用开始和结束点来区分,所以也可以省略// op: 'start' | 'end';unit: Unit;
}type Calc = Offset | Period;interface TimeRange {start: Array<Calc>;end: Array<Calc>;
}
复制代码

另外只要根据这个数据结构实现一个展示Label的函数和一个计算Duration的函数就行了。

结构化数据提供了很好的灵活性但暴露了几个缺点:

  1. 展示Label的函数不好写,尤其是对于两步以上的计算就得写很多特殊判断,比如 上周 我们的数据长这样(对象写起来太长,用moment表示一下)[moment().sutract(1, 'w').startOf('w'), moment().sutract(1, 'w').endOf('w')],反过来将该对象格式化就得写很多判断代码才行。
  2. 为了方便使用,肯定是需要快速筛选,无论这个列表放在前端还是后端都需要写一大堆代码(快速筛选如下)
  3. 对象不太方便放到query里,比如在我们监控看板里有一个功能,可以让用户在query里带上时间参数来覆盖看板里的默认配置,如果这里是对象的话就不太方便了。

使用相对时间表达式

如果能用表达式来表示上面的结构化数据的话不就能解决以上几条缺点了吗?

相对时间表达式

在这点上Grafana已经提供了一个可用的雏形,我在其语法基础上重写了逻辑,增加了容错性以及语法特性,独立出来了一个库(主页)。这个表达式是基于上一节结构化数据实现的,但是能更简单明了。比如(取自examples)

  • now - 12h: 12 hours ago, same as moment().subtract(12, 'hours')
  • -1d: 1 day ago, same as moment().subtract(1, 'day')
  • now / d: the start of today, same as moment().startOf('day')
  • now \ w: the end of this week, same as moment().endOf('week')
  • now - w / w: the start of last week, same as moment().subtract(1, 'week').startOf('week')

如何解决结构化数据的缺陷

如何解决格式化问题

将表达式格式化的话特殊区间就不需要写代码进行判断了,只需像第一种方式里一样将标准格式的表达式映射到相应的文本上就行了。比如

const LabelMap = {'now-d/d to now-d\\d': '昨天','now-w/d to now-w\\d': '上周的同一天',// so on..
}import { standardize } from 'relative-time-expression';
const start = standardize(' now   - 1   d /d'); // return now-d/d
const end = standardize('-d\\d'); // return now-d\d
const label = LabelMap[`${start} to ${end}`] || `${start} to ${end}`;
expect(label).toEqual('昨天');
复制代码

当然在处理 前x小时, 前x天 这种情况还是需要写一些判断,和上节的处理差不多,如下

// const start, end = ...import { parse } from 'relative-time-expression';if (end === 'now') {// omit error catch codeconst ast = parse(start);if (ast.body.length === 1 && ast.body[0].type === 'Offset') {// 如果start只有一项偏移,那么就可以格式化成 `前{number}{单位}` 了return `前${ast.body[0].number}${ast.body[0].unit}`;}// ...
}
复制代码

解决剩下两个问题

值一旦变成普通字符串的话这两个问题也就迎刃而解了。

时区问题

区间首尾的计算是基于时区的,比如now/d, 用户期望的通常是他所在地区一天的开始时间(当然也不排除想通过另外时区的时间查数据的情况)。如果计算相对时间实在客户端的话,浏览器其实已经帮我们设定好了正确的时区,但是服务端就不一样了,它只能拿到服务器系统所在时区的时间。

所以考虑服务端计算相对时间的需求(监控看板里就有类似需求:通过看板组件id直接调用后端接口拿到数据),客户端在调用这些接口时需要带上时区信息。服务端的处理代码如下

import parse from 'rte-moment';
import moment from 'moment-timezone';
const m = parse('now/d', { base: moment().tz(clientTimezone || 'Asia/Shanghai') });
moment().tz('Asia/Shanghai').startOf('day').isSame(m); // true
复制代码

结语

在监控项目里的时间组件基本参照了Grafana的时间组件,不得不说其在监控方面还有很多值得学习的地方。

另外该项目除了typescript外还用rust练手写了一遍,rust给我印象最深的一点是整套项目构建、文档生成、依赖管理的工具非常好用,上手就可以专心写代码了。


本文转自我的博客

相对时间表达式 —— 解决相对时间序列化的问题相关推荐

  1. 如何修改qq服务器时间,如何解决qq时间与电脑时间不一致问题

    我们在使用qq的过程中,有时会发现qq的时间与电脑的时间不一致,对于这样的问题,我们应该如何解决呢?下面就让学习啦小编告诉你如何解决qq时间与电脑时间不一致问题. 解决qq时间与电脑时间不一致的方法一 ...

  2. php获取时间不正确,php date()获取的时间不对解决办法

    因为php默认获取的是格林威治时间,与北京时间相差8小时. 我们要获取到北京时间有两个办法: 1.修改php.ini配置文件: 打开php.ini文件,一般在php配置根目录下,找到其中的 ;date ...

  3. linux无法设置日期 不允许的操作,如何解决系统时间无法修改的问题

    今天有网友咨询小编"系统时间无法修改"怎么解决的问题,小编也是第一次遇到这种问题,于是请教了我们的技术部的电脑工程师,其实导致系统时间无法修改的原因有很多,我们要耐心的逐一去排查原 ...

  4. 解决虚拟机时间引起的奇怪问题

    一直使用得好好的虚拟机最近出了一个奇怪问题 在虚拟机装好的lamp 在客户端访问phpmyadmin的时候,使用firefox登录没问题,但是使用IE不行 总是停留在登录的界面,而且没有提供任何的出错 ...

  5. 论文浅尝 | 基于模式的时间表达式识别

    本文转载自公众号:南大Websoft.  时间表达式识别是自然语言理解中一个重要而基础的任务.在以前的研究工作中,研究人员已经发现时间词的类型信息可以给识别提供明显的帮助.本文中我们以词类型序列作为表 ...

  6. ios请求头解决参数中文乱码_花了一天时间就解决了一个的请求头传参参数格式bug...

    一天的时间就解决了一个bug就这么过去了,但不能让他就这么过去了,加班要加的有价值,所以现在记录一下这一天的经历,以防下次再踩坑 大致说下我的情况,入坑的不久的前端新手,在做一个项目的重构,用的框架式 ...

  7. 使用faketime修改docker内的时间,解决date: cannot set date: Operation not permitted问题

    使用faketime修改Docker容器时间,解决date:cannot set date operation not permitted问题 docker本质是个进程,有很多资源是使用宿主机的,比如 ...

  8. win10修改时间同步服务器,解决win10时间服务器同步问题|重置win10时间服务配置...

    Windows 任务栏右下角的日期时间,平时可以方便我们查看,帮助我们更好的安排时间.但是有些用户发现Windows时间错误了,调整过好几次,时间还是不对,这篇文章是PE吧给大家带来的使用命令提示符重 ...

  9. java时间的整的表达式_Quartz中时间表达式的设置-----corn表达式

    Quartz中时间表达式的设置-----corn表达式 时间格式: ,   分别对应: 秒>分>小时>日>月>周>年, 举例: 1.每天什么时候执行: 0 59 2 ...

最新文章

  1. struts2学习笔记--线程安全问题小结
  2. python3中的编码与解码
  3. webpack使用加载器来加载CSS样式
  4. LiveVideoStack线上分享第三季(十):Flutter浪潮下的音视频研发探索
  5. Android SlidingMenu插件的使用
  6. 真就卖爆了!两分钟破万台:1999元实在无敌
  7. Golang sync.WaitGroup 简介与用法
  8. Wannafly挑战赛27: D. 绿魔法师(莫比乌斯函数)
  9. 通过GPS测试跑步速度可行性验证
  10. 面经:中国人民银行金融科技研究院
  11. 服务器刷新率和显示器刷新率,什么是屏幕刷新率
  12. 幼儿园故事导入语案例_幼儿园活动教案导入语
  13. fastdfs添加storage节点
  14. PMBOK6相关方:权利利益方格
  15. Object.assign()用法小结
  16. iOS硬编解码相关知识
  17. 百度人脸比对Demo
  18. 人工智能——线性回归(Python实现)
  19. java全栈系列之JavaSE-面向对象(封装详解)034
  20. Tessellation on Any Budget

热门文章

  1. 基于 Hadoop 的58同城离线计算平台设计与实践
  2. “数学界的诺贝尔”公布2019年得主,首位女数学家获奖...
  3. 【高盛重磅报告干货解读】中国 AI 剑指全球第一,BAT 实力对比
  4. 算法提升:图的拓扑排序算法
  5. Springboot实体类配置索引注解
  6. 前端,前端技术,前端技术栈,前端工具等词汇的区别
  7. Android表情文字EmotionText解析
  8. 光纤传感器实验模块_光纤位移传感器实验教学改进
  9. 异步调用如何使用是最好的方式?
  10. 瑞星发布路由器安全报告 无线路由器安全成高危区