💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
- 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老
- 导航
- 檀越剑指大厂系列:全面总结 java 核心技术,jvm,并发编程 redis,kafka,Spring,微服务等
- 常用开发工具系列:常用的开发工具,IDEA,Mac,Alfred,Git,typora 等
- 数据库系列:详细总结了常用数据库 mysql 技术点,以及工作中遇到的 mysql 问题等
- 新空间代码工作室:提供各种软件服务,承接各种毕业设计,毕业论文等
- 懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
- 数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨
博客目录
- 1.隐藏特性
- 2.单 node 创建 index
- 3.横向扩容
- 4.数据路由
- 5.增删改内部机制
- 6.查询的内部机制
- 7.bulk api 奇特的 json 格式
- 8.写入机制
- 9.bulk 性能问题
1.隐藏特性
分布式机制
:分布式数据存储及共享。分片机制
:数据存储到哪个分片,副本数据写入。集群发现机制
:cluster discovery。新启动 es 实例,自动加入集群。shard 负载均衡
:大量数据写入及查询,es 会将数据平均分配。shard 副本
:新增副本数,分片重分配。
扩容:
- 垂直扩容:使用更加强大的服务器替代老服务器。但单机存储及运算能力有上线。且成本直线上升。如 10t 服务器 1 万。单个 10T 服务器可能 20 万。
- 水平扩容:采购更多服务器,加入集群。大数据。
rebalance:
新增或滅少 es 实例时,es 集群会将数据重新分配。
master节点:
- 创建删除节点
- 创建蒯除索引
节点对等:
- 节点对等,每个节点都能接收所有的请求
- 自动请求路由
- 响应收集
shard&replica机制:
- 每个 index 包含一个或多个 shard
- 每个 shard 都是一个最小工作单元,承载部分数据,lucene 实例,完整的建立索引和处理请求的能力
- 增减节点时,shard 会自动在 nodes 中负载均衡
- primary shard 和 replica shard,每个 document 肯定只存在于某一个 primary shard 以及其对应的 replica shard 中,不可能存在于多个 primary shard
- replica shard 是 primary shard 的副本,负责容错,以及承担读请求负载
- primary shard 的数量在创建索引的时候就固定了,replica shard 的数量可以随时修改
- primary shard 的默认数量是 1,replica 默认是 1,默认共有 2 个 shard,1 个 primary shard , 1 个 replica shard 注意:es7 以前 primary shard 的默认数量是 5,replica 默认是 1,默认有 10 个 shard,5 个 primary shard,5 个 replica shard
- primary shard 不能和自己的 replica shard 放在同一个节点上(否则节点宕机,primary shard 和副本都丢失,起不到容错的作用),但是可以和其他 primary shard 的 replica shard 放在同一个节点上
2.单 node 创建 index
number_of_shards 是指索引要做多少个分片,只能在创建索引时指定,后期无法修改。
number_of_replicas 是指每个分片有多少个副本,后期可以动态修改
- 单 node 环境下,创建一个 index,有 3 个 primary shard,3 个 replica shard
- 集群 status 是 yellow
- 这个时候,只会将 3 个 primary shard 分配到仅有的一个 node 上去,另外 3 个 replica shard 是无法分配的
- 集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法承接任何请求
PUT /test_index1
{"settings" : {"number_of_shards" : 3,"number_of_replicas" : 1}
}
3.横向扩容
- 分片自动负载均衡,分片向空闲机器转移。
- 每个节点存储更少分片,系统资源给与每个分片的资源更多,整体集群性能提高。
- 扩容极限:节点数大于整体分片数,则必有空闲机器。
- 容错性:只要一个索引的所有主分片在,集群就就可以运行。
4.数据路由
一个文档,最终会落在主分片的一个分片上,到底应该在哪一个分片?这就是数据路由。
路由算法:
shard = hash(routing) % number_of_primary_shards
哈希值对主分片数取模。
举例:
对一个文档经过 crud 时,都会带一个路由值 routing number。默认为文档_id(可能是手动指定,也可能是自动生成)。
存储 1 号文档,经过哈希计算,哈希值为 2,此索引有 3 个主分片,那么计算 2%3=2,就算出此文档在 P2 分片上。
决定一个 document 在哪个 shard 上,最重要的一个值就是 routing 值,默认是_id,也可以手动指定,相同的 routing 值,每次过来,从 hash 函数中,产出的 hash 值一定是相同的
手动指定routing number
PUT /test_index/_doc/15?routing=num
{"num": 0,"tags": []
}
场景:在程序中,架构师可以手动指定已有数据的一个属性为路由值,好处是可以定制一类文档数据存储到一个分片中。缺点是设计不好,会造成数据倾斜。
所以,不同文档尽量放到不同的索引中。剩下的事情交给 es 集群自己处理。
涉及到以往数据的查询搜索,所以一旦建立索引,主分片数不可变。
检索文件的步骤:
从主分片或者副本分片检索文档的步骤顺序:
- 客户端发送请求到一个
coordinate node
。 - 协调节点将搜索请求转发到所有的 shard 对应的
primary shard
或replica shard
,都可以。 - query phase:每个 shard 将自己的搜索结果(其实就是一些
doc id
)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。 - fetch phase:接着由协调节点根据
doc id
去各个节点上拉取实际的document
数据,最终返回给客户端。
注意:
- 在处理读取请求时,协调节点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
- 在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的
5.增删改内部机制
增删改可以看做 update,都是对数据的改动。一个改动请求发送到 es 集群,经历以下四个步骤:
-
客户端选择一个
node
发送请求过去,这个 node 就是coordinating node
(协调节点) -
coordinating node
,对document
进行路由,将请求转发给对应的node
(有 primary shard) -
实际的
node
上的primary shard
处理请求,然后将数据同步到replica node
。 -
coordinating node
,如果发现primary node
和所有replica node
都搞定之后,就返回响应结果给客户端。
6.查询的内部机制
-
客户端发送请求到任意一个
node
,成为coordinate node
-
coordinate node
对document
进行路由,将请求转发到对应的node
,此时会使用round-robin
随机轮询算法,在primary shard
以及其所有replica
中随机选择一个,让读请求负载均衡 -
接收请求的
node
返回document
给coordinate node
-
coordinate node
返回document
给客户端 -
特殊情况:
document
如果还在建立索引过程中,可能只有 primary shard 有,任何一个 replica shard 都没有,此时可能会导致无法读取到 document,但是 document 完成索引建立之后,primary shard 和 replica shard 就都有了。
7.bulk api 奇特的 json 格式
POST /_bulk
{"action": {"meta"}}\n
{"data"}\n
{"action": {"meta"}}\n
{"data"}\n
[{"action": {"method": "create"},"data": {"id": 1,"field1": "java","field1": "spring"}},{"action": {"method": "create"},"data": {"id": 2,"field1": "java","field1": "spring"}}
]
首先,bulk 中的每个操作都可能要转发到不同的 node 的 shard 去执行
如果采用比较良好的 json 数组格式,允许任意的换行,整个可读性非常棒,读起来很爽,es 拿到那种标准格式的 json 串以后,要按照下述流程去进行处理
-
将 json 数组解析为 JSONArray 对象,这个时候,整个数据,就会在内存中出现一份一模一样的拷贝,一份数据是 json 文本,一份数据是 JSONArray 对象
-
解析 json 数组里的每个 json,对每个请求中的 document 进行路由
-
为路由到同一个 shard 上的多个请求,创建一个请求数组。100 请求中有 10 个是到 P1.
-
将这个请求数组序列化
-
将序列化后的请求数组发送到对应的节点上去
耗费更多内存,更多的 jvm gc 开销,之前提到过 bulk size 最佳大小的那个问题,一般建议说在几千条那样,然后大小在 10MB 左右,所以说,可怕的事情来了。假设说现在 100 个 bulk 请求发送到了一个节点上去,然后每个请求是 10MB,100 个请求,就是 1000MB = 1GB,然后每个请求的 json 都 copy 一份为 jsonarray 对象,此时内存中的占用就会翻倍,就会占用 2GB 的内存,甚至还不止。因为弄成 jsonarray 之后,还可能会多搞一些其他的数据结构,2GB+的内存占用。
占用更多的内存可能就会积压其他请求的内存使用量,比如说最重要的搜索请求,分析请求,等等,此时就可能会导致其他请求的性能急速下降。
另外的话,占用内存更多,就会导致 java 虚拟机的垃圾回收次数更多,跟频繁,每次要回收的垃圾对象更多,耗费的时间更多,导致 es 的 java 虚拟机停止工作线程的时间更多。
现在的奇特格式:
POST /_bulk
{ "delete": { "_index": "test_index", "_id": "5" }} \n
{ "create": { "_index": "test_index", "_id": "14" }}\n
{ "test_field": "test14" }\n
{ "update": { "_index": "test_index", "_id": "2"} }\n
{ "doc" : {"test_field" : "bulk test"} }\n
-
不用将其转换为 json 对象,不会出现内存中的相同数据的拷贝,直接按照换行符切割 json
-
对每两个一组的 json,读取 meta,进行 document 路由
-
直接将对应的 json 发送到 node 上去
最大的优势在于,不需要将 json 数组解析为一个 JSONArray 对象,形成一份大数据的拷贝,浪费内存空间,尽可能地保证性能。
8.写入机制
先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。
如果 buffer 快满了,或者到一定时间,就会将内存 buffer 数据 refresh
到一个新的 segment file
中,但是此时数据不是直接进入 segment file
磁盘文件,而是先进入 os cache
。这个过程就是 refresh
。
每隔 1 秒钟,es 将 buffer 中的数据写入一个新的 segment file
,每秒钟会产生一个新的磁盘文件 segment file
,这个 segment file
中就存储最近 1 秒内 buffer 中写入的数据。
但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。
操作系统里面,磁盘文件其实都有一个东西,叫做 os cache
,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入 os cache
,先进入操作系统级别的一个内存缓存中去。只要 buffer
中的数据被 refresh 操作刷入 os cache
中,这个数据就可以被搜索到了。
为什么叫 es 是准实时的?NRT
,全称 near real-time
。默认是每隔 1 秒 refresh 一次的,所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。可以通过 es 的 restful api
或者 java api
,手动执行一次 refresh 操作,就是手动将 buffer 中的数据刷入 os cache
中,让数据立马就可以被搜索到。只要数据被输入 os cache
中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。
重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将 buffer
数据写入一个又一个新的 segment file
中去,每次 refresh
完 buffer 清空,translog 保留。随着这个过程推进,translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发 commit
操作。
commit 操作发生第一步,就是将 buffer 中现有数据 refresh
到 os cache
中去,清空 buffer。然后,将一个 commit point
写入磁盘文件,里面标识着这个 commit point
对应的所有 segment file
,同时强行将 os cache
中目前所有的数据都 fsync
到磁盘文件中去。最后清空 现有 translog 日志文件,重启一个 translog,此时 commit 操作完成。
这个 commit 操作叫做 flush
。默认 30 分钟自动执行一次 flush
,但如果 translog 过大,也会触发 flush
。flush 操作就对应着 commit 的全过程,我们可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去。
translog 日志文件的作用是什么?你执行 commit 操作之前,数据要么是停留在 buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件 translog
中,一旦此时机器宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。
translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会丢失 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 fsync
到磁盘,但是性能会差很多。
实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的数据丢失。
总结一下,数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。
数据写入 segment file 之后,同时就建立好了倒排索引。
9.bulk 性能问题
Elasticsearch bulk 操作的性能问题可能有很多原因,以下是一些常见的原因及对应的解决方案:
- 索引分片过多:如果索引的分片过多,会导致 bulk 操作的性能下降。因为 bulk 操作会同时操作多个分片,分片过多会导致操作变慢。解决方案是减少索引的分片数,通常建议不要超过 5 个分片。
- 磁盘 I/O 瓶颈:如果磁盘 I/O 性能不足,会导致 bulk 操作的性能下降。可以通过优化磁盘 I/O 性能来提高 bulk 操作的性能,例如使用更快的磁盘、使用 RAID 阵列等。
- 索引过大:如果索引过大,会导致 bulk 操作的性能下降。因为 bulk 操作需要同时操作多个文档,文档数量过多会导致操作变慢。可以通过分割索引或者使用时间序列索引等方式来解决问题。
- 网络带宽瓶颈:如果网络带宽不足,会导致 bulk 操作的性能下降。可以通过优化网络带宽来提高 bulk 操作的性能,例如使用更快的网络、增加网络带宽等。
- 索引设置不合理:如果索引的设置不合理,也会导致 bulk 操作的性能下降。例如,如果禁用了副本,可以通过启用副本来提高 bulk 操作的性能。
- 索引的文档大小不合适:如果索引的文档大小过大或者过小,也会影响 bulk 操作的性能。可以根据实际情况调整文档大小,通常建议文档大小控制在 1MB 左右。
以上是一些可能导致 Elasticsearch bulk 操作性能下降的原因及对应的解决方案,可以根据实际情况进行优化。
觉得有用的话点个赞
👍🏻
呗。
❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍
🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