写在前面
好久没更新博客了,应届狗没办法啊╮(╯▽╰)╭为了秋招搞了小半年,从去年5月到现在搞了两段实习(京东、游戏公司),最终年前拿到一家还行的offer,现在已经入职实习了,不出意外的话以后就在这家wlb公司长干啦~
还在奔波的兄弟们继续加油,虽然疫情解封后,情况好了一些,但是药效还在,而且主力军已经是24届的同学了,但终究会功夫不负有心人!
废话不多说,学习不能停,虽然走的很慢,但仍在前进~
之前简单学习过es,见往期博客
- ElasticSearch学习篇1_ES简介、安装使用(ES、head可视化、Kibana可视化、IK分词器)
- ElasticSearch学习篇2_Rest格式操作(索引、文档)、文档的简单操作(增、删、改、查)、复杂查询操作(排序、分页、高亮)
- ElasticSearch学习篇3_整合SpringBoot、索引、文档基本操作API练习
- ElasticSearch学习篇4_仿京东搜索案例练习
目录
一、基础知识
二、分析单个节点的启动和关闭流程
三、集群启动流程
四、集群选主流程
一、基础知识
根据往期知识加上书中更详细的知识来记录
1、ES数据模型理解:初学者牵强理解,ES的数据表示形式与传统的关系型数据库类比(只是类比),indices索引(相当于数据库)、types类型(相当于表)、fileds(相当于字段,可以理解为key)、documents(相当于行数据),
- 在存储结构上indices、type、id唯一标识一个文档document
- 在ES6.x版本中1个索引indices只能创建对应一个types,因为不同types下的字段不能冲突,删除types也不会释放空间,推荐需要多个types时候直接创建多个indices。在ES7.x版本中直接删除掉了type的概念。
2、倒排索引:采用Lucene倒排作为底层,这种结构适用于快速的全文搜索。ElasticSearch的倒排索引是一种索引结构,它为每个单词创建了一个索引,并且记录了该单词出现在哪些文档中。当用户搜索特定的单词时,ElasticSearch可以快速地检索出相关的文档,从而提供更好的搜索体验。实现原理:为了创建倒排索引,首先需要将文档documents(可以简单看作为json数据集合)拆分为独立的词条tokens,然后创建一个包含所有不重复的词条的排序列表,然后列出每个词条出现在哪个文档里面。搜索某句话(会先被分词)会转为一个个词,根据词的 待查找 key 去 排序列表查找,根据权重映射出 待查找的文档。
3、全文搜索:全文的概念是对全部的文本内容进行分析,建立索引,使之可以被搜索。传统的索引想要支持全文搜索,那么就得扫描整个内容,但是倒排索引不一样的就是 将文档中的小块内容(词) 散列起来为各个tokens,然后将tokens和小块内容(词)的位置 映射起来,可以达到快速定位。
4、分片:单机无法存储规模巨大的数据,一般通过增加机器水平拓展,将数据分为若干块放到不同机器上,然后利用某种路由策略找到某个数据块所在的位置,从而形成ES集群。分片是底层基础的读写单元,分片的目的是分割巨大索引,让读写可以并行多台机器完成,分片是数据的容器,文档保存在分片内,不会跨分片存储。一个分片是一个Lucene索引,一个Lucene又分成很多段(每段都是一个倒排索引)。比如有100个indices(数据库),可以拆分片到5台机器,每台20个indices。
5、副本:除了将数据分片放在不同的机器上增加水平拓展能力,分布式存储还会把数据复制多个副本,放在不同机器上,可以进行读写分离。为了应对并发更新的问题,ES将数据以及副本数据分为主从两个部分,即主分片和从分片,主数据作为权威数据,写过程先写主分片,成功后再写副分片,恢复阶段以主分片为主。
- 为了系统可用性,数据拆分的part1、part2等等。每一份如part1也会存在副本放在其他机器上,简单说就是part1在机器1,那么part1副本需要在其他机器上也存一份。
- 为了应对并发更新,分为主、从分片,及先写主分片,再写从分片。读写请求会落在不同的分片上,不同的机器上,做到读写分离。
- 分片数量的权衡:分片的数量在5.x之前不能修改,在5.x-6.x之后支持一定条件的修改,可以对主分片大小拆分和缩小,分片越小,分的片就越多,应该根据硬件和业务数据量来进行拆分。1、分片数量不够时,可以考虑重新建立索引,搜索1个50分片的索引和搜索50个一分片的索引效果一样,建议是周期性创建新索引,如website索引index每天创建一个website_时间戳index,然后在website主索引进行软连接,这样删除数据时可以直接删除某个索引,避免以id删除文档不会立即释放空间,删除的document时候只有在Lucene分段(倒排索引)合并时候才会从磁盘删除,手动合并会导致较高的I\O压力的问题。2、分片数量过多:若是每天一个索引,但是某天数据量很小,可以_shrink API来减少主分片数量,减低集群管理很多分片的负载。
5、动态更新索引:倒排索引一旦写入文件就具有不可变性,具有读取文件不需要加锁和利用文件系统缓存的优势。
- 新增文档内容需要动态更新索引:不可变情况下,加内容则需要加上新的倒排索引,每次从内存写入文件,会产生一个新的Lucene段,这些Lucene段(倒排索引)信息会被汇聚记录到一个元信息文件。即一个主分片可以简单理解为一个Map集合,每个段就是一个Map,某个Map一旦创建不可改变。
- 以id删除doc空间不会立即释放的原因:倒排索引的不可变性,因此删除、更新数据实际上是把数据标记为删除。真正删除的时机是在分片内段合并的时候,即理解为多个不可变的Map合成一个大的Map。
6、近实时搜索:写操作一般是先在内存缓冲一段数据称为写缓冲,然后写入磁盘。通过os的write函数先写进内存,write函数返回成功此时还没刷盘,但是数据就已经可见,稍后执行flash刷盘。
- 记录事务日志防止数据丢失:若是在write成功之后,该段数据已经在内存,但是flash刷盘之前,机器宕机,就会有丢失数据的风险,解决方案就是记录事务日志,每次对ES操作均记录日志,当ES启动,重新扫描translog日志把未提及的重新提交,也就是刷盘
7、分片内分段合并:ES每秒清空一次写缓冲,将数据刷盘,称为refersh,因为动态更新索引,近实时搜索机制,在一个分片内,每次就会创建一个新的Lucene段(倒排索引),导致占用内存有可能会很高,每个搜索请求都需要查询每个Lucene段,久而久之,随着分段数量的增多,查询因此会很耗时,因此需要段合并,将差不多大小的段合并,此时标记删除的数据不会进行合并到新段,段合并完成,从磁盘删除。
8、集群扩容:当集群扩容时候,也就是增加机器的时候,分片会均衡的分配到集群的各个节点,从而对索引index搜索、更新过程进行负载均衡,这些都是系统自动完成的。分片副本为了实现数据冗余,防止机器硬件故障导致的数据丢失。
9、主要模块:ES的架构设计功能实现主要分为8个模块,使用Guice框架进行模块化管理(Guice是Google开发的轻量级的IoC依赖注入框架)
- Cluster:主节点执行集群管理封装实现(在各个节点迁移分片,保持数据平衡),管理集群状态(将新生成的集群状态发布到各个节点),维护集群层面的配置信息,调用allocation模块进行分片分配。
- allocation:封装了分片分配的功能和策略,包括主分片的分配和副分片的分配,由主节点调用,集群完全重启,创建新索引都需要分片分配的过程。
- Discovery:发现模块负责发现集群中的节点,以及选取主节点,可以类比Zookeeper,选主节点并管理集群拓扑。
- gateway:负责收到Master广播下来的集群状态数据持久化存储,并在集群完全重启的时候恢复它们。
- Indices:管理全局级的索引设置,不包括索引级的设置(索引设置分为全局级别、索引级别),还封装了数据恢复的功能。
- HTTP:该模块允许通过JSON over HTTP的方式访问ES的API,该模块完全是异步的,没有阻塞线程等待,使用异步通信进行HTTP的好处是解决C10k问题(10k量级的并发连接)。
- Transport:传输模块负责集群各个节点之间的通信,从一个节点到另外一个节点的每个请求都是使用传输模块,本质上也是使用异步的。使用TCP通信,节点之间维持长连接。
- Engine:封装了对Lucene的操作以及translog的调用,他是对一个分片读写操作的最终提供者。
10、集群主从模式:分布式系统的集群方式分为主从模式和无主模式,ES、HDFS、HBase使用主从模式,主从可以简化系统设计,master作为权威节点,负责管理元信息,缺点是存在单点故障,需要解决灾备问题。从机器的角度看分布式系统,每个机器可以放多个节点,分片数据有规则的和节点对应起来。
ps:集群指的就是多个ES APP进程组成的分布式系统。
- 主节点Master Node:设置可以作为主节点资格后,可以被选举,主节点是全局唯一的,主节点也可以作为数据节点,但是数据量不要太多,为了防止数据丢失,有主节点资格的节点需要知道有资格成为主节点的节点数量,默认为1
- 数据节点Data Node:crud数据,对cpu和内存、I\O要求较高。
- 预处理节点:5.0后引入的概念,允许在索引文档之前,写入数据之前,通过事先定义好的一系列processors和pipeline,对数据进行处理,富化。
- 协调节点:处理客户端请求,每个节点都知道任意文档所处的位置,然后转发这些请求到数据节点,收集数据合并返回给客户端。
- 部落节点:5.0之前有个处理请求的客户端节点,可以理解为负载均衡,在5.0之后被协调节点取代。
集群健康状态
- green:所有节点均正常
- yellow:主节点正常,但是不是所有的副分片都正常
- red:有主分片异常
二、分析单个节点的启动和关闭流程
启动流程中进程如何解析配置,检查环境、初始化内部模块。
1、当执行bin/elasticsearch启动ES时候,脚本通过exec加载Java程序,其中JVM的配置在config/jvm.options指定。
启动脚本后面可以加上参数
- E:设置某项配置项,如-E “cluster.name = my_cluster”
- V:打印版本号信息
- d:后台运行
- p:启动时候在指定路径创建一个pid文件,其中保存了当前进程的pid。之后可以通过查看这个pid文件来关闭进程。
- q:关闭控制台的标准输出和标准错误输出。
- s:终端输出最小信息
- v:终端输出详细信息
2、然后就是Java程序解析配置文件elasticsearch.yml即主要配置文件、log4j2.properties日志配置文件。
3、接着是加载安全配置(敏感信息不适合放在配置文件中的配置)、检查内部环境(Lucene版本防止有人替换不兼容的jar包、检测jar冲突)、检测外部环境(节点实现时候被封装进Node模块,Node.start()就是进行此步骤)主要包括:
- 1、堆大小检查
- 2、文件描述符检查
- 3、内存锁定检查
- 4、最大线程数检查
- 5、最大虚拟内存检查
- 6、最大文件大小检查
- 7、虚拟内存区域最大数量检查
- 8、JVM Client模式检查
- 9、串行收集器检查
- 10、系统调用过滤器检查
- 11、OnError和OOM检查
- 12、Early-access检查
- 13、G1GC检查
4、检查完毕之后就是启动ES的内部子模块(见上文介绍),它们启动方法被封装在Node类,如discovery.start()、clusterService.start()等
5、启动keep-alive线程:线程本身不做具体的工作,主线程执行完启动流程后会退出,keepalive线程是唯一的用户线程,作用是保证进程运行,在Java程序中,至少要有一个用户线程,否则进程就会退出。
关闭流程中,需要按照一定的顺序,综合来看大致为
- 关闭快照和HTTP Server ,不再相应用户REST请求
- 关闭集群拓扑管理,不在响应ping请求
- 关闭网络模块,让节点离线
- 执行各个插件的关闭流程
- 关闭IndicesService,最后关闭因为耗时最长
三、集群启动流程
1、集群启动指的是集群首次启动或者是完全重启的启动过程,期间要经历选举ES主节点、主分片、数据恢复等重要阶段。其过程可能会出现脑裂、无主、恢复慢、丢数据等问题。
-
selectmaster
:集群启动,从众多ES节点(ES进程)选取一个主节点,选举算法是Bully算法的改进,每个节点都有节点ID,然后每个节点都会对当前已知活跃排序,理论上取ID最大的为主节点,但是会存在由于网络分区或者节点启动速度相差太大的时候,会导致节点最大ID统计不同一,如1节点统计1,2,3,4,但是2节点统计2,3,4,5,此时就会不一致,因此此节点会先半数选举一个临时主节点,然后半数投票才确认最终的主节点。选举完成后若有节点下线,需要判断存活节点数是否大于当前检测到存活的一半节点数,达不到就要放弃master,重新设置集群,假如5台机器网络出现故障分区,1、2一组,3、4、5一组,产生分区前,master位于1或2,此时三台一组的节点会重新并成功选取master,产生双主,俗称脑裂。 -
gateway
:被选举的master存储的集群元信息不一定是最新的,需要将其他节点元信息发过来,根据版本号来确定最新的元信息。然后把这个元信息广播,更新其他节点,称为集群元信息的选举。 -
allocation
:节点分配分片(全部index需要均分分片到对应ES节点),构建路由表,1、先要选出主分片,所有分配工作由master节点来做。开始所有的分片信息都处于unassigned状态,ES中通过分配过程决定哪个分片位于哪个节点,因此首先需要选出主分片。首先询问所有节点依次索要part1分片、part2分片…的元信息,询问量 = 节点数 * 分片数(part1、2、3、4…),由此可以看出效率受分片数量影响,所以最好是控制分片数量。现在拿到了所有的分片信息,5.x之前是将所有的从分片元信息汇总比较,选出版本号最大的作为主分片,但是存在分片所在机器启动慢问题,5.x之后给每个分片设置一个uuid,然后再集群的元信息记录那个shared是最新的。2、选取从分片从众多收集的分片信息选取一个作为从分片。 -
recovery
:分片分配到节点后,开始统一各主、副分片数据,主分片有可能写的数据还没刷盘,主分片recovery不会等到副分片分配成功才开始,但是副分片recovery需要等到主分片recovery之后(因为主写副读,主的数据副分片有可能还没统一)。一次Lucene倒排索引的提交,就会一次写缓冲区fsync刷盘过程。主的recovery就是将最后一次提交之后的translog进行重放。副的recovery会分为两个阶段,为了不影响读,**1、全量同步:**获取主的translog锁,保证不会受主的fsync改变translog,然后备份主分片快照,直接更新副分片。此阶段完成前,通知完副分片启动engine,然后可以接受读写请求了。2、增量同步:主分片在上述过程中可能写入新的数据和translog,因此副需要增量将translog新增的索引重放恢复,增量translog数据指的是对translog从加锁开始到副分片复制完主分片的快照的时刻产生的新增数据,可以对主的translog做一个快照,发送到副就能找到差异数据。- 分片数据的完整性:第二阶段的translog快照包含第一阶段所有的新增操作,如果在第一阶段还未执行完,主发生数据 lucene commit(将文件系统写缓冲的数据刷盘,并清空translog)呢?这样是不是在第二阶段就拿不到translog快照了呢?在ES2.0之前是阻止刷盘操作,这样可能会导致一直往translog写数据而不刷盘,2.0之后到6.0之前,为了防止期间出现过大的translog,使用translog.view来获取后续所有操作。从6.0之后,引入TranslogDeletingPolicy的概念,他将translog做一个快照保证translog不被清理掉。
- 数据的一致性:在ES2.0之前,副分片恢复过程其实是有三个阶段的,第三阶段会阻塞主的更新数据的操作,传输第二阶段执行期间新增的translog,这个时间很短,在2.0之后第三个阶段就被删除了,恢复期间没有任何写阻塞过程,副重放translog的时候,第一阶段和第二阶段的写操作 与 第二阶段重放translog操作之间的时序错误和冲突,通过写流程中进行异常处理,对比版本号来过滤掉过期操作。遮这样就把正对于某个doc只有最新的一次操作生效,保证了主副分片一致。
3.1、集群选Master主节点流程
Discovery模块负责发现集群中的节点,以及选取主节点,因为是分布式系统,自然要处理一致性问题,一般解决方案
(1)试图避免不一致情况发生
(2)发生不一致如何挽救。第二种一般对数据模型有着较高的要求。
集群的架构可以为主从模式、哈希表模式
- 哈希表模式:每小时可支持数千个节点的加入和离开,其可以在不了解底层网络拓扑的情况下,查询相应很快,如Cassandra就是这种模式
- 主从模式:在网络相对稳定的情况下较为适合,当集群没那么多节点的时候,通常节点的数量远远小于单个节点能够维持的连接数,也就是连接多,并且节点不经常变动,因此es选择这种模式。
选举算法
- Bully算法:选举Leader的基本算法之一,假设每个节点都有一个唯一的ID,然后根据ID排序,任何时候选取最大ID对应的节点为Leader,这种方式是实现比较简单,不足是容易产生脑裂,比如A节点之前为Leader,但是后来由于负载过重出现假死,这个时候排名第二的节点B被选为Leader,然后A节点又突然恢复正常了,造成脑裂效应。
- Paxos算法:选举更灵活、简单,但是实现起来比较复杂。参考:https://zhuanlan.zhihu.com/p/31780743
详细流程
在ES选Master过程相关的重要配置其中之一discovery.zen.minimum_master_nodes 最小主节点数量
,值最好设置ES集群总节点数的半数以上,比如共三个节点,最好设置为 3 / 2 + 1 = 2
个,这是防止脑裂、数据丢失及其重要的参数,作为其他几种集群行为的判断依据。详细流程:
1、触发选主:当参选的节点数量大于设置的最小节点数,才能进行选主
2、确定Master,主要分为下面的选出临时Master和确定最终的Master两个步骤,原因上文也有说。
- 2.1、选出临时Master:通过配置
discovry.zen.ping.unicast.hosts
指定集群中的节点列表(包含ES进程的ip、port),各节点之间投票,根据Bully选举算法,每个节点计算出一个最小的已知节点ID(可以通过启动时间、网络响应时间等等确定),详细的流程就是- (1)每个节点Ping所有的集群节点,获取可到达的节点列表加入到
fullPingResponses
中,然后把自己也加入列表 - (2)构建两个列表,
activeMasters
列表存储当前活跃的允许被选为Master的节点列表,这个列表的数据来自遍历每个节点,根据每个节点选出的ID最小的加入activeMasters
列表(不包括自身节点,其中配置了discovery.zen.master_election.ignore_non_master_pings为true
的节点并且配置不具备Master也不会被加入)。另外一个是masterCandidates
列表是master的候选者列表,如果activeMasters
为空那么从这个列表选取。 - (3)从
activeMasters
列表中选取一个做为临时Master,比较方法是选出一个列表中ID最小值的节点。
- (1)每个节点Ping所有的集群节点,获取可到达的节点列表加入到
- 2.2、投票确定最终Master:选出后需要半数该值节点数认同才能成为真正的Master,否则该临时Master就会加入集群,发送投票就是发送加入集群的请求,获得的票数就是该临时Master接收到其他节点的加入集群的请求数量。,投票过程中对于莫i个临时Master会存在两种情况:
- (1)被选上:等待其他具备Master资格的节点加入集群即投票达到法定人数,默认30s超时未达到法定人数则选举失败,选举成功的话发布集群状态clusterState。
- (2)其他临时Master被选上:当前临时Master不在接受投票信息,向被确定为Master的节点发送加入请求,并默认等待1min,超时会重试三次。最终确定的Master会先发布集群状态,然后在确认加入请求。
2.2、选取元信息:像有Master资格节点(配置了node.master = true
的节点)发请求获取元数据,获取响应数量必须达到最小节点数才会选为元信息。
2.2、Master发布集群状态文章来源:https://www.toymoban.com/news/detail-461474.html
3.2、集群节点失效检测
选举完成之后集群状态发布,后面集群需要探测到某些节点失效的异常情况,不执行的话可能会造成脑裂(双主、多主),因此需要启动两种失效探测器:文章来源地址https://www.toymoban.com/news/detail-461474.html
- 在Master节点:启动NodesFaultDetection,简称NodesFD,定期探测加入集群的节点是否活跃。检查下当前集群存活节点是否达到法定节点数(半数以上),如果不足则会放弃Master,重新加入集群。
- 在其他节点:启动MasterFaultDetection,简称MasterFD,定期探测Master节点是否活跃。尝试重新加入集群,发送加入申请。
到了这里,关于《Elasticsearch源码解读与优化实战》张超-读书笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!