无论才能、知识多么卓著,如果缺乏热情,则无异纸上画饼充饥,无补于事。
分词器

分词器

如何在ES中搜索值为空的键值对

Elasticsearchliaosy 发表了文章 • 0 个评论 • 2550 次浏览 • 2023-07-24 18:19 • 来自相关话题

问题背景

今天早上,接到开发那边一个特殊的查询需求,在 Kibana 中搜索一个 json 类型日志中值为一个空大括号的键值对, 具体的日志示例如下:

{
    "clientIp": "10.111.121.51",
    "query": "{}",
    "serviceUrl": "/aaa/bbb/cc",
}

也就是说针对这个类型的日志过滤出 query 值为空的请求 "query": "{}", 开发同学测试了直接在 kibana 中查询这个字符串 "query": "{}" 根本查不到我们想要的结果。 我们使用的是 ELK 8.3 的全家桶, 这个日志数据使用的默认 standard analyzer 的分词器。

初步分析

我们先对这个要查询的字符串进行下分词测试:

GET /_analyze
{
"analyzer" : "standard",
"text": "\"query\":\"{}\""
}

结果不出所料,我们想要空大括号在分词的时候直接就被干掉了,仅保留了 query 这一个 token:

{
  "tokens": [{
    "token": "query",
    "start_offset": 1,
    "end_offset": 6,
    "type": "<ALPHANUM>",
    "position": 0
  }]
}

我们使用的 standard analyzer 在数据写入分词时直接抛弃掉{}等特殊字符,看来直接搜索 "query": "{}" 关键词这条路肯定是走不通。

换个思路

在网上搜索了一下解决的办法,有些搜索特殊字符的办法,但需要修改分词器,我们已经写入的日志数据量比较大,不太愿意因为这个搜索请求来修改分词器再 reindex。 但是我们的日志格式是固定的,serviceUrl 这个键值对总是在 query 后面的,那么我们可以结合前后文实现相同的 搜索效果:

GET /_analyze
{
"analyzer" : "standard",
"text": "\"query\":\"{}\",\"serviceUrl\""
}

可以看到这段被分为 2 个相邻的单词

{
"tokens": [{
"token": "query",
"start_offset": 1,
"end_offset": 6,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "serviceurl",
"start_offset": 14,
"end_offset": 24,
"type": "<ALPHANUM>",
"position": 1
}
]
}

那么通过搜索 query 和 serviceUrl 为相邻的 2 个字是完全可以实现 query 的值为空的同样的查询效果。 为了确认在我们已经写入的数据中 query 和 serviceurl 也是相邻的,我们通过 ES termvectors API 确认了已经在 es 中的数据和我们这里测试的情况相同:

GET /<index>/_termvectors/<_id>?fields=message

"query" : {
  "term_freq" : 1,
  "tokens" : [
    {
      "position" : 198,
      "start_offset" : 2138,
      "end_offset" : 2143
    }
  ]
},
"serviceurl" : {
  "term_freq" : 1,
  "tokens" : [
    {
      "position" : 199,
      "start_offset" : 2151,
      "end_offset" : 2161
    }
  ]
},

这里我们可以看到 query 在 message 字段里面出现一次,其 end_offset 和 serviceurl 的 start_offset 之前也是相差 8, 和我们测试的结果相同。 这个时候我们就将原来的查询需求,转化为了对 "query serviceurl" 进行按顺序的精准查询就行了, 使用 match_phrase 可以达到我们的目的。

GET /_search
"query": {
    "match_phrase": {
        "message": {
            "query": "query serviceurl",
            "slop" : 0

        }
    }
}

这里顺便说一下,slop 这个参数,slop=n 表示,表示可以隔 n 个字(英文词)进行匹配, 这里设置为 0 就强制要求 query 和 serviceurl 这 2 个单词必须相邻,0 也是 slop 的默认值,在这个请求中是可以省略的,这是为什么 match_phrase 是会获得精准查询的原因之一。 好了,我们通过 console 确定了有效的 query 之后,对于开发同学查看日志只需要在 Kibana 的搜索栏中直接使用双引号引起来的精确搜索 "query serviceurl" 就可以了。

继续深挖一下,ngram 分词器

