MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

ElasticSearch中的几个简单知识点梳理

2023-10-025.7k 阅读

ElasticSearch 的基本概念

ElasticSearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。

索引(Index)

索引在 ElasticSearch 中类似于关系型数据库中的数据库概念,它是一个存储相关文档的地方。例如,我们可以创建一个名为 “employees” 的索引来存储公司员工的相关信息。在 ElasticSearch 中创建索引可以使用如下的 API 请求(这里以使用 curl 命令为例):

curl -X PUT "localhost:9200/employees" -H 'Content-Type: application/json' -d'
{
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 1
    }
}
'

上述代码中,通过 PUT 请求创建了名为 “employees” 的索引,并设置了分片数为 3,副本数为 1。分片是将索引数据分割存储的机制,有助于提高数据处理的并行性和可扩展性;副本则是为了数据冗余和高可用性,提高系统的容错能力。

类型(Type)

在 ElasticSearch 早期版本中,类型用于在索引中进一步划分不同的数据类别,类似于关系型数据库中的表。比如在 “employees” 索引中,我们可以定义 “engineer” 类型存储工程师信息,“manager” 类型存储经理信息。不过从 ElasticSearch 7.0 版本开始,逐渐弃用了类型的概念,到 8.0 版本完全移除。在弃用之前创建类型的示例如下:

curl -X PUT "localhost:9200/employees/engineer/1" -H 'Content-Type: application/json' -d'
{
    "name": "John Doe",
    "age": 30,
    "department": "Engineering"
}
'

这里在 “employees” 索引的 “engineer” 类型下创建了一个文档,文档 ID 为 1,包含了员工的姓名、年龄和部门信息。

文档(Document)

文档是 ElasticSearch 中最小的存储单元,它是一个 JSON 格式的数据结构。一个文档可以被索引到某个索引下的某个类型中(在有类型的版本中)。例如上面创建的员工信息就是一个文档。文档具有唯一的 ID,我们可以通过 ID 来获取、更新或删除文档。获取文档的示例如下:

curl -X GET "localhost:9200/employees/engineer/1"

此命令将获取 “employees” 索引下 “engineer” 类型中 ID 为 1 的文档。

ElasticSearch 的数据写入

单条文档写入

向 ElasticSearch 写入单条文档非常简单,通过前面创建类型的例子我们已经有所了解。再次强调,在没有类型的版本中,直接将文档写入索引即可。例如,向 “products” 索引写入一个产品文档:

curl -X PUT "localhost:9200/products/_doc/1" -H 'Content-Type: application/json' -d'
{
    "product_name": "Smartphone",
    "price": 599.99,
    "description": "A high - end smartphone with advanced features"
}
'

这里使用 PUT 方法,指定索引为 “products”,文档类型为 _doc(这是通用的文档类型标识),ID 为 1,然后在请求体中传入产品的具体信息。

批量文档写入

当需要写入大量文档时,使用批量写入可以显著提高效率。ElasticSearch 提供了 _bulk API 来实现批量操作。示例如下:

curl -X POST "localhost:9200/products/_bulk" -H 'Content-Type: application/x-ndjson' -d'
{"index":{"_id":"2"}}
{"product_name":"Tablet","price":299.99,"description":"A portable tablet for daily use"}
{"index":{"_id":"3"}}
{"product_name":"Laptop","price":999.99,"description":"A powerful laptop for work and play"}
'

在上述示例中,_bulk API 的请求体采用 NDJSON(Newline - Delimited JSON)格式。每一行是一个 JSON 对象,首先是操作元数据,如 {"index":{"_id":"2"}} 表示要对 ID 为 2 的文档进行索引操作,紧接着下一行就是具体的文档数据。

ElasticSearch 的数据查询

简单查询

简单查询可以通过 _search API 来实现。例如,要查询 “products” 索引中所有价格大于 500 的产品:

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
    "query": {
        "range": {
            "price": {
                "gt": 500
            }
        }
    }
}
'

这里使用了 range 查询,gt 表示大于。查询结果将返回符合价格大于 500 条件的产品文档。

复合查询

ElasticSearch 支持将多个查询条件组合起来形成复合查询。例如,要查询价格大于 500 且描述中包含 “advanced” 的产品:

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
    "query": {
        "bool": {
            "must": [
                {
                    "range": {
                        "price": {
                            "gt": 500
                        }
                    }
                },
                {
                    "match": {
                        "description": "advanced"
                    }
                }
            ]
        }
    }
}
'

