第一章 走进ElasticSearch
基本概念
1.1.1 索引结构
ES是面向文档的,各种文本内容以文档的形式存储在ES中
在存储结构上, index、type、id标识一个唯一的文档
类比RDBMS的概念,可以快速理解概念,但本质上是完全不同的概念
RDBMS ES
库 index
表 type
行 文档数据
在实际应用中,当数据模型不同时,应该使用不同的index而不是使用不同的_type,在ES6.x版本每个index只允许存在一个type,ES7.x完全删除type的概念
ES7.x移除type的原因:数据库中两个不同的表拥有同名的字段是相互隔离、互补影响的,但是ES是基于lucene存储的,不同type的同名字段在lucene中存储时是一个字段,这就带来了字段冲突,会影响ES的处理效率
1.1.2 分片
单机无法存储巨量的数据,纵向扩展除了会带来几何倍数增长的成本外同时具有较低的瓶颈限制。因此,需要将数据拆分到各个机器上,然后通过某种路由策略可以根据条件找到数据所在的位置。
为了保证数据的可靠性,ES还提供了副本机制,副本是基于分片来做的,可分为主分片、副分片
分片与副本的关系如图:
分片是底层读写的基本单元,目的就是分割巨大的索引,让读写可以并行操作。读写最终会落到某个分片上,每个分片都是一个完整的Lucene索引,可以单独执行搜索任务。一个Lucene索引包含多个Lucene分段,每次执行refresh操作时,都会生成一个新的Lucene分段,每个Lucene分段中包含部分文档数据,针对文档中的每个字段会建立一个倒排索引,倒排索引由单词词典和倒排列表组成,单词词典就是Term集合,倒排列表由倒排项组成,每个倒排项包括单词以及单词出现的所有文档的信息(这里的文档信息存储的内容可以由index_options进行控制,包括文档的ID、词频、词出现的偏移量)
在创建索引的时候,就应该确定主分片数,ES5.x之前主分片数是不可修改的,ES5.x开始可以使用shrink API减小主分片数
但其实在实际应用中,不应该一直往单个索引中添加数据,因为数据老化后难以删除,以_id为单位删除文档不会立即释放磁盘空间,需要进行Lucene段合并才会将数据从磁盘彻底删除,即使手动触发段合并,也会带来较大的IO压力,并且可能因为分段巨大而导致剩余的磁盘空间不足(新分段的大小大于剩余磁盘空间)。所以建议是周期性创建新索引,比如每天创建一个新索引,使用索引别名关联这些索引,业务方读取时通过索引别名进行读取名称不变,当需要删除老化数据时,直接删除整个索引即可。
索引别名就类似于一个软链接,不同的是它可以指向一个也可以指向多个索引
1.1.3 动态更新索引
lucene分段具有不可变性,好处就是不需要加锁,读取时可以被文件系统缓存
新添加的文档如何被搜索到?答案是每次新添加的文档都会在一个新的lucene分段中,搜索时是遍历所有的lucene分段
更新、删除文档时,是将旧文档标记为删除,记录到单独的位置,删除文档不会立即释放磁盘空间
1.1.4 近实时搜索
ES写数据时,是先将数据写到Index buffer,每隔一秒调用操作系统的write接口将数据写入磁盘,write接口将数据写到操作系统缓存中就返回成功了,并不会立即刷盘,需要手动执行flush或者操作系统通过一定的策略将数据刷到磁盘,但是当write接口返回成功时,即使数据没有写到磁盘,数据也对搜索可见
每隔一秒写入的这批数据就是一个lucene段
由于写入的数据是先在内存中,然后写入磁盘,在这期间如果机器宕机,那么就存在丢失数据的风险。通用的做法是记录事务日志,对ES进行的操作做记录,当ES重启时,重放translog中最后一次提交点后的操作。
集群内部原理
1.2.1 集群节点角色
- 主节点
主节点负责集群层面的工作,管理集群变更
通过配置node.master : true(默认)让节点成为master_eligible节点即有资格成为master的节点
为避免网络分区时出现脑裂,配置discover.zen.min_master_nodes为(master_eligible_nodes / 2) + 1 - 数据节点
负责保存数据,执行数据相关的操作;CRUD、聚合、搜索
通过配置node.data : true(默认)来让节点成为数据节点 - 预处理节点
这是从ES5.0开始引入的概念, 作用就是在写入数据前,通过事先定义好的一系列处理器对数据进行处理、转换,然后在将数据写入
默认,所有节点都会启用预处理功能,可以通过node.ingest : false关闭 - 协调节点
协调节点就是接收客户端请求的节点,该节点知道任意文档的位置,然后将请求转发给数据节点,对数据节点返回的结果进行合并,最后将合并的结果返回给客户端
默认每个节点都是协调节点,如果需要一个单纯的协调节点,可以进行如下设置:
node.master:false
node.data:false
node.ingest:false
1.2.2 集群健康状态
集群健康状态分为3种:
1. Green
主、副分片都分配正常
2. Yellow
副分片未正常分配
3. Red
主分片未正常分配
1.2.3 集群状态
集群状态是全局信息,包括内容路由和配置信息,内容路由描述了哪个分片位于哪个节点
主节点负责维护集群状态,从数据节点接收更新,将这些更新广播到集群的其他节点,让每个节点上的集群状态保持最新。ES2.0之后,更新的集群状态是增量的并且是压缩过的。
1.2.4 集群扩容
当扩容集群,添加节点时,分片会均匀地分配到各个节点
分片分配过程中除了让节点间分片分配均匀,还要保证主副分片不在同一节点,避免单个节点故障引起数据丢失
如下:起初,NODE1上有个三个主分片,没有副分片
P代表主分片、R代表副分片
添加第二个节点后,副分片被分配到NODE2
添加第三个节点后,6个分片被均匀分配到3个节点,主副分片不会在同一个节点
当主节点出现异常,集群会重新选举主节点;当某个主分片出现异常时,也会将副分片升级为主分片
疑问:副分片可以分配在同一个节点吗???????
1.4 主要内部模块简介
-
Cluster
Cluser模块是主节点执行集群管理的封装实现
主要功能如下:- 管理集群状态,将最新的集群状态广播给各个节点
- 调用allocation模块执行分片分配,决策哪些分片应该分配到哪个节点。(因此负责创建索引)
- 在集群各个节点中直接迁移分片,保持数据平衡
-
allocation
封装了分片分配相关的功能和策略,包括主分片分配和副分片分配,本模块由主节点调用。创建索引、集群完全重启都需要进行分片分配 -
Discovery
发现模块负责发现集群中的节点、主节点选举以及维护集群拓扑。当节点加入或离开集群时,主节点采取相应措施 -
gateway
负责对收到Master广播下来的集群状态数据做持久化存储,并且在集群完全重启时恢复它们 -
Indices
索引模块管理全局级的索引设置,不包括索引级的。它还封装了索引数据恢复功能,例如启动阶段的主分片恢复和副分片恢复。 -
HTTP
HTTP模块允许通过JSON over HTTP的方式访问ES的API,HTTP模块本质上是完全异步的,这意味着没有阻塞线程等待响应 -
Transport
传输模块用于集群内节点之间的通信
本质上是完全异步的
传输模块使用TCP通信,每个节点和其他节点维持13个TCP长连接 -
Engine
Engine模块封装了对Lucene的操作及translog的调用,它是对一个分片读写操作的最终提供者
1.4.2 模块结构
一个典型的模块由Service和Module类组成,Service用于实现业务功能,Module类配置绑定Service的实现类
1.4.3 模块管理
定义好的模块由ModulesBuilder类统一管理,该类对外提供两个方法
1. add方法:添加定义好的模块
2. createInjector:返回injector对象,后续通过injector获取响应的Service类的实例
模块化的封装让ES易于扩展,插件本身也是一个模块,节点启动时被模块管理器添加进来
第三章 集群启动流程
3.1 选举主节点
集群启动的第一件事就是选举主节点,选主之后的流程由主节点触发
ES选主的思路是对节点ID排序,取ID值最大的节点作为主节点,选择出的主节点不一定具有最新的集群元信息,后面在想办法获取最新的集群元信息(非集群状态信息)
选举主节点有三个约束条件:
1. master_eligible节点数要达到quorum值(多数),选出临时的主。为什么是临时的主呢?因为每个节点选举出的ID最大的节点不一定相同。例如,集群有5台机器,ID分别是1、2、3、4、5,由于产生网络分区或者启动速度差异较大,节点1看到的是1、2、3、4,选出4;节点2看到的是2、3、4、5,选出5,结果就不一致了
2. 节点得票数过半即加入该节点的节点数过半,才确认主节点,解决第一个问题
3. 当探测到节点离开时,必须判断master_eligible节点数是否达到quorum值,如果达不到quorum值那么要放弃master身份,重新加入集群。如果不这么做,设想一下:假设5台机器,产生网络分区,2台一组,3台一组,原来的master在2台中的一个,3台一组的节点会重新选举出一个master,产生双主,俗称脑裂。
quorum值就是配置 discovery.zen.minimum_master_nodes的值
3.2 选举集群元信息
选举出的Master第一个任务是选举集群元信息,让各节点把各自存储的集群元信息发过来,根据版本号确定最新的集群元信息,然后把这个信息广播到每一个节点
集群元信息的选举包括两个级别:集群级、索引级
为了集群一致性,参与选举的元信息数量需要过半,Master发布集群状态成功的规则也是发布成功的节点数过半
集群元信息选举完毕后,Master首次发布集群状态,然后开始选举shard级元信息
3.3 allocation过程
初始化阶段,所有分片都是未分配状态,通过分片分配决定哪个分片位于哪个节点,重构内容路由表。此时,首先要做的就是分配主分片。
1. 分配主分片
Master向所有节点询问指定分片的元数据信息,然后根据策略选举一个分片作为主分片
ES 5.x之前是根据分片元信息的版本来决定谁是主分片,但是存在一个问题:拥有高版本分片的节点可能在后面启动,导致决定出错
ES 5.x开始给每个分片分配一个UUID,然后在集群元信息中记录哪个分片是最新的。由于ES是先写主分片,再写副分片,所以主分片肯定是最新的
3.4 Index Recovery
分片分配完成后,进行数据恢复阶段,主分片的恢复不需要等待副分片分配完成,副分片的恢复需要等待主分片恢复完成
为什么要进行数据恢复呢?
主分片可能有一些数据没有来得及刷盘;副分片可能有一些数据没有刷盘,也可能有一些数据主分片写成功而副分片没有写,主副分片数据不一致
恢复过程:
1. 主分片恢复
重放最后一次提交后的translog事物日志。一次提交就是一次fsync异步刷盘。
2. 副分片恢复
分为两个阶段:
phase1:
在主分片获取translog保留锁保证主分片translog不被删除,然后调用lucene接口对主分片数据进行快照,这是已经刷盘的数据。把这些数据复制到副分片。
在phase1结束前会告诉副分片启动Engine,在phase2开始之前,副分片就可以正常处理写请求了。
phase2:
对主分片translog做快照,这个快照包含从phase1开始到执行translog快照期间的所有操作,将这些translog发送到副分片进行回放
在副分片节点,重放translog时,translog中的操作和副分片在phase1和phase2阶段的写操作会有冲突,在写流程中通过对比版本号来过滤掉过期操作
第四章 节点的启动与关闭
4.1 启动流程做了什么
解析配置,包括配置文件和命令行参数
检查外部环境和内部环境
初始化内部资源,创建内部模块,初始化探测器
启动各个子模块和keepalive线程
4.2 启动流程分析
4.2.1 启动脚本
当通过启动脚本bin/elasticsearch启动ES时,会对命令行参数、config/jvm.options、elasticsearcg.yml、log4j2.properties配置文件进行解析
当前支持的命令行参数:
-E 设置某项配置,例如设置集群名称-E “cluster.name=my_cluster”
-V 打印版本号信息
-d 后台启动
-h 打印帮助信息
-p 启动时在指定路径创建一个pid文件,其中保存了当前进程的pid
-q 关闭控制台的标准输出和标准错误输出
-s 终端输出最少信息
-v 终端输出详细信息
4.2.3 加载安全配置
安全配置就是敏感配置信息,不适合明文放在配置文件,ES把这些敏感配置放在config/elasticsearch.keystore中,然后提供一些命令查看、添加和删除配置
4.2.4 检查内部环境
内部环境指ES软件包本身的完整性和正确性
1. 检查lucene版本,ES各版本对lucene的版本有要求
2. 检查jar冲突
4.2.5 外部环境检查
-
堆大小检测
检测JVM初始堆大小和最大堆大小的值是否相同。不相同会导致在使用期间JVM堆大小动态调整出现停顿。 -
文件描述符检查
ES进程需要很多的文件描述符,例如一个分片包含很多分段,每个分段又包含很多文件,同时ES节点又要和其他节点保持很多的网络连接等
需要调整系统的默认设置,在Linux下执行 ulimit -n 65536(只对当前终端生效),或者在/etc/security/limits/conf文件中配置"* - nofile 65536" -
内存锁定检查
ES允许进程只使用物理内存,而不使用交换分区。建议生产环境直接禁用操作系统的交换分区,因为对于服务器来说,当内存用完时,交换到硬盘上会引起更多的问题。
开启bootstrap.memory_lock选项让ES锁定内存 -
最大线程数检查
ES将请求分解为多个阶段执行,每个阶段使用不同的线程池来执行,因此ES需要创建很多线程。
修改/etc/security/limits.conf文件的nprocs来设置 -
最大虚拟内存检查
Lucene使用mmap映射部分索引到进程地址空间,最大虚拟内存确保ES进程拥有足够多的地址空间
修改/etc/security/limits.conf文件,设置as为unlimited -
最大文件大小检查
Lucene分段文件和事务日志文件存储在磁盘,可能会非常大,如果超过最大文件大小会导致写入失败,因此最好将最大文件大小设置为无限
修改/etc/security/limits.conf文件,修改fsize为unlimited -
虚拟内存区域最大数量检查
Lucene需要将部分索引映射到地址空间,因此ES需要很多虚拟内存映射区,本项检查确保内核允许创建至少262144个内存映射区
在/etc/sysctl.conf文件中添加一行 vm.max_map_count=26144,然后执行sysctl -p永久生效 -
JVM 模式检查
OpenJDK提供了两种JVM的运行模式:client JVM模式与server JVM模式。client调优了启动时间和内存消耗,server具有更高的性能。该项检测要求是server模式。 -
串行收集检查
串行垃圾回收器适合单CPU机器,不适合ES,本项检查就是检查是否使用了串行垃圾回收器。ES默认使用CMS垃圾回收器。 -
系统调用过滤器检查
由于某些系统漏洞,攻击者可能取得进程的权限,从而拥有启动该进程的用户权限可以执行一些操作。
系统调用过滤器可以限制进程可以操作的系统调用函数
使用普通用户启动进程最合适 -
Early-access 检查
OpenJDK 为即将发布的版本提供了early-access版本,但是这个版本不适合线上使用,要想通过此项检查,要运行在JVM的稳定版本 -
G1GC检查
JDK8早期版本有问题,JDK 8u40之前都会受影响,本项检查验证是否是早期版本
4.2.6 启动内部模块
环境检查完毕,开始启动内部模块,调用内部模块的start方法
内部模块开始初始化内部数据、创建线程池、启动线程池
4.2.7 启动keepalive线程
启动keepalive线程,该线程本身不做任何工作,目的是保证ES程序的运行。因为主线程执行完启动流程后就退出了,该线程是唯一的用户线程,在JAVA程序中,至少要有一个用户线程,否则程序会退出
4.2.8 节点关闭流程
ES进程会捕获SIGTERM信号,调用各模块的stop方法,让它们有机会停止服务,安全退出
如果是主节点关闭,则集群会重新选主。如果主节点是单独部署即仅仅是主节点不是其他类型节点,那么可以跳过gateway和recovery阶段;否则新主需要重新分配旧主持有的分片
如果是数据节点关闭,则读写请求的TCP连接被关闭,对客户端来说写操作执行失败,但是写流程已经到达Engine环节的会正常写完,只是客户端无法感知结果,此时客户端重试如果使用自动生成ID,
则数据内容会重复
4.4 节点关闭流程顺序
关闭快照和HTTPServer,不再响应用户REST请求
关闭集群拓扑管理,不再响应ping请求(内部节点间不响应了)
关闭网络模块,让节点离线
执行各个插件的关闭流程
关闭IndicesService
4.5 分片读写过程中关闭
写入过程中关闭。线程写入数据时,会对Engine加写锁,IndexServices关闭时也要对Engine加写锁,因此会等待写操作执行完在关闭IndexServices,但是节点已经离线了,客户端的连接已经断了感知不到结果会认为执行失败。
读取过程中关闭。线程读取数据时,会对Engine加读锁,IndexServices关闭时要对Engine加写锁,因此会等待读操作执行完在关闭IndexServices,但是节点已经离线了,客户端的连接已经断了感知不到结果会认为执行失败。
4.6 主节点关闭
主节点正常执行关闭流程,当关闭集群拓扑管理时,集群重新选举Master
第五章
5.2 为什么使用主从模式
除主从模式外,还有一种选择是分布式哈希表
分布式哈希表是什么????????
主从模式适合节点数较少,网络中不必经常处理节点的加入和离开,因此更适合ES
分布式哈希表适合节点数较多,网络中节点经常加入和离开
5.3 选举算法
1. Bully算法
每个节点都有一个唯一的ID,使用该ID对节点进行排序,ID最高的节点就是leader节点。优点是易于实现
2. Paxos算法
优点是灵活性更强,可以解决更复杂的情况;缺点是太复杂
paxos算法思路详解??????????
5.4 相关配置
1. discovery.zen.minimum_master_nodes:最小主节点数
该参数可以防止脑裂、防止数据丢失
多处使用地:
1. 触发选主:选主之前,参选的节点数需要达到该值
2. 决定Master:选出的临时Master,判断加入自己的节点数是否达到该值,如果是那么确认选举成功
3. gateway选举元信息:向Master_eligible节点发送请求,获取元信息,响应数必须达到该值
4. Master发布集群状态:发布成功数量达到该值
参数值设置:该值应该设置为(master_eligible/2+1)
5.5 流程概述
ZenDiscovery选主过程:
1. 每个节点计算最小的已知节点ID,该节点为临时Master,向该节点发送领导投票
2. 如果一个节点收到quorum值的投票数,并且该节点也为自己投票,那么它就确认是Master,开始选举集群元信息
5.6 流程分析
整体流程概括为:选举临时Master,如果本节点当选,则等待确立Master;如果其他节点当选,则尝试加入集群,然后启动节点失效探测器
5.6.1 选举临时Master
选举临时Master过程:
1. 每个节点ping所有节点,获取节点列表,ping结果不包含本节点,将本节点单独添加到结果中
2. 构建两个列表
activeMasters:活跃的Master列表。遍历第一步拿到的节点列表(自己除外),将节点认为的Master添加到该列表中,遍历过程中如果配置了discovery.zen.master_election.ignore_non_master_pings为true
(默认是false),而节点又不是Master_eligible节点那么跳过该节点
masterCandidates列表:Master候选者列表。遍历第一步拿到的节点列表,去掉不是Master_eligible的节点,添加到这个列表
3. 选举临时Master
如果activeMasters不为空那么从activeMasters中选举合适的节点作为Master;否则从masterCandidates中选举合适的节点为Master,可能选举成功,也可能选举失败
从masterCandidates中选主时,首先需要判断当前候选者人数是否达到quorum值,如果没达到那么选举失败;如果达到quorum值,那么选择ID值最小的节点作为Master节点
从activeMasters列表中选择,选择ID最小的节点作为Master节点
5.6.2 投票与得票的实现
发送投票就是发送加入集群请求,得票就是申请加入该节点的请求数量
当节点检查收到的投票是否足够时,就是检查加入它的连接数是否足够,其中会去掉不是Master_eligible节点的投票
5.6.3 确立Master或加入集群
选举出的临时Master有两种情况,自己是临时Master或者自己不是临时Master
自己是临时Master那么就等待确立Master,否则加入自己认为的临时Master
1. 如果自己是临时Master
1. 等待足够多的Master_eligible节点加入(投票达到法定数)
2. 超时(默认是30s)后还没有达到法定值的join请求,则选举失败,进行新一轮选举
3. 成功后发布集群状态
2. 如果自己不是临时Master
1. 不再接受其他节点的join请求
2. 向自己认为的临时Master发送加入请求,并等待回复
3. 最终当选的Master会先发布集群状态,才确认客户的join请求
5.7 节点失效检测
节点失效检测会监控节点是否离线,然后处理其中的异常
需要启动两种失效探测器:
在Master节点,启动NodesFD,定期探测加入集群的节点是否活跃
在非Master节点,启动MasterFD,定期探测Master节点是否活跃
失效探测都是定期(默认1S)发送ping请求探测节点是否正常,当失败达到一定次数(默认为3次),就认为节点离线,开始处理节点离开事件
5.7.1 NodesFD事件处理
检查当前集群master_eligible节点数是否达到quorum值,如果不足那么放弃Master身份,重新加入集群。目的是防止出现脑裂。
5.7.2 MasterFD事件处理
重新加入集群,本质就是在进行一次选主流程
第六