ES使用Ngram分词器实现wildcard高性能替代方案

1、wildcard 检索

wildcard 检索可定义为:支持通配符的模糊检索,类似 Mysql 中的 like 模糊匹配模式,如下使用非分词器(ik)方式实现模糊匹配。

  • 创建常规支持wildcard索引

PUT idx_recommend_words
{
  "settings": {
    "index": {
      "number_of_shards": "5",
      "number_of_replicas": "3",
      "refresh_interval": "5s"
    }
  },
  "mappings": {
    "rec_words": {
      "_all": {
        "enabled": false
      },
      "dynamic_templates": [
        {
          "attribute_values": {
            "match_mapping_type": "*",
            "mapping": {
              "type": "keyword"
            }
          }
        }
      ],
      "properties": {
        "recId": {
          "type": "long"
        },
        "recWord": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "recType": {
          "type": "keyword"
        },
        "brandName": {
          "type": "keyword"
        },
        "catName": {
          "type": "keyword"
        },
        "enabled": {
          "type": "integer"
        },
        "clicks": {
          "type": "long"
        },
        "createdBy": {
          "type": "keyword",
          "index": false
        },
        "createdAt": {
          "type": "date",
          "index": false,
          "format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
        },
        "updatedBy": {
          "type": "keyword",
          "index": false
        },
        "updatedAt": {
          "type": "date",
          "index": false,
          "format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
        }
      }
    }
  }
}
  • wildcard查询
GET rec_words/_search
{
  "query": {
    "bool": {
      "should": [{
        "wildcard": {
          "recWord.keyword": {
            "value": "*北京*"
          }
        }
      }]
    }
  }
}

2、wildcard性能风险

官方文档说明如下:在这里插入图片描述
翻译过来中文含义是:避免以*或?开头的模式,这会增加查找匹配项所需的迭代次数并降低搜索性能。

3、替代方案

3.1 Ngram定义

Ngram是一种基于统计语言模型的算法。

Ngram的基本思想:是将文本里面的内容按照字节进行大小为N的滑动窗口操作,形成了长度是N的字节片段序列。每一个字节片段称为gram,对所有gram的出现频度进行统计,并且按照事先设定好的阈值进行过滤,形成关键gram列表,也就是这个文本的向量特征空间,列表中的每一种gram就是一个特征向量维度。

该模型基于这样一种假设,第N个词的出现只与前面N-1个词相关,而与其它任何词都不相关,整句的概率就是各个词出现概率的乘积。

这些概率可以通过直接从语料中统计N个词同时出现的次数得到。常用的是二元的Bi-Gram(二元语法)和三元的Tri-Gram(三元语法)。

3.2 Ngram举例

例如:中文句子:“你今天上班了吗”,它的Bi-Gram(二元语法)分词结果为:

你今
今天
天上
上班
班了
了吗

3.3 Ngram 应用场景

场景1:文本压缩、检查拼写错误、加速字符串查找、文献语种识别;
场景2:自然语言处理自动化领域得到新的应用,如自动分类、自动索引、超链的自动生成、文献检索、无分隔符语言文本的切分等;
场景3:自然语言的自动分类功能,对应到Elasticsearch检索,应用场景就更加明确:无分隔符语言文本的切分分词,提高检索效率(相比:wildcard 查询和正则查询)。

  • 实践:创建N-gram分词器索引
PUT idx_recommend_words
{
  "settings": {
    "index": {
      "number_of_shards": "5",
      "number_of_replicas": "3",
      "refresh_interval": "5s"
    },
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 3,
          "token_chars": [
            "letter",
            "digit"
          ]
        }
      }
    }
  },
  "mappings": {
    "rec_words": {
      "_all": {
        "enabled": false
      },
      "dynamic_templates": [
        {
          "attribute_values": {
            "match_mapping_type": "*",
            "mapping": {
              "type": "keyword"
            }
          }
        }
      ],
      "properties": {
        "recId": {
          "type": "long"
        },
        "recWord": {
          "type": "text",
          "analyzer": "my_analyzer",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "recType": {
          "type": "keyword"
        },
        "brandName": {
          "type": "keyword"
        },
        "catName": {
          "type": "keyword"
        },
        "enabled": {
          "type": "integer"
        },
        "clicks": {
          "type": "long"
        },
        "createdBy": {
          "type": "keyword",
          "index": false
        },
        "createdAt": {
          "type": "date",
          "index": false,
          "format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
        },
        "updatedBy": {
          "type": "keyword",
          "index": false
        },
        "updatedAt": {
          "type": "date",
          "index": false,
          "format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
        }
      }
    }
  }
}
  • 替代wildcard使用match_phrase查询
POST rec_words/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match_phrase": {
            "acWord": {
              "query": "北京"
            }
          }
        }
      ]
    }
  }
}
  • 注意三个核心参数
    min_gram:最小字符长度(切分),默认为1;
    max_gram:最大字符长度(切分),默认为2;
    token_chars:生成的分词结果中包含的字符类型,默认是全部类型;
    官方参数设置说明:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html

4、结论

  • Ngram分词的本质:用空间换时间,其能匹配的前提是写入的时候已经按照:min_gram、max_gram切词;
  • 数据量非常少且不要求子串高亮,可以考虑keyword;
  • 数据量大且要求子串高亮,推荐使用:Ngram分词结合match或者match_phrase检索实现;
  • 数据量大,切记不要使用wildcard前缀匹配
    原因:带有通配符的pattern构造出来的DFA(Deterministic Finite Automaton)可能会很复杂,开销很大!甚至可能导致线上环境宕机;
    特别注意:wildcard query应杜绝使用通配符打头,实在不得已要这么做,就一定需要限制用户输入的字符串长度。

版权声明:本文为software444原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。