写了个深入详解Elasticsearch专栏,欢迎大家品鉴!

 
1、深入详解Elasticearch:http://blog.csdn.net/column/de ... .html
 
2、这个专栏,起初主要用来研究关系型数据mysql/oracle非关系型数据库mongo与Elaticsearch的实时同步问题,随着延伸和项目需要,还会路线拓展ES java开发等问题;
 
3、之前的研究都是基于Elasticsearch2.3.4版本。基础原理想通。 5.X,6.X版本后续也会跟进。
 
欢迎大家品鉴,多提不足!
 
终生学习者:铭毅天下,博客地址:http://blog.csdn.net/laoyang360
继续阅读 »
 
1、深入详解Elasticearch:http://blog.csdn.net/column/de ... .html
 
2、这个专栏,起初主要用来研究关系型数据mysql/oracle非关系型数据库mongo与Elaticsearch的实时同步问题,随着延伸和项目需要,还会路线拓展ES java开发等问题;
 
3、之前的研究都是基于Elasticsearch2.3.4版本。基础原理想通。 5.X,6.X版本后续也会跟进。
 
欢迎大家品鉴,多提不足!
 
终生学习者:铭毅天下,博客地址:http://blog.csdn.net/laoyang360 收起阅读 »

GZIP造成JAVA Native Memory泄漏案例

[携程旅行网  吴晓刚]

近期公司某个线上JAVA应用出现内存泄漏问题,整个排查过程颇费周折,前后耗费了近2周才定位到问题根源并予以修复。排查问题过程中在网上翻查了大量的资料,发现国内几乎没有文章能对同类问题做透彻的分析和找到问题真正根源的。即使国外的各类博客和文章,也少有正确的分析。因此感觉有必要对问题根源和相关案例做一个总结,希望能为国内开发者避免踩上同类陷阱提供一些帮助。

开门见山,先列一下收集到的同类问题案例集:


这些案例涉及到的不乏一些流行的开源软件如Lucene, Elasticsearch, Kafka,并且某些Bug版本在大量公司有线上部署。这些案例的问题根源都惊人的一致,即在JAVA里使用GZIP库进行数据流的压缩/解压之后,忘记调用流的close()方法,从而造成native memory的泄漏。

关于这类问题的分析方法和工具,上面收集的案例集里有非常详尽的描述,这里就不再班门弄斧一一赘述了。只结合我们自己的案例,做一个比较简短的介绍和总结。

