亲,只收二进制

听说你还没掌握 Normalizer 的使用方法?

rockybean 发表了文章 • 2 个评论 • 4736 次浏览 • 2018-08-28 12:43 • 来自相关话题

在 Elasticsearch 中处理字符串类型的数据时,如果我们想把整个字符串作为一个完整的 term 存储,我们通常会将其类型 type 设定为 keyword。但有时这种设定又会给我们带来麻烦,比如同一个数据再写入时由于没有做好清洗,导致大小写不一致,比如 appleApple两个实际都是 apple,但当我们去搜索 apple时却无法返回 Apple的文档。要解决这个问题,就需要 Normalizer出场了。废话不多说,直接上手看!


1. 上手


我们先来重现一下开篇的问题

```json
PUT test_normalizer
{
"mappings": {
"doc":{
"properties": {
"type":{
"type":"keyword"
}
}
}
}
}

PUT test_normalizer/doc/1
{
"type":"apple"
}

PUT test_normalizer/doc/2
{
"type":"Apple"
}


查询一

GET test_normalizer/_search
{
"query": {
"match":{
"type":"apple"
}
}
}


查询二

GET test_normalizer/_search
{
"query": {
"match":{
"type":"aPple"
}
}
}
``<br /> <br /> 大家执行后会发现查询一返回了文档1,而查询二`没有文档返回,原因如下图所示:

