最近ElasticSearch集群出现了 https://elasticsearch.cn/article/171 文章中描述的情况,现在转载全文警示下自己。

原文地址:https://elasticsearch.cn/article/171

许多有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如下图:

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 existsif (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);}
  1. 此时生成的状态机是不确定状态机,也就是Non-deterministic Finite Automaton(NFA)。
  2. 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来加速字符串匹配速度的。

原文地址:https://elasticsearch.cn/article/171

ElasticSearch集群故障案例分析: 警惕通配符查询相关推荐

  1. 【Elasticsearch】 es ElasticSearch集群故障案例分析: 警惕通配符查询 Wildcard

    1.概述 转载:https://elasticsearch.cn/article/171 许多有RDBMS/SQL背景的开发者,在初次踏入ElasticSearch世界的时候,很容易就想到使用(Wil ...

  2. 【Elastic Stack学习】ELK日志分析平台(一)ELK简介、ElasticSearch集群

    * ELK简介: ELK是Elasticsearch . Logstash.Kibana三个开源软件的缩写.ELK Stack 5.0版本之后新增Beats工具,因此,ELK Stack也改名为Ela ...

  3. 400+节点的 Elasticsearch 集群运维

    作者:Anton Hägerstrand 翻译:杨振涛 Meltwater每天要处理数百万量级的帖子数据,因此需要一种能处理该量级数据的存储和检索技术. 从0.11.X 版本开始我们就已经是Elast ...

  4. 数据源管理 | 搜索引擎框架,ElasticSearch集群模式

    本文源码:GitHub·点这里 || GitEE·点这里 一.集群环境搭建 1.环境概览 ES版本6.3.2,集群名称esmaster,虚拟机centos7. 服务群 角色划分 说明 en-maste ...

  5. 从400+节点ElasticSearch集群的运维中,我们总结了这些经验

    墨墨导读:国外一家舆情监控公司Meltwater每天处理的数据非常庞大--在高峰期需要索引大约300多万社论文章,和近1亿条社交帖子数据.其中社论数据长期保存以供检索(可回溯到2009年),社交帖子数 ...

  6. 400+节点的Elasticsearch集群运维

    墨墨导读:本文将分享我们所学到的经验.如何调优Elasticsearch,以及要绕过的一些陷阱. Meltwater每天要处理数百万量级的帖子数据,因此需要一种能处理该量级数据的存储和检索技术. 从0 ...

  7. 【Elasticsearch】我在 Elasticsearch 集群内应该设置多少个分片?

    1.概述 转载:https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster Ela ...

  8. Elasticsearch集群扩容踩坑记录

    ES集群扩容构建踩坑总结 文章目录 ES集群扩容构建踩坑总结 @[toc] 需求 配置 参数说明 Data node's cluster uuid diffrent from master node' ...

  9. Elasticsearch 集群内应该设置多少个分片(shard)?

    我应该设置多少个分片? 我应该设置多大的分片? Elasticsearch 是一个功能十分丰富的平台,支持各种用例,能够在数据整理和复制战略方面提供很大的灵活性.然而这一灵活性有时也会带来困扰,让您在 ...

最新文章

  1. 流量不够用?Facebook要帮你发现附近的免费WiFi
  2. javascript中select的常用操作
  3. batch size 越大,学习率也要越大
  4. oracle11g 查看磁盘,oracle11g 磁盘
  5. 文件管理、命令别名和glob
  6. QGrapicsScene类
  7. 【LeetCode 327】区间和的个数
  8. ASP.NET MVC过滤器
  9. 第二篇:基于小米手机的,第三方recovery教学
  10. 晶闸管的原理及伏安特性
  11. 360度 EC11 旋转编码器模块 数字脉冲电位器
  12. python 四舍五入到整数_python “四舍五入”
  13. 永利宝与火理财涉嫌非法吸收公众存款 6名犯罪嫌疑人抓捕
  14. QT图形显示和处理5
  15. [- 多媒体 -] OpenGLES3.0 接入视频实现特效 - 引言
  16. 模拟卷Leetcode【普通】198. 打家劫舍
  17. linux下利用MP4v2封装H264 aac为mp4
  18. 出现 Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation 问题
  19. Java秒杀系统实战系列~数据库级别Sql的优化与代码的调整
  20. java创建不定长数组_java如何创建不定长的数组?

热门文章

  1. STM32+MAX6675 获取4路温度数据原理图及代码
  2. 360度舵机和180度舵机控制方法小结
  3. bat脚本实现打开关闭exe应用
  4. pyCharm 社区版搭建Django项目环境
  5. 用Python+OpenCV+PIL构建猫脸识别器
  6. (新手)腾讯云MySQL安装教程(Windows版本)
  7. oracle松散索引扫描,oracle跳跃式索引扫描测试
  8. c语言 cgi php,C语言CGI编程入门(一)
  9. 在Linux中安装P4遇到的问题
  10. android四大组件之活动组件