我们先来看看Solr日期类型的一些内幕,然后讨论一下Solr日期类型存在的一些问题,最后我们看看怎么解决现存的问题。
概述

DateField

在Solr4.x之前,我们只有DateField,这类型现在用的应该比较少了,它对应Java中的java.util.Date类型。实现上,如你所知它就是一个long的时间戳。所以它相当于我们用LongField。在高版本的Solr已经看不到这个类了。

TrieDateField

在Solr4.x之后,Solr带来一系列的TrieField,其中就有TrieDateField。它对应TrieLongField,Trie是一种数据结构,也叫字典树,又叫前缀树。这种结构非常适合用于区间搜索,这也是一种空间换时间的方式。这里就不展开来聊Trie树了。

DateRangeField

这个可高级了,DateRangeField主要是在区间搜索上做优化,这个优化是从更新存储结构上进行的。前面提到TrieDateField是内部的存储结构是Long,也就是时间戳。但现在不是了,他存储的是String,相当于TextField。

DatePointField

出生于Solr6.5,是一种新的结构PointField,此结构与TrieField类似。DatePointField实际上就是TrieDateField在Trie上的优化,此外便没有其他更改了, 其根本也还是一个Long(LongPointField)的时间戳。

想解释TrieDateField与DateRangeField之间的差异,关键是理解Trie结构。从名字上就可以知道DateRangeField更适合区间搜索的。简单的说,用TrieDateField的话,它就是一个数值,我们很难控制它的有现实意义区间,比如一天、一小时。它只能按数值上意义进行,即按几个bit来分区。因为很难用几个bit来描述一天、一小时的意义。

但我们知道DateRangeField,它是根本是一个字符串,那么它就可以很轻易按我们的现实意义的东西来分区。

你可以这么理解,2017-02-14T12:36:48Z是一个TextField,然后它采用类似于EdgeNGramTokenizer分词器。所以可得到如下的分词结果:

2017
    201702
    20170214
    2017021412
    201702141236
    20170214123648

因此,我们可以用2017表示2017全年的时间区间,即是2017-01-01T00:00:00Z至2017-12-31T23:59:59Z。

之后,我们想要检索2017年06月05日十二点的数据,便可用q=daterange:2017-06-05T12的方式。之后我们可以很方便的检索某个单位的所有数据。当然,同时我们也可以用过检索某天,某月的数据。这些便是时间区间的概念了。后面会详细介绍。

DateRangeField所有属性与TextField雷同,它也不支持docValues=true等。

而DatePointField和TrieDateField实际上就是一个Long/TrieLong,所以它支持docValues=true,可以通过它来加速Facet和Sort的效率。

二、深入理解DateField

在Solr的世界里,其实除了有你熟悉的DatePointField和TireDateField,还有DateRangeField另外一种日期类型。整体来说,Solr所有日期时间类型都是以一个utc时区存储的。对于DatePointField我的态度跟Solr文档一样,不会过多的介绍,因为它TrieDateField在结构用法上完全一样,TrieDateField仅仅只是优化区间搜索,这一点我们强调无数次了。

不要再用TrieDateField

我们前面说过了,TrieField是以空间换时间的一种方式,TrieField优化了区间检索的性能。关于TrieField找机会再来细说。这就是说TrieField不是适合所场景,它仅适合用区间检索,同时这个区间还不能太小。

那为什么我建议大家弃用TrieDateField呢?

因为DateRangeField的出现,使得TrieDateField的存在非常尴尬。因为它的区间很难控制,毕竟TrieDateField的根本还是TrieLong嘛。

A.Solr蹩足时间日期类型

对于DatePointField和TrieDateField便是Solr蹩足时间日期类型的代表,后面DateRangeField有不小进步,但依然不行。好吧,我们还是先来看一下格式。
A.1. Solr支持哪些时间日期格式呢

Solr-Ref-Guide说了,Solr的日期遵循DateTimeFormatter.ISO_INSTANT,即是XML Schema specification中IOS-8601。
这种格式可以描述为yyyy-MM-ddTHH:mm:ssZ,这里的Z表示采用了UTC时区。

关于 DateField 有效格式有且仅有以下几种:
1. 2017-07-06T00:00:00Z
2. 2017-07-06T00:00:00.0Z
3. 2017-07-06T00:00:00.00Z
4. 2017-07-06T00:00:00.000Z

可以用"把日期包起来,也可以在:前面加一个\,此外都不允许。

包括solr-ref-guide提及的datefield:[1972-05-20T17:33:18.772 TO *]也非法的。

A.2. DateRangeField的一些特殊技能