虽然开发同学搜索的问题解决了,但我仍然不太满意,毕竟这次的问题我们的日志格式是固定的,如果我们一定要搜索到 "query": "{}" 这个应该怎么办呢? 首先很明确,使用我们默认的 standard analyzer 不修改任何参数肯定是不行的,"{}" 这些特殊字符都直接被干掉了, 参考了网上找到的这篇文章,https://blog.csdn.net/fox_233/article/details/127388058 按照这个 ngram 分词器的思路,我动手对我们的需求进行了下测试

首先先看看我们使用 ngram 分词器的分词效果, 我们这里简化了一下,去掉了原来的双引号,以避免过多 \:

GET _analyze
{
  "tokenizer": "ngram",
  "text": "query:{}"
}

{
  "tokens" : [
    {
      "token" : "q",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    ...
    {
      "token" : "{",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "word",
      "position" : 12
    },
    {
      "token" : "{}",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "word",
      "position" : 13
    },
    {
      "token" : "}",
      "start_offset" : 7,
      "end_offset" : 8,
      "type" : "word",
      "position" : 14
    }
  ]
}

可以很明显的看到大括号被成功的分词了,果然是有戏。 直接定义一个 index 实战一下搜索效果

PUT specialchar_debug
{
  "settings": {
    "analysis": {
      "analyzer": {
        "specialchar_analyzer": {
          "tokenizer": "specialchar_tokenizer"
        }
      },
      "tokenizer": {
        "specialchar_tokenizer": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 2
        }
      }
    }
  },
  "mappings": {
      "properties": {
        "text": {
          "analyzer": "specialchar_analyzer",
          "type": "text"
        }
      }
    }
}

插入几条测试数据:

PUT specialchar_debug/_doc/1
{  "text": "query:{},serviceUrl"
}

PUT specialchar_debug/_doc/2
{  "text": "query:{aaa},serviceUrl"
}

PUT specialchar_debug/_doc/3
{  "text": "query:{bbb}, ccc, serviceUrl"
}

我们再测试一下搜索效果,

GET specialchar_debug/_search
{
  "query": {
    "match_phrase": {
      "text": "query:{}"
    }
  }
}

结果完全是我们想要的,看来这个方案可行

"hits" : [
  {
    "_index" : "specialchar_debug",
    "_id" : "1",
    "_score" : 2.402917,
    "_source" : {
      "text" : "query:{},serviceUrl"
    }
  }
]

小结

对于日志系统,我们一直在使用 ES 默认的 standard analyzer 的分词器, 基本上满足我们生产遇到的 99% 的需求,但面对特殊字符的这种搜索请求,确实比较无奈。这次遇到的空键值对的需求,我们通过搜索 2 个相邻的键绕过了问题。 如果一定要搜索这个字符串的话,我们也可以使用 ngram 分词器重新进行分词再进行处理, 条条大路通罗马。

作者介绍

卞弘智,研发工程师,10 多年的 SRE 经验,工作经历涵盖 DevOps,日志处理系统,监控和告警系统研发,WAF 和网关等系统基础架构领域,致力于通过优秀的开源软件推动自动化和智能化基础架构平台的演进。

ik分词器搜不出单个中文词

Elasticsearch王培坤 回复了问题 • 4 人关注 • 4 个回复 • 7894 次浏览 • 2021-09-26 20:37 • 来自相关话题

自己写了一个elasticsearch中文分词插件

ElasticsearchBKing 回复了问题 • 4 人关注 • 2 个回复 • 2470 次浏览 • 2020-11-23 04:55 • 来自相关话题

es安装分词器插件hanlp之后并不能用

ElasticsearchGod_lockin 回复了问题 • 2 人关注 • 2 个回复 • 2218 次浏览 • 2019-12-06 15:28 • 来自相关话题

es安装分词器插件hanlp之后并不能用

Elasticsearchliuxg 回复了问题 • 3 人关注 • 3 个回复 • 3338 次浏览 • 2019-12-06 14:19 • 来自相关话题

不规则的产品编号该如何进行分词呢

Elasticsearchmedcl 回复了问题 • 2 人关注 • 1 个回复 • 3253 次浏览 • 2019-10-16 21:23 • 来自相关话题

