Elasticsearch(ES),这是一个开源的搜索和分析引擎,广泛用于各种应用中以实现全文搜索、结构化搜索、分析和复杂的数据查询。下面我将详细介绍Elasticsearch的基本概念、架构和工作原理。
Elasticsearch的基本概念
节点(Node)
Elasticsearch运行的单个实例称为节点。每个节点都存储数据,并参与集群的索引和搜索功能。
集群(Cluster)
集群是由一个或多个节点组成的集合,协同工作以存储数据并提供索引和搜索能力。每个集群都有一个唯一的名字。
索引(Index)
索引是具有相似特征的一组文档的集合。在关系型数据库中,相当于数据库。
文档(Document)
文档是可以被索引的基本单元,是JSON格式的。它类似于关系型数据库中的行。
类型(Type)(在Elasticsearch 6.x及以后版本中已经弃用)
类型是文档的一个逻辑分类。在一个索引中,可以有多个类型,每个类型有自己的字段。
分片(Shard)
索引可以被分为多个分片。每个分片都是一个独立的Lucene索引,可以存储在集群中的任何节点上。分片可以是主分片或副本分片。
Elasticsearch的架构
分布式
Elasticsearch是分布式的,意味着它能够处理大规模的数据存储和搜索请求。数据可以分布在多个节点上。
高可用性
通过副本分片机制,Elasticsearch保证了数据的冗余和高可用性。如果一个节点失效,数据可以从其他节点上的副本分片中恢复。
近实时
Elasticsearch提供近实时的搜索能力。索引的数据几乎可以立即被搜索到。
Elasticsearch的工作原理
索引(Indexing)
当一个文档被索引时,Elasticsearch会将其放入一个主分片,并根据配置将其复制到相应的副本分片。索引操作包括解析文档、创建倒排索引和存储数据。
搜索(Searching)
当接收到一个搜索请求时,Elasticsearch会将请求发送到所有相关的分片。每个分片独立执行搜索,然后将结果合并返回给用户。
分片管理
Elasticsearch通过集群管理机制来自动处理分片的分配、重新平衡和副本管理。
使用Elasticsearch的Java代码示例
下面是一个简单的Java代码示例,展示了如何使用Elasticsearch客户端来索引和搜索文档:
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
public class ElasticsearchExample {
public static void main(String[] args) {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new RestClientBuilder.HttpHosts(
new HttpHost("localhost", 9200, "http"))));
// 索引一个文档
IndexRequest indexRequest = new IndexRequest("myindex")
.id("1")
.source("{\"field1\":\"value1\"}", XContentType.JSON);
try {
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
System.out.println("Index response: " + indexResponse.getResult().toString());
} catch (IOException e) {
e.printStackTrace();
}
// 搜索文档
SearchRequest searchRequest = new SearchRequest("myindex");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("field1", "value1"));
searchRequest.source(searchSourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("Search response: " + searchResponse.toString());
} catch (IOException e) {
e.printStackTrace();
}
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Elasticsearch 写入数据的工作原理
接收请求
客户端发送一个写入请求(如索引文档)到Elasticsearch集群的某个节点。
路由到主分片
Elasticsearch根据文档ID计算目标主分片的位置,默认情况下使用哈希算法进行计算。然后请求被路由到包含该主分片的节点。
写入主分片
主分片节点接收到写入请求后,将文档写入到本地的Lucene索引中。Lucene将文档分解成倒排索引并存储。
复制到副本分片
主分片节点将写操作传递给所有副本分片。副本分片节点接收到请求后,也会将文档写入本地的Lucene索引中。
确认写入完成
当所有副本分片都成功写入后,主分片节点返回确认消息给客户端,表示写入操作完成。
Elasticsearch 查询数据的工作原理
接收请求
客户端发送一个查询请求到Elasticsearch集群的某个节点。
分发到所有分片
请求节点将查询请求分发到相关索引的所有主分片和副本分片。
执行查询
每个分片独立执行查询。Lucene使用倒排索引快速查找与查询匹配的文档ID。
聚合结果
每个分片将其查询结果返回给请求节点。请求节点将所有分片的结果合并、排序、去重等处理。
返回结果
最终结果集返回给客户端。
底层的Lucene
Apache Lucene是一个高性能、全功能的搜索库。Elasticsearch使用Lucene作为其底层引擎,负责索引和搜索功能。
索引
Lucene将文档拆分为字段,然后将字段内容拆分为词条(terms)。
词条被存储在倒排索引中,其中每个词条记录了包含该词条的文档ID列表。
搜索
Lucene使用倒排索引来快速查找包含特定词条的文档。
Lucene还提供了多种查询类型(如布尔查询、短语查询、范围查询等)来满足不同的搜索需求。
倒排索引
倒排索引是一种数据结构,主要用于全文检索系统中。它从文档内容中提取出所有的词条,并建立词条到文档的映射关系。
构建过程
每个文档被解析成独立的词条。
词条作为索引项,记录下其所在的文档ID和词频等信息。
查询过程
查询时,根据查询词条在倒排索引中查找,获取包含该词条的文档ID列表。
然后根据文档ID列表进行进一步的处理,如计算相关性、排序等。
倒排索引的示例
假设我们有三个文档:
Doc1: "I love programming"
Doc2: "Programming is fun"
Doc3: "I love fun"
构建的倒排索引如下:
Term Document IDs
-------------------------
I [Doc1, Doc3]
love [Doc1, Doc3]
programming [Doc1, Doc2]
is [Doc2]
fun [Doc2, Doc3]
查询"love"时,返回[Doc1, Doc3]。
综合示例
为了更好地理解,我们结合上述概念用代码来演示Elasticsearch的写入和查询过程。假设我们使用Java客户端。
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
public class ElasticsearchExample {
public static void main(String[] args) {
// 创建客户端
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new RestClientBuilder.HttpHosts(
new HttpHost("localhost", 9200, "http"))));
// 索引一个文档
IndexRequest indexRequest = new IndexRequest("myindex")
.id("1")
.source("{\"field1\":\"I love programming\"}", XContentType.JSON);
try {
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
System.out.println("Index response: " + indexResponse.getResult().toString());
} catch (IOException e) {
e.printStackTrace();
}
// 搜索文档
SearchRequest searchRequest = new SearchRequest("myindex");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("field1", "love"));
searchRequest.source(searchSourceBuilder);
try {
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("Search response: " + searchResponse.toString());
} catch (IOException e) {
e.printStackTrace();
}
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Elasticsearch写数据的底层原理
这个过程涉及多个组件和步骤,从接收到写请求到最终将数据持久化到磁盘。
1. 写入内存 Buffer 和 Translog
接收写请求
当一个写请求(如索引一个文档)到达Elasticsearch集群时,它会被路由到包含目标主分片的节点。这个分片位置是通过文档ID哈希计算得到的。
写入 Buffer
文档被写入到内存中的 buffer。这个 buffer 是暂存数据的地方。
写入 Translog
同时,写操作被记录到事务日志(translog)中。Translog 用于数据恢复,确保数据在系统崩溃时不会丢失。
内存 Buffer <--- 文档
|
v
Translog <--- 写操作日志
2. 刷新(Refresh)操作
触发刷新
默认情况下,每秒钟会触发一次刷新操作。刷新操作将 buffer 中的数据写入一个新的 segment file。
写入 OS Cache
数据不会立即写入磁盘,而是先写入操作系统缓存(OS Cache)。这个缓存是操作系统级别的内存缓存。
清空 Buffer
一旦数据进入 OS Cache,就可以被搜索到,因此 buffer 可以被清空。
Buffer --> 刷新操作 --> OS Cache --> Segment File
3. Segment File
-
Segment File 生成
每次刷新操作都会生成一个新的 segment file,这些文件存储在磁盘上但首先进入 OS Cache。 -
合并 Segment
随着时间的推移,Elasticsearch会自动执行段合并操作,将多个小的 segment file 合并成一个大的 segment file。这有助于提高搜索性能并减少文件数量。
4. Flush 操作
- 触发条件
Flush 操作通常在以下情况下触发:
- 每隔 30 分钟。
- 当 translog 达到一定大小。
- 过程
- 刷新 Buffer:确保 buffer 中的数据被刷新到 OS Cache 中。
- 写入 Commit Point:记录当前所有 segment file 的标识。
- 强制写入磁盘:将 OS Cache 中的数据强制写入磁盘(fsync)。
- 清空 Translog:清空现有的 translog,并开始一个新的 translog。
Buffer --> 刷新 --> OS Cache --> Segment File --> Flush --> Disk
Translog (FSYNC)
5. Translog
-
Translog 的作用
Translog 用于保证数据的持久性,即使系统崩溃。每次写操作都会被记录到 translog 中。默认情况下,translog 每 5 秒 fsync 到磁盘。 -
恢复数据
在系统重启时,Elasticsearch 会读取 translog 并恢复未持久化的数据到 buffer 和 OS Cache 中。
配置参数
- index.translog.sync_interval:
控制 translog 多久 fsync 到磁盘,默认是 5 秒钟,最小为 100ms。
-
index.translog.durability:
- 设置 translog 是每 5 秒钟刷新一次还是每次请求都 fsync。这个参数有两个取值:
- request:每次请求都执行 fsync,确保每次写操作都被持久化。
- async:默认值,translog 每隔 5 秒钟 fsync 一次。
- 设置 translog 是每 5 秒钟刷新一次还是每次请求都 fsync。这个参数有两个取值:
数据写入流程总结
- 接收写请求:
请求被路由到包含目标主分片的节点。 - 写入 Buffer 和 Translog:
文档写入内存 buffer,同时记录到 translog。 - 刷新操作:
每秒钟刷新 buffer,将数据写入 OS Cache 中的 segment file,并清空 buffer。 - Flush 操作:
每 30 分钟或 translog 达到一定大小时,触发 flush 操作,确保所有数据都持久化到磁盘。 - Translog 恢复:
在系统重启时,通过 translog 恢复未持久化的数据,确保数据完整性。