Elasticsearch 是一个分布式、免费和开放的搜索和分析引擎,适用于所有类型的数据,例如文本、数字、地理空间、结构化和非结构化数据。 它基于 Apache Lucene 构建,Apache Lucene 是一个全文搜索引擎,可用于各种编程语言。 由于其速度、可扩展性以及对不同类型内容进行索引的能力,Elasticsearch 已在多种用例中得到应用,例如:
- 企业搜索
- 日志记录和日志分析
- 应用搜索
- 商业分析
- 地理空间数据分析和可视化
它是如何工作的?
Elasticsearch 不是将信息存储为列式数据行,而是存储已序列化为 JSON 文档的复杂数据结构。 每个文档由一组键(文档中的字段或属性的名称)及其相应的值(字符串、数字、布尔值、日期、值数组、地理位置或其他类型的数据)组成。 它使用一种称为倒排索引的数据结构,列出任何文档中出现的每个唯一单词,并标识每个单词出现的所有文档。
字段类型 - 分析或未分析
Elasticsearch 中的字符串文字要么被分析,要么未被分析。 那么分析到底是什么意思呢? 已分析字段是指在索引之前经过分析过程的字段。 然后,该分析的结果存储在倒排索引中。 分析过程基本上涉及对文本块进行分词和规范化。 这些字段被分词为术语,并且术语被转换为小写字母。 这是标准分析器的行为,也是默认行为。 但是,如果需要,我们可以指定我们自己的分析器,例如,如果你还想索引特殊字符,而标准分析器则不会这样做。如果你想对 analyzer 有更多的了解,请阅读文章 “Elasticsearch: analyzer”。
我们尝试使用如下的命令来进行分词:
GET _analyze
{
"analyzer": "standard",
"text" : "Beijing is a beautiful city"
}
Elasticsearch 的标准分析器会将此文本转换为以下内容:
{
"tokens": [
{
"token": "beijing",
"start_offset": 0,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "is",
"start_offset": 8,
"end_offset": 10,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "a",
"start_offset": 11,
"end_offset": 12,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "beautiful",
"start_offset": 13,
"end_offset": 22,
"type": "<ALPHANUM>",
"position": 3
},
{
"token": "city",
"start_offset": 23,
"end_offset": 27,
"type": "<ALPHANUM>",
"position": 4
}
]
}
通配符(wildcard)搜索快速介绍
通配符是特殊字符,充当文本值中未知字符的占位符,并且可以方便地查找具有相似但不相同数据的多个项目。 通配符搜索基于查询中提到的字符与包含这些字符模式的文档中的单词之间的字符模式匹配。
现在我们已经基本了解了 Elasticsearch 的工作原理、分析字段和通配符搜索是什么,让我们更深入地了解本文的主题 — 字符串字段并对其运行通配符搜索。
字符串字段和通配符搜索
Elasticsearch 中的每个字段都有一个字段数据类型。 此类型指示字段包含的数据类型(例如字符串或布尔值)及其预期用途。 Elasticsearch 中可用于字符串的两种字段类型是 — text(默认)和 keyword。 它们之间的主要区别在于,文本字段在索引时进行分析,而关键字字段则不然。 这意味着,文本字段在索引之前会被标准化并分解为单独的分词,而关键字字段则按原样存储。 此外,由于文本字段已标准化,因此它们支持不区分大小写的搜索。 为了对关键字字段实现相同的效果,我们必须在创建索引时定义一个 normalizer,然后在定义字段映射时指定相同的 normalizer。有关 nomalizer 的详细介绍,请阅读文章 “Elasticsearch:词分析中的 Normalizer 的使用”。
PUT wildcard
{
"settings": {
"analysis": {
"normalizer": {
"lowercase_normalizer": {
"type": "custom",
"char_filter": [],
"filter": [
"lowercase",
"asciifolding"
]
}
}
}
},
"mappings": {
"properties": {
"text-field": {
"type": "text"
},
"keyword-field": {
"type": "keyword",
"normalizer": "lowercase_normalizer"
}
}
}
}
现在进行通配符查询,假设我们有以下文档,并且我们想要对其运行一些通配符搜索:
PUT wildcard/_doc/1
{
"text-field": "Mockingbirds don’t do one thing but make music for us to enjoy.",
"keyword-field": "Mockingbirds don’t do one thing but make music for us to enjoy."
}
如下所示的查询可以很好地处理文本字段:
GET wildcard/_search?filter_path=**.hits
{
"_source": false,
"fields": [
"text-field"
],
"query": {
"wildcard": {
"text-field": {
"value": "*birds*"
}
}
}
}
上面的搜索返回结果:
{
"hits": {
"hits": [
{
"_index": "wildcard",
"_id": "1",
"_score": 1,
"fields": {
"text-field": [
"Mockingbirds don’t do one thing but make music for us to enjoy."
]
}
}
]
}
}
然而,下面的搜索则不会:
GET wildcard/_search?filter_path=**.hits
{
"_source": false,
"fields": [
"text-field"
],
"query": {
"wildcard": {
"text-field": {
"value": "*birds*music*"
}
}
}
}
它返回的结果是:
{
"hits": {
"hits": []
}
}
原因是,该字段的单词已被分析并存储为分词。 因此,elasticsearch 无法找到与给定表达式(*birds*music*)对应的分词。
但是,这适用于关键字字段,因为它们按原样存储。我们来尝试如下的搜索:
GET wildcard/_search?filter_path=**.hits
{
"_source": false,
"fields": [
"keyword-field"
],
"query": {
"wildcard": {
"keyword-field": {
"value": "*birds*music*"
}
}
}
}
上面的命令返回的结果是:
{
"hits": {
"hits": [
{
"_index": "wildcard",
"_id": "1",
"_score": 1,
"fields": {
"keyword-field": [
"mockingbirds don't do one thing but make music for us to enjoy."
]
}
}
]
}
}
现在,让我们讨论从 ElasticSearch v7.9 引入的另一个字符串字段——通配符。 这是一种专门的字段类型,主要用于非结构化机器生成的内容。更多阅读,请参阅文章 “Elasticsearch:使用新的 wildcard 字段更快地在字符串中查找字符串 - 7.9 新功能”。
以下是对这 3 种字段类型运行几个通配符查询的性能统计数据:
我们可以清楚地看到,关键字字段的性能在所有搜索查询和索引大小中是最一致的。 文本字段也做得不错,但它们不能用于搜索像 *Elastic*stash* 这样的值,这使得关键字类型成为明显的赢家。
那么为什么要引入通配符字段呢? 那么,引入通配符字段是为了解决文本和关键字字段存在的以下限制:
- 文本字段 - 将任何通配符表达式的匹配限制为单个分词,而不是字段中保存的原始整个值。
- 关键字字段 - 当搜索子字符串和有许多唯一值时,关键字字段的速度很慢。 关键字字段还存在数据大小限制的缺点。 默认字符串映射会忽略长度超过 256 个字符的字符串。 这可以扩展到单个令牌 32k 的 Lucene 硬限制。 当您尝试搜索系统日志和类似文档时,这可能会产生问题。
通配符字段解决了上述限制。 它不会将字符串视为由标点符号分隔的标记集合,而是通过首先对所有文档进行近似匹配,然后对通过匹配接收到的文档子集应用详细比较来执行模式匹配。
文本、关键字和通配符字段之间的详细比较可以在此处阅读。
上述统计信息是通过在 v8.9 上运行的 elasticsearch 索引上运行搜索获得的,映射如下:
{
"wildcard-search-demo-index": {
"mappings": {
"properties": {
"field1": {
"type": "text"
},
"field2": {
"type": "keyword"
},
"field3": {
"type": "wildcard"
}
}
}
}
}
索引的文档在所有字段中具有统一的数据,即文档中的所有 3 个字段都具有相同的值。 例如,
"hits": [
{
"_index": "wildcard-search-demo-index",
"_type": "_doc",
"_id": "vlPiHYYB6ikeelRg4I8n",
"_score": 1.0,
"_source": {
"field1": "It started as a scalable version of the Lucene open-source search framework then added the ability to horizontally scale Lucene indices.",
"field2": "It started as a scalable version of the Lucene open-source search framework then added the ability to horizontally scale Lucene indices.",
"field3": "It started as a scalable version of the Lucene open-source search framework then added the ability to horizontally scale Lucene indices."
}
},
{
"_index": "wildcard-search-demo-index",
"_type": "_doc",
"_id": "v1PiHYYB6ikeelRg4I87",
"_score": 1.0,
"_source": {
"field1": "Elasticsearch allows you to store, search, and analyze huge volumes of data quickly and in near real-time and give back answers in milliseconds.",
"field2": "Elasticsearch allows you to store, search, and analyze huge volumes of data quickly and in near real-time and give back answers in milliseconds.",
"field3": "Elasticsearch allows you to store, search, and analyze huge volumes of data quickly and in near real-time and give back answers in milliseconds."
}
}
]
综上所述,字段类型的选择并没有固定的规则。 它取决于多种因素,例如数据类型、必须涵盖的不同用例集等。
在设置数据存储时,决定字段类型是一个非常关键的因素,因为它极大地影响性能,并且应该通过考虑所有可能的场景和因素来决定。
Elasticsearch 还有一种称为通配符的查询类型,可用于运行通配符查询。
另外值得指出的是:由于通配符搜索带来很多的性能问题,有时甚至会吃掉很多的系统资源。在生成环境中,有的建议关掉这个功能以避免影响系统的运行。建议阅读文章:
-
Kibana:如何在 Kibana 中禁止查询中使用前置通配符(wildcard)查询
-
Elasticsearch:如何提高查询性能