我们公司这个案例的排查之所以花了近2个礼拜,其中一个重要原因是这个应用是通过Docker部署的。应用上线运行一段时间后,会被Docker的OOM killer给Kill掉,查看JVM监控数据却发现Heap使用得很少,甚至都没有old GC发生过,top里看这个JAVA进程的RSS内存占用远高于分配的Heap大小。很自然的,研发人员第一反应是底层系统的问题,注意力被转移到研究各种Docker内存相关的参数上。 而我知道ElasticCloud曾经也被某些版本的linux内核bug困扰,docker可能会误杀JVM (参见 Memory Issues We'll Remember),bug的内核版本和docker版本和我们线上部署的又很接近,因此这个内核bug也被加入到了怀疑列表中。 事后证明这个方向是错误的,浪费了一些时间。

在一段时间排查无果后,为了缩小排查范围,我们决定将这个应用部署到VM上做对比测试。结果内存泄漏问题依然存在,因而排除掉了Linux内核和docker本身的问题。

同期也参考过一篇关于DirectByteByffer造成堆外内存泄漏问题的分析博客,JVM源码分析之堆外内存完全解读 ,考虑到问题现象和我们类似,我们的应用也有用到netty,DBB泄漏也被列为怀疑对象。然而在JVM启动里参数里对MaxDirectMemorySize做了限制后,经过一段时间对外服务,JAVA进程的RSS仍然会远超过HEAP + MDM设置的大小。

这期间我们也使用过NMT 工具分析HEAP内存占用情况,然而这个工具报告出来的内存远小于RSS,也就是说这多出来的内存并没有被JVM本身用到,泄漏的是native memory。 JAVA应用产生native memory泄漏通常是在使用某些native库时造成的,因此注意力转移到JNI。

最终帮助我们找到正确方向的是开头列的 Debugging Java Native Memory Leaks 这篇由Twitter工程师写的博客。 博客里介绍了如何使用jemalloc来替换glibc的malloc,通过拦截和追踪JVM对native memory的分配申请,从而可以分析出HEAP以外的内存分配由哪些方法调用产生的。博客里提到产生泄漏的原因是忘记关闭GZIPOutputStream,巧合的是我们线上应用也使用了gzip压缩服务请求数据,于是查看了一下相关的代码,果然发现有忘记关闭的stream。 找到根源后,解决问题就简单了,一行代码修复。
 
对于ElasticSearch用户,要注意的是某些版本存在这个泄漏问题,对于小内存机器上运行的ES服务可能会有较大的影响。 可是官方没有明确列出所有受影响的版本,只在博客里提到5.2.1修复了这些问题。 因此如果你有顾虑的话,可以用top命令看一下ES JAVA进程的RSS消耗,如果大大于分配的HEAP,有可能就是中招啦。 
继续阅读 »
[携程旅行网  吴晓刚]

近期公司某个线上JAVA应用出现内存泄漏问题,整个排查过程颇费周折,前后耗费了近2周才定位到问题根源并予以修复。排查问题过程中在网上翻查了大量的资料,发现国内几乎没有文章能对同类问题做透彻的分析和找到问题真正根源的。即使国外的各类博客和文章,也少有正确的分析。因此感觉有必要对问题根源和相关案例做一个总结,希望能为国内开发者避免踩上同类陷阱提供一些帮助。

开门见山,先列一下收集到的同类问题案例集:


这些案例涉及到的不乏一些流行的开源软件如Lucene, Elasticsearch, Kafka,并且某些Bug版本在大量公司有线上部署。这些案例的问题根源都惊人的一致,即在JAVA里使用GZIP库进行数据流的压缩/解压之后,忘记调用流的close()方法,从而造成native memory的泄漏。

关于这类问题的分析方法和工具,上面收集的案例集里有非常详尽的描述,这里就不再班门弄斧一一赘述了。只结合我们自己的案例,做一个比较简短的介绍和总结。

我们公司这个案例的排查之所以花了近2个礼拜,其中一个重要原因是这个应用是通过Docker部署的。应用上线运行一段时间后,会被Docker的OOM killer给Kill掉,查看JVM监控数据却发现Heap使用得很少,甚至都没有old GC发生过,top里看这个JAVA进程的RSS内存占用远高于分配的Heap大小。很自然的,研发人员第一反应是底层系统的问题,注意力被转移到研究各种Docker内存相关的参数上。 而我知道ElasticCloud曾经也被某些版本的linux内核bug困扰,docker可能会误杀JVM (参见 Memory Issues We'll Remember),bug的内核版本和docker版本和我们线上部署的又很接近,因此这个内核bug也被加入到了怀疑列表中。 事后证明这个方向是错误的,浪费了一些时间。

在一段时间排查无果后,为了缩小排查范围,我们决定将这个应用部署到VM上做对比测试。结果内存泄漏问题依然存在,因而排除掉了Linux内核和docker本身的问题。

同期也参考过一篇关于DirectByteByffer造成堆外内存泄漏问题的分析博客,JVM源码分析之堆外内存完全解读 ,考虑到问题现象和我们类似,我们的应用也有用到netty,DBB泄漏也被列为怀疑对象。然而在JVM启动里参数里对MaxDirectMemorySize做了限制后,经过一段时间对外服务,JAVA进程的RSS仍然会远超过HEAP + MDM设置的大小。

这期间我们也使用过NMT 工具分析HEAP内存占用情况,然而这个工具报告出来的内存远小于RSS,也就是说这多出来的内存并没有被JVM本身用到,泄漏的是native memory。 JAVA应用产生native memory泄漏通常是在使用某些native库时造成的,因此注意力转移到JNI。

最终帮助我们找到正确方向的是开头列的 Debugging Java Native Memory Leaks 这篇由Twitter工程师写的博客。 博客里介绍了如何使用jemalloc来替换glibc的malloc,通过拦截和追踪JVM对native memory的分配申请,从而可以分析出HEAP以外的内存分配由哪些方法调用产生的。博客里提到产生泄漏的原因是忘记关闭GZIPOutputStream,巧合的是我们线上应用也使用了gzip压缩服务请求数据,于是查看了一下相关的代码,果然发现有忘记关闭的stream。 找到根源后,解决问题就简单了,一行代码修复。
 
对于ElasticSearch用户,要注意的是某些版本存在这个泄漏问题,对于小内存机器上运行的ES服务可能会有较大的影响。 可是官方没有明确列出所有受影响的版本,只在博客里提到5.2.1修复了这些问题。 因此如果你有顾虑的话,可以用top命令看一下ES JAVA进程的RSS消耗,如果大大于分配的HEAP,有可能就是中招啦。  收起阅读 »

找寻TF_IDF和BM25的评分计算优化排序

1.下面简述下如何根据explain解释TFIDF和BM25的评分计算
2.首先是TFIDF
使用ik_smart分词器,ES为2.3.3
文档是:分词结果是
"伟业我爱我家"     分词结果:【伟业,我,爱我,家】
"我爱我家"     【我,爱我,家】
这两个。
multi_match  匹配,query=我爱我家
排名如下
-----------------------------------------------------------
"伟业我爱我家"    "_score": 6.8563557,
详细参数 
"我":tf=1,idf=6.7638364,fieldNorm=0.5,queryNorm=0.07292504,
“爱我”: tf=1,idf=6.7638364,fieldNorm=0.5,queryNorm=0.07292504
“家”: tf=1,idf=6.278329,fieldNorm=0.5,queryNorm=0.07292504
----------------------------------------------------------
"我爱我家"          "_score": 6.7839246,
"我":tf=1,idf=6.9336233,fieldNorm=0.5,queryNorm=0.07370365,
“爱我”: tf=1,idf=6.9336233,fieldNorm=0.5,queryNorm=0.07370365
“家”: tf=1,idf=6.9336233,fieldNorm=0.5,queryNorm=0.07370365
---------------------------------------------------------
其中queryNorm是由每个term词项的idf综合计算而来,所以在每个文档中,他都是一样的。
然后仔细比较得分,觉得每个得分都可以被推算出来
但是排序结果不符合期望:
queryNorm 官方文档也说了基本没有什么用
tf=1没什么可说
idf有些问题,比如"爱我"在这两个文档中是不同的(这是因为这两个文档在不同的分片中引起的)
那这么说来,TFIDF的得分就仅仅受tf,idf,fieldNorm控制,
而idf因为分片不均匀可能会出现一点差异,fieldNorm又犹由于精度让长度为3或者4 的文档值都为0.5
。综上:tfidf在这种量不多(200万)的短文本检索下,效果很差。

这种情况下,我该怎么优化这个排序呢(让“我爱我家”,排在"伟业我爱我家"前面呢?)
 
 
 
------------------BM25的详情稍后补上-------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
继续阅读 »
1.下面简述下如何根据explain解释TFIDF和BM25的评分计算
2.首先是TFIDF
使用ik_smart分词器,ES为2.3.3
文档是:分词结果是
"伟业我爱我家"     分词结果:【伟业,我,爱我,家】
"我爱我家"     【我,爱我,家】
这两个。
multi_match  匹配,query=我爱我家
排名如下
-----------------------------------------------------------
"伟业我爱我家"    "_score": 6.8563557,
详细参数 
"我":tf=1,idf=6.7638364,fieldNorm=0.5,queryNorm=0.07292504,
“爱我”: tf=1,idf=6.7638364,fieldNorm=0.5,queryNorm=0.07292504
“家”: tf=1,idf=6.278329,fieldNorm=0.5,queryNorm=0.07292504
----------------------------------------------------------
"我爱我家"          "_score": 6.7839246,
"我":tf=1,idf=6.9336233,fieldNorm=0.5,queryNorm=0.07370365,
“爱我”: tf=1,idf=6.9336233,fieldNorm=0.5,queryNorm=0.07370365
“家”: tf=1,idf=6.9336233,fieldNorm=0.5,queryNorm=0.07370365
---------------------------------------------------------
其中queryNorm是由每个term词项的idf综合计算而来,所以在每个文档中,他都是一样的。
然后仔细比较得分,觉得每个得分都可以被推算出来
但是排序结果不符合期望:
queryNorm 官方文档也说了基本没有什么用
tf=1没什么可说
idf有些问题,比如"爱我"在这两个文档中是不同的(这是因为这两个文档在不同的分片中引起的)
那这么说来,TFIDF的得分就仅仅受tf,idf,fieldNorm控制,
而idf因为分片不均匀可能会出现一点差异,fieldNorm又犹由于精度让长度为3或者4 的文档值都为0.5
。综上:tfidf在这种量不多(200万)的短文本检索下,效果很差。

这种情况下,我该怎么优化这个排序呢(让“我爱我家”,排在"伟业我爱我家"前面呢?)
 
 
 
------------------BM25的详情稍后补上-------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  收起阅读 »

Emacs与ElasticSearch

一直想找一个数据库用于存储各种的代码片段

mysql会一点 mongodb也看过

但是最终都没有弄成(或许是我性格的原因,想的太多,做的太少(算是过度设计的一种吧))

后来发现了ElasticSearch

觉得他能够实现我的想法(或许只是他简介里的一句话,大致的意思是:多看看数据,而非让他们躺在仓库里)

我会将过程记录下来,希望对后来人有帮助

(不过一切都是在Emacs环境下的,知道的人应该不多,所以能帮到的人也就更少了(其实他们也不用我帮))
继续阅读 »
一直想找一个数据库用于存储各种的代码片段

mysql会一点 mongodb也看过

但是最终都没有弄成(或许是我性格的原因,想的太多,做的太少(算是过度设计的一种吧))

后来发现了ElasticSearch

觉得他能够实现我的想法(或许只是他简介里的一句话,大致的意思是:多看看数据,而非让他们躺在仓库里)

我会将过程记录下来,希望对后来人有帮助

(不过一切都是在Emacs环境下的,知道的人应该不多,所以能帮到的人也就更少了(其实他们也不用我帮)) 收起阅读 »

分布式搜索引擎教程-Elasticsearch

      大家好,我是重构人生,很开心能在这里和大家分享我的免费Elasticsearch 视频教程。 我是一个Elasticsearch 的DevOps ,使用了很久的开源软件,在开源社区也获得过很多小伙伴的帮助。我希望自己能为社区贡献自己的一份力。
 
      我应该算是个热心肠的人吧,喜欢乐于助人,不过有些人的提问方式让我非常反感,甚至是厌恶,什么样的人呢,第一种,官方文档,或者百度,或者GOOGLE,等方式轻易的就可以找到解决方案却仍然拿出来提问的人。 第二种就是什么条件、环境都不描述,背景也不说上来就问,怎么样最好,如何性能最快,怎么做才能最稳定的人,我说话比较直,勿怪,对于这类人,你至少要让他知道一些概念,不然根本没法交流。  
 
       为了解决这些问题,我想提供一种快速让一些没有经验或者经验不足的朋友去快速了解Elasticsearch,了解ES能做什么,了解ES怎么做才能达到你的要求,带着这些问题,我在头脑里构思了 两个系列的视频,第一个是ES-教程篇, 第二个是ES-实战篇,把实际的生产环境中的一些场景,剥离业务相关的,保密性的东西,以纯技术的方式与大家分享下,看我们是如何踩坑,填坑的。

由于视频是个人行为,非商业性质,所以不可能做到定时更新,这点还请大家见谅,不过我肯定会用心去做。
 
所有的视频会以两个方式去提供,一个是百度云盘里下载完整的压缩包,里面包含了视频、PPT、以及配置文件信息。另一种是通过优库视频直接看视频,PPT和配置文件到百度云上去下载。
 
 
百度云: https://pan.baidu.com/s/1i4ZsORF
优酷:http://i.youku.com/rickywag
 
 
感谢你们的支持,如果觉得不错可以打赏我哟。
继续阅读 »
      大家好,我是重构人生,很开心能在这里和大家分享我的免费Elasticsearch 视频教程。 我是一个Elasticsearch 的DevOps ,使用了很久的开源软件,在开源社区也获得过很多小伙伴的帮助。我希望自己能为社区贡献自己的一份力。
 
      我应该算是个热心肠的人吧,喜欢乐于助人,不过有些人的提问方式让我非常反感,甚至是厌恶,什么样的人呢,第一种,官方文档,或者百度,或者GOOGLE,等方式轻易的就可以找到解决方案却仍然拿出来提问的人。 第二种就是什么条件、环境都不描述,背景也不说上来就问,怎么样最好,如何性能最快,怎么做才能最稳定的人,我说话比较直,勿怪,对于这类人,你至少要让他知道一些概念,不然根本没法交流。  
 
       为了解决这些问题,我想提供一种快速让一些没有经验或者经验不足的朋友去快速了解Elasticsearch,了解ES能做什么,了解ES怎么做才能达到你的要求,带着这些问题,我在头脑里构思了 两个系列的视频,第一个是ES-教程篇, 第二个是ES-实战篇,把实际的生产环境中的一些场景,剥离业务相关的,保密性的东西,以纯技术的方式与大家分享下,看我们是如何踩坑,填坑的。

由于视频是个人行为,非商业性质,所以不可能做到定时更新,这点还请大家见谅,不过我肯定会用心去做。
 
所有的视频会以两个方式去提供,一个是百度云盘里下载完整的压缩包,里面包含了视频、PPT、以及配置文件信息。另一种是通过优库视频直接看视频,PPT和配置文件到百度云上去下载。
 
 
百度云: https://pan.baidu.com/s/1i4ZsORF
优酷:http://i.youku.com/rickywag
 
 
感谢你们的支持,如果觉得不错可以打赏我哟。 收起阅读 »

[原创] ElasticSearch集群故障案例分析: 警惕通配符查询

[携程旅行网: 吴晓刚]
 许多有RDBMS/SQL背景的开发者,在初次踏入ElasticSearch世界的时候,很容易就想到使用(Wildcard Query)来实现模糊查询(比如用户输入补全),因为这是和SQL里like操作最相似的查询方式,用起来感觉非常舒适。然而近期我们线上一个搜索集群的故障揭示了,滥用wildcard query可能带来灾难性的后果。

故障经过
线上有一个10来台机器组成的集群,用于某个产品线的产品搜索。数据量并不大,实时更新量也不高,并发搜索量在几百次/s。通常业务高峰期cpu利用率不超过10%,系统负载看起来很低。 但最近这个集群不定期(1天或者隔几天)会出现CPU冲高到100%的问题,持续时间从1分钟到几分钟不等。最严重的一次持续了20来分钟,导致大量的用户搜索请无求响应,从而造成生产事故。

问题排查
细节太多,此处略过,直接给出CPU无故飙高的原因: 研发在搜索实现上,根据用户输入的关键词,在首尾加上通配符,使用wildcard query来实现模糊搜索,例如使用"*迪士尼*"来搜索含有“迪士尼”关键字的产品。 然而用户输入的字符串长度没有做限制,导致首尾通配符中间可能是很长的一个字符串。 后果就是对应的wildcard Query执行非常慢,非常消耗CPU。

复现方法
1. 创建一个只有一条文档的索引
POST test_index/type1/?refresh=true
{
"foo": "bar"
}
2. 使用wildcard query执行一个首尾带有通配符*的长字符串查询
POST /test_index/_search
{
"query": {
"wildcard": {
"foo": {
"value": "*在迪士尼乐园,点亮心中奇梦。它是一个充满创造力、冒险精神与无穷精彩的快地。您可在此游览全球最大的迪士尼城堡——奇幻童话城堡,探索别具一格又令人难忘的六大主题园区——米奇大街、奇想花园、梦幻世界、探险岛、宝藏湾和明日世界,和米奇朋友在一起,感觉欢乐时光开业于2016年上海国际旅游度假区秀沿路亚朵酒店位于上海市浦东新区沪南公路(沪南公路与秀沿路交汇处),临近周浦万达广场、地铁11号线秀沿路站,距离上海南站、人民广场约20公里,距离迪线距*"
}
}
}
}
3. 查看结果
{
"took": 3445,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 0,
"max_score": null,
"hits":
}
}
即使no hits,耗时却是惊人的3.4秒 (测试机是macbook pro, i7 CPU),并且执行过程中,CPU有一个很高的尖峰。
 
线上的查询比我这个范例要复杂得多,会同时查几个字段,实际测试下来,一个查询可能会执行十几秒钟。 在有比较多长字符串查询的时候,集群可能就DOS了。

探查深层次根源
为什么对只有一条数据的索引做这个查询开销这么高? 直觉上应该是瞬间返回结果才对!

回答这个问题前,可以再做个测试,如果继续加大查询字符串的长度,到了一定长度后,ES直接抛异常了,服务器ES里异常给出的cause如下:


 
Caused by: org.apache.lucene.util.automaton.TooComplexToDeterminizeException: Determinizing automaton with 22082 states and 34182 transitions would result in more than 10000 states. at org.apache.lucene.util.automaton.Operations.determinize(Operations.java:741) ~[lucene-core-6.4.1.jar:6.4.1
 


该异常来自org.apache.lucene.util.automaton这个包,异常原因的字面含义是说“自动机过于复杂而无法确定状态: 由于状态和转换太多,确定一个自动机需要生成的状态超过10000个上限"

网上查找了大量资料后,终于搞清楚了问题的来龙去脉。为了加速通配符和正则表达式的匹配速度,Lucene4.0开始会将输入的字符串模式构建成一个DFA (Deterministic Finite Automaton),带有通配符的pattern构造出来的DFA可能会很复杂,开销很大。这个链接的博客using-dfa-for-wildcard-matching-problem比较形象的介绍了如何为一个带有通配符的pattern构建DFA。借用博客里的范例,a*bc构造出来的DFA如下图:

屏幕快照_2017-05-11_18.56_.06_.png


Lucene构造DFA的实现
看了一下Lucene的里相关的代码,构建过程大致如下:
1. org.apache.lucene.search.WildcardQuery里的toAutomaton方法,遍历输入的通配符pattern,将每个字符变成一个自动机(automaton),然后将每个字符的自动机链接起来生成一个新的自动机
public static Automaton toAutomaton(Term wildcardquery) {
List<Automaton> automata = new ArrayList<>();

String wildcardText = wildcardquery.text();

for (int i = 0; i < wildcardText.length();) {
final int c = wildcardText.codePointAt(i);
int length = Character.charCount(c);
switch(c) {
case WILDCARD_STRING:
automata.add(Automata.makeAnyString());
break;
case WILDCARD_CHAR:
automata.add(Automata.makeAnyChar());
break;
case WILDCARD_ESCAPE:
// add the next codepoint instead, if it exists
if (i + length < wildcardText.length()) {
final int nextChar = wildcardText.codePointAt(i + length);
length += Character.charCount(nextChar);
automata.add(Automata.makeChar(nextChar));
break;
} // else fallthru, lenient parsing with a trailing \
default:
automata.add(Automata.makeChar(c));
}
i += length;
}

return Operations.concatenate(automata);
}
2. 此时生成的状态机是不确定状态机,也就是Non-deterministic Finite Automaton(NFA)。
3. org.apache.lucene.util.automaton.Operations类里的determinize方法则会将NFA转换为DFA  
/**
* Determinizes the given automaton.
* <p>
* Worst case complexity: exponential in number of states.
* @param maxDeterminizedStates Maximum number of states created when
* determinizing. Higher numbers allow this operation to consume more
* memory but allow more complex automatons. Use
* DEFAULT_MAX_DETERMINIZED_STATES as a decent default if you don't know
* how many to allow.
* @throws TooComplexToDeterminizeException if determinizing a creates an
* automaton with more than maxDeterminizedStates
*/
public static Automaton determinize(Automaton a, int maxDeterminizedStates) {
 代码注释里说这个过程的时间复杂度最差情况下是状态数量的指数级别!为防止产生的状态过多,消耗过多的内存和CPU,类里面对最大状态数量做了限制
  /**
* Default maximum number of states that {@link Operations#determinize} should create.
*/
public static final int DEFAULT_MAX_DETERMINIZED_STATES = 10000;
在有首尾通配符,并且字符串很长的情况下,这个determinize过程会产生大量的state,甚至会超过上限。
 
至于NFA和DFA的区别是什么? 如何相互转换? 网上有很多数学层面的资料和论文,限于鄙人算法方面有限的知识,无精力去深入探究。 但是一个粗浅的理解是: NFA在输入一个条件的情况下,可以从一个状态转移到多种状态,而DFA只会有一个确定的状态可以转移,因此DFA在字符串匹配时速度更快。 DFA虽然搜索的时候快,但是构造方面的时间复杂度可能比较高,特别是带有首部通配符+长字符串的时候。

回想Elasticsearch官方文档里对于wildcard query有特别说明,要避免使用通配符开头的term。


" Note that this query can be slow, as it needs to iterate over many terms. In order to prevent extremely slow wildcard queries, a wildcard term should not start with one of the wildcards * or ?."



结合对上面wildcard query底层实现的探究,也就不难理解这句话的含义了!

总结: wildcard query应杜绝使用通配符打头,实在不得已要这么做,就一定需要限制用户输入的字符串长度。 最好换一种实现方式,通过在index time做文章,选用合适的分词器,比如nGram tokenizer预处理数据,然后使用更廉价的term query来实现同等的模糊搜索功能。 对于部分输入即提示的应用场景,可以考虑优先使用completion suggester, phrase/term suggeter一类性能更好,模糊程度略差的方式查询,待suggester没有匹配结果的时候,再fall back到更模糊但性能较差的wildcard, regex, fuzzy一类的查询。
 
-----------
补记: 有同学问regex, fuzzy query是否有同样的问题,答案是有,原因在于他们底层和wildcard一样,都是通过将pattern构造成DFA来加速字符串匹配速度的。 
继续阅读 »
[携程旅行网: 吴晓刚]
 许多有RDBMS/SQL背景的开发者,在初次踏入ElasticSearch世界的时候,很容易就想到使用(Wildcard Query)来实现模糊查询(比如用户输入补全),因为这是和SQL里like操作最相似的查询方式,用起来感觉非常舒适。然而近期我们线上一个搜索集群的故障揭示了,滥用wildcard query可能带来灾难性的后果。

故障经过
线上有一个10来台机器组成的集群,用于某个产品线的产品搜索。数据量并不大,实时更新量也不高,并发搜索量在几百次/s。通常业务高峰期cpu利用率不超过10%,系统负载看起来很低。 但最近这个集群不定期(1天或者隔几天)会出现CPU冲高到100%的问题,持续时间从1分钟到几分钟不等。最严重的一次持续了20来分钟,导致大量的用户搜索请无求响应,从而造成生产事故。

问题排查
细节太多,此处略过,直接给出CPU无故飙高的原因: 研发在搜索实现上,根据用户输入的关键词,在首尾加上通配符,使用wildcard query来实现模糊搜索,例如使用"*迪士尼*"来搜索含有“迪士尼”关键字的产品。 然而用户输入的字符串长度没有做限制,导致首尾通配符中间可能是很长的一个字符串。 后果就是对应的wildcard Query执行非常慢,非常消耗CPU。

复现方法
1. 创建一个只有一条文档的索引
POST test_index/type1/?refresh=true
{
"foo": "bar"
}
2. 使用wildcard query执行一个首尾带有通配符*的长字符串查询
POST /test_index/_search
{
"query": {
"wildcard": {
"foo": {
"value": "*在迪士尼乐园,点亮心中奇梦。它是一个充满创造力、冒险精神与无穷精彩的快地。您可在此游览全球最大的迪士尼城堡——奇幻童话城堡,探索别具一格又令人难忘的六大主题园区——米奇大街、奇想花园、梦幻世界、探险岛、宝藏湾和明日世界,和米奇朋友在一起,感觉欢乐时光开业于2016年上海国际旅游度假区秀沿路亚朵酒店位于上海市浦东新区沪南公路(沪南公路与秀沿路交汇处),临近周浦万达广场、地铁11号线秀沿路站,距离上海南站、人民广场约20公里,距离迪线距*"
}
}
}
}
3. 查看结果
{
"took": 3445,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 0,
"max_score": null,
"hits":
}
}
即使no hits,耗时却是惊人的3.4秒 (测试机是macbook pro, i7 CPU),并且执行过程中,CPU有一个很高的尖峰。
 
线上的查询比我这个范例要复杂得多,会同时查几个字段,实际测试下来,一个查询可能会执行十几秒钟。 在有比较多长字符串查询的时候,集群可能就DOS了。

探查深层次根源
为什么对只有一条数据的索引做这个查询开销这么高? 直觉上应该是瞬间返回结果才对!

回答这个问题前,可以再做个测试,如果继续加大查询字符串的长度,到了一定长度后,ES直接抛异常了,服务器ES里异常给出的cause如下:


 
Caused by: org.apache.lucene.util.automaton.TooComplexToDeterminizeException: Determinizing automaton with 22082 states and 34182 transitions would result in more than 10000 states. at org.apache.lucene.util.automaton.Operations.determinize(Operations.java:741) ~[lucene-core-6.4.1.jar:6.4.1
 


该异常来自org.apache.lucene.util.automaton这个包,异常原因的字面含义是说“自动机过于复杂而无法确定状态: 由于状态和转换太多,确定一个自动机需要生成的状态超过10000个上限"

网上查找了大量资料后,终于搞清楚了问题的来龙去脉。为了加速通配符和正则表达式的匹配速度,Lucene4.0开始会将输入的字符串模式构建成一个DFA (Deterministic Finite Automaton),带有通配符的pattern构造出来的DFA可能会很复杂,开销很大。这个链接的博客using-dfa-for-wildcard-matching-problem比较形象的介绍了如何为一个带有通配符的pattern构建DFA。借用博客里的范例,a*bc构造出来的DFA如下图:

屏幕快照_2017-05-11_18.56_.06_.png


Lucene构造DFA的实现
看了一下Lucene的里相关的代码,构建过程大致如下:
1. org.apache.lucene.search.WildcardQuery里的toAutomaton方法,遍历输入的通配符pattern,将每个字符变成一个自动机(automaton),然后将每个字符的自动机链接起来生成一个新的自动机
public static Automaton toAutomaton(Term wildcardquery) {
List<Automaton> automata = new ArrayList<>();

String wildcardText = wildcardquery.text();

for (int i = 0; i < wildcardText.length();) {
final int c = wildcardText.codePointAt(i);
int length = Character.charCount(c);
switch(c) {
case WILDCARD_STRING:
automata.add(Automata.makeAnyString());
break;
case WILDCARD_CHAR:
automata.add(Automata.makeAnyChar());
break;
case WILDCARD_ESCAPE:
// add the next codepoint instead, if it exists
if (i + length < wildcardText.length()) {
final int nextChar = wildcardText.codePointAt(i + length);
length += Character.charCount(nextChar);
automata.add(Automata.makeChar(nextChar));
break;
} // else fallthru, lenient parsing with a trailing \
default:
automata.add(Automata.makeChar(c));
}
i += length;
}

return Operations.concatenate(automata);
}
2. 此时生成的状态机是不确定状态机,也就是Non-deterministic Finite Automaton(NFA)。
3. org.apache.lucene.util.automaton.Operations类里的determinize方法则会将NFA转换为DFA  
/**
* Determinizes the given automaton.
* <p>
* Worst case complexity: exponential in number of states.
* @param maxDeterminizedStates Maximum number of states created when
* determinizing. Higher numbers allow this operation to consume more
* memory but allow more complex automatons. Use
* DEFAULT_MAX_DETERMINIZED_STATES as a decent default if you don't know
* how many to allow.
* @throws TooComplexToDeterminizeException if determinizing a creates an
* automaton with more than maxDeterminizedStates
*/
public static Automaton determinize(Automaton a, int maxDeterminizedStates) {
 代码注释里说这个过程的时间复杂度最差情况下是状态数量的指数级别!为防止产生的状态过多,消耗过多的内存和CPU,类里面对最大状态数量做了限制
  /**
* Default maximum number of states that {@link Operations#determinize} should create.
*/
public static final int DEFAULT_MAX_DETERMINIZED_STATES = 10000;
在有首尾通配符,并且字符串很长的情况下,这个determinize过程会产生大量的state,甚至会超过上限。
 
至于NFA和DFA的区别是什么? 如何相互转换? 网上有很多数学层面的资料和论文,限于鄙人算法方面有限的知识,无精力去深入探究。 但是一个粗浅的理解是: NFA在输入一个条件的情况下,可以从一个状态转移到多种状态,而DFA只会有一个确定的状态可以转移,因此DFA在字符串匹配时速度更快。 DFA虽然搜索的时候快,但是构造方面的时间复杂度可能比较高,特别是带有首部通配符+长字符串的时候。

回想Elasticsearch官方文档里对于wildcard query有特别说明,要避免使用通配符开头的term。


" Note that this query can be slow, as it needs to iterate over many terms. In order to prevent extremely slow wildcard queries, a wildcard term should not start with one of the wildcards * or ?."



结合对上面wildcard query底层实现的探究,也就不难理解这句话的含义了!

总结: wildcard query应杜绝使用通配符打头,实在不得已要这么做,就一定需要限制用户输入的字符串长度。 最好换一种实现方式,通过在index time做文章,选用合适的分词器,比如nGram tokenizer预处理数据,然后使用更廉价的term query来实现同等的模糊搜索功能。 对于部分输入即提示的应用场景,可以考虑优先使用completion suggester, phrase/term suggeter一类性能更好,模糊程度略差的方式查询,待suggester没有匹配结果的时候,再fall back到更模糊但性能较差的wildcard, regex, fuzzy一类的查询。
 
-----------
补记: 有同学问regex, fuzzy query是否有同样的问题,答案是有,原因在于他们底层和wildcard一样,都是通过将pattern构造成DFA来加速字符串匹配速度的。  收起阅读 »

ES5.2.2支持geo排序+聚合

ES5.2.2真的 支持geo排序+聚合
具体查询条件,这里不写了;
查询结果如下:

{ "took": 90, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 10, "max_score": null, "hits": [ { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000000", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000000", "equipName": "灭火剂0", "equipValue": 73, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000003", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000003", "equipName": "灭火剂3", "equipValue": 47, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000001", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000001", "equipName": "灭火剂1", "equipValue": 12, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000004", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000004", "equipName": "灭火剂4", "equipValue": 2, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000002", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000002", "equipName": "灭火剂2", "equipValue": 57, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000018", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000018", "equipName": "灭火剂8", "equipValue": 61, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000014", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000014", "equipName": "灭火剂4", "equipValue": 81, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000016", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000016", "equipName": "灭火剂6", "equipValue": 81, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000015", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000015", "equipName": "灭火剂5", "equipValue": 87, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000017", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000017", "equipName": "灭火剂7", "equipValue": 90, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] } ] }, "aggregations": { "aggEquip": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "1000000", "doc_count": 1, "sumEquipValue": { "value": 73 } }, { "key": "1000001", "doc_count": 1, "sumEquipValue": { "value": 12 } }, { "key": "1000002", "doc_count": 1, "sumEquipValue": { "value": 57 } }, { "key": "1000003", "doc_count": 1, "sumEquipValue": { "value": 47 } }, { "key": "1000004", "doc_count": 1, "sumEquipValue": { "value": 2 } }, { "key": "1000014", "doc_count": 1, "sumEquipValue": { "value": 81 } }, { "key": "1000015", "doc_count": 1, "sumEquipValue": { "value": 87 } }, { "key": "1000016", "doc_count": 1, "sumEquipValue": { "value": 81 } }, { "key": "1000017", "doc_count": 1, "sumEquipValue": { "value": 90 } }, { "key": "1000018", "doc_count": 1, "sumEquipValue": { "value": 61 } }, { "key": "10000210", "doc_count": 1, "sumEquipValue": { "value": 70 } }, { "key": "10000211", "doc_count": 1, "sumEquipValue": { "value": 55 } }, { "key": "1000027", "doc_count": 1, "sumEquipValue": { "value": 19 } }, { "key": "1000028", "doc_count": 1, "sumEquipValue": { "value": 25 } }, { "key": "1000029", "doc_count": 1, "sumEquipValue": { "value": 3 } }, { "key": "10000311", "doc_count": 1, "sumEquipValue": { "value": 97 } }, { "key": "10000312", "doc_count": 1, "sumEquipValue": { "value": 79 } }, { "key": "10000313", "doc_count": 1, "sumEquipValue": { "value": 95 } }, { "key": "10000314", "doc_count": 1, "sumEquipValue": { "value": 6 } }, { "key": "10000315", "doc_count": 1, "sumEquipValue": { "value": 8 } } ] } } }
 
