彻底解决Solr日期类型的时区问题
声明
- 文档是基于Solr6.6写的
- Solr是部署在Tomcat上的
3.Tomcat是部署在CentOS上的,不过Linux、Windows差不多 - 文章的问题的最终解决是用第四种方式,前三种想看看看,不想看可以直接看第四种方式
- 本文使用的最终解决方案适用于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的时区问题-Scham配置文件1.png
解决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管理界面查询结果.png
通过Java API查询结果:
解决Solr的时区问题-Java API查询结果.png
对比发现work_time、import_time、capture_time与cur_date日期是一致的
问题解决
参考文章是:Hadoop技巧(04):简易处理solr date 时区问题
最后
看了下这篇博客的作者用的Solr版本是Solr4的,我用的Solr版本是6.6的,用同样的方式解决都没有问题,所以如果你用的是在这之间的Solr版本都是没有问题的