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

news2025/1/16 0:58:51

[携程旅行网: 吴晓刚]
 许多有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来加速字符串匹配速度的。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1111621.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

常见面试题-Redis专栏(一)

typora-copy-images-to: imgs了解 redis 中的大key吗&#xff1f;多大算是大key呢&#xff1f;如何解决&#xff1f; 答&#xff1a; redis 的大 key 指的是 key 对应的 value 所占用的内存比较大。 对于 string 类型来说&#xff0c;一般情况下超过 10KB 则认为是大 key&…

waf、yakit和ssh免密登录

WAF安全狗 脏数据适用于所有漏洞绕过waf&#xff0c;但是前提条件垃圾信息必须放在危险信息前&#xff0c;是不能打断原有数据包的结构&#xff0c;不能影响后端对数据包的解析。 以DVWA靶场文件上传为例 新建php文件 上传文件被安全狗拦截 使用bp抓包查看 在数据包Content-…

Anomalib 图像异常检测算法

图像异常检测算法 1.简介1.1. 问题描述1.2. 挑战与需求 2. 图像异常检测定义2.1 异常的定义及类型2.2 图像数据中的异常 3. 图像异常检测技术研究现状4.方法5.安装和使用5.1 安装PyPI 安装本地安装 5.2 训练5.3 特征提取与&#xff08;预训练&#xff09;backones5.4 自定义数据…

DVWA-弱会话IDS

弱会话IDS Session简介&#xff1a; 用户登录后&#xff0c;在服务器就会创建一个会话(session)&#xff0c;叫做会话控制&#xff0c;接着访问页面的时候就不用登录&#xff0c;只需要携带Session去访问即可。 sessionID作为特定用户访问站点所需要的唯一内容。如果能够计算…

HTML构建图文并茂类页面----HTML插入图片

插入图片 插入方式1&#xff1a;使用img标签插入图片 例如&#xff1a;<img src"img/2ee18-231100-163232346010b0.jpg" alt"用户注册按钮位置" title"用户注册1" width"1138px"/> 插入方式2 图片做背景&#xff1a;使用styl…

HarmonyOS音频开发指导:使用AVPlayer开发音频播放功能

如何选择音频播放开发方式 在HarmonyOS系统中&#xff0c;多种API都提供了音频播放开发的支持&#xff0c;不同的API适用于不同音频数据格式、音频资源来源、音频使用场景&#xff0c;甚至是不同开发语言。因此&#xff0c;选择合适的音频播放API&#xff0c;有助于降低开发工…

二维码智慧门牌管理系统:打造智慧城市之路

文章目录 前言一、二维码智慧门牌管理系统开发背景二、二维码智慧门牌管理系统开发目标三、二维码智慧门牌管理系统的实现四、二维码智慧门牌管理系统在智慧城市构建中的重要性 前言 随着科技的不断发展&#xff0c;智慧城市已经成为了现代城市规划与发展的核心。为了实现智慧…

C/C++ 快速入门

参考&#xff1a;https://blog.csdn.net/gao_zhennan/article/details/128769439 1 下载Visual Studio Code并安装中文插件&#xff0c;此处不再叙述 2 插件安装C/C插件 3 使用快捷键【Ctr ~】打打开终端 验证并未安装编译器 4 我们即将使用【MinGW-64】做为编译器 https:…

谷歌浏览器网页显示不完整解决方法

谷歌浏览器是非常多用户都在用的一款电脑软件&#xff0c;谷歌浏览器以启动速度快、浏览速度快、界面简单、极强的稳定性等优点受到大家的喜爱&#xff0c;在使用的时候&#xff0c;您可能会遇到打不开网页或显示不全等情况&#xff0c; 那么谷歌浏览器显示不完全怎么解决呢&am…

水族店通过小程序商城经营的作用是什么