继续阅读 »
ES5.2.2真的 支持geo排序+聚合
具体查询条件,这里不写了;
查询结果如下:

{ "took": 90, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 }, "hits": { "total": 10, "max_score": null, "hits": [ { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000000", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000000", "equipName": "灭火剂0", "equipValue": 73, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000003", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000003", "equipName": "灭火剂3", "equipValue": 47, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000001", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000001", "equipName": "灭火剂1", "equipValue": 12, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000004", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000004", "equipName": "灭火剂4", "equipValue": 2, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "1-1000002", "_score": null, "_source": { "comId": 1, "comName": "佛山消防支队", "elemetType": 2, "equipCode": "1000002", "equipName": "灭火剂2", "equipValue": 57, "location": { "lat": 23.027237, "lon": 113.123618 }, "phone": "15986190299", "userName": "李成龙" }, "sort": [ 3740.583503580435 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000018", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000018", "equipName": "灭火剂8", "equipValue": 61, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000014", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000014", "equipName": "灭火剂4", "equipValue": 81, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000016", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000016", "equipName": "灭火剂6", "equipValue": 81, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000015", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000015", "equipName": "灭火剂5", "equipValue": 87, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] }, { "_index": "testindex", "_type": "testdoc07", "_id": "2-1000017", "_score": null, "_source": { "comId": 2, "comName": "禅城大队", "elemetType": 2, "equipCode": "1000017", "equipName": "灭火剂7", "equipValue": 90, "location": { "lat": 23.057237, "lon": 113.103618 }, "phone": "13925403119", "userName": "郭武斌" }, "sort": [ 7648.2045946025555 ] } ] }, "aggregations": { "aggEquip": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "1000000", "doc_count": 1, "sumEquipValue": { "value": 73 } }, { "key": "1000001", "doc_count": 1, "sumEquipValue": { "value": 12 } }, { "key": "1000002", "doc_count": 1, "sumEquipValue": { "value": 57 } }, { "key": "1000003", "doc_count": 1, "sumEquipValue": { "value": 47 } }, { "key": "1000004", "doc_count": 1, "sumEquipValue": { "value": 2 } }, { "key": "1000014", "doc_count": 1, "sumEquipValue": { "value": 81 } }, { "key": "1000015", "doc_count": 1, "sumEquipValue": { "value": 87 } }, { "key": "1000016", "doc_count": 1, "sumEquipValue": { "value": 81 } }, { "key": "1000017", "doc_count": 1, "sumEquipValue": { "value": 90 } }, { "key": "1000018", "doc_count": 1, "sumEquipValue": { "value": 61 } }, { "key": "10000210", "doc_count": 1, "sumEquipValue": { "value": 70 } }, { "key": "10000211", "doc_count": 1, "sumEquipValue": { "value": 55 } }, { "key": "1000027", "doc_count": 1, "sumEquipValue": { "value": 19 } }, { "key": "1000028", "doc_count": 1, "sumEquipValue": { "value": 25 } }, { "key": "1000029", "doc_count": 1, "sumEquipValue": { "value": 3 } }, { "key": "10000311", "doc_count": 1, "sumEquipValue": { "value": 97 } }, { "key": "10000312", "doc_count": 1, "sumEquipValue": { "value": 79 } }, { "key": "10000313", "doc_count": 1, "sumEquipValue": { "value": 95 } }, { "key": "10000314", "doc_count": 1, "sumEquipValue": { "value": 6 } }, { "key": "10000315", "doc_count": 1, "sumEquipValue": { "value": 8 } } ] } } }
  收起阅读 »

