1.集群
一个运行的Elasticsearch实例被称为一个节点,而集群是有一个或多个拥有相同claster.name配置的节点组成,他们共同承担数据和负载压力,当有节点加入或从集群中移除的时候,集群或自动平局分布所有数据。
当一个节点被选举成为主节点时,他哈不负责额管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。而主节点不涉及文档级别的变更和搜索操作,所以集群只有一个主节点,即使流量增加,他也不会成为瓶颈。任何节点都可以成为主节点。
3.添加索引
PUT /blogs
{
"settings":{
"number_of_shards":3,
"number_of_replicas":1
}
}
设置三个主分片,1个复制分片。
4.水平扩展
PUT /blogs/_settings
{
"number_of_replicas":2
}
复制分片从一个增加到两个。不能修改主分片个数。
5.路由一个文档到分片
当索引一个文档时,文档会被存储到主分片中。存到哪个主分片由下面这个公式决定:
shard = hash(routing) % number_of_primary_shards.
routing是一个可变值,默认是文档_id,也可以设置一个自定义的值。
number_of_primary_shards主分片数。
这里解释了为什么不能改变主分片数量:因为如果数量变化了,那么所有的之前路由的值都会无效,文档也找不到了。
6.新建、索引和删除单个文档
1).客户端向集群中的一个节点(协调节点)发送请求。
2).节点使用文档的_id确定文档所属的分片,转发请求到分片所属的节点(master).
3).主分片执行请求。若执行成功,将请求并行转发到复制节点。一旦所有复制节点报告执行成功。master节点向协调节点报告成功,协调节点向客户端报告成功。
7.检索单个文档
1).客户端向集群中的一个节点(node1)发出请求。
2).node1节点使用_id确定文档所属分片,将请求转发大其中个一个分片所属节点(node2)。
3).node2将文档返回给node1,然后将文档返回给客户端。
协调节点每次请求的时候选择不同的副本分片达到负载均衡。
8.局部更新
1).客户端向node1发送更新请求。
2).转发请求到主分片所在的node2。
3).node2从主分片检索文档,修改_source字段中的JSON,并尝试重新索引主分片文档。如果文档已经被另一个进程修改了,他将重试步骤3,超过retry_on_conflict次后放弃。
4).如果node2成功更新索引,它将新版本的文档转发到所有副本分片所在的节点,重新建立索引。一旦所有副本分片都成功返回了,node2向协调节点返回成功,协调节点向客户端返回成功。
9.倒排索引
Elasticsearch使用一种称为倒排索引的结构,它适用于快速的全文搜索。一个倒排索引有文档中所有不重复的词的列表构成,对于其中每个词,有一个包含他的文档列表。类似下面:(被索引的字段都有自己的倒排索引)
Term | Doc 1 | Doc 2 | Doc 3 | ...
------------------------------------
brown | X | | X | ...
fox | X | X | X | ...
quick | X | X | | ...
the | X | | X | ...
它会保存每一个词项出现过的文档总数, 在对应的文档中一个具体词项出现的总次数,词项在文档中的顺序,每个文档的长度,所有文档的平均长度,等等。
不变性:
倒排索引被芯茹磁盘后是不可改变的:它永远不会被修改。不可修改的意义:
1).不需要锁。
2).写入单个大的倒排索引允许被压缩。
3).一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
4).其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档 可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。
10.动态更新索引
Elasticsearch 基于 Lucene, 这个 java 库引入了 按段搜索(per-segment search) 的概念。 每一 segment 本身都是一个倒排索引, 但 索引 在 Lucene 中除表示所有 segment 的集合外, 还增加了 提交点 的概念 — 一个列出了所有已知段的文件。
索引与分片的比较:
被混淆的概念是,一个 Lucene 索引 我们在 Elasticsearch 称作 分片 。 一个 Elasticsearch 索引 是分片的集合。 当 Elasticsearch 在索引中搜索的时候, 他发送查询到每一个属于索引的分片(Lucene 索引),然后像 执行分布式检索 提到的那样,合并每个分片的结果到一个全局的结果集。
一个 Lucene 索引包含一个提交点和三个段:
一个在内存缓存中包含新文档的 Lucene 索引:
在一次提交后,一个新的segment被添加到提交点而且缓存被清空:
1).新文档被收集到内存索引缓存, “一个在内存缓存中包含新文档的 Lucene 索引” 。
2).不时地, 缓存被 提交 :
一个新的segment --一个追加的倒排索引--被写入磁盘。
一个新的包含新segment 名字的 提交点 被写入磁盘。
磁盘进行 同步 — 所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件。
3).新的segment 被开启,让它包含的文档可见以被搜索。
4).内存缓存被清空,等待接收新的文档。
删除和更新:
段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。 取而代之的是,每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档的段信息。
当一个文档被 “删除” 时,它实际上只是在 .del 文件中被 标记 删除。一个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除。
文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。 可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。
11.近实时搜索
随着按段(per-segment)搜索的发展, 一个新的文档从索引到可被搜索的延迟显著降低了。新文档在几分钟之内即可被检索,但这样还是不够快。
磁盘在这里成为了瓶颈。提交(Commiting)一个新的段到磁盘需要一个 fsync 来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据。 但是 fsync 操作代价很大; 如果每次索引一个文档都去执行一次的话会造成很大的性能问题。
我们需要的是一个更轻量的方式来使一个文档可被搜索,这意味着 fsync 要从整个过程中被移除。
在Elasticsearch和磁盘之间是文件系统缓存,在内存索引缓冲区中的文档会被写入到一个新的segment中。 但是这里新段会被先写入到文件系统缓存--这一步代价会比较低,稍后再被刷新到磁盘--这一步代价比较高。不过只要文件已经在缓存中, 就可以像其它文件一样被打开和读取了。
在内存缓冲区中包含了新文档的 Lucene 索引:
缓冲区的内容已经被写入一个可被搜索的段中,但还没有进行提交:
Refresh API
在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 近 实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。
POST /_refresh :刷新(Refresh)所有的索引。
POST /blogs/_refresh :只刷新(Refresh) blogs 索引。
并不是所有的情况都需要每秒刷新。可能你正在使用 Elasticsearch 索引大量的日志文件, 你可能想优化索引速度而不是近实时搜索, 可以通过设置 refresh_interval , 降低每个索引的刷新频率:
PUT /my_logs
{
"settings": {
"refresh_interval": "30s"
}
}
在生产环境中,当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来:
PUT /my_logs/_settings
{ "refresh_interval": -1 }
PUT /my_logs/_settings
{ "refresh_interval": "1s" }
12.持久化变更
如果没有用 fsync 把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在。为了保证 Elasticsearch 的可靠性,需要确保数据变化被持久化到磁盘。
Elasticsearch 增加了一个 translog ,或者叫事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录。通过 translog ,整个流程看起来是下面这样:
1).一个文档被索引之后,就会被添加到内存缓冲区,并且 追加到了 translog.
2).刷新(refresh)使分片,分片每秒被刷新(refresh)一次:
a.这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作。 ** 这个段被打开,使其可被搜索。
b.内存缓冲区被清空。
3).这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志.
4).每隔一段时间--例如 translog 变得越来越大--索引被刷新(flush);一个新的 translog 被创建,并且一个全量提交被执行:
a.所有在内存缓冲区的文档都被写入一个新的段。
b.缓冲区被清空。
c.一个提交点被写入硬盘。
d.文件系统缓存通过 fsync 被刷新(flush)。
e.老的 translog 被删除。
translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录。当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。
Flush API
这个执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush 。 分片每30分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新。可以手动刷新:
POST /blogs/_flush
POST /_flush?wait_for_ongoing
Translog 的安全问题:默认,在每次请求后都会通过fsync把请求写到日志文件。你也可以使用异步的 fsync ,写入的数据被缓存到内存中,每5秒执行一次 fsync :
PUT /my_index/_settings
{
"index.translog.durability": "async",
"index.translog.sync_interval": "5s"
}
13.segment合并
由于自动刷新流程每秒会创建一个新的segment ,这样会导致短时间内的segment 数量暴增。而segment 数目太多会带来较大的麻烦。 每一个segment 都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个segment ;所以segment 越多,搜索也就越慢。
Elasticsearch通过在后台进行segment 合并来解决这个问题。小的segment 被合并到大的segment ,然后这些大的segment 再被合并到更大的segment 。
segment 合并的时候会将那些旧的已删除文档 从文件系统中清除。 被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大segment 中。
启动segment 合并不需要你做任何事。进行索引和搜索时会自动进行。
1).当索引的时候,刷新(refresh)操作会创建新的segment 并将segment 打开以供搜索使用。
2).合并进程选择一小部分大小相似的segment ,并且在后台将它们合并到更大的segment 中。这并不会中断索引和搜索。
3).一旦合并结束,老的段被删除, 说明合并是完成的活动:
a.新的段被刷新(flush)到了磁盘。 ** 写入一个包含新段且排除旧的和较小的段的新提交点。
b.新的段被打开用来搜索。
c.老的段被删除。
合并大的segment需要消耗大量的I/O和CPU资源,如果任其发展会影响搜索性能。Elasticsearch在默认情况下会对合并流程进行资源限制,所以搜索仍然 有足够的资源很好地执行。
Optimize API
optimize API大可看做是 强制合并 API 。它会将一个分片强制合并到 max_num_segments 参数指定大小的段数目。 这样做的意图是减少段的数量(通常减少到一个),来提升搜索性能。
optimize API 不应该 被用在一个动态索引————一个正在被活跃更新的索引。后台合并流程已经可以很好地完成工作。 optimizing 会阻碍这个进程。不要干扰它!
在特定情况下,使用 optimize API 颇有益处。例如在日志这种用例下,每天、每周、每月的日志被存储在一个索引中。 老的索引实质上是只读的;它们也并不太可能会发生变化。在这种情况下,使用optimize优化老的索引,将每一个分片合并为一个单独的段就很有用了;这样既可以节省资源,也可以使搜索更加快速:
POST /logstash-2014-10/_optimize?max_num_segments=1
请注意,使用 optimize API 触发段合并的操作一点也不会受到任何资源上的限制。这可能会消耗掉你节点上全部的I/O资源, 使其没有余裕来处理搜索请求,从而有可能使集群失去响应。 如果你想要对索引执行 `optimize`,你需要先使用分片分配(查看 迁移旧索引)把索引移到一个安全的节点,再执行。
原文地址:https://www.cnblogs.com/ydxblog/p/8304266.html