分布式机制
:分布式数据存储及共享。分片机制
:数据存储到哪个分片,副本数据写入。集群发现机制
:cluster discovery。新启动 es 实例,自动加入集群。shard 负载均衡
:大量数据写入及查询,es 会将数据平均分配。shard 副本
:新增副本数,分片重分配。扩容:
rebalance:
新增或滅少 es 实例时,es 集群会将数据重新分配。
master节点:
节点对等:
shard&replica机制:
number_of_shards 是指索引要做多少个分片,只能在创建索引时指定,后期无法修改。
number_of_replicas 是指每个分片有多少个副本,后期可以动态修改
PUT /test_index1
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
一个文档,最终会落在主分片的一个分片上,到底应该在哪一个分片?这就是数据路由。
路由算法:
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
。primary shard
或 replica shard
,都可以。doc id
)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。doc id
去各个节点上拉取实际的 document
数据,最终返回给客户端。注意:
增删改可以看做 update,都是对数据的改动。一个改动请求发送到 es 集群,经历以下四个步骤:
node
发送请求过去,这个 node 就是 coordinating node
(协调节点)coordinating node
,对 document
进行路由,将请求转发给对应的 node
(有 primary shard)node
上的 primary shard
处理请求,然后将数据同步到 replica node
。coordinating node
,如果发现 primary node
和所有 replica node
都搞定之后,就返回响应结果给客户端。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 就都有了。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 串以后,要按照下述流程去进行处理
耗费更多内存,更多的 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 数组解析为一个 JSONArray 对象,形成一份大数据的拷贝,浪费内存空间,尽可能地保证性能。
先写入内存 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 之后,同时就建立好了倒排索引。
Elasticsearch bulk 操作的性能问题可能有很多原因,以下是一些常见的原因及对应的解决方案:
Elasticsearch 的主要优点包括:
Elasticsearch 的使用场景包括:
Elasticsearch 的引入主要是为了应对大数据环境下的海量数据检索和实时分析需求,它通过分布式架构和高效的索引机制,提供了快速的搜索和分析能力。然而,Elasticsearch 也存在一些潜在风险,如响应时间问题和任务恢复延迟等,需要通过优化配置和维护来降低这些风险的影响。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。