_source/_all特性效果

我经过实际测试es5.2.2,发现_source/_all特性很好用:
1. _source可用通过配置includes、excludes获取应用需要的field
"_source": {
          "enabled": true,
          "includes": [
            "comId",
            "name",
            "userName",
            "equips.name",
            "equips.amount"
          ],
          "excludes": [
            "phone",
            "equips.code"
          ]
        },
2.设置enabled=false关闭_source功能,关闭后,查询结果只返回doc的ID,而不会返回_source
 "_source": {
          "enabled": false,
3._all、include_in_all结合使用,是用户可用通过_all分词查询多个字段,而不需要写多个查询条件
 "mappings": {
      "testdoc03": {
        "_all": {
          "enabled": true
        },
        "_source": {
          "enabled": false,
          "includes": [
            "comId",
            "name",
            "userName",
            "equips.name",
            "equips.amount"
          ],
          "excludes": [
            "phone",
            "equips.code"
          ]
        },
        "properties": {
          "comId": {
            "type": "long"
          },
          "equips": {
            "properties": {
              "amount": {
                "type": "double",
                "include_in_all": true
              },
              "code": {
                "type": "text"
              },
              "name": {
                "type": "text",
                "include_in_all": true
              }
            }
          },
          "name": {
            "type": "text",
            "include_in_all": true
          },
          "phone": {
            "type": "keyword"
          },
          "userName": {
            "type": "text",
            "include_in_all": true
          }
        }
      }
    }
  }
 