这里使用了 bool 查询,must 子句表示所有条件都必须满足。其中一个条件是价格范围查询,另一个是 match 查询,用于在描述字段中匹配 “advanced” 关键词。

ElasticSearch 的映射(Mapping)

什么是映射

映射定义了文档中字段的数据类型、索引方式等相关属性。例如,我们可以定义一个 “date” 类型的字段,指定其日期格式。在创建索引时可以同时定义映射,示例如下:

curl -X PUT "localhost:9200/orders" -H 'Content-Type: application/json' -d'
{
    "mappings": {
        "properties": {
            "order_date": {
                "type": "date",
                "format": "yyyy - MM - dd"
            },
            "order_amount": {
                "type": "float"
            }
        }
    }
}
'

上述代码在创建 “orders” 索引时,定义了 “order_date” 字段为日期类型,并指定格式为 “yyyy - MM - dd”,“order_amount” 字段为浮点数类型。

动态映射与静态映射

动态映射是 ElasticSearch 自动根据写入文档的字段推断其数据类型并创建映射的机制。例如,当我们首次向索引写入一个包含 “new_field” 字段的文档时,如果该字段在映射中不存在,ElasticSearch 会自动为其创建一个映射。然而,动态映射有时可能会导致不符合预期的数据类型推断,所以在一些场景下需要使用静态映射,即手动精确地定义每个字段的映射属性,就像上面 “orders” 索引的例子一样。

ElasticSearch 的聚合分析

聚合的基本概念

聚合分析允许我们对文档数据进行统计分析,例如计算平均值、最大值、最小值、分组统计等。聚合可以与查询结合使用,先筛选出符合条件的数据,再进行聚合操作。

简单聚合示例

计算 “products” 索引中所有产品的平均价格:

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
    "size": 0,
    "aggs": {
        "avg_price": {
            "avg": {
                "field": "price"
            }
        }
    }
}
'

这里通过设置 size 为 0,表示不返回具体的文档,只关注聚合结果。aggs 部分定义了一个名为 “avg_price” 的聚合,使用 avg 聚合类型来计算 “price” 字段的平均值。

分组聚合示例

按产品类别对产品数量进行分组统计:

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
    "size": 0,
    "aggs": {
        "product_categories": {
            "terms": {
                "field": "category.keyword"
            }
        }
    }
}
'

上述示例中,使用 terms 聚合类型按 “category.keyword” 字段进行分组,统计每个类别下的产品数量。这里使用 “category.keyword” 是因为对于文本类型字段,通常需要使用 keyword 子类型来进行精确匹配和分组操作。

ElasticSearch 的分布式特性

分片机制

ElasticSearch 将索引数据分割成多个分片,每个分片是一个独立的 Lucene 索引。这样做的好处是可以并行处理查询和写入操作,提高系统的性能和可扩展性。例如,在前面创建 “employees” 索引时设置了 3 个分片,当有查询请求时,ElasticSearch 可以同时在这 3 个分片上并行搜索,然后合并结果返回。不同的分片可以分布在不同的节点上,从而充分利用集群的计算资源。

副本机制

副本是分片的复制,用于提高数据的可用性和容错能力。当某个分片所在的节点出现故障时,副本分片可以替代它继续提供服务。同时,副本分片也可以用于处理读请求,进一步提高系统的并发处理能力。如前面创建 “employees” 索引时设置了 1 个副本,这意味着每个分片都有一个对应的副本分片,总共会有 6 个分片(3 个主分片 + 3 个副本分片)在集群中分布。

ElasticSearch 的搜索相关性

相关性算法

ElasticSearch 使用 BM25(Best Matching 25)算法来计算文档与查询之间的相关性得分。BM25 算法考虑了多个因素,如查询词在文档中的出现频率、文档的长度、查询词在整个索引中的分布情况等。例如,一个查询词在文档中出现的次数越多,该文档与查询的相关性得分可能越高;但同时,文档长度也会对得分产生影响,较长的文档可能需要更多的查询词出现次数才能获得较高的相关性得分。

影响相关性的因素

