目录
4、那么,到底是 Solr 还是 Elasticsearch?
方案二:使用 index alias(索引别名)实现数据扩展
1、操作文档报错(Unable to parse response body...)
3、查询(searchSourceBuilder.query())
一、概述
The Elastic Stack, 包括 Elasticsearch、Kibana、Beats 和 Logstash(也称为 ELK Stack)。能够安全可靠地获取任何来源、任何格式的数据,然后实时地对数据进行搜索、分析和可视化。Elaticsearch,简称为 ES,ES 是一个开源的高扩展的分布式全文搜索引擎,是整个 Elastic Stack 技术栈的核心。它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别的数据。
1、全文搜索引擎
Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。
一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。
- 基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差的:
- 搜索的数据对象是大量的非结构化的文本数据。
- 文件记录量达到数十万或数百万个甚至更多。
- 支持大量基于交互式文本的查询。
- 需求非常灵活的全文搜索查询。
- 对高度相关的搜索结果的有特殊需求,但是没有可用的关系数据库可以满足。
- 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。为了解决结构化数据搜索和非结构化数据搜索性能问题,我们就需要专业,健壮,强大的全文搜索引擎。
这里说到的全文搜索引擎指的是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。
2、Elasticsearch And Solr
Lucene 是 Apache 软件基金会 Jakarta 项目组的一个子项目,提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在 Java 开发环境里 Lucene 是一个成熟的免费开源工具。就其本身而言,Lucene 是当前以及最近几年最受欢迎的免费 Java 信息检索程序库。但 Lucene 只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来进行应用。
目前市面上流行的搜索引擎软件,主流的就两款:Elasticsearch 和 Solr,这两款都是基于 Lucene 搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作 修改、添加、保存、查询等等都十分类似。
在使用过程中,一般都会将 Elasticsearch 和 Solr 这两个软件对比,然后进行选型。这两个搜索引擎都是流行的,先进的的开源搜索引擎。它们都是围绕核心底层搜索库 - Lucene构建的 - 但它们又是不同的。像所有东西一样,每个都有其优点和缺点:
3、Elasticsearch Or Solr
Elasticsearch 和 Solr 都是开源搜索引擎,那么我们在使用时该如何选择呢?
- Google 搜索趋势结果表明,与 Solr 相比,Elasticsearch 具有很大的吸引力,但这并不意味着 Apache Solr 已经死亡。虽然有些人可能不这么认为,但 Solr 仍然是最受欢迎的搜索引擎之一,拥有强大的社区和开源支持。
- 与 Solr 相比,Elasticsearch 易于安装且非常轻巧。此外,你可以在几分钟内安装并运行Elasticsearch。但是,如果 Elasticsearch 管理不当,这种易于部署和使用可能会成为一个问题。基于 JSON 的配置很简单,但如果要为文件中的每个配置指定注释,那么它不适合您。总的来说,如果你的应用使用的是 JSON,那么 Elasticsearch 是一个更好的选择。否则,请使用 Solr,因为它的 schema.xml 和 solrconfig.xml 都有很好的文档记录。
- Solr 拥有更大,更成熟的用户,开发者和贡献者社区。ES 虽拥有的规模较小但活跃的 用户社区以及不断增长的贡献者社区。
- Solr 贡献者和提交者来自许多不同的组织,而 Elasticsearch 提交者来自单个公司。
- Solr 更成熟,但 ES 增长迅速,更稳定。
- Solr 是一个非常有据可查的产品,具有清晰的示例和 API 用例场景。 Elasticsearch 的文档组织良好,但它缺乏好的示例和清晰的配置说明。
4、那么,到底是 Solr 还是 Elasticsearch?
无论选择 Solr 还是 Elasticsearch,首先需要了解正确的用例和未来需求。
- 由于易于使用,Elasticsearch 在新开发者中更受欢迎。一个下载和一个命令就可以启动一切。
- 如果除了搜索文本之外还需要它来处理分析查询,Elasticsearch 是更好的选择
- 如果需要分布式索引,则需要选择 Elasticsearch。对于需要良好可伸缩性和以及性能分布式环境,Elasticsearch 是更好的选择。
- Elasticsearch 在开源日志管理用例中占据主导地位,许多组织在 Elasticsearch 中索引它们的日志以使其可搜索。
- 如果你喜欢监控和指标,那么请使用 Elasticsearch,因为相对于 Solr,Elasticsearch 暴露了更多的关键指标。
二、ElasticSearch存储结构原理
ES是一个分布式搜索引擎,其底层基于Lucene,并通过分片和副本机制实现高可用性和高效搜索。
1、ES存储架构
1.1、逻辑结构
- Index(索引):类似于数据库,存储一个业务类型的数据
- Document(文档):类似于数据库的行(Row),存储具体数据
- Field(字段):类似于数据库的列(Column),用于存储数据属性
- Type(类型,已废弃):分类类型,7.x版本后移除
- Shard(分片):一个索引被拆分成多个分片,每个分片是一个独立的Lucene索引
- Replica(副本):分片的副本,提高可用性和查询性能
1.2、物理存储
ES的底层存储是基于Lucene,每个Shard(分片)都是一个独立的Lucene索引,其存储结构如下:
- 倒排索引:核心数据结构,用于加速搜索;
- 正排索引:用于存储字段值,加速排序、聚合操作;
- 存储字段:用于存储完整的 _source 文档数据,类似于数据库的行存储。
数据存储过程:
- 数据写入 _source(完整 json 数据存储);
- 建立倒排索引(分词、归一化);
- 存储 Doc Values(便于排序、聚合);
- 分片路由存储(Hash 计算 _id 决定数据存储在哪个分片)。
2、ES分片机制
2.1、为什么要分片?
- 提高存储能力:单个索引过大,可拆分存储;
- 提高查询效率:多个分片可以并行处理查询;
- 实现数据均衡:多节点存储,防止单点故障。
2.2、分片策略
ES在创建时,会根据 index.number_of_shards 参数确定主分片数量(不可变),副本数可动态调整。
主分片:负责索引和存储数据
副本分片:负责查询和数据冗余,防止数据丢失
数据如何分配到分片?
ES通过 _id 计算分片号:
shard_num = hash(_id) % number_of_primary_shards
示例:
number_of_primary_shards=3
_id="abc123"
shard_num = hash("abc123") % 3 → 数据存入分片 1
副本分配:副本分片不能和主分片放在同一节点,ES自动分配副本,提升查询能力。
2.3、ES搜索流程
当 ES 收到查询请求时,搜索过程如下:
- 路由计算:根据 _id 计算数据所在分片;
- 协调节点:负责解析查询请求,分发到相关分片,并合并结果;
- 分片搜索:在匹配的 主分片/副本分片 执行查询;
- 聚合结果:汇总所有分片的查询结果,排序、分页、去重,返回最终结果。
2.4、ES如何高效?
(1)倒排索引
ES 主要依赖倒排索引,相比数据库的 B+树索引,适用于全文搜索:
- 数据库索引:适合精确匹配(如ID、主键)
- 倒排索引:适合快速搜索关键词、模糊查询
示例:
文档ID | 内容 |
1 | ElasticSearch是一个搜索引擎 |
2 | 搜索引擎需要高效的索引结构 |
倒排索引:
词语 | 文档ID |
ElasticSearch | 1 |
搜索引擎 | 1,2 |
高效 | 2 |
搜索“搜索引擎”,ES直接查询倒排索引获取文档1和2,比数据库的模糊查询更快。
(2)批量写入(Segment + Merge 机制)
ES 采用 写入 —> 分段存储(Segment)—> 定期合并(Merge)的方式:
- 新数据写入Translog(事务日志),防止数据丢失;
- 数据进入内存(Buffer),达到一定阈值后写入 Lucene Segment;
- 定期 Merge(段合并),优化查询性能。
优势:
- 写入快:先写内存,批量刷入磁盘;
- 查询快:分段存储,减少锁冲突。
(3)并行搜索
- 分片查询并行执行,提高查询吞吐量;
- 主分片/副本分片共同参与查询,减少单节点压力;
- 缓存查询结果,加速相同查询。
3、ES分片机制扩容方案
ES采用主分片+副本分片的架构。
- 主分片:负责索引和存储数据,数量在索引创建时确定,不可变;
- 副本分片:用于查询负载均衡和数据冗余,可以动态调整,ES会自动分配副本到不同节点,提升查询能力和容灾能力。
3.1、如何扩容主分片
由于主分片数量固定,直接扩容不会影响现有索引。因此,扩容时通常有以下三种方案:
方案一:使用 reindex 创建新索引
思路:创建一个新的索引,指定更大的主分片数量,并将旧数据迁移到新索引。“适用于数据量较大且需要扩展写入能力的场景”
步骤一:创建新索引,指定更大的 number_of_shards
PUT new_index
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1
}
}
步骤二:使用 reindex 将旧索引数据迁移到新索引
POST _reindex
{
"source": {
"index": "old_index"
},
"dest": {
"index": "new_index"
}
}
步骤三:删除旧索引,切换应用到新索引
DELETE old_index
- 优点:
- 灵活调整主分片数,避免单节点存储过多数据
- 无数据丢失,新索引完全复制旧索引
- 缺点:
- 需要额外存储空间(新旧索引共存)
- _reindex 过程可能影响性能,需要在低峰期操作
方案二:使用 index alias(索引别名)实现数据扩展
思路:创建多个索引,并用别名(alias)将他们统一管理,达到逻辑扩容效果
步骤一:创建多个索引
PUT index_2024_01
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
PUT index_2024_02
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
步骤二:创建别名(alias),让应用可以查询多个索引
POST _aliases
{
"actions": [
{ "add": { "index": "index_2024_01", "alias": "log_index" }},
{ "add": { "index": "index_2024_02", "alias": "log_index" }}
]
}
- 优点:
- 无需重新索引,可以持续扩展;
- 支持时间分区(按时间周期创建索引);
- 查询时ES会自动并行查询多个索引
- 缺点:
- 查询时需要扫描多个索引,可能影响性能
方案三:使用 shrink 合并索引
思路:如果原索引分片过多,可以合并分片,提高查询效率
步骤一:将索引设为 readonly
PUT old_index/_settings
{
"settings": {
"index.blocks.write": true
}
}
步骤二:合并索引到较少的主分片
POST old_index/_shrink/new_index
{
"settings": {
"number_of_shards": 3
}
}
步骤三:删除旧索引
DELETE old_index
- 优点:
- 适用于老数据归档,提高查询效率
- 无需 reindex,性能影响小
- 缺点:
- 只能减少分片数,不能增加分片
3.2、ES如何自动分配副本分片?
ES默认会确保副本不会与主分片放在同一节点,具体规则如下:
副本分片自动分配策略
- 副本永远不会与主分片放在同一节点
- 避免单节点宕机时,主副本都丢失
- ES尝试均衡分布分片
- 负载均衡:如果新增节点,ES会自动重新分配副本,提高查询能力
- 均衡磁盘占用:默认情况下,ES避免让某个节点存储过多分片
- 手动调整副本数
- 增加副本数(提高查询能力)
- 减少副本数(节省存储)
副本调整示例
假设集群有3个节点(node1,node2,node3)
初始状态
节点 | 存储的分片 |
---|---|
node1 | p1,p2 |
node2 | p3,r1 |
node3 | r2,r3 |
新增节点node4
节点 | 调整后的分片 |
---|---|
node1 | p1 |
node2 | p3 |
node3 | p2 |
node4 | r1,r2,r3 |
ES发现node4可用后,自动迁移副本到node4,达到负载均衡。
3.3、ES扩容时副本数如何调整?
如果ES需要横向扩展查询能力,可以增加副本数:
PUT my_index/_settings
{
"number_of_replicas": 2
}
副本数 = 物理节点 - 1 是最优方案,保证每个节点存一份数据。
三、安装
1、准备工作
- Linux需要先安装jdk1.8以上版本
- 下载es安装包:ES官网下载地址
- 在Linux创建目录:/app/elastic-stack
2、安装
- 解压安装包
tar -zxcf elasticsearch-8.3.3-linux-x86_64.tar.gz
- 创建es操作用户(es不能使用root账户直接启动)
# 添加用户
useradd es
# 设置的密码
passwd es
# 赋予新用户权限
chown -R es /app/elastic-stack/
- 修改配置(conf/elasticsearch.yml)
# ----------------------------------- Paths ------------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
path.data: /app/elastic-stack/data
#
# Path to log files:
#
path.logs: /app/elastic-stack/log
#
# 集群名
cluster.name: elasticsearch
# 节点名
node.name: node-1
# 允许外界访问的 ip
network.host: 0.0.0.0
# http 访问端口
http.port: 9200
# 集群节点的 master
cluster.initial_master_nodes: ["node-1"]
- 开启/关闭安全认证
#----------------------- BEGIN SECURITY AUTO CONFIGURATION -----------------------
#
# The following settings, TLS certificates, and keys have been automatically
# generated to configure Elasticsearch security features on 12-12-2023 08:05:28
#
# --------------------------------------------------------------------------------
# Enable security features
xpack.security.enabled: false
xpack.security.enrollment.enabled: false
# Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
# Enable encryption and mutual authentication between cluster nodes
xpack.security.transport.ssl:
enabled: true
verification_mode: certificate
keystore.path: certs/transport.p12
truststore.path: certs/transport.p12
#----------------------- END SECURITY AUTO CONFIGURATION -------------------------
这里是关闭状态,开启时:
xpack.security.enabled: true
xpack.security.enrollment.enabled: true
xpack.security.transport.ssl.enabled: true
开启后需要通过https访问,并开启账户认证。
- JVM参数调整
es的jvm启动参数在 /app/elastic-stack/es/config/jvm.options
## IMPORTANT: JVM heap size
################################################################
##
## The heap size is automatically configured by Elasticsearch
## based on the available memory in your system and the roles
## each node is configured to fulfill. If specifying heap is
## required, it should be done through a file in jvm.options.d,
## which should be named with .options suffix, and the min and
## max should be set to the same value. For example, to set the
## heap to 4 GB, create a new file in the jvm.options.d
## directory containing these lines:
##
## -Xms4g
## -Xmx4g
-Xms512m
-Xmx512m
- 启动
# 切换用户
su es
# 进入安装目录
cd /app/elastic-stack/es/bin
# -d 代表后台启动
./elasticsearch -d
- 测试
3、账户
如果在上面没有关闭安全认证,那么在第一次启动es的时候控制台会打印用户名和密码,切记要保存下来!访问:https://127.0.0.1:9200/,输入用户名及密码,用户名为elastic,密码就是第一次启动时控制台显示的密码。
如果忘记了密码也不需要太担心,在bin里有重置和修改密码的工具:
./bin/elasticsearch-setup-passwords interactive
es会重置六种账号的密码:elastic、apm_system、kibana、logstash_system、beats_system、remote_monitoring_user
./bin/elasticsearch-reset-password -u 用户名
重置某一种用户的密码
./bin/elasticsearch-reset-password --username kibana -i
自定义某一种用户的密码
四、基本操作(HTTP方式)
1、数据格式
ElasticSearch是面向文档型数据库,一条数据在这里就是一个文档。我们可以把ElasticSearch里存储文档数据和关系型数据库MySQL存储数据的概念进行一个类比。es里的index可以看作一个库,types相当于表,documents相当于行。
这里types的概念已经被逐渐弱化,es 6.x中,一个index下已经只能包含一个type,es 7.x中,type的概念已经被删除了。
用户json作为文档序列化的格式,比如一条用户信息:
{
"name" : "John",
"sex" : "Male",
"age" : 25,
"birthDate": "1990/05/01",
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
2、索引操作
2.1、创建索引 - PUT
对比关系型数据库,创建索引就等于创建数据库。
PUT 索引名
响应分析
{
"acknowledged": true, # true 操作成功
"shards_acknowledged": true, # 分片操作成功
"index": "user" # 索引名称
}
添加重复索引?重复添加会报错
2.2、查看全部索引-GET
GET _cat/indices?v
- _cat:表示查看的意思;
- indices:表示索引;
- health:当前服务器健康状态:green(集群完整)、yellow(单点正常、集群不完整)、red(单点不正常);
- status:索引打开、关闭状态;
- index:索引名;
- uuid:索引统一编号;
- pri:主分片数量;
- rep:副本数量;
- docs.count:可用文档数量;
- docs.deleted:文档删除状态(逻辑删除);
- store.size:主分片和副分片整体占空间大小;
- pri.store.size:主分片占空间大小
2.3、查看单个索引-GET
GET user
字段含义:
{
"user"【索引名】: {
"aliases"【别名】: {},
"mappings"【映射】: {},
"settings"【设置】: {
"index"【设置 - 索引】: {
"routing"【设置 - 索引路由】: {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards"【设置 - 索引 - 主分片数量】: "1",
"provided_name"【设置 - 索引 - 名称】: "user",
"creation_date"【设置 - 索引 - 创建时间】: "1659678930693",
"number_of_replicas"【设置 - 索引 - 副分片数量】: "1",
"uuid"【设置 - 索引 - 唯一标识】: "P0pIpPyTSa-zS7kJCeE7Ng",
"version"【设置 - 索引版本号】: {
"created": "8030399"
}
}
}
}
}
2.4、删除索引-DELETE
DELETE user
3、文档操作
3.1、创建文档-POST
先创建一个索引(PUT user),下面来创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为json格式。
POST 索引名/_doc
响应字段说明:
{
"_index"【索引】: "user",
"_id"【唯一标识,支持自定义】: "C1yqbIIBJVfoW_YKu2D5",
"_version"【版本】: 1,
"result"【结果】: "created",#这里的 create 表示创建成功
"_shards"【分片】: {
"total"【分片 - 总数】: 2,
"successful"【分片 - 成功】: 1,
"failed"【分片 - 失败】: 0
},
"_seq_no": 0,
"_primary_term": 1
}
自定义唯一标识:POST 索引名/_doc/(自定义ID)
3.2、查看文档-GET
查看文档时,需要指明文档的唯一标识,类似于MySQL中数据的主键查询
查看指定文档:GET 索引名/_doc/(唯一标识)
{
"_index"【索引】: "user",
"_id": "10001",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found"【查询结果】: true,
"_source"【文档源信息】: {
"userName": "李四",
"age": 20
}
}
查看所有数据:GET 索引名/_search
3.3、修改文档-POST
全局修改
POST 索引名/_doc/(唯一标识)
{
"_index": "user",
"_id": "10001",
"_version": 2,
"result"【更新操作】: "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
局部修改
POST 索引名/_updata/(唯一标识)
3.4、删除文档-DELETE
删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。
DELETE 索引名/_doc/(唯一标识)
{
"_index": "user",
"_id": "C1yqbIIBJVfoW_YKu2D5",
"_version"【版本:对数据的操作,都会更新版本】: 5,
"result"【结果】: "deleted",# deleted 表示数据被标记为删除
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 4,
"_primary_term": 1
}
3.5、条件删除文档
先添加一些数据:
POST 索引名/_delete_by_query + 请求体条件
{
"took"【耗时】: 17,
"timed_out"【是否超时】: false,
"total"【总数】: 1,
"deleted"【删除数量】: 1,
"batches": 1,
"version_conflicts": 0,
"noops": 0,
"retries": {
"bulk": 0,
"search": 0
},
"throttled_millis": 0,
"requests_per_second": -1,
"throttled_until_millis": 0,
"failures": []
}
查看文档,年龄20已被删除
4、映射基本操作
4.1、原理
有了索引库,等于有了数据库中的 database。
接下来就需要建索引库(index)中的映射了,类似于数据库(database)中的表结构。创建数据库表需要设置字段名称、类型、长度、约束等,索引库也一样,需要知道这个类型下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)。
4.2、创建索引-PUT
4.3、创建映射-PUT
PUT 索引名/_mapping + 请求体内容
映射数据说明:
- 字段名:任意填写
- type:类型,ElasticSearch中支持的数据类型非常丰富,说几个关键的:
- String,