继续阅读 »
我经过实际测试es5.2.2,发现_source/_all特性很好用:
1. _source可用通过配置includes、excludes获取应用需要的field
"_source": {
          "enabled": true,
          "includes": [
            "comId",
            "name",
            "userName",
            "equips.name",
            "equips.amount"
          ],
          "excludes": [
            "phone",
            "equips.code"
          ]
        },
2.设置enabled=false关闭_source功能,关闭后,查询结果只返回doc的ID,而不会返回_source
 "_source": {
          "enabled": false,
3._all、include_in_all结合使用,是用户可用通过_all分词查询多个字段,而不需要写多个查询条件
 "mappings": {
      "testdoc03": {
        "_all": {
          "enabled": true
        },
        "_source": {
          "enabled": false,
          "includes": [
            "comId",
            "name",
            "userName",
            "equips.name",
            "equips.amount"
          ],
          "excludes": [
            "phone",
            "equips.code"
          ]
        },
        "properties": {
          "comId": {
            "type": "long"
          },
          "equips": {
            "properties": {
              "amount": {
                "type": "double",
                "include_in_all": true
              },
              "code": {
                "type": "text"
              },
              "name": {
                "type": "text",
                "include_in_all": true
              }
            }
          },
          "name": {
            "type": "text",
            "include_in_all": true
          },
          "phone": {
            "type": "keyword"
          },
          "userName": {
            "type": "text",
            "include_in_all": true
          }
        }
      }
    }
  }
  收起阅读 »

