目录
分布式系统的最大难点,就是各个节点的状态如何同步。CAP 定理是这方面的基本定理,也是理解分布式系统的起点。
C:一致性 (Consistency)
强一致:在任意时刻,所有节点中的数据是一样的。 (如银行)
弱一致:最终一致性就属于弱一致性。 (大部分)
A:可用性 (Availability)
系统提供的服务必须一直处于可用的状态,每次只要收到用户的请求,服务器就必须给出回应。 时刻给用户提供可用的状态,即使服务器宕机
P:分区容错性 (Partition tolerance)
在分布式系统中,不同的节点分布在不同的子网络中,由于一些特殊的原因,这些子节点之间出现了网络不通的状态,但他们的内部子网络是正常的。从而导致了整个系统的环境被切分成了若干个孤立的区域。这就是分区
没有最好的策略,根据业务场景来进行架构设计,只有适合的才是最好的。
组合 | 分析结果 |
---|---|
CA | 满足原子(一致性)和可用,放弃分区容错。说白了,就是一个整体的应用(单机部署)。 |
CP | 满足原子(一致性)和分区容错,也就是说,要放弃可用。当系统被分区,为了保证原子性,必须放弃可用性,让服务停用。 |
AP | 满足可用性和分区容错,当出现分区,同时为了保证可用性,必须让节点继续对外服务,这样必然导致失去原子性。 |
单台Elasticsearch服务器提供服务,往往都有最大的负载能力,超过这个阈值,服务器性能就会大大降低甚至不可用,所以生产环境中,一般都是运行在指定服务器集群中。ES天生支持集群。
1. 负载能力、性能问题
2. 单台机器存储容量有限
3. 单服务器容易出现单点故障,无法实现高可用
4. 单服务的并发处理能力有限
一般出于高性能及高可用方面来考虑集群中节点数量都是3个以上。
1. 复制ES程序 (用新的ES,存在数据无法搭建集群)
复制原始程序包,复制到 elasticsearch-cluster
2. 添加ik分词词库
3. 修改配置文件
(1).修改jvm.options 内存为512m
(2).修改 elasticsearch.yml 配置项 (不能使用中文注解!!,不然UTF-8转码)
- #集群名称
- cluster.name: my-application
- #默认为true。设置为false禁用磁盘分配决定器。
- cluster.routing.allocation.disk.threshold_enabled: false
- #节点名称
- node.name: node-3
- #配置允许的访问网络
- network.host: 0.0.0.0
- #http服务端口
- http.port: 9203
- #集群间通信端口号,在同一机器下必须不一样
- transport.tcp.port: 9303
- #是否允许为主节点,默认true
- node.master: true
- #是否为数据节点,默认true
- node.data: true
- #初始配置选举master节点
- cluster.initial_master_nodes: ["node-1"]
- #节点发现
- discovery.seed_hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
- #elasticsearch-head 跨域解决
- http.cors.allow-origin: "*"
- http.cors.enabled: true
3. 复制副本,修改名称
4. 修改节点名称、http服务端口、集群间通信端口号
5. 依次启动三个节点
6. 修改kibana 配置项
elasticsearch.hosts: ["http://127.0.1:9201","http://127.0.1:9202","http://1270.0.1:9203"]
7. 启动kibana
如果报错:
Another Kibana instance appears to be migrating the index. Waiting for that migration to complete. If no other Kibana instance is attempting migrations, you can get past this message by deleting index .kibana_1 and restarting Kibana.
使用head插件,删除 kibana_1、.kibana_task_manager_1节点,重新启动
8. 启动完毕后查看节点健康状态:
将课件中【ElasticSearch-head-Chrome-0.1.5-Crx4Chrome.crx】文件拖到扩展程序页面上即可。
- # 请求方法:PUT
- PUT /shopping
- {
- "settings": {},
- "mappings": {
- "properties": {
- "title":{
- "type": "text",
- "analyzer": "ik_max_word"
-
- },
- "subtitle":{
- "type": "text",
- "analyzer": "ik_max_word"
- },
- "images":{
- "type": "keyword",
- "index": false
- },
- "price":{
- "type": "float",
- "index": true
- }
- }
- }
- }
-
- # 添加文档
- POST /shopping/_doc/1
- {
- "title":"小米手机",
- "images":"http://www.gulixueyuan.com/xm.jpg",
- "price":3999.00
- }
关于服务器运行状态
Green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。不过,你的高可用性在某种程度上被弱化。如果 更多的 分片消失,你就会丢数据了。把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。
1. 集群 (Cluster)
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个数据,并一起提供索引和搜索功能。一个Elasticsearch集群有一个唯一的名字标识,这个名字默认就是”elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。
2. 节点 (Node)
集群中包含很多服务器,一个节点就是其中的一个服务器。作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
3. 分片 (Shards)
Elasticsearch提供了将索引划分成多份的能力,每一份就称之为分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
1. 允许你水平分割 / 扩展你的内容容量。
2. 允许你在分片之上进行分布式的、并行的操作,进而提高性能/吞吐量。
4. 副本 (Replicas)
Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片(副本)。完成ES的高可用性。ES7.* 默认一个主分片一个副本
1. 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。
2. 扩展你的搜索量/吞吐量,因为搜索可以在所有的副本上并行运行。
5. 分配 (Allocation)
将分片分配给某个节点的过程,包括分配主分片或者副本。如果是副本,还包含从主分片复制数据的过程。这个过程是由master节点完成的。 即:Elasticsearch的分片分配和均衡机制。
6. 节点类型
管理索引(创建索引、删除索引)、分配分片,维护元数据,管理集群节点状态
DataNode:
在Elasticsearch集群中,会有N个DataNode节点。DataNode节点主要负责:
数据写入、数据检索,大部分Elasticsearch的压力都在DataNode节点上,在生产环境中,内存最好配置大一些。
数据分三片,一套副本
一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。
我们可以在建立索引的时候创建分片信息:主分片3,副本2套,共9个分片
- #number_of_shards:主分片数量,默认1(6.x版本默认为5)
- #number_of_replicas:每个主分片对应的副本数量,默认1
- PUT /users
- {
- "settings": {
- "number_of_shards": 3,
- "number_of_replicas": 2
- }
- }
注意:主分片数量一旦设定不可变,否则会影响后续的数据操作(分片位置路由是取模主分片数量)。
- #修改副本数
- PUT users/_settings
- {
- "number_of_replicas": 0
- }
1. 写流程
新建和删除请求都是写操作, 必须在主分片上面完成之后才能被复制到相关的副本分片
- 第一步: 客户端选择DataNode节点发送请求,如上图架构,假设发送到node-2节点上。此时被选择的node-2节点也称为coordinating node(协调节点)
- 第二步: 协调节点根据路由规则计算分片索引位置。并将请求发送到分片索引对应的主分片机器上(这里假设分片计算后的值为0,那么请求会命中到node-3节点上)。
- 计算分片索引位置: shard = hash(routing) % number_of_primary_shards,routing可以自己设定,一般默认为文档的ID。- 第三步: 当主分片文档写入完成后,同时将数据推送到与之对应的副本分片进行写入操作
- 第四步: 当分片完成了写入后再由协调节点将操作结果返回给客户端
2. 读流程
- 第一步:客户端选择DataNode节点发送请求,如上图架构,假设发送到node-2节点上。此时被选择的node-2节点也称为coordinating node(协调节点)
- 第二步: 协调节点将从客户端获取到的请求数据转发到其它节点
- 第三步: 轮询其它节点将查询结果文档ID、节点、分片信息返回给协调节点
- 第四步: 协调节点通过文档ID、节点信息等发送get请求给其它节点进行数据获取,最后进行汇总排序将数据返回给客户端
1. 正排索引:根据id去找词,进行匹配
所谓的正向索引,就是搜索引擎会将待搜索的文件都对应一个文件ID,搜索时将这个ID和搜索关键字进行对应,形成K-V对,然后对关键字进行统计计数
2. 倒排索引:根据词去找文档。不在关注id,而是关注词。
把文件ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。
一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
1. 固定不变倒排索引
早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。
早期的倒排索引写入磁盘后是不可改变的。
不变性有重要的价值:
- 不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
- 一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
- 其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
- 写入单个大的倒排索引允许数据被压缩,减少磁盘 I/O 和 需要被缓存到内存的索引的使用量。
2. 动态更新索引
固定不变的索引无法满足现在的需求
用更多的索引。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。
Elasticsearch 基于 Lucene,这个java库引入了按段搜索的概念。 每一 段本身都是一个倒排索引。 但索引在 Lucene 中除表示所有段的集合外, 还增加了提交点的概念 — 一个列出了所有已知段的文件。
读取硬盘的文件,需要先加载到内存的缓存中
- 分段数据先写入到内存缓存中,同时文档操作也会记录translog日志
- 内存的数据对查询不可见,默认间隔1s将内存中数据写入到文件系统缓存中,这里面的数据对查询可见。
- 文件系统缓存数据间隔30分钟再将数据刷入磁盘中。
- 如果文件系统缓存数据在没有刷新到硬盘时宕机了,可以从translog中恢复数据到磁盘,数据恢复完成后translog数据也会清理。
进行删除时,并不会真正删除,而是提交一个没有该数据的版本,并使用docs_deleted字段标注
段合并的时候会将那些旧的已删除文档从文件系统中清除(根据is_deleted)。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。
启动段合并不需要你做任何事。进行索引和搜索时会自动进行。
1. 两个提交了的段和一个未提交的段只在被合并到一个更大的段
2. 一旦合并结束, 老的段被删除
1、硬件策略
固态硬盘的存储方式不一样。磁盘删除时很快,因为只是删除了索引;写入操作时很慢,先删除一部分再写入
l 使用 SSD。就像其他地方提过的, 他们比机械磁盘优秀多了。
l 使用 RAID 0。条带化 RAID 会提高磁盘 I/O,代价显然就是当一块硬盘故障时整个就故障了。不要使用镜像或者奇偶校验 RAID 因为副本已经提供了这个功能。
l 另外,使用多块硬盘,并允许 Elasticsearch 通过多个 path.data 目录配置把数据条带化分配到它们上面。
l 不要使用远程挂载的存储,比如 NFS 或者 SMB/CIFS。这个引入的延迟对性能来说完全是背道而驰的。
2、分片策略
分片不是越多越好,合理设置分片数,最好不要让多个分片在一个节点上竞争
节点数<=主分片数*(副本数+1)
- 一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU 运转。
- 每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好, 但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。
- 用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度。
- 控制每个分片占用的硬盘容量不超过ES的最大JVM的堆空间设置(一般设置不超过32G,参考下文的JVM设置原则),因此,如果索引的总容量在500G左右,那分片大小在16个左右即可;当然,最好同时考虑原则2。
- 考虑一下node数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了1个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以, 一般都设置分片数不超过节点数的3倍。
6.1 为什么要使用Elasticsearch?
系统中的数据,随着业务的发展,时间的推移,将会非常多,而业务中往往采用模糊查询进行数据的搜索,而模糊查询会导致查询引擎放弃索引,导致系统查询数据时都是全表扫描,在百万级别的数据库中,查询效率是非常低下的,而我们使用ES做一个全文索引,将经常查询的系统功能的某些字段,比如说电商系统的商品表中商品名,描述、价格还有id这些字段我们放入ES索引库里,可以提高查询速度。
6.2 Elasticsearch的master选举流程?
参考: ElasticSearch - 新老选主算法对比_Aaron_涛的博客-CSDN博客
7.x之前选主流程
采用Bully算法,它假定所有节点都有一个唯一的ID,使用该ID对节点进行排序。任何时候的当前Leader都是参与集群的最高ID节点。该算法的优点是易于实现。但是,当拥有最大ID的节点处于不稳定状态的场景下会有问题。例如,Master负载过重而假死,集群拥有第二大ID的节点被选为新主,这时原来的Master恢复,再次被选为新主,然后又假死。 ES 通过推迟选举,直到当前的 Master 失效来解决上述问题,只要当前主节点不挂掉,就不重新选主。但是容易产生脑裂(双主),为此,再通过“法定得票人数过半”解决脑裂问题。
7.x之后选主流程
7.X之后的ES,采用一种新的选主算法,实际上是 Raft 的实现,但并非严格按照 Raft 论文实现,而是做了一些调整。
raft选举过程:
- 选举超时时间:150~300ms的随机数
- term每次选举+1
- 同一个任期内每个节点只能有一张选票
- 选票超过半数则当选为leader
- 当leader被选举后,其它剩余投票将废弃
6.3 Elasticsearch集群脑裂问题?
“脑裂”问题可能的成因:
网络问题:集群间的网络延迟导致一些节点访问不到master,认为master挂掉了从而选举出新的master,并对master上的分片和副本标红,分配新的主分片
节点负载:主节点的角色既为master又为data,访问量较大时可能会导致ES停止响应造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
内存回收:data节点上的ES进程占用的内存较大,引发JVM的大规模内存回收,造成ES进程失去响应。
脑裂问题解决方案:
减少误判:discovery.zen.ping_timeout节点状态的响应时间,默认为3s,可以适当调大,如果master在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如6s,discovery.zen.ping_timeout:6),可适当减少误判。
选举触发: discovery.zen.minimum_master_nodes:1
7.x之前版本:
该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值,且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n/2)+1,n为主节点个数 (即有资格成为主节点的节点个数)
7.x之后版本:
不需要手动配置,系统自己会维护最小集群节点数
角色分离:即master节点与data节点分离,限制角色
主节点配置为:node.master: true node.data: false
从节点配置为:node.master: false node.data: true
6.4 Elasticsearch索引文档的流程?
- 协调节点默认使用文档ID参与计算(也支持通过routing),以便为路由提供合适的分片:
***\*shard = hash(document_id) % (num_of_primary_shards)\****
- 当分片所在的节点接收到来自协调节点的请求后,会将请求写入到Memory Buffer,然后定时(默认是每隔1秒)写入到Filesystem Cache,这个从Memory Buffer到Filesystem Cache的过程就叫做**refresh**;
- 当然在某些情况下,存在Momery Buffer和Filesystem Cache的数据可能会丢失,ES是通过**translog**的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到translog中,当Filesystem cache中的数据写入到磁盘中时,才会清除掉,这个过程叫做**flush**;
- 在flush过程中,内存中的缓冲将被清除,内容被写入一个新段,段的fsync将创建一个新的提交点,并将内容刷新到磁盘,旧的translog将被删除并开始一个新的translog。
- flush触发的时机是定时触发(默认30分钟)或者translog变得太大(默认为512M)时;
6.5 Elasticsearch更新和删除文档的流程?
删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更;
磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。
在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
6.6 Elasticsearch搜索的流程?
搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;
在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。 每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询Filesystem Cache的,但是有部分数据还在Memory Buffer,所以搜索是近实时的。
每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
接下来就是取回阶段,协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并丰富文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。
6.7 Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法?
64 GB 内存的机器是非常理想的, 但是32 GB 和16 GB 机器也是很常见的。少于8 GB 会适得其反。
如果你要在更快的 CPUs 和更多的核心之间选择,选择更多的核心更好。多个内核提供的额外并发远胜过稍微快一点点的时钟频率。
如果你负担得起 SSD,它将远远超出任何旋转介质。 基于 SSD 的节点,查询和索引性能都有提升。如果你负担得起,SSD 是一个好的选择。
即使数据中心们近在咫尺,也要避免集群跨越多个数据中心。绝对要避免集群跨越大的地理距离。
通过设置gateway.recover_after_nodes、gateway.expected_nodes、gateway.recover_after_time可以在集群重启的时候避免过多的分片交换,这可能会让数据恢复从数个小时缩短为几秒钟。
不要随意修改垃圾回收器(CMS)和各个线程池的大小。
Lucene 使用了大量的文件。同时,Elasticsearch在节点和 HTTP客户端之间进行通信也使用了大量 的套接字。 所有这一切都需要足够的文件描述符。你应该增加你的文件描述符,设置一个很大的值,如 64000。
补充:索引阶段性能提升方法
使用批量请求并调整其大小:每次批量数据 5–15 MB 大是个不错的起始点。
如果你的搜索结果不需要近实时的准确度,考虑把每个索引的index.refresh_interval 改到30s。
如果你在做大批量导入,考虑通过设置index.number_of_replicas: 0 关闭副本。
6.8 GC方面,在使用Elasticsearch时要注意什么?
Ø 倒排词典的索引需要常驻内存,无法GC,需要监控data node上segment memory增长趋势。
Ø 各类缓存,field cache, filter cache, indexing cache, bulk queue等等,要设置合理的大小,并且要应该根据最坏的情况来看heap是否够用,也就是各类缓存全部占满的时候,还有heap空间可以分配给其他任务吗?避免采用clear cache等“自欺欺人”的方式来释放内存。
Ø 避免返回大量结果集的搜索与聚合。确实需要大量拉取数据的场景,可以采用scan & scroll api来实现。
Ø cluster stats驻留内存并无法水平扩展,超大规模集群可以考虑分拆成多个集群通过tribe node连接。
Ø 想知道heap够不够,必须结合实际应用场景,并对集群的heap使用情况做持续的监控。
6.9 在并发情况下,Elasticsearch如果保证读写一致?
可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;
另外对于写操作,一致性级别支持quorum/one/all,默认为quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。
对于读操作,可以设置replication为sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置replication为async时,也可以通过设置搜索请求参数_preference为primary来查询主分片,确保文档是最新版本。
6.10 如何监控 Elasticsearch 集群状态?
elasticsearch-head插件
通过 Kibana 监控 Elasticsearch。你可以实时查看你的集群健康状态和性能,也可以分析过去的集群、索引和节点指标。
GET /_cluster/health
6.11 Elasticsearch中的集群、节点、索引、文档、类型是什么?
- 集群是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设置为按名称加入群集,则该节点只能是群集的一部分。
- 节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。
- 索引就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一个或多个主分片,并且可以有零个或多个副本分片。 MySQL =>数据库 ;Elasticsearch =>索引
- 文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。
- MySQL => Databases => Tables => Columns/Rows
- Elasticsearch => Indices => Types => 具有属性的文档
- 类型是索引的逻辑类别/分区,其语义完全取决于用户。
6.12 Elasticsearch中的倒排索引是什么?
倒排索引是搜索引擎的核心。
搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。
ES中的倒排索引其实就是lucene的倒排索引,区别于传统的正向索引,倒排索引会在存储数据时将关键词和数据进行关联,保存到倒排表中,然后查询时,将查询内容进行分词后在倒排表中进行查询,最后匹配数据即可。