请问各位大神有没有类似于中英文互译的分词器存在呢

回复

Elasticsearchjiaxs 发起了问题 • 1 人关注 • 0 个回复 • 2072 次浏览 • 2019-08-27 16:23 • 来自相关话题

如%#¥这种特殊符号需要搜索出来应该如何处理

Elasticsearchlaoyang360 回复了问题 • 6 人关注 • 4 个回复 • 8009 次浏览 • 2019-05-30 17:53 • 来自相关话题

java中使用ik分词

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 4548 次浏览 • 2018-10-19 16:41 • 来自相关话题

elasticsearch如何用java查看索引使用的分词器

Elasticsearchrochy 回复了问题 • 3 人关注 • 5 个回复 • 2937 次浏览 • 2018-10-12 16:46 • 来自相关话题

倒排索引怎么识别词元位置信息

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 1834 次浏览 • 2018-09-25 18:57 • 来自相关话题

查询企业名称的分词问题

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

ES如何实现实时自定义分词?

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 4160 次浏览 • 2018-08-23 16:26 • 来自相关话题

googleplay分成google play,什么分词器支持呢?

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 2653 次浏览 • 2018-07-13 11:02 • 来自相关话题

新人求助。如何使用分词器查询。

Elasticsearchlaoyang360 回复了问题 • 2 人关注 • 1 个回复 • 2816 次浏览 • 2018-05-24 23:17 • 来自相关话题

ik分词器搜不出单个中文词

回复

Elasticsearch王培坤 回复了问题 • 4 人关注 • 4 个回复 • 7894 次浏览 • 2021-09-26 20:37 • 来自相关话题

自己写了一个elasticsearch中文分词插件

回复

ElasticsearchBKing 回复了问题 • 4 人关注 • 2 个回复 • 2470 次浏览 • 2020-11-23 04:55 • 来自相关话题

es安装分词器插件hanlp之后并不能用

回复

ElasticsearchGod_lockin 回复了问题 • 2 人关注 • 2 个回复 • 2218 次浏览 • 2019-12-06 15:28 • 来自相关话题

es安装分词器插件hanlp之后并不能用

回复

Elasticsearchliuxg 回复了问题 • 3 人关注 • 3 个回复 • 3338 次浏览 • 2019-12-06 14:19 • 来自相关话题

不规则的产品编号该如何进行分词呢

回复

Elasticsearchmedcl 回复了问题 • 2 人关注 • 1 个回复 • 3253 次浏览 • 2019-10-16 21:23 • 来自相关话题

请问各位大神有没有类似于中英文互译的分词器存在呢

回复

Elasticsearchjiaxs 发起了问题 • 1 人关注 • 0 个回复 • 2072 次浏览 • 2019-08-27 16:23 • 来自相关话题

如%#¥这种特殊符号需要搜索出来应该如何处理

回复

Elasticsearchlaoyang360 回复了问题 • 6 人关注 • 4 个回复 • 8009 次浏览 • 2019-05-30 17:53 • 来自相关话题

java中使用ik分词

回复

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 4548 次浏览 • 2018-10-19 16:41 • 来自相关话题

elasticsearch如何用java查看索引使用的分词器

回复

Elasticsearchrochy 回复了问题 • 3 人关注 • 5 个回复 • 2937 次浏览 • 2018-10-12 16:46 • 来自相关话题

倒排索引怎么识别词元位置信息

回复

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 1834 次浏览 • 2018-09-25 18:57 • 来自相关话题

查询企业名称的分词问题

回复

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

ES如何实现实时自定义分词?

回复

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 4160 次浏览 • 2018-08-23 16:26 • 来自相关话题

googleplay分成google play,什么分词器支持呢?

回复

Elasticsearchrochy 回复了问题 • 2 人关注 • 1 个回复 • 2653 次浏览 • 2018-07-13 11:02 • 来自相关话题

新人求助。如何使用分词器查询。

回复

Elasticsearchlaoyang360 回复了问题 • 2 人关注 • 1 个回复 • 2816 次浏览 • 2018-05-24 23:17 • 来自相关话题

请问写一个自己的es分词器需要从哪开始

回复

