热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

平时开发监控系统时免不了与时序数据库的查询打交道,在查时序数据库时时间范围通常会展示为两种形式:相对时间和绝对时间。对于监控系统来说,日常观察指标、建立看板基本都是使用相对时间,因为使用绝对时间的话一是不能及时更新,二是容易引发慢查询。而绝对时间的使用场景一般是定位具体问题。在我们的监控前端里主要使用相对时间的地方有两个,一是adhoc查询,另一个是看板。在这两处需求里都需要对相对时间序列化,前者

平时开发监控系统时免不了与时序数据库的查询打交道,在查时序数据库时 时间范围 是必不可少的条件,所以在查询的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;
  end: Array;
}

另外只要根据这个数据结构实现一个展示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 code
  const 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给我印象最深的一点是整套项目构建、文档生成、依赖管理的 工具 非常好用,上手就可以专心写代码了。

本文转自我的博客


以上所述就是小编给大家介绍的《相对时间表达式 —— 解决相对时间序列化的问题》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 我们 的支持!


推荐阅读
  • 本文详细介绍了在Linux虚拟化部署中进行VLAN配置的方法。首先要确认Linux系统内核是否已经支持VLAN功能,然后配置物理网卡、子网卡和虚拟VLAN网卡的关系。接着介绍了在Linux配置VLAN Trunk的步骤,包括将物理网卡添加到VLAN、检查添加的VLAN虚拟网卡信息以及重启网络服务等。最后,通过验证连通性来确认配置是否成功。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 本文讨论了如何使用IF函数从基于有限输入列表的有限输出列表中获取输出,并提出了是否有更快/更有效的执行代码的方法。作者希望了解是否有办法缩短代码,并从自我开发的角度来看是否有更好的方法。提供的代码可以按原样工作,但作者想知道是否有更好的方法来执行这样的任务。 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • 微软评估和规划(MAP)的工具包介绍及应用实验手册
    本文介绍了微软评估和规划(MAP)的工具包,该工具包是一个无代理工具,旨在简化和精简通过网络范围内的自动发现和评估IT基础设施在多个方案规划进程。工具包支持库存和使用用于SQL Server和Windows Server迁移评估,以及评估服务器的信息最广泛使用微软的技术。此外,工具包还提供了服务器虚拟化方案,以帮助识别未被充分利用的资源和硬件需要成功巩固服务器使用微软的Hyper - V技术规格。 ... [详细]
  • 本文介绍了在Go语言中可见性与scope的规则,包括在函数内外声明的可见性、命名规范和命名风格,以及变量声明和短变量声明的语法。同时,还介绍了变量的生命周期,包括包级别变量和局部变量的生命周期,以及变量在堆和栈上分配的规则和逃逸分析的概念。 ... [详细]
  • Iwanttointegratesort,order,maxandoffsetinafindAllquery.Thefollowingworksfine:我想在fin ... [详细]
author-avatar
Magic淘
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有