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

深入理解ElasticSearch索引API

2023-06-207.9k 阅读

索引的基本概念

在Elasticsearch中,索引(Index)是一个存储和组织文档(Document)的逻辑容器。它类似于关系型数据库中的数据库概念,但更加灵活和轻量级。一个索引可以包含多个类型(Type),而每个类型又可以包含多个文档。不过从Elasticsearch 7.0版本开始,逐渐弱化了类型的概念,到8.0版本完全移除了类型,一个索引直接包含多个文档。

索引具有以下特点:

  1. 分布式存储:索引的数据会分布在多个节点上,以实现高可用性和可扩展性。
  2. 实时搜索:文档一旦被索引,就可以立即被搜索到。
  3. 动态映射: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部分定义了文档的字段映射,这里定义了titlecontent为文本类型,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代码中:

  1. 首先创建了RestHighLevelClient连接到本地的Elasticsearch实例。
  2. 然后创建CreateIndexRequest对象,设置索引名称、索引设置和映射。
  3. 最后执行创建索引的操作,并检查创建是否被确认。

索引设置

索引设置在创建索引时定义,也可以在索引创建后进行部分修改。索引设置对索引的性能、可用性和功能有重要影响。

分片和副本设置

  1. 分片(Shards):分片是Elasticsearch将索引数据进行水平分割的单位。主分片负责实际的数据存储和读写操作。通过设置number_of_shards参数来定义主分片的数量。例如,在创建索引时设置"number_of_shards": 3,表示将索引数据平均分配到3个主分片中。
    • 合适的分片数量选择很重要。如果分片过多,会增加管理开销和搜索时的合并成本;如果分片过少,可能会导致单个分片数据量过大,影响性能。一般来说,需要根据数据量、节点数量和预期的查询负载来综合考虑。
  2. 副本(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 - sizetiered

  1. log - byte - size策略:这是Elasticsearch早期版本的默认策略。它根据段的大小和日志大小来决定何时进行合并。
  2. tiered策略:从Elasticsearch 5.0开始成为默认策略。它将段分为不同的层级,根据层级的大小和数量来触发合并。例如,在创建索引时可以设置使用log - byte - size策略:
PUT /my_index
{
    "settings": {
        "index.merge.policy.type": "log_byte_size"
    }
}

通过调整合并策略和相关参数,可以优化索引的性能和存储效率。

索引映射

索引映射定义了文档中字段的数据类型、分词器、是否存储等属性。正确的映射对于准确搜索和高效存储非常重要。

字段类型

Elasticsearch支持多种字段类型,常见的有:

  1. 文本类型(text):用于存储文本数据,会进行分词处理。例如,titlecontent字段适合定义为text类型。
  2. 关键字类型(keyword):用于存储精确值,如ID、类别等,不会进行分词。例如,商品的SKU编号可以定义为keyword类型。
  3. 数值类型(number):包括longintegershortbytedoublefloat等,用于存储数值数据。根据数据范围选择合适的数值类型,以节省存储空间。
  4. 日期类型(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"
            }
        }
    }
}

在上述示例中:

  • titlecontent字段使用了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会自动为titlecontent字段推断为text类型,pricefloat类型,publish_datedate类型。虽然动态映射很方便,但在生产环境中,建议尽量提前定义好映射,以确保数据的一致性和性能。

更新索引映射

在索引创建后,可以更新部分字段的映射。但需要注意,不是所有的映射更新都支持,例如不能直接修改已存在字段的数据类型。

添加新字段

可以在现有索引的映射中添加新字段。例如,为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代码中:

  1. 首先创建了Elasticsearch客户端连接到本地Elasticsearch实例。
  2. 然后使用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_aliasmy_index索引进行搜索。

别名的使用场景

  1. 索引切换:当需要对索引进行重建或者数据迁移时,可以先创建新索引,然后通过别名切换,使应用程序无感知地使用新索引。例如,先创建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"
            }
        }
    ]
}
  1. 多索引操作:可以为多个索引创建同一个别名,通过别名对多个索引进行统一操作。例如,为index1index2创建别名common_alias
POST /_aliases
{
    "actions": [
        {
            "add": {
                "index": "index1",
                "alias": "common_alias"
            }
        },
        {
            "add": {
                "index": "index2",
                "alias": "common_alias"
            }
        }
    ]
}

这样,对common_alias的操作(如搜索)会同时作用于index1index2

索引模板

索引模板(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模板的设置和映射。

通过合理使用索引模板,可以提高索引创建的效率和一致性,特别是在大规模部署和管理多个索引的场景中。

索引的性能优化

  1. 合理设置分片和副本:根据数据量和查询负载,选择合适的主分片和副本分片数量。一般来说,每个分片的数据量控制在几十GB到几百GB之间比较合适。如果读请求较多,可以适当增加副本数量;如果写请求较多,要避免副本过多导致的写入性能下降。
  2. 优化映射:选择合适的字段类型,避免使用不必要的字段。对于文本字段,选择合适的分词器,以提高搜索的准确性和性能。尽量提前定义好映射,避免动态映射带来的性能开销。
  3. 批量操作:在写入数据时,使用批量操作(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();
    }
}
  1. 调整刷新间隔:根据业务需求,适当增大刷新间隔,减少I/O操作,提高写入性能。但要注意数据延迟搜索的时间会相应增加。
  2. 监控和调优:使用Elasticsearch提供的监控工具(如Elasticsearch API、Kibana等),监控索引的性能指标,如写入速率、搜索延迟、内存使用等。根据监控结果,及时调整索引设置和映射。

通过以上对Elasticsearch索引API的深入理解和相关操作的实践,可以更好地利用Elasticsearch的强大功能,构建高效、可靠的搜索应用。在实际应用中,要根据具体的业务场景和数据特点,灵活运用这些知识,不断优化索引性能。