ElasticsearchelasticStack 发起了问题 • 3 人关注 • 0 个回复 • 2590 次浏览 • 2018-05-15 14:49 • 来自相关话题

如何在ES中搜索值为空的键值对

Elasticsearchliaosy 发表了文章 • 0 个评论 • 2550 次浏览 • 2023-07-24 18:19 • 来自相关话题

问题背景

今天早上,接到开发那边一个特殊的查询需求,在 Kibana 中搜索一个 json 类型日志中值为一个空大括号的键值对, 具体的日志示例如下:

{
    "clientIp": "10.111.121.51",
    "query": "{}",
    "serviceUrl": "/aaa/bbb/cc",
}

也就是说针对这个类型的日志过滤出 query 值为空的请求 "query": "{}", 开发同学测试了直接在 kibana 中查询这个字符串 "query": "{}" 根本查不到我们想要的结果。 我们使用的是 ELK 8.3 的全家桶, 这个日志数据使用的默认 standard analyzer 的分词器。

初步分析

我们先对这个要查询的字符串进行下分词测试:

GET /_analyze
{
"analyzer" : "standard",
"text": "\"query\":\"{}\""
}

结果不出所料,我们想要空大括号在分词的时候直接就被干掉了,仅保留了 query 这一个 token:

{
  "tokens": [{
    "token": "query",
    "start_offset": 1,
    "end_offset": 6,
    "type": "<ALPHANUM>",
    "position": 0
  }]
}

我们使用的 standard analyzer 在数据写入分词时直接抛弃掉{}等特殊字符,看来直接搜索 "query": "{}" 关键词这条路肯定是走不通。

换个思路

在网上搜索了一下解决的办法,有些搜索特殊字符的办法,但需要修改分词器,我们已经写入的日志数据量比较大,不太愿意因为这个搜索请求来修改分词器再 reindex。 但是我们的日志格式是固定的,serviceUrl 这个键值对总是在 query 后面的,那么我们可以结合前后文实现相同的 搜索效果:

GET /_analyze
{
"analyzer" : "standard",
"text": "\"query\":\"{}\",\"serviceUrl\""
}

可以看到这段被分为 2 个相邻的单词

{
"tokens": [{
"token": "query",
"start_offset": 1,
"end_offset": 6,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "serviceurl",
"start_offset": 14,
"end_offset": 24,
"type": "<ALPHANUM>",
"position": 1
}
]
}

那么通过搜索 query 和 serviceUrl 为相邻的 2 个字是完全可以实现 query 的值为空的同样的查询效果。 为了确认在我们已经写入的数据中 query 和 serviceurl 也是相邻的,我们通过 ES termvectors API 确认了已经在 es 中的数据和我们这里测试的情况相同:

GET /<index>/_termvectors/<_id>?fields=message

"query" : {
  "term_freq" : 1,
  "tokens" : [
    {
      "position" : 198,
      "start_offset" : 2138,
      "end_offset" : 2143
    }
  ]
},
"serviceurl" : {
  "term_freq" : 1,
  "tokens" : [
    {
      "position" : 199,
      "start_offset" : 2151,
      "end_offset" : 2161
    }
  ]
},

这里我们可以看到 query 在 message 字段里面出现一次,其 end_offset 和 serviceurl 的 start_offset 之前也是相差 8, 和我们测试的结果相同。 这个时候我们就将原来的查询需求,转化为了对 "query serviceurl" 进行按顺序的精准查询就行了, 使用 match_phrase 可以达到我们的目的。

GET /_search
"query": {
    "match_phrase": {
        "message": {
            "query": "query serviceurl",
            "slop" : 0

        }
    }
}

这里顺便说一下,slop 这个参数,slop=n 表示,表示可以隔 n 个字(英文词)进行匹配, 这里设置为 0 就强制要求 query 和 serviceurl 这 2 个单词必须相邻,0 也是 slop 的默认值,在这个请求中是可以省略的,这是为什么 match_phrase 是会获得精准查询的原因之一。 好了,我们通过 console 确定了有效的 query 之后,对于开发同学查看日志只需要在 Kibana 的搜索栏中直接使用双引号引起来的精确搜索 "query serviceurl" 就可以了。

