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

这篇具有很好参考价值的文章主要介绍了[] 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如下图:
 

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



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来加速字符串匹配速度的。 文章来源地址https://www.toymoban.com/news/detail-722787.html

到了这里,关于[] ElasticSearch集群故障案例分析: 警惕通配符查询的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深度探索 Elasticsearch 8.X:function_score 参数解读与实战案例分析

    在 Elasticsearch 中,function_score 可以让我们在查询的同时对搜索结果进行自定义评分。 function_score 提供了一系列的参数和函数让我们可以根据需求灵活地进行设置。 近期有同学反馈,function_score 的相关参数不好理解,本文将深入探讨 function_score 的核心参数和函数。 Elasticsear

    2024年02月14日
    浏览(33)
  • spark案例分析-搜索引擎日志分析案例

    1.业务分析 2.数据截图 3.代码实现:         main.py:         defs.py:

    2024年02月08日
    浏览(32)
  • 对应分析介绍及SPSS案例分析

    在开展统计分析的过程中,分类变量(定序和定类变量)是我们研究的一个重点。通常我们分析分类变量间关系时,最常用的分析方法是卡方检验,其次是逻辑回归和对数线性模型等。 如果类别变量的分类较少,我们可以通过卡方检验判断行变量和列变量间是否相互独立,同

    2024年02月13日
    浏览(30)
  • python案例讲解视频,python简单案例分析

    大家好,给大家分享一下python案例讲解视频,很多人还不知道这一点。下面详细解释一下。现在让我们来看看!   前言 Python 是一种面向对象、解释型、弱类型的脚本语言,它也是一种功能强大而完善的通用型语言。 相比其他编程语言(比如 Java),Python 代码非常简单,上手

    2024年04月11日
    浏览(34)
  • Python案例分析|文本相似度比较分析

     本案例通过设计和实现有关文本相似度比较的类Vector和Sketch,帮助大家进一步掌握设计Python类来解决实际问题的能力。 通过计算并比较文档的摘要可实现文本的相似度比较。 文档摘要的最简单形式可以使用文档中的k-grams(k个连续字符)的相对频率的向量来表示。假设字符

    2024年02月16日
    浏览(36)
  • C++软件分析工具案例分析集锦汇总

    本文是 C++常用软件分析工具从入门到精通案例集锦 专栏的导航贴( 点击链接,跳转到专栏主页,欢迎订阅,持续更新… )。 专栏介绍 :根据近几年C++软件异常排查的项目实践,详细地讲述如何使用PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clum

    2024年02月11日
    浏览(37)
  • python数据分析案例——天猫订单综合分析

    前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 什么是数据分析 明确目的–获得数据(爬虫,现有,公开的数据)–数据预处理——数据可视化——结论 准备 环境使用: 在开始写我们的代码之前,我们要准备好运行代码的程序 Anaconda (python3.9) – 识别我们写的代码 开发工

    2024年02月03日
    浏览(31)
  • C++常用软件分析工具案例分析集锦汇总

    本文是 C++常用软件分析工具从入门到精通案例集锦 专栏的导航贴( 点击链接,跳转到专栏主页,欢迎订阅,持续更新… )。 专栏介绍 :根据近几年C++软件异常排查的项目实践,详细地讲述如何使用PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clum

    2024年02月11日
    浏览(31)
  • es 查询案例分析

    有这样一种场景,比如我们想搜索 title:Brown fox body:Brown fox 文章索引中有两条数据,兔子和狐狸两条数据 结果肯定是想要数据二,狐狸优先展示 但是,然后搜索的时候,会对搜素词 Brown fox 进行分词,导致数据一优先级更高 可以看下结果: 优先展示的是兔子,有 0.8 的算

    2024年03月19日
    浏览(37)
  • GreatSQL 死锁案例分析

    客户业务发生死锁的报错,根据业务程序日志及业务流程,发现造成死锁的原因是:事务1 delete + insert ,事务2 delete + insert 2个事务交替执行导致的死锁;由于GAP锁阻塞了插入意向锁,并且当delete的数据存在时死锁不会发生,当delete的数据不存在时,会发生死锁。 本次测试基

    2024年04月22日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包