Q:有两个人掉到陷阱里了,死的人叫死人,活人叫什么?
分词器

分词器

IK 字段级别词典升级:IK reload API

EasysearchINFINI Labs 小助手 发表了文章 • 0 个评论 • 110 次浏览 • 9 小时前 • 来自相关话题

之前介绍 IK 字段级别字典 使用的时候,对于字典的更新只是支持词典库的新增,并不支持对存量词典库的修改或者删除。经过这段时间的开发,已经可以兼容词典库的更新,主要通过 IK reload API 来实现。

IK reload API

IK reload API 通过对词典库的全量重新加载来实现词典库的更新或者删除。用户可以通过下面的命令实现:

# 测试索引准备

PUT my-index-000001
{
  "settings": {
    "number_of_shards": 3,
    "analysis": {
      "analyzer": {
        "my_custom_analyzer": {
          "type": "custom",
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {

          "type": "ik_smart",
          "custom_dict_enable": true,
          "load_default_dicts":false, # 这里不包含默认词库
          "lowcase_enable": true,
          "dict_key": "test_dic"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "test_ik": {
        "type": "text",
        "analyzer": "my_custom_analyzer"
      }
    }
  }
}

# 原来词库分词效果,只预置了分词“自强不息”
GET my-index-000001/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text":"自强不息,杨树林"
}

{
  "tokens": [
    {
      "token": "自强不息",
      "start_offset": 0,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "杨",
      "start_offset": 5,
      "end_offset": 6,
      "type": "CN_CHAR",
      "position": 1
    },
    {
      "token": "树",
      "start_offset": 6,
      "end_offset": 7,
      "type": "CN_CHAR",
      "position": 2
    },
    {
      "token": "林",
      "start_offset": 7,
      "end_offset": 8,
      "type": "CN_CHAR",
      "position": 3
    }
  ]
}

# 更新词库
POST .analysis_ik/_doc
{
  "dict_key": "test_dic",
  "dict_type": "main_dicts",
  "dict_content":"杨树林"
}
# 删除词库,词库文档的id为coayoJcBFHNnLYAKfTML
DELETE .analysis_ik/_doc/coayoJcBFHNnLYAKfTML?refresh=true

# 重载词库
POST _ik/_reload
{}

# 更新后的词库效果
GET my-index-000001/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text":"自强不息,杨树林"
}

{
  "tokens": [
    {
      "token": "自",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_CHAR",
      "position": 0
    },
    {
      "token": "强",
      "start_offset": 1,
      "end_offset": 2,
      "type": "CN_CHAR",
      "position": 1
    },
    {
      "token": "不",
      "start_offset": 2,
      "end_offset": 3,
      "type": "CN_CHAR",
      "position": 2
    },
    {
      "token": "息",
      "start_offset": 3,
      "end_offset": 4,
      "type": "CN_CHAR",
      "position": 3
    },
    {
      "token": "杨树林",
      "start_offset": 5,
      "end_offset": 8,
      "type": "CN_WORD",
      "position": 4
    }
  ]
}

这里是实现索引里全部的词库更新。

也可以实现单独的词典库更新

POST _ik/_reload
{"dict_key":"test_dic”}

# debug 日志
[2025-07-09T15:30:29,439][INFO ][o.e.a.i.ReloadIK         ] [ik-1] 收到重载IK词典的请求,将在所有节点上执行。dict_key: test_dic, dict_index: .analysis_ik
[2025-07-09T15:30:29,439][INFO ][o.e.a.i.a.TransportReloadIKDictionaryAction] [ik-1] 在节点 [R6ESV5h1Q8OZMNoosSDEmg] 上执行词典重载操作,dict_key: test_dic, dict_index: .analysis_ik

这里传入的 dict_key 对应的词库 id。

对于自定义的词库存储索引,也可以指定词库索引的名称,如果不指定则默认使用 .analysis_ik

POST _ik/_reload
{"dict_index":"ik_index"}

# debug 日志
[2025-07-09T15:32:59,196][INFO ][o.e.a.i.a.TransportReloadIKDictionaryAction] [ik-1] 在节点 [R6ESV5h1Q8OZMNoosSDEmg] 上执行词典重载操作,dict_key: null, dict_index: test_ik
[2025-07-09T15:32:59,196][INFO ][o.w.a.d.ReloadDict       ] [ik-1] Reloading all dictionaries

注:

  1. 更新或者删除词库重载后只是对后续写入的文档生效,对已索引的文档无效;
  2. 因为用户无法直接更改 IK 内置的词库(即默认配置路径下的词库文件),因此 reload API 不会影响内置词库的信息。

相关阅读

关于 IK Analysis

IK Analysis 插件集成了 Lucene IK 分析器,并支持自定义词典。它支持 Easysearch\Elasticsearch\OpenSearch 的主要版本。由 INFINI Labs 维护并提供支持。

该插件包含分析器:ik_smart 和 ik_max_word,以及分词器:ik_smart 和 ik_max_word

开源地址:https://github.com/infinilabs/analysis-ik

作者:金多安,极限科技(INFINI Labs)搜索运维专家,Elastic 认证专家,搜索客社区日报责任编辑。一直从事与搜索运维相关的工作,日常会去挖掘 ES / Lucene 方向的搜索技术原理,保持搜索相关技术发展的关注。
原文:https://infinilabs.cn/blog/2025/ik-field-level-dictionarys-2/

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

Elasticsearchliaosy 发表了文章 • 0 个评论 • 3268 次浏览 • 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 个回复 • 8554 次浏览 • 2021-09-26 20:37 • 来自相关话题

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

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

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

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

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

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

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

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

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

回复

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

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

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

java中使用ik分词

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

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

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

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

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

查询企业名称的分词问题

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

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

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

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

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

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

回复

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

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

回复

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

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

回复

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

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

回复

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

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

回复

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

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

回复

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

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

回复

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

java中使用ik分词

回复

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

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

回复

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

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

回复

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

查询企业名称的分词问题

回复

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

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

回复

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

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

回复

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

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

回复

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

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

回复

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

IK 字段级别词典升级:IK reload API

EasysearchINFINI Labs 小助手 发表了文章 • 0 个评论 • 110 次浏览 • 9 小时前 • 来自相关话题

之前介绍 IK 字段级别字典 使用的时候,对于字典的更新只是支持词典库的新增,并不支持对存量词典库的修改或者删除。经过这段时间的开发,已经可以兼容词典库的更新,主要通过 IK reload API 来实现。

IK reload API

IK reload API 通过对词典库的全量重新加载来实现词典库的更新或者删除。用户可以通过下面的命令实现:

# 测试索引准备

PUT my-index-000001
{
  "settings": {
    "number_of_shards": 3,
    "analysis": {
      "analyzer": {
        "my_custom_analyzer": {
          "type": "custom",
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {

          "type": "ik_smart",
          "custom_dict_enable": true,
          "load_default_dicts":false, # 这里不包含默认词库
          "lowcase_enable": true,
          "dict_key": "test_dic"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "test_ik": {
        "type": "text",
        "analyzer": "my_custom_analyzer"
      }
    }
  }
}

# 原来词库分词效果,只预置了分词“自强不息”
GET my-index-000001/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text":"自强不息,杨树林"
}

{
  "tokens": [
    {
      "token": "自强不息",
      "start_offset": 0,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "杨",
      "start_offset": 5,
      "end_offset": 6,
      "type": "CN_CHAR",
      "position": 1
    },
    {
      "token": "树",
      "start_offset": 6,
      "end_offset": 7,
      "type": "CN_CHAR",
      "position": 2
    },
    {
      "token": "林",
      "start_offset": 7,
      "end_offset": 8,
      "type": "CN_CHAR",
      "position": 3
    }
  ]
}

# 更新词库
POST .analysis_ik/_doc
{
  "dict_key": "test_dic",
  "dict_type": "main_dicts",
  "dict_content":"杨树林"
}
# 删除词库,词库文档的id为coayoJcBFHNnLYAKfTML
DELETE .analysis_ik/_doc/coayoJcBFHNnLYAKfTML?refresh=true

# 重载词库
POST _ik/_reload
{}

# 更新后的词库效果
GET my-index-000001/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text":"自强不息,杨树林"
}

{
  "tokens": [
    {
      "token": "自",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_CHAR",
      "position": 0
    },
    {
      "token": "强",
      "start_offset": 1,
      "end_offset": 2,
      "type": "CN_CHAR",
      "position": 1
    },
    {
      "token": "不",
      "start_offset": 2,
      "end_offset": 3,
      "type": "CN_CHAR",
      "position": 2
    },
    {
      "token": "息",
      "start_offset": 3,
      "end_offset": 4,
      "type": "CN_CHAR",
      "position": 3
    },
    {
      "token": "杨树林",
      "start_offset": 5,
      "end_offset": 8,
      "type": "CN_WORD",
      "position": 4
    }
  ]
}

这里是实现索引里全部的词库更新。

也可以实现单独的词典库更新

POST _ik/_reload
{"dict_key":"test_dic”}

# debug 日志
[2025-07-09T15:30:29,439][INFO ][o.e.a.i.ReloadIK         ] [ik-1] 收到重载IK词典的请求,将在所有节点上执行。dict_key: test_dic, dict_index: .analysis_ik
[2025-07-09T15:30:29,439][INFO ][o.e.a.i.a.TransportReloadIKDictionaryAction] [ik-1] 在节点 [R6ESV5h1Q8OZMNoosSDEmg] 上执行词典重载操作,dict_key: test_dic, dict_index: .analysis_ik

这里传入的 dict_key 对应的词库 id。

对于自定义的词库存储索引,也可以指定词库索引的名称,如果不指定则默认使用 .analysis_ik

POST _ik/_reload
{"dict_index":"ik_index"}

# debug 日志
[2025-07-09T15:32:59,196][INFO ][o.e.a.i.a.TransportReloadIKDictionaryAction] [ik-1] 在节点 [R6ESV5h1Q8OZMNoosSDEmg] 上执行词典重载操作,dict_key: null, dict_index: test_ik
[2025-07-09T15:32:59,196][INFO ][o.w.a.d.ReloadDict       ] [ik-1] Reloading all dictionaries

注:

  1. 更新或者删除词库重载后只是对后续写入的文档生效,对已索引的文档无效;
  2. 因为用户无法直接更改 IK 内置的词库(即默认配置路径下的词库文件),因此 reload API 不会影响内置词库的信息。

相关阅读

关于 IK Analysis

IK Analysis 插件集成了 Lucene IK 分析器,并支持自定义词典。它支持 Easysearch\Elasticsearch\OpenSearch 的主要版本。由 INFINI Labs 维护并提供支持。

该插件包含分析器:ik_smart 和 ik_max_word,以及分词器:ik_smart 和 ik_max_word

开源地址:https://github.com/infinilabs/analysis-ik

作者:金多安,极限科技(INFINI Labs)搜索运维专家,Elastic 认证专家,搜索客社区日报责任编辑。一直从事与搜索运维相关的工作,日常会去挖掘 ES / Lucene 方向的搜索技术原理,保持搜索相关技术发展的关注。
原文:https://infinilabs.cn/blog/2025/ik-field-level-dictionarys-2/

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

Elasticsearchliaosy 发表了文章 • 0 个评论 • 3268 次浏览 • 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 个评论 • 3200 次浏览 • 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