对水族店商家而言&#xff0c;市场高需求下&#xff0c;自然可售卖的产品非常广&#xff0c;除了鱼苗外&#xff0c;还有配套的鱼缸、鱼料、驱虫剂、氧气套具等。这些产品中部分是常需的&#xff0c;同时也有较强的同城属性。 在实际经营中&#xff0c;水族店商家经营难题也不…

浅析CRM系统中的“联系人”概念

CRM客户管理系统中的联系人指的是为了产生商机和建立关系而需要进行沟通的对象&#xff0c;强调联系和活动对象而客户强调的是交易对象。联系人管理也是CRM系统的核心功能之一。下面我们来详细说说&#xff0c;CRM系统中的联系人是什么&#xff1f;如何进行联系人管理&#xff…

解决方案|智能制造升级,汽车行业借力法大大电子签进入“快车道”

《“十四五”智能制造发展规划》明确智能制造是制造强国建设的主攻方向&#xff0c;其发展程度直接关乎我国制造业质量水平。发展智能制造对于巩固实体经济根基、建成现代化产业体系、实现新型工业化具有重要作用。 规划明确指出要深入实施智能制造工程&#xff0c;着力提升创…

【红日靶场】vulnstack4-完整渗透过程

系列文章目录 【红日靶场】vulnstack1-完整渗透过程 【红日靶场】vulnstack2-完整渗透过程 【红日靶场】vulnstack3-完整渗透过程 文章目录 系列文章目录前言一、p环境初始化web虚拟机配置win7配置&#xff1a;DC配置&#xff1a;kaliLinux配置&#xff1a;机器密码 二、开始渗…

[论文精读]Graph Attention Networks

论文原文&#xff1a;[1710.10903] Graph Attention Networks (arxiv.org) 论文代码&#xff1a;https://github.com/PetarV-/GAT 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指…

《文献阅读》- 遗传算法作为量子近似优化算法的经典优化器(未完成)

文章目录 标题摘要关键词结论研究背景1.介绍 研究内容、成果3. 量子近似优化算法&#xff1a;基本概念及应用 常用基础理论知识2.相关工作酉矩阵 潜在研究点文献链接 标题 Genetic algorithms as classical optimizer for the Quantum Approximate Optimization Algorithm 参…

并发编程——2.基础概念及其它相关的概述

这篇文章我们来讲一下并发编程中的线程及其相关的概述内容。 目录 1.J.U.C 2.进程、线程、协程 2.1进程 2.2线程 2.3纤程&#xff08;协程&#xff09; 2.4概念小结 3.并发、并行、串行 3.1并发 3.2并行 3.3串行 3.4概念小结 4.CPU核心数和线程数的关系 5.上下文…

如何对发电机组进行负载测试

发电机组负载测试是为了评估其在不同负载条件下的性能和稳定性&#xff0c;下面是对发电机组进行负载测试的步骤&#xff1a; 准备工作&#xff1a; - 确定测试负载的类型和大小&#xff0c;例如&#xff0c;纯阻性负载、电动机负载等。 - 确保测试负载设备符合安全要求&#x…

C#选择排序(Selection Sort)算法

选择排序&#xff08;Selection Sort&#xff09;原理介绍 选择排序&#xff08;Selection Sort&#xff09;是一种简单的排序算法&#xff0c;其实现原理如下&#xff1a; 遍历待排序数组&#xff0c;从第一个元素开始。 假设当前遍历的元素为最小值&#xff0c;将其索引保存…

正点原子嵌入式linux驱动开发——Linux按键输入

在前几篇笔记之中都是使用的GPIO输出功能&#xff0c;还没有用过GPIO输入功能&#xff0c;本章就来学习一下如果在Linux下编写GPIO输入驱动程序。正点原子STM32MP1开发板上有三个按键&#xff0c;就使用这些按键来完成GPIO输入驱动程序&#xff0c;同时利用原子操作来对按键值进…

【Vue】v-if和v-show的区别

v-if&#xff1a;按条件渲染&#xff0c;判定为true时渲染&#xff0c;否则不渲染 v-show&#xff1a;按条件展示&#xff0c;区别在于切换的是display属性的值 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">&l…