继续深挖一下,ngram 分词器

虽然开发同学搜索的问题解决了,但我仍然不太满意,毕竟这次的问题我们的日志格式是固定的,如果我们一定要搜索到 "query": "{}" 这个应该怎么办呢? 首先很明确,使用我们默认的 standard analyzer 不修改任何参数肯定是不行的,"{}" 这些特殊字符都直接被干掉了, 参考了网上找到的这篇文章,https://blog.csdn.net/fox_233/article/details/127388058 按照这个 ngram 分词器的思路,我动手对我们的需求进行了下测试

首先先看看我们使用 ngram 分词器的分词效果, 我们这里简化了一下,去掉了原来的双引号,以避免过多 \:

GET _analyze
{
  "tokenizer": "ngram",
  "text": "query:{}"
}

{
  "tokens" : [
    {
      "token" : "q",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "word",
      "position" : 0
    },
    ...
    {
      "token" : "{",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "word",
      "position" : 12
    },
    {
      "token" : "{}",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "word",
      "position" : 13
    },
    {
      "token" : "}",
      "start_offset" : 7,
      "end_offset" : 8,
      "type" : "word",
      "position" : 14
    }
  ]
}

可以很明显的看到大括号被成功的分词了,果然是有戏。 直接定义一个 index 实战一下搜索效果

PUT specialchar_debug
{
  "settings": {
    "analysis": {
      "analyzer": {
        "specialchar_analyzer": {
          "tokenizer": "specialchar_tokenizer"
        }
      },
      "tokenizer": {
        "specialchar_tokenizer": {
          "type": "ngram",
          "min_gram": 1,
          "max_gram": 2
        }
      }
    }
  },
  "mappings": {
      "properties": {
        "text": {
          "analyzer": "specialchar_analyzer",
          "type": "text"
        }
      }
    }
}

插入几条测试数据:

PUT specialchar_debug/_doc/1
{  "text": "query:{},serviceUrl"
}

PUT specialchar_debug/_doc/2
{  "text": "query:{aaa},serviceUrl"
}

PUT specialchar_debug/_doc/3
{  "text": "query:{bbb}, ccc, serviceUrl"
}

我们再测试一下搜索效果,

GET specialchar_debug/_search
{
  "query": {
    "match_phrase": {
      "text": "query:{}"
    }
  }
}

结果完全是我们想要的,看来这个方案可行

"hits" : [
  {
    "_index" : "specialchar_debug",
    "_id" : "1",
    "_score" : 2.402917,
    "_source" : {
      "text" : "query:{},serviceUrl"
    }
  }
]

小结

对于日志系统,我们一直在使用 ES 默认的 standard analyzer 的分词器, 基本上满足我们生产遇到的 99% 的需求,但面对特殊字符的这种搜索请求,确实比较无奈。这次遇到的空键值对的需求,我们通过搜索 2 个相邻的键绕过了问题。 如果一定要搜索这个字符串的话,我们也可以使用 ngram 分词器重新进行分词再进行处理, 条条大路通罗马。

作者介绍

卞弘智,研发工程师,10 多年的 SRE 经验,工作经历涵盖 DevOps,日志处理系统,监控和告警系统研发,WAF 和网关等系统基础架构领域,致力于通过优秀的开源软件推动自动化和智能化基础架构平台的演进。

社区日报 第255期 (2018-04-28)

社区日报elk123 发表了文章 • 0 个评论 • 2838 次浏览 • 2018-04-28 12:17 • 来自相关话题

1、在docker上搭建Elasticsearch全文索引应用 http://t.cn/REh0ucW 2、Elasticsearch写入优化 http://t.cn/Ruazvzt 3、关于分词器的各个方面 http://t.cn/Rua7iMu  编辑:wt 归档:https://elasticsearch.cn/article/595 订阅:https://tinyletter.com/elastic-daily
1、在docker上搭建Elasticsearch全文索引应用 http://t.cn/REh0ucW 2、Elasticsearch写入优化 http://t.cn/Ruazvzt 3、关于分词器的各个方面 http://t.cn/Rua7iMu  编辑:wt 归档:https://elasticsearch.cn/article/595 订阅:https://tinyletter.com/elastic-daily