引言
Elasticsearch(ES)作为一个开源的分布式搜索引擎,以其高性能、可扩展性和实时分析能力赢得了广泛的应用。本文将深入探讨ES的分布式架构,帮助读者理解其背后的工作原理和实际应用。
ES基础架构概览
ES的分布式架构由多个核心组件构成,包括集群(Cluster),节点(Node)、索引(Index)、分片(Shard)、副本(Replica)、段(Segment)等。
集群(Cluster):一个es集群是由多个节点组成的。
节点(Node):ES集群中的一个单独服务器,可以承载多个索引。节点之间通过互相通信进行数据交换和协调工作,共同维持集群的稳定性和高可用性。
索引表(Index):类似于关系数据库中的表,是ES中存储数据的基本单位。每个索引可以包含多个分片,用于实现数据的分布式存储和查询。
分片(Shard):为了实现横向扩展和高可用性,ES将每个索引表划分成多个分片。每个分片是一个独立的Lucene索引,可以存储和处理特定量的数据。分片是数据分布和并行处理的基础。
副本(Replica):ES支持为每个分片创建多个副本,以提高数据冗余和故障恢复能力。副本可以分布在不同节点上,提升查询性能和系统可用性。
段(segment):Lucene索引中最小的独立存储单元。一个Lucene索引文件由一个或者多个段组成。在Luence中的段有不变性,也就是说段一旦生成,在其上只能有读操作,不能有写操作。频繁写入,会产生大量的小段,就像内存碎片一样,会影响长尾查询性能。因此在写入频繁时,必要时需要合并segment。
ES工作原理
ES的分布式架构使得其能够高效地处理大规模数据。以下是ES处理数据的基本流程:
写入数据:
客户端选择一个节点作为协调节点(Coordinating Node),发送写入请求。
协调节点将请求转发到包含主分片(Primary Shard)的节点。
主分片处理请求,并将数据同步到相应的副本分片(Replica Shard)。
协调节点确认所有分片写入成功后,向客户端返回响应。
查询数据:
客户端发送查询请求到任意一个节点,该节点成为协调节点。
协调节点将查询请求转发到包含相关分片的节点。
分片处理查询请求,并将结果返回给协调节点。
协调节点合并来自各分片的查询结果,并返回给客户端。
ES节点角色与扩展性
ES集群中的节点可以根据需要配置为不同的角色,以适应不同的使用场景。主要节点角色包括:
主节点(Master Node):负责管理集群的元数据,如节点信息、索引分片映射等。一个集群只能有一个活动主节点,否则会混乱,产生脑裂现象。
数据节点(Data Node):用于存储和管理数据,可进一步细分为冷、温、热等多种类型,以优化存储效率和查询性能。
协调节点:集群中的任何节点都可以充当协调节点的角色。用户的请求可以发往任何一个节点,并由该节点负责分发请求、收集结果等操作,而不需要主节点转发。这种节点可称之为协调节点,协调节点是不需要指定和配置的。
主节点和其他节点之间通过 Ping 的方式互检查,主节点负责 Ping 所有其他节点,判断是否有节点已经挂掉。其他节点也通过 Ping 的方式判断主节点是否处于可用状态。
ES的扩展性是其一大亮点。通过增加节点数量,可以轻松实现集群的横向扩展,以应对不断增长的数据量和查询需求。
ES主节点选举
主节点负责管理和协调整个集群的状态。它处理集群范围的操作,如创建或删除索引、跟踪哪些节点是集群的一部分,并决定分片的分配。当一个集群启动时,或者当前的主节点不可用时,会触发主节点选举过程。以下是Elasticsearch主节点选举的工作机制:
选举触发条件
集群启动:当一个新的集群启动时,需要选出第一个主节点。
主节点故障:如果当前的主节点由于某种原因变得不可用(例如网络分区或硬件故障),则集群中的其他节点将开始新的选举过程。
选举过程
发现阶段:
每个节点都会广播一条消息到所有已知的节点,通知它们自己认为的集群状态。
这条消息包含了该节点认为的最新集群状态信息,包括最近一次看到的主节点ID和任期(term)。
投票阶段:
如果一个节点发现当前没有活动的主节点,或者收到的消息表明有更新的集群状态,则它会发起一个新的选举。
发起选举的节点会给自己投一票,并且广播这个投票给集群中的其他节点。
其他节点接收到投票请求后,会根据自己的状态来决定是否支持这个候选节点。支持的标准通常基于节点的任期号和最新的集群状态。
获胜条件:
为了成为主节点,一个候选节点必须获得超过半数的选票。这意味着在一个N节点的集群中,至少需要floor(N/2) + 1个节点的支持。
如果一个节点获得了足够的选票,它就会被宣布为新的主节点,并向所有节点发送确认消息,告知它们新的主节点已经当选。
新的主节点还会更新其任期号,并确保所有节点都同步到了最新的集群状态。
脑裂
产生原因
(1)主节点访问故障(负载过大,资源耗尽,ping不通)
(2)网络故障,导致不可用。
集群中的一些节点访问不到master,认为master挂掉了,从而选举出新的master。导致会出现集群中存在多个活动主节点,这可能导致短暂的脑裂(split-brain)现象。
冲突解决
如果出现了脑裂,有多个主节点,怎么办?Elasticsearch通过比较候选主节点的任期号和其他因素(如节点ID)来解决这种冲突,确保只有一个节点能够成功当选为主节点。
如果两个节点具有相同的任期号,那么具有较高节点ID的节点将赢得选举。
避免脑裂
(1)一个集群部署不要跨机房
主节点和备选主节点尽量部署在同一个局域网(同一个机房内),这样网络环境更下安全可靠,信息传输效率也高;
(2)角色分离(单一职责原则,一个节点只做一件事)
master节点与data节点分离,限制角色;数据节点时需要承担存储和搜索的工作的,压力会很大。所以如果该节点同时作为候选主节点和数据节点,那么一旦选上它作为主节点了,这时主节点的工作压力将会非常大,出现脑裂现象的概率就增加了。
(3)延长ping超时设置
置主节点的响应时间,在默认情况下,主节点3秒没有响应,其他节点就认为主节点宕机了,那我们可以把该时间设置得长一点,该配置是:discovery.zen.ping_timeout:5
(4)提高主节点选举票数,这是es默认的配置。
ES实际应用场景
ES因其强大的搜索和分析能力,被广泛应用于多个领域,如:
日志分析:在ELK(Elasticsearch、Logstash、Kibana)日志分析系统中,ES负责存储和查询日志数据。
电商搜索:电商平台利用ES实现商品搜索功能,提供快速、准确的搜索体验。
数据监控:在大数据监控系统中,ES用于存储和分析监控数据,帮助用户及时发现潜在问题。
es安装
参考https://blog.youkuaiyun.com/okiwilldoit/article/details/137107087
再安装kibana,在它的控制台里写es查询语句。
es指南
es权威指南-中文版:
kibana用户手册-中文版:
es中文社区
es参考手册API
es客户端API
es查询语句
# 查询es版本
GET /
# 查询集群下所有index
GET /_cat/indices?v
# 查询节点
GET _cat/nodes?format=json
# 查询某个index的结构
GET /index_name/_mapping?pretty
#查询doc_id=123的详细信息
GET /index_name/_doc/123
# 查询作者为"猫腻"的作品,取出4个字段,100条数据,按照star_num和like_num倒排
GET /index_name/_search
{
"query" : {
"match" : {
"authorname" : "猫腻"
}
},
"_source": ["ID","title","authorname","desc"],
"size": 100,
"sort": [{"star_num": {"order": "desc"}}, {"like_num":{"order": "desc"}}]
}
在语句中加入"explain":true(与size对齐),会返回计算BM25的过程分。
es bool查询
Bool查询则可以实现查询的组合,并支持多字段查询和精确匹配、模糊匹配、范围匹配等多种查询方式。下面我们来看一下Bool查询的基本语法:
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Search" }},
{ "match": { "content": "Elasticsearch" }}
],
"filter": [
{ "term": { "status": "published" }},
{ "range": { "publish_date": { "gte": "2019-01-01" }}}
],
"should": [
{ "match": { "author": "John" }},
{ "match": { "author": "Doe" }}
],
"must_not": [
{ "match": { "category": "Marketing" }}
]
}
}
}
must:表示必须匹配的条件,相当于AND。
filter:表示过滤条件,可以提高查询效率,相当于WHERE。
should:表示应该匹配的条件,可以有多个,相当于OR。should里的所有条件bm25分数是相加关系。
must_not:表示必须不匹配的条件,相当于NOT。
dis_max:所有条件bm25分数的最高分。
在这个查询中,必须同时匹配 title 和 content 字段,同时 status 字段必须为 published,publish_date 字段必须大于等于 2019-01-01,并且 author 字段必须匹配 John 或者 Doe 中的至少一个,同时 category 字段不能匹配 Marketing
ES新增字段
在my_index索引表里新增new_feild字段
PUT my_index/_mapping
{
"properties": {
"new_feild" : {
"type" : "boolean",
"doc_values" : false
}
}
}
增加预定义脚本
PUT /_scripts/my_script_staticscore
{
"script": {
"lang" : "painless",
"source": """
double staticscore = doc['staticscore'].empty ? 0.0: doc['staticscore'].value;
double staticscore_norm = staticscore / 60000.0;
return (1 + staticscore_norm) * _score;
"""
}
}
清空索引所有数据
POST /your_index/_delete_by_query
{
"query": {
"match_all": {}
}
}
新增doc
PUT myindex/_doc/16515175900086404
{
"articleid": 10,
"allwords" : 5000,
"authorname" : "一只简单的猪",
"authorid" : 3860320803982101,
"title" : "名字很长很长很长很长很长很长很",
"enable_visible" : [
"10_12_30",
"10_10_1"
],
"newchaptertime" : 1722672398
}
ES更新字段
#更新单个字段
//字段名为item_name的值更新为2322332
POST index_name/_update/$id/
{
"doc": {
"item_name": "2322332"
}
}
#更新数组字段,新增值
//字段名是tags,是个多值字段
//tags更新前是["1"],更新后是["1","blue"]
POST index_name/_update/$id/
{
"script": {
"source": "ctx._source.tags.add(params.tag)",
"lang": "painless",
"params": {
"tag": "blue"
}
}
}
//判断是否存在,不存在再添加
POST index_name/_update/$id/
{
"script": {
"source": """
def tags = ctx._source.tags;
def value = "-1";
if (!tags.contains(value)) {
tags.add(value);
}
""",
"lang": "painless"
}
}
#更新数组字段,删除值
//字段名是tags,是个多值字段
//tags更新前是["1","blue"],删除值为"blue"后,变成["1"]
POST index_name/_update/$id/
{
"script": {
"source": """
for(int i = 0; i < ctx._source.tags.size(); i++) {
if(ctx._source.tags[i] == 'blue') {
ctx._source.tags.remove(i);
break;
}
}
""",
"lang": "painless"
}
}
ES SQL
6.3版本后支持SQL,但是不支持join等复杂操作。
https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-spec.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-search-api.html
POST _sql?format=txt
{
"query": "SELECT author, title FROM index_name WHERE MATCH(title, '猎鬼') ORDER BY updateTime DESC LIMIT 100"
}
ES监控
通过kibana可以看到es的监控信息,包括每个索引的查询耗时等。
ES段的概念
在 Elasticsearch 中,段是倒排索引的物理存储单元。倒排索引是一种数据结构,它将文档中的每个词(term)映射到包含该词的文档列表。当文档被索引到 Elasticsearch 中时,它们会被分解成更小的单元,这些单元就是段。
例如,当有一个包含多个文档的索引,Elasticsearch 会把这些文档划分成多个段来存储和管理。每个段都是独立的,包含了倒排索引的一部分,并且是不可变的(immutable),这意味着一旦段被创建,就不能再修改。
查看某个索引的段
GET index_name/_segments
段的生命周期和操作
创建:
当新的文档被写入 Elasticsearch 时,会触发段的创建过程。在后台,Elasticsearch 会定期(由index.refresh_interval设置控制,默认是 1 秒)或者在达到一定的文档数量或大小阈值时,将内存中的索引数据刷新(flush)到磁盘上形成新的段。
例如,当向一个索引写入大量文档后,经过刷新间隔,这些文档就会被组织成新的段存储在磁盘上。
合并(Merge):
随着时间的推移,索引中会积累许多小的段。为了提高查询性能和磁盘利用率,Elasticsearch 会自动合并这些小的段。段合并操作会读取多个小的段,将它们合并成一个更大的段,并删除原来的小的段。
例如,有多个小的段,每个段包含部分文档的倒排索引,合并后可以减少段的数量,使得查询时需要搜索的段数量减少,从而提高查询效率。同时,合并后的段可以更好地利用磁盘空间,因为它可以更紧凑地存储倒排索引。
删除:
当文档从索引中删除时,Elasticsearch 不会立即从段中删除相应的信息。而是在段合并时,会标记要删除的文档,在合并过程中真正从新的段中去除这些已删除文档的信息。
段对查询性能的影响
积极影响:
合理的段结构可以提高查询性能。因为段是倒排索引的存储单元,查询操作实际上是在段上进行的。当段的大小适中且经过优化(如通过合并)时,查询可以更快地定位到包含查询词的文档。例如,经过段合并后,查询时需要扫描的段数量减少,I/O 操作的次数也会相应减少,从而加快查询速度。
消极影响:
过多的小的段可能会导致查询性能下降。因为每个段都需要被搜索,过多的段会增加查询时的 I/O 开销和内存占用。例如,如果索引中有大量未合并的小的段,查询可能需要在许多小的段之间频繁切换,导致查询速度变慢。
如何管理段
控制刷新频率:
可以通过调整index.refresh_interval参数来控制段的创建频率。如果对数据的实时性要求不是很高,可以适当延长刷新间隔,减少小的段的产生。但是要注意,延长刷新间隔可能会导致新写入的文档在一段时间内无法被查询到。
触发合并操作:
虽然 Elasticsearch 会自动进行段合并,但在某些情况下,也可以手动触发合并操作。可以使用 Elasticsearch 的 API 来控制合并策略和触发合并。例如,在索引负载较低的时段,可以手动触发合并操作,以优化索引的性能。不过,要注意合并操作可能会消耗大量的磁盘 I/O 和 CPU 资源,所以要谨慎操作。
手动触发合并代码
集群中的所有索引进行段合并,将每个索引的段合并到尽可能少的数量,以优化索引性能和减少磁盘空间占用。
POST /_forcemerge
将 your_index_name 替换为你实际要合并段的索引名称,只会对指定的索引进行段合并操作。
POST /your_index_name/_forcemerge
查看合并进度
将 your_index_name 替换为具体的索引名称,即可查看该索引的段信息和合并进度。
GET /your_index_name/_cat/segments?v