引言
在我之前做社交架构设计的时候,我们有一项关键且必要的需求:需要存储并记录用户的所有聊天记录。这些记录不仅用于业务需求,也承担了风控审查的职责。因此,在架构设计中,我们需要考虑每天海量的聊天消息量,以及可能遇到的复杂查询场景。面对这样的业务需求场景,经过分析,我们决定将Elasticsearch(简称ES)作为用户聊天记录的存储引擎。
本篇文章我只想总结下Elasticsearch的基本概念和常见用法,我们将从核心概念入手,逐步深入探索其数据模型、增删改查操作以及聚合分析功能,为大家展示如何使用Elasticsearch来高效地管理和查询数据。
什么是Elasticsearch
Elasticsearch,简称ES,是一个分布式、RESTful搜索和分析引擎,基于Apache Lucene构建。它能够以高效、灵活的方式存储、检索和分析大量数据,因此在全文搜索、实时数据分析和日志监控等场景中得到了广泛应用。
核心特性
Elasticsearch的核心特性使其在处理大规模数据上表现出色,主要体现在以下几方面:
- 分布式架构:Elasticsearch采用分布式架构,数据会被分片并分布在多个节点上,支持数据的横向扩展,以应对海量数据处理需求。
- 全文检索:Elasticsearch基于倒排索引,能快速高效地检索文本数据,适用于复杂的全文搜索场景,如模糊搜索、词语匹配、排序等。
- 实时数据分析:Elasticsearch能够实时地对数据进行复杂的查询和聚合分析,使其成为实时数据监控和分析的理想选择。
- RESTful API:Elasticsearch提供RESTful API接口,支持多种编程语言的调用,方便开发者与其交互,执行查询、更新和数据操作等。
为什么我们要用Elasticsearch?MySQL不行吗?
在我之前的社交场景业务中,数据查询需求常常不只是精确匹配,还涉及大量的模糊查询,比如我们社交场景中有一个风控部门重点核查聊天数据的场景。需要检索所有已经确定的黑产数据 (“黑产”指的是黑色产业链,通常是通过隐晦的方式在聊天中向用户发送不易被系统识别的联系方式,以达到引流的目的。为了打击这种违规行为,我们需要检索聊天数据,提取出包含这些隐晦联系方式的内容。然而,黑产数据通常会对内容进行变形,以规避传统的关键词过滤)。比如我需要查询所有聊天内容包含 “壹叁捌”的消息,如果我用Mysql来模糊查询的话 大概就是要这样进行模糊匹配:
SELECT * FROM chat_logs WHERE message LIKE '%壹叁捌%';
这样写确实可以查询出所有符合条件的记录,但这个查询存在一个严重的性能问题:它不走索引,即需要对整个表进行全表扫描。
全表扫描的问题
- 查询效率低下:在我们的业务场景中,每天新增的聊天记录数量达到上千万,累积的数据量甚至超过亿级。全表扫描意味着每次查询都要遍历整个表,对于如此庞大的数据量,查询速度会极其缓慢,严重影响查询的响应时间。在实际环境中,可能导致一次查询需要数分钟甚至更长时间,无法满足实时监控和分析的需求。
- 数据库资源消耗巨大:全表扫描对数据库资源(CPU、内存、I/O)消耗极高。每次查询都需要读取大量的数据页,并进行模糊匹配,会让数据库负载急剧增加。当有多个类似查询同时运行时,会导致数据库出现性能瓶颈,影响其他正常业务的运行,甚至导致数据库瘫痪。
- 锁定和阻塞问题:全表扫描还会产生大量的锁,尤其在数据量大、并发高的场景下,可能导致严重的锁定和阻塞问题。当某个查询正在执行时,其他的查询请求可能被锁住,进而影响到整个系统的正常运作,导致用户体验下降。
- 可扩展性差:在数据量增长的情况下,MySQL的
LIKE
查询没有较好的扩展性。随着数据量的增加,查询时间会成倍增长,性能迅速下降。这对业务系统的长期发展和可维护性造成很大挑战。
由于MySQL模糊查询的全表扫描问题,无法高效、可靠地完成海量聊天数据的实时查询需求。这就是为什么我们不考虑这种存储以及查询的方案。
而Elasticsearch正是专门为搜索设计的,能够有效解决上面提到的性能问题。以下几点说明了为什么ES更适合这种需求:
- 倒排索引:ES使用倒排索引来加速搜索,避免了全表扫描,可以快速定位到包含关键词的文档。
- 强大的模糊查询:ES支持复杂的模糊匹配,同义词、变形字符等均可高效检索,非常适合处理变形内容。
- 分布式架构:ES天生支持分布式,可以轻松扩展,处理海量数据时查询依然快速稳定。
- 实时性能:ES在大规模数据场景下能保持毫秒级的查询响应,适用于需要快速响应的风控查询。
至于Elasticsearch为什么能做到这些效果,我们就需要了解它的核心概念,比如倒排索引的数据结构、分词器的工作原理等。倒排索引使得ES能够快速定位关键词所在的位置,而分词器则让ES能够对复杂文本进行细致的拆解和匹配。
关于这些ES的原理和概念,我在这里不过多展开解释,有兴趣的可以查阅几篇关于ES原理的文章进行深入了解。接下来,我将重点讲解Elasticsearch的字段说明以及常用的搜索查询语句。
Elasticsearch的常见术语
在使用Elasticsearch的过程中,有一些关键术语需要理解,以下是其中最常见的一些:
- Index(索引) :Elasticsearch的基本数据存储单位,类似于关系型数据库中的“数据库”。每个索引包含多个文档,并可以存储特定类型的数据。例如,可以为聊天记录、用户信息分别创建不同的索引。
- Document(文档) :Elasticsearch中的数据基本单元,相当于关系型数据库中的一行数据。每个文档都以JSON格式存储,并包含多个字段(Field),每条聊天记录或用户数据都可以是一个文档。
- ID:文档的唯一标识符,类似于关系型数据库中的主键。每个文档在其索引中都有一个唯一的ID,可以通过ID直接检索特定的文档。
- Type(类型) :在早期版本的Elasticsearch中,Type用于在同一个索引中区分不同类型的文档。然而,在最新版本中,Type的概念已被逐渐淘汰,Elasticsearch推荐每个索引只包含一种数据类型。
- Field(字段) :文档中的数据项,相当于关系型数据库中的列。例如,一个聊天记录文档可能包含
sender
、message
、timestamp
等字段。 - Mapping(映射) :用于定义索引中文档和字段的结构和数据类型,类似于数据库中的表结构。通过Mapping,我们可以指定每个字段的数据类型(如
text
、keyword
、integer
等),以及字段如何分词和存储。 - Analyzer(分词器) :用于将文本字段拆解成独立的词项,便于建立索引和搜索。分词器可以处理不同语言、支持停用词过滤、同义词替换等,是Elasticsearch实现复杂文本检索的重要组件。
- Shard(分片) :为了实现分布式存储和查询,Elasticsearch将索引划分为多个分片,每个分片都是一个完整的倒排索引。这样可以将数据分布在多个节点上,提升数据处理能力。
- Replica(副本) :副本是主分片的备份,用于在主分片不可用时继续提供服务。副本提高了数据的高可用性和查询性能。
Elasticsearch的增删改查字段说明
在Elasticsearch中进行增、删、改、查操作前,了解一些常见字段和参数非常重要,这些字段定义了操作的数据范围、目标索引、文档ID等内容。以下是常见字段说明:
- index:指定要操作的索引名称,例如
chat_logs
。所有增删改查操作都需要指定目标索引,类似于数据库中的表名。 - id:用于唯一标识某个文档,相当于数据库中的主键。可以在创建文档时自定义ID,也可以让Elasticsearch自动生成。
- body:操作请求的主体数据。通常以JSON格式提供,在创建或更新文档时包含具体的数据字段及其值,例如发送者、消息内容、时间戳等。
- doc:在更新请求中,
doc
字段用于指定要更新的字段和值。通过doc
可以只更新文档的一部分,而不影响其他字段内容。 - _source:用于控制返回的数据字段,避免返回不必要的数据。在查询或读取文档时,可以通过
_source
指定只返回所需的字段。
使用Elasticsearch操作聊天日志记录的示例
在我们的业务场景中,我们需要使用Elasticsearch来存储和管理聊天日志。接下来,我们将基于聊天日志的增、删、改、查操作逐一说明。
1. 创建索引
在Elasticsearch中,创建索引的格式如下:
PUT /index_name
{
"settings": {
"number_of_shards": <主分片数>,
"number_of_replicas": <副本分片数>
},
"mappings": {
"properties": {
"field_name": { "type": "<字段类型>" },
...
}
}
}
其中:
-
index_name:要创建的索引名称。
-
settings:指定索引的设置,如分片和副本数量。
- number_of_shards:指定主分片的数量,决定了索引的并行度和存储容量。
- number_of_replicas:指定副本分片的数量,提高数据的容灾能力。
-
mappings:定义索引中字段的结构,包括字段名称、类型和其他设置。
示例:创建聊天记录索引
在我们的业务场景中,我们可以创建一个名为chat_logs
的索引,用于存储聊天日志,包含以下字段:
- msg_id:消息ID,用于唯一标识每条消息。
- from_uid:发送方用户ID。
- to_uid:接收方用户ID。
- from_sex:发送方性别。
- to_sex:接收方性别。
- msg_type:消息类型,1表示文字,2表示语音,3表示图片。
- content:消息内容。
- url:图片或语音的地址。
- create_time:消息创建时间。
创建chat_logs
索引的请求如下:
PUT /chat_logs
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"msg_id": { "type": "keyword" },
"from_uid": { "type": "keyword" },
"to_uid": { "type": "keyword" },
"from_sex": { "type": "integer" },
"to_sex": { "type": "integer" },
"msg_type": { "type": "integer" },
"content": { "type": "text", "analyzer": "standard" },
"url": { "type": "keyword" },
"create_time": { "type": "date" }
}
}
}
- msg_id:
keyword
类型,表示消息ID,用于唯一标识消息内容。 - from_uid 和 to_uid:
keyword
类型,分别表示发送方和接收方ID,用于精确匹配。 - from_sex 和 to_sex:
integer
类型,表示用户性别。 - msg_type:
integer
类型,表示消息的类型,比如1为文字,2为语音,3为图片。 - content:
text
类型,包含实际消息内容,并使用standard
分词器进行分词,便于模糊查询。 - url:
keyword
类型,用于存储图片或语音的地址。 - create_time:
date
类型,表示消息的创建时间。
通过这个索引设置,我们便能够为聊天记录创建高效的存储和查询结构。
添加数据
在Elasticsearch中,可以通过POST
请求将数据添加到指定的索引中。每一条数据称为一个“文档”,每个文档都以JSON格式存储在指定的索引中。添加数据的基本格式如下:
POST /index_name/_doc/<id>
{
"field1": "value1",
"field2": "value2",
...
}
- index_name:要添加数据的索引名称。
- _doc:文档的类型,通常使用
_doc
(在新版本中已统一为_doc
)。 - id:文档的唯一标识符,可以省略,让Elasticsearch自动生成一个唯一ID。
- field 和 value:需要添加的数据内容,以字段名和字段值的形式表示。
示例:添加聊天记录数据
接下来,我们基于聊天日志场景,向chat_logs
索引中添加一条消息数据。以下是数据结构及字段说明:
- msg_id:消息ID,用于唯一标识消息内容。
- from_uid:发送方用户ID。
- to_uid:接收方用户ID。
- from_sex:发送方性别,1为男性,2为女性。
- to_sex:接收方性别,1为男性,2为女性。
- msg_type:消息类型,1表示文字,2表示语音,3表示图片。
- content:消息内容。
- url:图片或语音的URL地址(如果是文字消息则为空)。
- create_time:消息的创建时间,格式为ISO 8601(例如
2024-10-17T14:35:00
)。
示例请求
以下是向chat_logs
索引中添加一条聊天记录的请求:
POST /chat_logs/_doc/1
{
"msg_id": "msg_1001",
"from_uid": "user123",
"to_uid": "user456",
"from_sex": 1,
"to_sex": 2,
"msg_type": 1,
"content": "你好,请问有时间聊聊吗?",
"url": "",
"create_time": "2024-10-17T14:35:00"
}
- msg_id:消息ID,指定为
msg_1001
。 - from_uid 和 to_uid:发送方ID
user123
和接收方IDuser456
。 - from_sex 和 to_sex:性别,1表示男性,2表示女性。
- msg_type:消息类型为
1
(文字消息)。 - content:消息内容为“你好,请问有时间聊聊吗?”。
- url:由于是文字消息,URL为空。
- create_time:消息的创建时间为
2024-10-17T14:35:00
。
执行此请求后,这条消息记录将被添加到chat_logs
索引中,并且可以随时通过文档ID(这里是1
)进行查询或更新。这种结构确保了聊天记录的所有重要信息均被完整记录,以便后续检索和分析。
查询数据 (普通查询)
普通查询语句的字段说明
在Elasticsearch中,查询语句包含多个字段和参数,它们决定了查询的具体逻辑和范围。以下是一些常见字段的说明:
-
index:指定要查询的索引名称,类似于数据库查询中的表名。例如,
/chat_logs/_search
表示在chat_logs
索引中执行查询操作。 -
body:查询语句的主体部分,以JSON格式传递实际的查询内容,包括查询类型、条件和其他配置。
-
query:用于定义查询逻辑的主字段,所有查询条件都放在
query
字段中。query
通常包含各种查询类型,如match
、term
、bool
等,以满足不同的查询需求。 -
bool:一种组合查询类型,用于将多个查询条件组合在一起。
bool
查询内部包含以下几个子字段:- must:指定必须匹配的查询条件。文档必须满足
must
中的所有条件才会被返回。 - should:指定可选条件。满足
should
中的条件将增加文档的匹配评分(不一定必须匹配)。 - must_not:指定排除条件。文档如果匹配
must_not
中的条件则不会被返回。 - filter:类似于
must
,但不会影响文档的匹配评分,通常用于数据过滤。
- must:指定必须匹配的查询条件。文档必须满足
-
match:用于进行全文检索的字段,适合文本字段的模糊匹配。比如
"match": {"message": "hello"}
会返回包含“hello”关键词的文档。 -
term:用于精确匹配的查询字段。
term
不会对字段值进行分词,因此适用于数值或关键字匹配。例如,"term": {"status": "active"}
会返回状态为active
的文档。 -
range:用于范围查询,适合数值、日期等字段。
range
查询可以指定上下限,例如"range": {"timestamp": {"gte": "2024-01-01", "lte": "2024-12-31"}}
。 -
from 和 size:用于分页控制,
from
指定查询的起始位置(默认从0开始),size
指定返回的文档数量(默认是10)。例如,"from": 0, "size": 20
表示从第1条记录开始,返回20条数据。 -
sort:用于排序控制。可以指定字段的排序方式(升序或降序),例如
"sort": [{"timestamp": {"order": "desc"}}]
表示按timestamp
字段降序排列结果。 -
_source:用于控制返回结果中显示的字段。可以通过指定字段来减少数据传输量,例如
"_source": ["sender", "message"]
表示只返回sender
和message
字段。 -
aggs(或aggregations):用于聚合查询,适合统计分析场景。例如,
"aggs": {"group_by_sender": {"terms": {"field": "sender"}}}
表示按sender
字段进行分组统计。
普通查询返回结果的字段说明
在执行查询后,Elasticsearch返回的结果包含多个字段,提供查询耗时、查询结果数等信息。以下是常见返回字段的说明:
-
took:表示查询耗时,单位为毫秒。在你的例子中,
"took": 50
表示查询耗时50毫秒。 -
timed_out:指示查询是否超时,
false
表示查询在超时时间内完成,true
表示超时。默认情况下,Elasticsearch会尽量在设置的时间内返回结果。 -
_shards:提供有关分片的查询统计信息,包括以下几个字段:
- total:总分片数,表示该查询涉及的分片数量。
- successful:成功查询的分片数。
- skipped:被跳过的分片数,通常在跨多个索引的查询中出现。
- failed:失败的分片数。如果此值不为0,表示有分片查询失败。
-
hits:包含查询的主要结果数据,包括匹配的文档总数、评分和实际匹配的文档内容:
-
total:表示总匹配数,包含两个子字段:
- value:表示符合查询条件的文档总数。
- relation:说明
total
的关系类型,通常为eq
(精确值)或gte
(大于等于某值,通常在结果超过一定限制时使用)。
-
max_score:表示匹配的文档的最高评分,Elasticsearch会基于查询条件给每个匹配文档评分。评分越高表示文档越符合查询条件。
-
hits:数组,包含每个匹配的文档信息,每个文档包含以下字段:
- _index:文档所在的索引名称。
- _id:文档的唯一ID。
- _score:文档的匹配评分,数值越高表示文档越符合查询条件。
- _source:包含文档的实际内容,显示了文档中的所有字段数据,例如
from_uid
、msg_type
、content
等。
-
普通查询数据的结构和示例
Elasticsearch的查询语句一般包含基本的结构,主要包括index
、query
等字段,用于指定查询的范围和条件。以下是查询数据的基本结构:
GET /index_name/_search
{
"query": {
// 查询条件
},
"_source": [ "field1", "field2" ], // 可选字段,控制返回字段
"sort": [
{ "field_name": { "order": "desc" }}
], // 可选字段,控制排序
"from": 0,
"size": 10
}
- index_name:指定查询的索引名称。
- query:包含实际的查询条件。
- _source:指定返回的字段,用于减少不必要的数据传输(可选)。
- sort:指定排序规则,可以按指定字段升序或降序排列结果(可选)。
- from 和 size:用于分页控制,
from
指定查询的起始位置,size
指定返回的文档数量(可选)。
示例1:查询指定ID的文档
可以通过文档ID直接查询特定记录,适合在已知唯一ID的情况下快速定位数据:
GET /chat_logs/_doc/1
此查询会返回chat_logs
索引中ID为1
的文档,适合在有明确文档ID的情况下使用。
示例2:关键字匹配查询(match
查询)
假设我们要查找消息内容包含“你好”的聊天记录,可以使用match
查询:
GET /chat_logs/_search
{
"query": {
"match": {
"content": "你好"
}
}
}
- match:用于文本字段的模糊匹配,在
content
字段中搜索包含“你好”的内容。
此查询会返回chat_logs
索引中所有包含“你好”关键词的聊天记录。
示例3:组合查询(bool
查询)
如果我们需要组合多个条件,可以使用bool
查询。例如,查询发送方是user123
且消息类型是文字(msg_type
为1)的聊天记录:
GET /chat_logs/_search
{
"query": {
"bool": {
"must": [
{ "term": { "from_uid": "user123" }},
{ "term": { "msg_type": 1 }}
]
}
}
}
- bool:组合查询,允许指定多个条件。
- must:必须满足的条件,
from_uid
为user123
且msg_type
为1。
此查询会返回chat_logs
索引中所有条件的聊天记录。
示例4:范围查询(range
查询)
范围查询适合查询特定时间段的数据。假设我们要查询2024年10月17日之后的所有消息记录:
GET /chat_logs/_search
{
"query": {
"range": {
"create_time": {
"gte": "2024-10-17T00:00:00"
}
}
}
}
- range:对
create_time
字段进行范围查询。 - gte:指定“晚于或等于”的条件。
此查询会返回chat_logs
索引中所有create_time
晚于或等于2024-10-17T00:00:00
的聊天记录。
示例5:指定返回字段
为了减少数据传输量,可以使用_source
字段只返回所需字段。例如,我们只想获取发送方ID和消息内容:
GET /chat_logs/_search
{
"_source": ["from_uid", "content"],
"query": {
"match": {
"content": "你好"
}
}
}
- _source:指定返回的字段,避免返回无关字段。
此查询会返回chat_logs
索引中符合条件的聊天记录,仅包含from_uid
和content
字段。
示例6:多字段匹配查询(multi_match
查询)
multi_match
查询允许在多个字段中同时搜索特定关键词。例如,搜索内容(content
字段)或发送者ID(from_uid
字段)包含“user123”的文档:
GET /chat_logs/_search
{
"query": {
"multi_match": {
"query": "user123",
"fields": ["content", "from_uid"]
}
}
}
- multi_match:同时在多个字段中搜索关键词,可以指定多个字段。
- fields:指定参与查询的字段数组,查询会在所有字段中查找匹配项。
示例7:通配符查询(wildcard
查询)
wildcard
查询用于模糊匹配文本字段,支持通配符操作,*
代表任意字符,?
代表任意单个字符。例如,查找from_uid
字段中以“user”开头的所有记录:
GET /chat_logs/_search
{
"query": {
"wildcard": {
"from_uid": "user*"
}
}
}
- wildcard:适合模糊匹配场景,但在大数据量场景中性能不如
match
或term
。
示例8:前缀匹配查询(prefix
查询)
prefix
查询用于匹配字段值的开头字符,可以高效地搜索以特定字符开头的记录。例如,查询from_uid
字段以“user”开头的文档:
GET /chat_logs/_search
{
"query": {
"prefix": {
"from_uid": "user"
}
}
}
- prefix:适合简单的前缀匹配,通常用于字符串字段。
示例9:正则表达式查询(regexp
查询)
regexp
查询可以使用正则表达式匹配字段值,适合复杂的模式匹配。例如,查找from_uid
字段中包含“user”后面带有数字的所有记录:
GET /chat_logs/_search
{
"query": {
"regexp": {
"from_uid": "user[0-9]+"
}
}
}
- regexp:正则表达式查询功能强大,但性能较低,建议在小规模数据或必要时使用。
示例10:ID查询(ids
查询)
ids
查询可以通过多个ID查找特定的文档,适合已知文档ID的场景。例如,查询ID为“1”和“2”的文档:
GET /chat_logs/_search
{
"query": {
"ids": {
"values": ["1", "2"]
}
}
}
- ids:直接通过文档ID查找指定文档,效率高。
查询数据(聚合查询)
聚合查询语句的字段说明
Elasticsearch的聚合查询允许在数据集上进行各种统计分析操作,其基本结构如下:
GET /index_name/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"aggregation_type": {
"field": "field_name",
"interval": "interval_value" // 仅对某些聚合类型适用,如直方图和日期直方图
},
"aggs": {
"nested_aggregation_name": {
"nested_aggregation_type": {
"field": "nested_field_name"
}
}
}
}
}
}
-
index_name:要执行聚合的索引名称。
-
size:指定查询结果的数量。对于纯聚合查询,将
size
设置为0
以节省资源。 -
aggs:定义聚合的主要字段,所有聚合条件都放在
aggs
字段中。- aggregation_name:聚合名称,可自定义,用于标识该聚合结果。
- aggregation_type:聚合类型,如
terms
、avg
、max
、min
等。 - field:指定进行聚合的字段。
- interval:间隔值,仅对某些聚合类型适用(如直方图和日期直方图)。
- nested aggs:嵌套聚合,用于在当前聚合基础上再细分。
聚合查询返回结果的字段的说明
Elasticsearch的聚合查询返回结果包含查询执行状态、命中信息和聚合结果等信息,以下是各个字段的说明:
-
took:表示查询耗时,单位为毫秒。例如,
"took": 13
表示查询耗时13毫秒。 -
timed_out:表示查询是否超时,
false
表示查询在设定的超时时间内完成,true
表示超时。 -
_shards:提供有关分片的查询统计信息,包括以下几个字段:
- total:查询涉及的总分片数。
- successful:成功查询的分片数。
- skipped:被跳过的分片数,通常在跨多个索引的查询中出现。
- failed:失败的分片数。
-
hits:包含查询命中信息,包括总匹配数、最大评分和实际匹配的文档内容。对于仅执行聚合查询的请求,
hits
通常为空。-
total:表示符合条件的文档总数,包含以下子字段:
- value:符合条件的文档数。
- relation:表示文档总数的关系类型,通常为
eq
(精确值)或gte
(大于等于某值)。
-
max_score:匹配文档的最高评分。对于仅执行聚合查询的请求,此字段通常为
null
。 -
hits:实际匹配的文档列表,对于仅执行聚合查询的请求,该列表为空。
-
-
aggregations:包含聚合查询的结果。每个聚合查询都会生成一个结果对象,聚合结果通常由聚合名称作为键(例如
messages_per_user
),并包含该聚合的具体结果。-
doc_count_error_upper_bound:记录结果中的最大文档计数误差。对于
terms
聚合,在处理大量不同值时可能出现误差。 -
sum_other_doc_count:表示其他未返回的桶的总文档数量。当仅返回部分桶时,
sum_other_doc_count
包含未返回的桶的文档计数。 -
buckets:聚合结果的主要内容,每个桶(bucket)代表一个分组,包含以下字段:
- key:桶的键值,表示当前分组的值。例如,
"key": "user123"
表示当前分组为user123
。 - doc_count:该桶中的文档数量。例如,
"doc_count": 2
表示user123
有2条消息。
- key:桶的键值,表示当前分组的值。例如,
-
聚合查询数据的结构和示例
示例1:按字段分组计数(terms
聚合)
使用terms
聚合对指定字段进行分组计数。以下示例按from_uid
字段分组,统计每个用户的消息数量:
GET /chat_logs/_search
{
"size": 0,
"aggs": {
"messages_per_user": {
"terms": {
"field": "from_uid"
}
}
}
}
- terms:按
from_uid
字段分组,统计每个用户发送的消息数量。
示例2:计算字段平均值(avg
聚合)
使用avg
聚合计算某字段的平均值。以下示例统计msg_type
字段的平均值:
GET /chat_logs/_search
{
"size": 0,
"aggs": {
"average_msg_type": {
"avg": {
"field": "msg_type"
}
}
}
}
- avg:计算
msg_type
字段的平均值,适用于数值字段。
示例3:日期直方图聚合(date_histogram
)
使用date_histogram
聚合对日期字段按时间分组。以下示例按天分组统计聊天记录数量:
GET /chat_logs/_search
{
"size": 0,
"aggs": {
"daily_messages": {
"date_histogram": {
"field": "create_time",
"calendar_interval": "day"
}
}
}
}
- date_histogram:按
create_time
字段的天为单位进行分组统计。
示例4:嵌套聚合
嵌套聚合允许在一个聚合中添加子聚合。以下示例按用户分组统计消息数量,并按消息类型进一步分组:
GET /chat_logs/_search
{
"size": 0,
"aggs": {
"messages_per_user": {
"terms": {
"field": "from_uid"
},
"aggs": {
"message_type_distribution": {
"terms": {
"field": "msg_type"
}
}
}
}
}
}
- terms(外层)按
from_uid
分组,统计每个用户的消息数量。 - terms(内层)按
msg_type
分组,统计每个用户的消息类型分布。
示例5:过滤聚合(filter
聚合)
使用filter
聚合可以在聚合中添加条件。以下示例统计发送者为user123
的消息数量:
GET /chat_logs/_search
{
"size": 0,
"aggs": {
"user123_messages": {
"filter": {
"term": { "from_uid": "user123" }
}
}
}
}
- filter:仅统计满足
from_uid
为user123
的记录。
示例6:最大值和最小值(max
和min
聚合)
使用max
和min
聚合可以计算字段的最大值和最小值。例如,查询聊天记录中的最早和最晚消息时间:
GET /chat_logs/_search
{
"size": 0,
"aggs": {
"latest_message": {
"max": {
"field": "create_time"
}
},
"earliest_message": {
"min": {
"field": "create_time"
}
}
}
}
- max:返回
create_time
字段的最大值(即最晚的消息时间)。 - min:返回
create_time
字段的最小值(即最早的消息时间)。
这些聚合查询示例展示了不同的统计分析场景。聚合查询可以帮助实现复杂的业务数据分析,使得Elasticsearch不仅能处理数据搜索,还能够执行实时数据统计和分析。
更改数据
在Elasticsearch中,更改数据主要通过_update
端点实现。更新文档可以指定更改的字段和值,其他字段保持不变。还可以使用script
脚本进行复杂更新操作。
更新数据的结构说明
POST /index_name/_update/{id}
{
"doc": {
"field1": "new_value1",
"field2": "new_value2"
}
}
- index_name:指定要更新数据的索引名称。
- id:文档的唯一标识符,指示要更新的具体文档。
- doc:包含更新内容的主体。指定的字段会被更新,不包含的字段保持不变。
示例1:更新指定字段
假设我们有一条聊天记录,from_uid
为user123
。现在要更新该消息的content
字段。
POST /chat_logs/_update/1
{
"doc": {
"content": "你好,我有空了!"
}
}
- content字段被更新为“你好,我有空了!”其他字段保持不变。
示例2:增加计数(使用script
更新)
在需要对某字段进行递增操作时,可以使用script
来更新字段的值。如果字段不存在,则可以先初始化。以下示例展示如何在msg_count
字段的当前值上增加1:
POST /chat_logs/_update/1
{
"script": {
"source": "if (ctx._source.msg_count == null) { ctx._source.msg_count = 1; } else { ctx._source.msg_count += 1; }"
}
}
-
script:指定更新脚本。
-
if (ctx._source.msg_count == null) :检查
msg_count
字段是否存在。- 如果不存在,则初始化为
1
。 - 如果存在,则在当前值基础上增加1。
- 如果不存在,则初始化为
-
这样可以确保计数字段在更新时不存在异常,同时保证操作的灵活性。
示例3:条件更新
如果我们希望根据特定条件来更新字段,可以在script
中添加判断条件。例如,只有当msg_type
为1
时,才更新content
字段:
POST /chat_logs/_update/1
{
"script": {
"source": "if (ctx._source.msg_type == 1) { ctx._source.content = '更新后的内容' }"
}
}
- script:通过条件判断更新数据,仅当条件满足时才执行更新。
示例4:覆盖式更新(使用_doc
替换整个文档)
如果需要替换整个文档,可以通过PUT
方式直接写入新内容:
PUT /chat_logs/_doc/1
{
"from_uid": "user123",
"to_uid": "user456",
"msg_type": 1,
"content": "替换后的内容",
"create_time": "2024-10-17T14:35:00"
}
这种方式将替换掉原有文档,且未指定的字段会被移除。
通过这些更新操作,可以灵活地更改Elasticsearch中的文档数据,适应不同的更新需求。
删除数据
Elasticsearch支持删除单个文档、批量删除和删除整个索引。以下是删除操作的基本结构说明和示例。
删除数据的结构说明
DELETE /index_name/_doc/{id}
- index_name:要删除数据的索引名称。
- id:文档的唯一标识符,指示要删除的具体文档。
示例1:删除指定ID的文档
假设要删除chat_logs
索引中ID为1
的文档:
DELETE /chat_logs/_doc/1
- 该命令将删除
chat_logs
索引中的ID为1
的文档。
对于单个文档删除(如DELETE /index_name/_doc/{id}
),返回结构如下:
{
"_index": "chat_logs",
"_id": "1",
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
-
_index:被删除文档所在的索引名称。
-
_id:被删除文档的唯一ID。
-
result:删除结果状态,
"deleted"
表示文档成功删除。如果文档不存在,则会返回"not_found"
。 -
_shards:分片统计信息,包括以下字段:
- total:该请求涉及的分片总数。
- successful:成功执行删除操作的分片数。
- failed:未能执行删除操作的分片数,通常为
0
表示无失败。
示例2:根据查询条件批量删除
Elasticsearch支持通过查询条件批量删除文档。可以使用_delete_by_query
接口指定条件删除符合条件的所有文档。例如,删除所有from_uid
为user123
的文档:
POST /chat_logs/_delete_by_query
{
"query": {
"term": {
"from_uid": "user123"
}
}
}
- _delete_by_query:根据查询条件批量删除文档。
- query:指定要删除文档的条件,在此示例中,删除所有
from_uid
为user123
的文档。
对于批量删除(如_delete_by_query
),返回结构如下:
{
"took": 15,
"timed_out": false,
"total": 10,
"deleted": 10,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": []
}
-
took:删除操作的耗时,单位为毫秒。
-
timed_out:删除操作是否超时,
false
表示在设定时间内完成。 -
total:符合查询条件的文档总数。
-
deleted:成功删除的文档数量。
-
batches:批量操作的批次数量。
-
version_conflicts:版本冲突的文档数。版本冲突通常在并发操作时发生。
-
noops:跳过的文档数量,通常指无需删除的文档。
-
retries:重试统计信息,包括以下子字段:
- bulk:批量重试次数。
- search:搜索重试次数。
-
throttled_millis:在限流策略下暂停的时间(毫秒)。
-
requests_per_second:请求的每秒限速值,
-1
表示不限制。 -
throttled_until_millis:限流策略生效前的等待时间(毫秒)。
-
failures:操作失败的信息列表,若为空数组表示无失败。
示例3:删除整个索引
删除整个索引会清空该索引中的所有文档。此操作不可逆,执行前请谨慎。以下示例将删除整个chat_logs
索引:
DELETE /chat_logs
- 该命令将删除
chat_logs
索引及其所有内容。
删除整个索引(如DELETE /index_name
)成功时返回结果如下:
{
"acknowledged": true
}
- acknowledged:表示删除请求是否被Elasticsearch成功接收并处理,
true
表示索引已成功删除。
总结
在Elasticsearch中,增、删、改、查操作提供了对海量数据的高效管理能力,使其在处理复杂数据结构和实时查询场景中具有明显优势。通过合理使用这些基本操作,可以实现对数据的精确控制,从而更好地满足业务需求:
- 新增操作:允许将文档数据快速存储到索引中,并支持指定或自动生成文档ID,确保数据的灵活存储和检索。
- 查询操作:Elasticsearch提供了强大的查询功能,包括精确匹配、全文检索、组合条件查询、聚合分析等,适用于多种复杂的查询场景。在数据量大的情况下,Elasticsearch依然能够快速响应,特别适合大规模搜索和数据分析场景。
- 更新操作:通过
_update
端点,Elasticsearch支持对文档的部分或条件更新,不需要重新索引整个文档。同时,支持脚本更新和复杂逻辑操作,为灵活的增量更新提供了便利。 - 删除操作:Elasticsearch支持单个文档删除、条件批量删除以及索引级删除,满足不同场景的删除需求。在批量删除场景中,Elasticsearch提供了详细的反馈信息,便于监控和调整操作。
综上所述,Elasticsearch的增、删、改、查操作组合使用,能够构建一个高效的数据存储与分析系统,特别适合需要实时搜索和统计的应用场景。通过掌握这些基础操作,可以在Elasticsearch中更好地管理数据,为业务需求提供强有力的支持。