DateRangeField自带一些特殊技能,它的表示方式比较丰富,除上面提及几种格式,还有如下几种:
1. yyyy
2. yyyy-MM
3. yyyy-MM-dd
4. yyyy-MM-ddTHH
5. yyyy-MM-ddTHH:mm
6. yyyy-MM-ddTHH:mm:ss

Solr-Ref-Guide对DateRangeField更是不得了,简直是开了挂了。但事实并没有那么的美好,接下来我们就看看这些黑洞。

yyyy-MMTHH 其实是不可以的
文档对yyyy-MMTHH的说明是这样的Likewise but for an hour of the day。由于文档用了the day和自己的实验结果,我认为文档写错了。应该是yyyy-MM-ddTHH,在DateRangeField,Solr把它解释为’yyyy-MM-dd`,这验证了我们的对DateRangeField存储的说法,以及它的分词方式。

很多情况下,DateRangeField表示就是一个时间区间。如,2017-05-20,正常来说它就是一个时间区间。但是在RangeQuery时,它就必须是一个时间点。当出现在时间区间的下限时,它是2017-05-20T00:00:00Z,如果出现在时间区间的上限时,它的意义是2017-05-20T23:59:59Z。

DateRangeField还支持下面几种区间检索。
1. dateRange:[2017 TO 2017] —— 等同于 dateRange:2017。
2. dateRange:[2017 TO 2017-05] —— 等同于 dateRange:[2017-01-01T00:00:00Z TO 2017-05-31T24:00:00Z]
3. dateRange:[2017-05 TO 2017] —— 等同于 dateRange:[2017-05-01T00:00:00Z TO 2017-12-31T24:00:00Z]

等等,可以自行组合。
B.开挂指令,DateMathParser

所有日期时间类型都是允许我们有一些简单的计算,不过要注意的是,它的所有关键字都是大写的。所有的计算功能都由DateMathParser提供。

先来看一下,DateMathParser内置的一些关键字:(必须是大写)
NOW
YEAR
MONTH
DAY
DATE
HOUR
MINUTE
SECOND
MILLI
MILLISECOND
TZ

注,所有时间单位都可以带S,也可以不带,意义一样。

DateMathParser基本可以分为两类:
- 取整

取整即是取指定单位后面的数值置零,比如自然月,自然日等。

例如:NOW/DAY, NOW/HOURS表示,取当天零点零分;取当时零分。如果时下是2017-05-20T23:32:33Z,那么即是2017-05-20T00:00:00Z,2017-05-20T23:00:00Z。

这里/并不是我们数学意义上的除,它是取整,相当于数学意义上的A/B*B。

加减

除了取整之外,还有另一个非常实用的功能便是时间前后推移了。
NOW-1DAY,往后推移一天。如果时下是2017-05-20T23:32:33Z,由Solr计算后便得到2017-05-19T23:32:33Z;当然后若是NOW+1DAY便会得到2017-05-21T23:32:33Z。

这两类计算都非常好理解,也非常好用。

时下依然是2017-05-20T23:32:33Z,我想想看看今天零点到在数据时,我们可以直接用NOW/DAY即可。
但如果我想搜索昨天零点到今天零点的数据,应该怎么办呢?对就是datetime:[NOW/DAY-1DAY TO NOW/DAY],便能得到datetime:[2017-05-19T00:00:00Z TO 2017-05-20T00:00:00Z]。

若仅仅如此,那你也太小看看我们大Solr了。Solr当然必须要能支持取整和加减的混合运算的啊。

需要注意的,Solr的时间计算都把时间转成时间戳进行计算的,因此计算结果必然是一个某的时刻,而非一个时间区间;在浏览器测试时,还需要要注意把+转义。
B.1 关键字 NOW

NOW可以指定自己的时间,用来修正当前时间。它仅支持时间戳,且精确到毫秒。也就是说它用来代替计算公式中NOW的含义的,当搜索时并没有采用时间计算公式时,它没有什么任意意义,当然也不会报错的。
B.2 关键字 TZ

TZ,TimeZone的缩写,它的作用非常单一。它仅仅只能修正DateMathParser在计算时的时间区,比如当q=daterange:NOW时,当有TZ=Asia/Shanghai时,它表示北京时间。否则NOW会表示为UTC时间。

它并不能修正Solr输出、输入时区。
C.接下来我们来看看Solr日期的那些坑

首先官方给出来文档SOLR-Ref-Guide中提及关于Working with Dates的很多东西,其实并不然,这给我这种文档狗带来极大的不便。

格式
    前面我们也提到过,Solr的日期时间格式的限制是非常苛刻的,并非像文档所介绍的那样。
    DateRangeField的搜索格式也有问题
    虽然介绍过,再提一次。q=dateRange:2017-06T12这格式并不支持。这种格式,当然在DateRangeField,Solr会把它解释为2017-06-12,不过其它日期时间类型并不支持了,这也又验证我们对DateRangeField分词解释了。
    对于格式NOW+6MONTHS+3DAYS/DAY的解释
    Solr文档对NOW+6MONTHS+3DAYS/DAY的解释很大高上,然并没有。这货有点难理解,其实它等同于NOW/DAY+6MONTHS+3DAYS。
    所以啊,我建议大家在使用DateMathParser的时候,尽量不要搞事情,不要写这些奇葩计算公式。
    为什么Solr输出输入都用UTC时区呢?
    我以为这是Solr最坑的地方了,实现对TrieDateField/DateField,Lucene存储的是时间戳。对于时间戳来说,并不存在时区问题,然后按用户指定时区进行转义即可嘛,为什么要搞成这样呢。

其实DateRangeField时,存储的数据是字符串。理论上,Solr并不需要强制使用UTC的时间的。即是你可以在提交文档时,可能自己先转成yyyy-MM-ddTHH:mm:ssZ的形式。这样,你就可以采用你机器的时区,但这样便有歧义了,不建议你这么用。
C.如何解决Solr时区问题

想要优雅解决这个问题其实并不难,自己自定义一个FieldType即可。
简单的说,对于DatePointField/TrieDateField的话,你只需要Copy对应的DateField代码,然后把toExternal(IndexableField f)中的Date#toInstant()更改为DateFormat.parse()即可。

对TrieDateField和DatePointField
    看一下TrieField实现的源码吧,了解java date的同学一眼就能看问题的所在,即toInstant是一定是UTC时区的,因此我们需要覆盖它的实现即好。

@Override
public String toExternal(IndexableField f) {
    return (type == NumberType.DATE)
        ? ((Date) toObject(f)).toInstant().toString()
            : toObject(f).toString();
}

1
    2
    3
    4
    5
    6

下面是TrieDateField的源码,同时我在最后加上我们对toExternal重新实现了。

package cn.dmsolr.schema;

import ...

public class TrieDateField extends TrieField implements DateValueFieldType {
    {
        this.type = NumberType.DATE;
    }

@Override
    public Date toObject(IndexableField f) {
        return (Date)super.toObject(f);
    }

@Override
    public Object toNativeType(Object val) {
        if (val instanceof String) {
            return DateMathParser.parseMath(null, (String)val);
        }
        return super.toNativeType(val);
    }

@Override // 关键代码
    public String toExternal(IndexableField f) {
        final DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        format.setTimeZone(SolrRequestInfo.getRequestInfo().getClientTimeZone());
        return format.format((Date) toObject(f));
    }
}

1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

对于DateRangeField
    再来看看DateRangeField而言,更简单,即是把所有含Z都删除即可。实现Solr在存储时不带存储Z。

上面我们已经怎么去扩展和实现自己的SchemaField了,接下来就是怎么用了。首先需要把上面的代码打成一个jar包,然后在solrconfig.xml把引用进来,然后在schema.xml加下面这行代码:

<fieldType name="dm_pdate" class="cn.dmsolr.schema.DatePointField" docValues="false" />
<fieldType name="dm_tdate" class="cn.dmsolr.schema.TrieDateField" docValues="false" />
<fieldType name="dm_range" class="cn.dmsolr.schema.DateRangeField" docValues="false" />

1
    2
    3

总结一下

TrieDateField不要再用了,需要用区间检索请采用DateRangeField,若不需要区间检索那就好好用DateField吧;Z表示UTC时区,因此我们只能自己去扩展DatePointField了。这也是为什么Solr输出都是UTC时间,因为都带用Z,所以我们自定义了DateField;又介绍使用过程需要用的一些小细节。等等

当你熟悉这些细节之后,才玩转Solr,而不是被Solr玩转了。

Solr Date类型的哪些你不得不了解的细节的更多相关文章

  1. 1.4.2 solr字段类型--(1.4.2.4)使用Dates(日期)

    1.4.2 solr字段类型 (1.4.2.1) 字段类型定义和字段类型属性. (1.4.2.2) solr附带的字段类型 (1.4.2.3) 使用货币和汇率 (1.4.2.4) 使用Dates(日期 ...

  2. 1.4.2 solr字段类型--(1.4.2.2)solr附带的字段类型

    1.4.2 solr字段类型 (1.4.2.1) 字段类型定义和字段类型属性. (1.4.2.2) solr附带的字段类型 (1.4.2.3) 使用货币和汇率 (1.4.2.4) 使用Dates(日期 ...

  3. 1.4.2 solr字段类型--(1.4.2.1)字段类型定义和字段类型属性

    1.4.2 solr字段类型 (1.4.2.1) 字段类型定义和字段类型属性. (1.4.2.2) solr附带的字段类型 (1.4.2.3) 使用货币和汇率 (1.4.2.4) 使用Dates(日期 ...

  4. Solr字段类型field type的定义

    摘要: Solr的字段类型定义了Solr如何解析字段数据并将数据检索出来,了解Solr的字段类型定义有助于更好的配置与使用Solr. 字段类型的定义 字段类型的定义主要包含如下四个方面的信息: 名称 ...

  5. 【Spring】SpringMVC中浅析Date类型数据的传递

    在控制器中加入如下代码: @InitBinder public void initBinder(ServletRequestDataBinder bin){ SimpleDateFormat sdf ...

  6. 向mysql中插入Date类型的数据

    先看数据库表的定义 date字段为sql.date类型.我要向其中插入指定的日期和当前日期. 一.插入当前日期 思路:先获取当前系统,在将当前系统时间转换成sql类型的时间,然后插入数据库.代码如下 ...

  7. java基础--java.util.Date类型小结

    首先先来了解一下Date数据类型: . Date类型通常要和另一个 java.text.SimpleDateFormat类联合使用. 把long-->Date: public Date(long ...

  8. ES数据-MySql处理Date类型的数据导入处理

    用ES的小伙伴们,相信大家都遇到过Mapping处理Date类型的数据头疼问题吧. 不用头疼了,我来给你提供一种解决方案: 1.Maping定义为: {  "mappings": ...

  9. java 中的SimpleDateFormat、Date函数以及字符串和Date类型互转

    SimpleDateFormat是一个以与语言环境有关的方式来格式化和解析日期的具体类.它允许进行格式化(日期 -> 文本).解析(文本 -> 日期)和规范化. SimpleDateFor ...

随机推荐

  1. kafka 的 docker 镜像使用

    Kafka 还没有提供官方的镜像(2019.01.29),能找到的都是一些社区维护的镜像包. 这里使用这个镜像:https://hub.docker.com/r/spotify/kafka

  2. MySQL 术语

    MySQL 术语: MySQL 术语 含义 B-树 英文:Balance Tree:读音:B树(中间的横线,是分隔符的意思:注意:不读"B减树")

  3. Nginx浏览目录配置及美化

    https://segmentfault.com/a/1190000012606305 在项目中有一个功能需要在浏览器页面中浏览服务器的目录.服务器使用Nginx,而Nginx提供了相应的ngx_ht ...

  4. Jmeter -- HTTP Request Defaults HTTP请求默认值

    一.HTTP Request Defaults的作用: 该组件可以为我们的http请求设置默认的值.假如,我们创建一个测试计划有很多个请求且都是发送到相同的server,这时我们只需添加一个Http ...

  5. JDK1.8中如何用ScriptEngine动态执行JS

    JDK1.8中如何用ScriptEngine动态执行JS jdk1.6开始就提供了动态脚本语言诸如JavaScript动态的支持.这无疑是一个很好的功能,毕竟Java的语法不是适合成为动态语言.而JD ...

  6. msp430学习笔记-DAC12

    MSP430F169 的DAC12 模块有2 个DAC 通道,并且可以用DAC12GRP控制位将多个DAC12通道组合起来,实现同步更新,硬件还能确保同步更新独立于任何中断或者NMI事件. DAC12 ...

  7. 关键两招就解决Wampserver 打开localhost显示IIS7图片问题

    我们在安装集成环境Wampserver之后,有时会遇到一个问题, 打开localhost显示一张IIS7图片,这个问题该如何解决呢,我在网上找了一些说的都很乱,我在这里简单整理了一下解决方法   1  ...

  8. C/C++基础----特殊工具和技术 (重载new和delete,RTT,限定作用域的枚举类型,类成员指针,嵌套类,局部类,volatile,链接指示 extern “C”)

    重载new和delete 1调用operator new( 或new[])标准库函数分配足够大的.原始的.未命名的内存空间以便存储特定类型的对象 2编译器运行相应地构造函数以构造这些对象,并为其传入初 ...

  9. Ubuntu 14.10 下安装伪分布式hive-0.14.0

    本地独立模式,MySQL作为元数据库 1 安装环境准备 1.1 安装JDK,在安装hadoop时候已经安装了,参考http://www.cnblogs.com/liuchangchun/p/40972 ...

  10. IDC:UPS(不间断电源)

    ylbtech-IDC:UPS(不间断电源) UPS(Uninterruptible Power System/Uninterruptible Power Supply),即不间断电源,是将蓄电池(多 ...