彻底解决Solr日期类型的时区问题

彻底解决Solr日期类型的时区问题

声明

  1. 文档是基于Solr6.6写的
  2. Solr是部署在Tomcat上的
    3.Tomcat是部署在CentOS上的,不过Linux、Windows差不多
  3. 文章的问题的最终解决是用第四种方式,前三种想看看看,不想看可以直接看第四种方式
  4. 本文使用的最终解决方案适用于Solr4到Solr6.6,Solr7没有试过

问题描述

Solr的日期类型是基于UTC时间的,也就是英国的格林尼治天文台的时间,而我们中国在东八区,用的是GMT时间,比UTC时间多了8个小时。真不知道开发Solr的程序员是怎么想的,全世界的人用Solr,不管你是在哪儿的,都得用UTC时间,太TMD坑了。
这个问题前前后后搞了一周了,总结起来有有四种解决思路,本文最终采用的是第四种方案,这里前三种方案也说一下。

解决方案

方案1:修改配置文件的时区改为东8区

先说下这种方法不可行
这种方式网上搜到的最多,大致意思是说在Tomcat的setenv.sh文件里添加配置时区为为东八区:

-Duser.timezone=Asia/Shanghai

我这边配置过以后重启Tomcat发现日志的时间是过来了,但是往Solr里写数据和从Solr里读数据不是一样少了8个小时,看下Solr的源码你就知道怎么回事儿了:
org.apache.solr.response.TextResponseWriter

public void writeDate(String name, Date val) throws IOException {
  writeDate(name, val.toInstant().toString());
}

这个是Solr写入日期类型的数据的方法,看源码我们就发现问题了:java.util.Date类的toInstant()方法返回的就是一个UTC时间,你在Tomcat的配置文件里改,当然不起作用啦

方案2:不用Solr的日期类型,直接用String代替

这种方式确实可以很多人用,我们之前的时候就是用这种方式,不过这不是我们讨论的范围,直接Pass掉
下面两种方式都是修改Solr的源码

方案3:修改数据类型源码,不使用UTC时间

修改TrieDateField类(DatePointField,DateRangeField)的源码
参考文章:
Solr Date类型的哪些你不得不了解的细节
文章的作者对Solr的日期类型讲解得非常详细,
按作者写的进行操作的时候,发现不太好操作,于是就联系到文章的作者,交流了一下,发现该博客的作者用的Solr是6.5的版本,源码有些不一样(我用的是Solr6.1),幸运的是作者人非常非常好,非常非常耐心(没错4个非常)地回复了我所有的疑问,直到把问题解决。这种方式这里就不再多说了,需要的话可以直接点开作者的博客
不过问题虽然解决了,但是要修改好几处地方,而且如果Solr再版本升级的话说不定需要修改的地方又变了,Solr6.1和Solr6.5两个版本需要修改的地方就不一样。
有没有更好的办法呢

方案4:修改工具类源码,使UTC时间与本地时间显示得一致(最终方案)

此方案只需要修改一个类的两个地方(读、写的方法),所有的日期类型全部搞定
大致思路是:
UTC时间不是比东八区少了8个小时吗?在往Solr里写数据的时候加上8个小时,从Solr里查数据的时候减去8个小时,这样UTC时间显示得就和咱大中国的一致了。
操作是:
修改JavaBinCodec工具类
具体路径是solr-core-6.6.jar包里的org.apache.solr.common.util.JavaBinCodec
把这个类的源码复制出来,包名类名不要变,修改readObject()和writePrimitive()方法具体如下:
readObject()方法:

switch (tagByte) {
    case NULL:
        return null;
    case DATE:
        //存储的时候solr的时间格式是utc的会少8个小时, 所以在writeVal方法里加上了8小时
        // 这里再读取的时候就需要再减去8个小时
        // 28800000l为8小时的毫秒数
        return new Date(dis.readLong() - 28800000l);
    case INT:
        return dis.readInt();
...

writePrimitive()

else if (val instanceof Date) {
            daos.writeByte(DATE);
            //UTF时间比东8区少了8个小时,这里加8小时
            daos.writeLong((((Date) val).getTime() / 1000) * 1000 + 28800000l);
            return true;
        } else if (val instanceof Boolean) {
...

然后打包,扔到Tomcat里,并重启Tomcat
Schema.xml的相关配置:

<!-- 当前时间与另外的三种类型做对比 -->
<field name="cur_date" type="string" indexed="false" stored="true"/>
<!-- DateField日期类型 -->
<field name="import_time" type="date" indexed="true" stored="false" docValues="true"/>
<!-- DatePointField日期类型 -->
<field name="capture_time" type="pdate" indexed="true" stored="false" docValues="true"/>
<!-- DateRangeField日期类型 -->
<field name="work_time" type="rdate" indexed="true" stored="true"/>
...
<fieldType name="date" class="solr.TrieDateField" docValues="true" precisionStep="0" positionIncrementGap="0"/>
<fieldType name="pdate" class="solr.DatePointField" docValues="true"/>
<fieldType name="rdate" class="solr.DateRangeField" docValues="false" />
彻底解决Solr日期类型的时区问题 解决Solr的时区问题-Scham配置文件1.png

 

彻底解决Solr日期类型的时区问题 解决Solr的时区问题-Schema配置文件2.png

下面是往Solr写测试数据的部分代码:
往Solr里写数据的代码:

SolrInputDocument doc = new SolrInputDocument();
doc.addField("cur_date", new SimpleDateFormat("yyyy-MM-dd HH:MM:SS").format(new Date()));
doc.addField("import_time", new Date());
doc.addField("capture_time", new Date());
doc.addField("work_time", new Date());
...

从Solr里读取数据的代码:

System.out.println("import_time \t" +  doc.get("import_time"));
System.out.println("capture_time \t" + doc.get("capture_time"));
System.out.println("work_time \t" + doc.get("work_time"));

说明一下:
import_time的类型是TrieDateField
capture_time的类型是DatePointField
work_time的类型是DateRangeField

下面是从Solr里查出出来的结果

彻底解决Solr日期类型的时区问题 解决Solr的时区问题-Solr管理界面查询结果.png

 

通过Java API查询结果:

 

彻底解决Solr日期类型的时区问题 解决Solr的时区问题-Java API查询结果.png


对比发现work_time、import_time、capture_time与cur_date日期是一致的
问题解决

参考文章是:Hadoop技巧(04):简易处理solr date 时区问题

最后

看了下这篇博客的作者用的Solr版本是Solr4的,我用的Solr版本是6.6的,用同样的方式解决都没有问题,所以如果你用的是在这之间的Solr版本都是没有问题的

上一篇:启动Solr提示Java版本低,无法启动的解决办法


下一篇:Solr Linux 安装