除了 BM25 算法本身的因素外,字段的索引方式也会影响相关性。例如,使用 text 类型进行全文索引的字段,会对文本进行分词处理,查询时会根据分词结果来计算相关性;而 keyword 类型字段用于精确匹配,不会进行分词,其相关性计算方式与 text 类型不同。另外,Boosting(权重提升)也是一种影响相关性的手段,我们可以为某些字段或查询条件设置更高的权重,使其在相关性计算中占据更重要的地位。例如:

curl -X GET "localhost:9200/products/_search" -H 'Content-Type: application/json' -d'
{
    "query": {
        "function_score": {
            "query": {
                "match": {
                    "description": "smartphone"
                }
            },
            "functions": [
                {
                    "field_value_factor": {
                        "field": "price",
                        "modifier": "log1p",
                        "factor": 10
                    }
                }
            ]
        }
    }
}
'

上述示例中,使用 function_score 来调整相关性得分,通过 field_value_factor 对 “price” 字段进行处理,使用 log1p 修饰符,并设置 factor 为 10,这意味着价格较高的产品在相关性得分计算中会有更高的权重,从而影响最终的搜索结果排序。

ElasticSearch 的性能优化

硬件优化

选择合适的硬件配置对于 ElasticSearch 的性能至关重要。首先,内存是关键因素之一,ElasticSearch 会将索引数据加载到内存中以提高查询速度,所以需要确保服务器有足够的内存。一般建议为 ElasticSearch 分配物理内存的一半左右。其次,磁盘 I/O 性能也很重要,使用 SSD 磁盘可以显著提高数据读写速度,特别是在数据写入和恢复场景下。

索引优化

在索引设计方面,合理设置分片和副本数量很重要。过多的分片会增加管理开销和网络传输成本,而过少的分片则会限制系统的扩展性和并行处理能力。对于读多写少的场景,可以适当增加副本数量以提高读性能;对于写多读少的场景,则需要关注写入性能,避免过多副本影响写入速度。另外,优化映射也是提高性能的重要手段,避免不必要的字段索引,对于不需要进行搜索的字段可以设置为 index: false,减少索引开销。

查询优化

在查询方面,尽量使用精确查询,避免使用通配符查询(如 * 开头的查询),因为通配符查询会对索引进行全量扫描,性能较低。对于复杂查询,可以使用缓存来提高查询效率,ElasticSearch 本身提供了查询缓存机制,可以通过配置参数来调整缓存的使用策略。同时,合理使用聚合操作,避免在大数据量上进行复杂的聚合计算,可以先通过查询筛选出部分数据,再进行聚合。

ElasticSearch 的监控与维护

监控指标

ElasticSearch 提供了丰富的监控指标,可以帮助我们了解集群的运行状态。例如,节点的 CPU 使用率、内存使用率、磁盘 I/O 情况等硬件相关指标;索引的文档数量、存储大小、分片状态等索引相关指标;以及查询的响应时间、吞吐量等查询相关指标。我们可以通过 ElasticSearch 的 _cat API 或 _stats API 来获取这些指标信息。例如,通过 _cat/nodes API 可以查看集群中各个节点的基本信息,包括节点状态、内存使用情况等:

curl -X GET "localhost:9200/_cat/nodes?v"

维护操作

定期进行索引的优化和清理是维护 ElasticSearch 集群的重要工作。例如,使用 _forcemerge API 可以对索引进行合并操作,减少索引文件数量,提高查询性能。另外,当索引不再使用时,及时删除索引可以释放磁盘空间。同时,定期备份数据也是必不可少的,ElasticSearch 提供了快照和恢复功能,可以将索引数据备份到远程存储(如 S3),在需要时进行恢复。例如,创建快照的示例如下:

curl -X PUT "localhost:9200/_snapshot/my_backup_repo/my_snapshot_1" -H 'Content-Type: application/json' -d'
{
    "indices": "employees,products",
    "ignore_unavailable": true,
    "include_global_state": false
}
'

上述代码创建了一个名为 “my_snapshot_1” 的快照,备份了 “employees” 和 “products” 索引,设置忽略不可用的索引,并排除全局状态信息。

通过对以上 ElasticSearch 各个知识点的梳理,我们可以更深入地理解和使用 ElasticSearch 来构建高效的搜索和数据分析应用。无论是数据的写入、查询、聚合分析,还是性能优化、监控维护等方面,都需要我们根据实际应用场景进行合理的配置和调整。