![](http://p8z8qq24s.bkt.clouddn.c ... 30.png)



  1. Docs写入 Elasticsearch时由于 typekeyword,分词结果为原始字符串
  2. 查询 Query 时分词默认是采用和字段写时相同的配置,因此这里也是 keyword,因此分词结果也是原始字符
  3. 两边的分词进行匹对,便得出了我们上面的结果



    2. Normalizer


    normalizerkeyword的一个属性,可以对 keyword生成的单一 Term再做进一步的处理,比如 lowercase,即做小写变换。使用方法和自定义分词器有些类似,需要自定义,如下所示:

    ```json
    DELETE test_normalizer

    自定义 normalizer

    PUT test_normalizer
    {
    "settings": {
    "analysis": {
    "normalizer": {
    "lowercase": {
    "type": "custom",
    "filter": [
    "lowercase"
    ]
    }
    }
    }
    },
    "mappings": {
    "doc": {
    "properties": {
    "type": {
    "type": "keyword"
    },
    "type_normalizer": {
    "type": "keyword",
    "normalizer": "lowercase"
    }
    }
    }
    }
    }


    PUT test_normalizer/doc/1
    {
    "type": "apple",
    "type_normalizer": "apple"
    }


    PUT test_normalizer/doc/2
    {
    "type": "Apple",
    "type_normalizer": "Apple"
    }

    查询三

    GET test_normalizer/_search
    {
    "query": {
    "term":{
    "type":"aPple"
    }
    }
    }

    查询四

    GET test_normalizer/_search
    {
    "query": {
    "term":{
    "type_normalizer":"aPple"
    }
    }
    }
    ``<br /> <br /> 我们第一步是自定义了名为lowercase的 normalizer,其中filter类似自定义分词器中的filter,但是可用的种类很少,详情大家可以查看官方文档。然后通过normalizer属性设定到字段type_normalizer中,然后插入相同的2条文档。执行发现,查询三无结果返回,查询四`返回2条文档。

    问题解决了!我们来看下是如何解决的

    ![](http://p8z8qq24s.bkt.clouddn.c ... 00.png)

  4. 文档写入时由于加入了 normalizer,所有的 term都会被做小写处理
  5. 查询时搜索词同样采用有 normalizer的配置,因此处理后的 term也是小写的
  6. 两边分词匹对,就得到了我们上面的结果


    3. 总结


    本文通过一个实例来给大家讲解了 Normalizer的实际使用场景,希望对大家有所帮助!

    ![](http://p8z8qq24s.bkt.clouddn.c ... 24.png)








es的data.path相关问题

chzhty001 回复了问题 • 3 人关注 • 5 个回复 • 3675 次浏览 • 2018-09-03 20:34 • 来自相关话题

线程池满 task_max_waiting_in_queue_millis" : 1290824 很大 es 写不进去数据了 怎么办

zqc0512 回复了问题 • 3 人关注 • 1 个回复 • 3438 次浏览 • 2018-08-28 10:22 • 来自相关话题

请问x-pack免费版和商用版的区别

zqc0512 回复了问题 • 3 人关注 • 2 个回复 • 10226 次浏览 • 2018-08-28 11:44 • 来自相关话题

ES数据节点load负载过高

lei2018 回复了问题 • 4 人关注 • 2 个回复 • 4374 次浏览 • 2018-09-04 14:47 • 来自相关话题

elasticsearch 6.3.2版本的使用许可证也是不能用机器学习功能是吗

zqc0512 回复了问题 • 2 人关注 • 1 个回复 • 2080 次浏览 • 2018-08-27 15:59 • 来自相关话题

es集群中存在超过100G的大分片是,怎么才能见效这个分片的质量(reindex成本过高..)

qw8613243 回复了问题 • 6 人关注 • 5 个回复 • 3387 次浏览 • 2018-09-04 18:25 • 来自相关话题

地理位置过滤盒模型精准度的问题

kennywu76 回复了问题 • 6 人关注 • 2 个回复 • 2383 次浏览 • 2018-08-28 09:52 • 来自相关话题

elasticsearch 模板问题

God_lockin 回复了问题 • 4 人关注 • 2 个回复 • 2696 次浏览 • 2018-08-27 13:55 • 来自相关话题

circuit_breaking_exception data too large

holly1202 回复了问题 • 2 人关注 • 2 个回复 • 8830 次浏览 • 2018-08-27 12:09 • 来自相关话题

查询企业名称的分词问题

rochy 回复了问题 • 2 人关注 • 1 个回复 • 6114 次浏览 • 2018-08-27 11:43 • 来自相关话题

掌握 analyze API,一举搞定 Elasticsearch 分词难题

rockybean 发表了文章 • 1 个评论 • 26597 次浏览 • 2018-08-25 22:44 • 来自相关话题

初次接触 Elasticsearch 的同学经常会遇到分词相关的难题,比如如下这些场景:

  1. 为什么明明有包含搜索关键词的文档,但结果里面就没有相关文档呢?
  2. 我存进去的文档到底被分成哪些词(term)了?
  3. 我得自定义分词规则,但感觉好麻烦呢,无从下手

    如果你遇到过类似的问题,希望本文可以解决你的疑惑。


    1. 上手


    让我们从一个实例出发,如下创建一个文档:

    json<br /> PUT test/doc/1<br /> {<br /> "msg":"Eating an apple a day keeps doctor away"<br /> }<br />

    然后我们做一个查询,我们试图通过搜索 eat这个关键词来搜索这个文档

    json<br /> POST test/_search<br /> {<br /> "query":{<br /> "match":{<br /> "msg":"eat"<br /> }<br /> }<br /> }<br />

    ES的返回结果为0。这不太对啊,我们用最基本的字符串查找也应该能匹配到上面新建的文档才对啊!

    各位不要急,我们先来看看什么是分词。


    2. 分词


    搜索引擎的核心是倒排索引(这里不展开讲),而倒排索引的基础就是分词。所谓分词可以简单理解为将一个完整的句子切割为一个个单词的过程。在 es 中单词对应英文为 term。我们简单看个例子:

    ![](http://p8z8qq24s.bkt.clouddn.c ... 54.png)

    ES 的倒排索引即是根据分词后的单词创建,即 北京天安门这4个单词。这也意味着你在搜索的时候也只能搜索这4个单词才能命中该文档。

    实际上 ES 的分词不仅仅发生在文档创建的时候,也发生在搜索的时候,如下图所示:

    ![](http://p8z8qq24s.bkt.clouddn.c ... 28.png)

    读时分词发生在用户查询时,ES 会即时地对用户输入的关键词进行分词,分词结果只存在内存中,当查询结束时,分词结果也会随即消失。而写时分词发生在文档写入时,ES 会对文档进行分词后,将结果存入倒排索引,该部分最终会以文件的形式存储于磁盘上,不会因查询结束或者 ES 重启而丢失。

    ES 中处理分词的部分被称作分词器,英文是Analyzer,它决定了分词的规则。ES 自带了很多默认的分词器,比如StandardKeywordWhitespace等等,默认是 Standard。当我们在读时或者写时分词时可以指定要使用的分词器。


    3. 写时分词结果


    回到上手阶段,我们来看下写入的文档最终分词结果是什么。通过如下 api 可以查看:

    json<br /> POST test/_analyze<br /> {<br /> "field": "msg",<br /> "text": "Eating an apple a day keeps doctor away"<br /> }<br />

    其中 test为索引名,_analyze 为查看分词结果的 endpoint,请求体中 field 为要查看的字段名,text为具体值。该 api 的作用就是请告诉我在 test 索引使用 msg 字段存储一段文本时,es 会如何分词。

    返回结果如下:

    json<br /> {<br /> "tokens": [<br /> {<br /> "token": "eating",<br /> "start_offset": 0,<br /> "end_offset": 6,<br /> "type": "<ALPHANUM>",<br /> "position": 0<br /> },<br /> {<br /> "token": "an",<br /> "start_offset": 7,<br /> "end_offset": 9,<br /> "type": "<ALPHANUM>",<br /> "position": 1<br /> },<br /> {<br /> "token": "apple",<br /> "start_offset": 10,<br /> "end_offset": 15,<br /> "type": "<ALPHANUM>",<br /> "position": 2<br /> },<br /> {<br /> "token": "a",<br /> "start_offset": 16,<br /> "end_offset": 17,<br /> "type": "<ALPHANUM>",<br /> "position": 3<br /> },<br /> {<br /> "token": "day",<br /> "start_offset": 18,<br /> "end_offset": 21,<br /> "type": "<ALPHANUM>",<br /> "position": 4<br /> },<br /> {<br /> "token": "keeps",<br /> "start_offset": 22,<br /> "end_offset": 27,<br /> "type": "<ALPHANUM>",<br /> "position": 5<br /> },<br /> {<br /> "token": "doctor",<br /> "start_offset": 28,<br /> "end_offset": 34,<br /> "type": "<ALPHANUM>",<br /> "position": 6<br /> },<br /> {<br /> "token": "away",<br /> "start_offset": 35,<br /> "end_offset": 39,<br /> "type": "<ALPHANUM>",<br /> "position": 7<br /> }<br /> ]<br /> }<br />

    返回结果中的每一个 token即为分词后的每一个单词,我们可以看到这里是没有 eat 这个单词的,这也解释了在上手中我们搜索 eat 没有结果的情况。如果你去搜索 eating ,会有结果返回。

    写时分词器需要在 mapping 中指定,而且一经指定就不能再修改,若要修改必须新建索引。如下所示我们新建一个名为ms_english 的字段,指定其分词器为 english

    json<br /> PUT test/_mapping/doc<br /> {<br /> "properties": {<br /> "msg_english":{<br /> "type":"text",<br /> "analyzer": "english"<br /> }<br /> }<br /> }<br />



    4. 读时分词结果


    由于读时分词器默认与写时分词器默认保持一致,拿 上手 中的例子,你搜索 msg 字段,那么读时分词器为 Standard ,搜索 msg_english 时分词器则为 english。这种默认设定也是非常容易理解的,读写采用一致的分词器,才能尽最大可能保证分词的结果是可以匹配的。

    然后 ES 允许读时分词器单独设置,如下所示:

    json<br /> POST test/_search<br /> {<br /> "query":{<br /> "match":{<br /> "msg":{<br /> "query": "eating",<br /> "analyzer": "english"<br /> }<br /> }<br /> }<br /> }<br />

    如上 analyzer 字段即可以自定义读时分词器,一般来讲不需要特别指定读时分词器。

    如果不单独设置分词器,那么读时分词器的验证方法与写时一致;如果是自定义分词器,那么可以使用如下的 api 来自行验证结果。

    json<br /> POST _analyze<br /> {<br /> "text":"eating",<br /> "analyzer":"english"<br /> }<br />

    返回结果如下:

    json<br /> {<br /> "tokens": [<br /> {<br /> "token": "eat",<br /> "start_offset": 0,<br /> "end_offset": 6,<br /> "type": "<ALPHANUM>",<br /> "position": 0<br /> }<br /> ]<br /> }<br />

    由上可知 english分词器会将 eating处理为 eat,大家可以再测试下默认的 standard分词器,它没有做任何处理。



    5. 解释问题


    现在我们再来看下 上手 中所遇问题的解决思路。

  4. 查看文档写时分词结果
  5. 查看查询关键词的读时分词结果
  6. 匹对两者是否有命中

    我们简单分析如下:

    ![](http://p8z8qq24s.bkt.clouddn.c ... 43.png)

    由上图可以定位问题的原因了。



    6. 解决需求


    由于 eating只是 eat的一个变形,我们依然希望输入 eat时可以匹配包含 eating的文档,那么该如何解决呢?

    答案很简单,既然原因是在分词结果不匹配,那么我们就换一个分词器呗~ 我们可以先试下 ES 自带的 english分词器,如下:

    ```json

    增加字段 msg_english,与 msg 做对比

    PUT test/_mapping/doc
    {
    "properties": {
    "msg_english":{
    "type":"text",
    "analyzer": "english"
    }
    }
    }

    写入相同文档

    PUT test/doc/1
    {
    "msg":"Eating an apple a day keeps doctor away",
    "msg_english":"Eating an apple a day keeps doctor away"
    }

    搜索 msg_english 字段

    POST test/_search
    {
    "query": {
    "match": {
    "msg_english": "eat"
    }
    }
    }
    ``<br /> <br /> <br /> <br /> 执行上面的内容,我们会发现结果有内容了,原因也很简单,如下图所示:<br /> <br /> ![](<a href="http://p8z8qq24s.bkt.clouddn.com/img20180825212639.pn" rel="nofollow" target="_blank">http://p8z8qq24s.bkt.clouddn.c ... 39.pn</a>g)<br /> <br /> 由上图可见english分词器会将eating分词为eat,此时我们搜索eat或者eating`肯定都可以匹配对应的文档了。至此,需求解决。


    7. 深入分析


    最后我们来看下为什么english分词器可以解决我们遇到的问题。一个分词器由三部分组成:char filter、tokenizer 和 token filter。各部分的作用我们这里就不展开了,我们来看下 standardenglish分词器的区别。

    ![](http://p8z8qq24s.bkt.clouddn.c ... 09.png)

    从上图可以看出,english分词器在 Token Filter 中和 Standard不同,而发挥主要作用的就是 stemmer,感兴趣的同学可以自行去看起它的作用。


    8. 自定义分词


    如果我们不使用 english分词器,自定义一个分词器来实现上述需求也是完全可行的,这里不详细讲解了,只给大家讲一个快速验证自定义分词器效果的方法,如下:

    json<br /> POST _analyze<br /> {<br /> "char_filter": [], <br /> "tokenizer": "standard",<br /> "filter": [<br /> "stop",<br /> "lowercase",<br /> "stemmer"<br /> ],<br /> "text": "Eating an apple a day keeps doctor away"<br /> }<br />

    通过上面的 api 你可以快速验证自己要定制的分词器,当达到自己需求后,再将这一部分配置加入索引的配置。



    至此,我们再看开篇的三个问题,相信你已经心里有答案了,赶紧上手去自行测试下吧!

    ![](http://p8z8qq24s.bkt.clouddn.c ... 24.png)

elasticsearch 均衡策略分片选择疑问

rochy 回复了问题 • 4 人关注 • 1 个回复 • 4813 次浏览 • 2018-08-27 09:19 • 来自相关话题

关闭index可能会消耗大量的磁盘空间

luman 回复了问题 • 2 人关注 • 1 个回复 • 2197 次浏览 • 2018-08-24 18:18 • 来自相关话题

ES 6.3+ Query Cache失效?

lingerchouzi 回复了问题 • 17 人关注 • 10 个回复 • 6338 次浏览 • 2019-12-30 16:12 • 来自相关话题