深入理解ElasticSearch索引API
索引的基本概念
在Elasticsearch中,索引(Index)是一个存储和组织文档(Document)的逻辑容器。它类似于关系型数据库中的数据库概念,但更加灵活和轻量级。一个索引可以包含多个类型(Type),而每个类型又可以包含多个文档。不过从Elasticsearch 7.0版本开始,逐渐弱化了类型的概念,到8.0版本完全移除了类型,一个索引直接包含多个文档。
索引具有以下特点:
- 分布式存储:索引的数据会分布在多个节点上,以实现高可用性和可扩展性。
- 实时搜索:文档一旦被索引,就可以立即被搜索到。
- 动态映射:Elasticsearch可以自动根据文档的数据类型推断字段的映射(Mapping),无需预先定义。
创建索引
创建索引是使用Elasticsearch的第一步。可以通过Elasticsearch的REST API或者各种客户端来创建索引。
使用REST API创建索引
使用HTTP的PUT请求来创建索引。例如,创建一个名为my_index
的索引:
PUT /my_index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
},
"mappings": {
"properties": {
"title": {
"type": "text"
},
"content": {
"type": "text"
},
"date": {
"type": "date"
}
}
}
}
在上述请求中:
settings
部分定义了索引的设置,number_of_shards
指定了主分片的数量,number_of_replicas
指定了副本分片的数量。mappings
部分定义了文档的字段映射,这里定义了title
、content
为文本类型,date
为日期类型。
使用Java客户端创建索引
使用Elasticsearch的Java High - Level REST Client创建索引的示例代码如下:
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
public class CreateIndexExample {
public static void main(String[] args) throws IOException {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
CreateIndexRequest request = new CreateIndexRequest("my_index");
request.settings(Settings.builder()
.put("number_of_shards", 3)
.put("number_of_replicas", 2));
request.mapping(
"{\n" +
" \"properties\": {\n" +
" \"title\": {\n" +
" \"type\": \"text\"\n" +
" },\n" +
" \"content\": {\n" +
" \"type\": \"text\"\n" +
" },\n" +
" \"date\": {\n" +
" \"type\": \"date\"\n" +
" }\n" +
" }\n" +
"}",
XContentType.JSON);
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
boolean acknowledged = createIndexResponse.isAcknowledged();
System.out.println("Index creation acknowledged: " + acknowledged);
client.close();
}
}
在上述Java代码中:
- 首先创建了
RestHighLevelClient
连接到本地的Elasticsearch实例。 - 然后创建
CreateIndexRequest
对象,设置索引名称、索引设置和映射。 - 最后执行创建索引的操作,并检查创建是否被确认。
索引设置
索引设置在创建索引时定义,也可以在索引创建后进行部分修改。索引设置对索引的性能、可用性和功能有重要影响。
分片和副本设置
- 分片(Shards):分片是Elasticsearch将索引数据进行水平分割的单位。主分片负责实际的数据存储和读写操作。通过设置
number_of_shards
参数来定义主分片的数量。例如,在创建索引时设置"number_of_shards": 3
,表示将索引数据平均分配到3个主分片中。- 合适的分片数量选择很重要。如果分片过多,会增加管理开销和搜索时的合并成本;如果分片过少,可能会导致单个分片数据量过大,影响性能。一般来说,需要根据数据量、节点数量和预期的查询负载来综合考虑。
- 副本(Replicas):副本是主分片的拷贝,用于提高可用性和读性能。通过设置
number_of_replicas
参数来定义副本分片的数量。例如,设置"number_of_replicas": 2
,表示每个主分片有2个副本。- 副本分片可以处理读请求,从而分担主分片的负载。当主分片所在节点出现故障时,副本分片可以提升为主分片,保证数据的可用性。
索引刷新间隔
索引刷新(Refresh)是指将内存中的数据写入磁盘并使其可搜索的过程。Elasticsearch默认每隔1秒执行一次刷新操作,这使得文档在写入后1秒内即可被搜索到。可以通过refresh_interval
设置来调整刷新间隔。例如,在创建索引时设置:
PUT /my_index
{
"settings": {
"refresh_interval": "5s"
}
}
将刷新间隔设置为5秒。如果对实时性要求不高,可以适当增大刷新间隔,以减少I/O操作,提高写入性能。但要注意,增大刷新间隔会导致数据延迟搜索的时间变长。
索引合并策略
索引合并(Merge)是将多个较小的段(Segment)合并成一个较大的段的过程,有助于减少文件句柄的使用和提高搜索性能。Elasticsearch提供了多种合并策略,如log - byte - size
和tiered
。
- log - byte - size策略:这是Elasticsearch早期版本的默认策略。它根据段的大小和日志大小来决定何时进行合并。
- tiered策略:从Elasticsearch 5.0开始成为默认策略。它将段分为不同的层级,根据层级的大小和数量来触发合并。例如,在创建索引时可以设置使用
log - byte - size
策略:
PUT /my_index
{
"settings": {
"index.merge.policy.type": "log_byte_size"
}
}
通过调整合并策略和相关参数,可以优化索引的性能和存储效率。
索引映射
索引映射定义了文档中字段的数据类型、分词器、是否存储等属性。正确的映射对于准确搜索和高效存储非常重要。
字段类型
Elasticsearch支持多种字段类型,常见的有:
- 文本类型(text):用于存储文本数据,会进行分词处理。例如,
title
和content
字段适合定义为text
类型。 - 关键字类型(keyword):用于存储精确值,如ID、类别等,不会进行分词。例如,商品的SKU编号可以定义为
keyword
类型。 - 数值类型(number):包括
long
、integer
、short
、byte
、double
、float
等,用于存储数值数据。根据数据范围选择合适的数值类型,以节省存储空间。 - 日期类型(date):用于存储日期和时间数据。可以使用多种日期格式,如
"yyyy - MM - dd HH:mm:ss"
。
自定义映射
在创建索引时,可以自定义字段的映射。例如:
PUT /my_index
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"content": {
"type": "text",
"analyzer": "ik_max_word"
},
"price": {
"type": "float"
},
"publish_date": {
"type": "date",
"format": "yyyy - MM - dd"
}
}
}
}
在上述示例中:
title
和content
字段使用了ik_max_word
分词器,适合处理中文文本。price
字段定义为float
类型,用于存储价格数据。publish_date
字段定义为date
类型,并指定了日期格式为"yyyy - MM - dd"
。
动态映射
Elasticsearch的动态映射功能可以在文档写入时自动推断字段的类型。例如,当写入以下文档:
POST /my_index/_doc
{
"title": "New Document",
"content": "This is the content of the new document.",
"price": 19.99,
"publish_date": "2023 - 10 - 01"
}
Elasticsearch会自动为title
和content
字段推断为text
类型,price
为float
类型,publish_date
为date
类型。虽然动态映射很方便,但在生产环境中,建议尽量提前定义好映射,以确保数据的一致性和性能。
更新索引映射
在索引创建后,可以更新部分字段的映射。但需要注意,不是所有的映射更新都支持,例如不能直接修改已存在字段的数据类型。
添加新字段
可以在现有索引的映射中添加新字段。例如,为my_index
索引添加一个author
字段:
PUT /my_index/_mapping
{
"properties": {
"author": {
"type": "text"
}
}
}
添加新字段后,新写入的文档可以包含该字段,而旧文档在检索时该字段值为null
。
修改字段属性
在某些情况下,可以修改字段的一些属性,如分词器。例如,将title
字段的分词器从ik_max_word
修改为ik_smart
:
PUT /my_index/_mapping
{
"properties": {
"title": {
"type": "text",
"analyzer": "ik_smart"
}
}
}
但要注意,如果字段已经有数据,修改分词器可能会导致搜索结果不准确,因为旧数据是按照旧分词器进行分词的。
删除索引
删除索引是一个不可逆的操作,会删除索引中的所有数据。可以使用REST API或者客户端来删除索引。
使用REST API删除索引
使用HTTP的DELETE请求来删除索引。例如,删除名为my_index
的索引:
DELETE /my_index
执行该请求后,my_index
索引及其所有数据将被永久删除。
使用Python客户端删除索引
使用Elasticsearch的Python客户端elasticsearch - py
删除索引的示例代码如下:
from elasticsearch import Elasticsearch
es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
index_name ='my_index'
es.indices.delete(index = index_name, ignore = [400, 404])
在上述Python代码中:
- 首先创建了
Elasticsearch
客户端连接到本地Elasticsearch实例。 - 然后使用
indices.delete
方法删除指定名称的索引,ignore
参数用于忽略400(Bad Request)和404(Not Found)错误,避免因索引不存在而导致程序报错。
索引别名
索引别名(Alias)是给索引起的一个或多个别名,通过别名可以更灵活地操作索引。
创建索引别名
可以在创建索引时或者索引创建后创建别名。例如,为my_index
索引创建一个名为my_alias
的别名:
POST /_aliases
{
"actions": [
{
"add": {
"index": "my_index",
"alias": "my_alias"
}
}
]
}
创建别名后,可以通过别名来进行索引的操作,如搜索、写入等。例如:
GET /my_alias/_search
{
"query": {
"match_all": {}
}
}
上述请求通过别名my_alias
对my_index
索引进行搜索。
别名的使用场景
- 索引切换:当需要对索引进行重建或者数据迁移时,可以先创建新索引,然后通过别名切换,使应用程序无感知地使用新索引。例如,先创建
my_index_v2
索引,然后将my_alias
别名从my_index
切换到my_index_v2
:
POST /_aliases
{
"actions": [
{
"remove": {
"index": "my_index",
"alias": "my_alias"
}
},
{
"add": {
"index": "my_index_v2",
"alias": "my_alias"
}
}
]
}
- 多索引操作:可以为多个索引创建同一个别名,通过别名对多个索引进行统一操作。例如,为
index1
和index2
创建别名common_alias
:
POST /_aliases
{
"actions": [
{
"add": {
"index": "index1",
"alias": "common_alias"
}
},
{
"add": {
"index": "index2",
"alias": "common_alias"
}
}
]
}
这样,对common_alias
的操作(如搜索)会同时作用于index1
和index2
。
索引模板
索引模板(Index Template)用于定义索引的设置和映射,当创建新索引时,如果索引名称匹配模板中的模式,就会应用模板的设置和映射。
创建索引模板
例如,创建一个名为my_template
的索引模板,匹配所有以log -
开头的索引:
PUT /_template/my_template
{
"index_patterns": ["log - *"],
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"log_level": {
"type": "keyword"
},
"log_message": {
"type": "text"
},
"log_timestamp": {
"type": "date"
}
}
}
}
在上述示例中:
index_patterns
指定了模板匹配的索引模式,这里匹配所有以log -
开头的索引。settings
定义了索引的设置,如主分片和副本分片数量。mappings
定义了文档的字段映射,适合日志数据的结构。
模板的优先级
当有多个模板匹配同一个索引时,Elasticsearch会根据模板的优先级来应用设置和映射。可以在创建模板时通过order
参数指定优先级,order
值越大,优先级越高。例如:
PUT /_template/my_template_high_priority
{
"index_patterns": ["log - *"],
"order": 100,
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
},
"mappings": {
"properties": {
"log_level": {
"type": "keyword"
},
"log_message": {
"type": "text"
},
"log_timestamp": {
"type": "date"
}
}
}
}
在上述示例中,my_template_high_priority
模板的order
为100,比之前的my_template
模板优先级高,当创建以log -
开头的索引时,会优先应用my_template_high_priority
模板的设置和映射。
通过合理使用索引模板,可以提高索引创建的效率和一致性,特别是在大规模部署和管理多个索引的场景中。
索引的性能优化
- 合理设置分片和副本:根据数据量和查询负载,选择合适的主分片和副本分片数量。一般来说,每个分片的数据量控制在几十GB到几百GB之间比较合适。如果读请求较多,可以适当增加副本数量;如果写请求较多,要避免副本过多导致的写入性能下降。
- 优化映射:选择合适的字段类型,避免使用不必要的字段。对于文本字段,选择合适的分词器,以提高搜索的准确性和性能。尽量提前定义好映射,避免动态映射带来的性能开销。
- 批量操作:在写入数据时,使用批量操作(Bulk API)可以减少网络开销,提高写入性能。例如,使用Java客户端进行批量写入:
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
public class BulkIndexExample {
public static void main(String[] args) throws IOException {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.add(new IndexRequest("my_index")
.id("1")
.source("{\"title\":\"Document 1\",\"content\":\"Content of document 1\"}", XContentType.JSON));
bulkRequest.add(new IndexRequest("my_index")
.id("2")
.source("{\"title\":\"Document 2\",\"content\":\"Content of document 2\"}", XContentType.JSON));
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
boolean hasFailures = bulkResponse.hasFailures();
System.out.println("Bulk index has failures: " + hasFailures);
client.close();
}
}
- 调整刷新间隔:根据业务需求,适当增大刷新间隔,减少I/O操作,提高写入性能。但要注意数据延迟搜索的时间会相应增加。
- 监控和调优:使用Elasticsearch提供的监控工具(如Elasticsearch API、Kibana等),监控索引的性能指标,如写入速率、搜索延迟、内存使用等。根据监控结果,及时调整索引设置和映射。
通过以上对Elasticsearch索引API的深入理解和相关操作的实践,可以更好地利用Elasticsearch的强大功能,构建高效、可靠的搜索应用。在实际应用中,要根据具体的业务场景和数据特点,灵活运用这些知识,不断优化索引性能。