Elasticsearch 6.0 将移除 Type

尽管之前在很多地方都提到过,不过还是有必要单独开篇文章提醒一下大家!
Type 已经打算在6.0移除了,所以在设计 elasticsearch 的数据结构的时候,要注意到后面版本的变化。
之前在很多的文章和 PPT 都有介绍Elasticsearch 的几个核心概念,Index 对应 DB,Type 对应表,Document 对应记录,然后就真的按数据库的路子用,一个 index 里面 n 个 type 的情况大有存在,但是在 Lucene 里面其实有很多问题,所以现在es移除也是考虑了很久的。

新增参数:
index.mapping.single_type: true
 
UID 也会移除掉 _type 的值。

Type 移除大概分为两个阶段:
第一步,不支持新的索引创建多个 type,一个索引只有一个 type,名称也是固定的,不能修改。
第二步,移除。
 
相应的 PR 已经 merge 了。
https://github.com/elastic/ela ... 24317
 
继续阅读 »
尽管之前在很多地方都提到过,不过还是有必要单独开篇文章提醒一下大家!
Type 已经打算在6.0移除了,所以在设计 elasticsearch 的数据结构的时候,要注意到后面版本的变化。
之前在很多的文章和 PPT 都有介绍Elasticsearch 的几个核心概念,Index 对应 DB,Type 对应表,Document 对应记录,然后就真的按数据库的路子用,一个 index 里面 n 个 type 的情况大有存在,但是在 Lucene 里面其实有很多问题,所以现在es移除也是考虑了很久的。

新增参数:
index.mapping.single_type: true
 
UID 也会移除掉 _type 的值。

Type 移除大概分为两个阶段:
第一步,不支持新的索引创建多个 type,一个索引只有一个 type,名称也是固定的,不能修改。
第二步,移除。
 
相应的 PR 已经 merge 了。
https://github.com/elastic/ela ... 24317
  收起阅读 »