(一)索引相关主要配置参数:
action.auto_create_index
index.mapper.dynamic
index.number_of_shards
index.number_of_replicas
index.store.throttle.type
index.store.throttle.max_bytes_per_sec
index.queries.cache.enabled
index.allocation.max_retries
index.refresh_interval
index.shared_filesystem
index.data_path
index.translog.flush_threshold_size
index.translog.flush_threshold_period
index.translog.flush_threshold_ops
index.translog.durability
index.max_adjacency_matrix_filters
indices.recovery.max_bytes_per_sec
indices.cluster.send_refresh_mapping
cluster.routing.allocation.balance.shard
cluster.routing.allocation.balance.index
cluster.routing.allocation.balance.threshold
cluster.routing.allocation.node_concurrent_recoveries
(二)相关ACTION_NAME(内部数据交互协议名)
internal:cluster/shard/started
internal:cluster/shard/failure
internal:cluster/node/mapping/refresh
internal:index/shard/recovery/start_recovery
internal:cluster/snapshot/update_snapshot
cluster:monitor/nodes/stats
indices:data/write/bulk
indices:data/write/bulk[s][p]:s表示shard,p表示primary
indices:data/write/bulk[s][r]:s表示shard,r表示replica
(三)主要操作类:
TransportAction
TransportIndexAction
TransportCreateIndexAction
TransportBulkAction
TransportShardBulkAction
TransportReplicationAction
TransportMasterNodeAction
TransportPutMappingAction
TransportWriteAction
MappingUpdatedAction
IndicesService
IndexService
MetaDataCreateIndexService
MetaDataMappingService
ReplicationOperation
IndexShard
InternalEngine
ClusterService
TransportService
RoutingAllocation
AllocationService
AllocationDeciders
GatewayAllocator
ShardsAllocator
(四)主要资源相关类
Index
IndexShard
IndexMetaData
MetaData
ClusterState
RoutingNodes
RoutingTable
ShardId
ShardRouting
ShardRoutingState
IndexRoutingTable
IndexShardRoutingTable
ShardStateInfo
ClusterChangedEvent
(五)通过HTTP的形式索引数据,demo如下(默认5个主分片,每个主分片1个副本,共10个Shard),下面详细说明POST请求之后的一系列处理过程。
POST http://localhost:9200/index_demo/type_demo/1
{
"username":"xiaoming"
}
(六)建索引及索引数据的过程:
约定:Shard用于表示Lucene索引;Index指elasticsearch的索引,由一个或多个主分片Shard和副本Shard组成
(1)、首先RestController类(类似于Spring的Controller)会收到请求,然后把请求分发给相应的RestHandler进行处理,具体就是各种Action实例(比如RestCreateIndexAction,RestDeleteIndexAction等),对于GET,POST,PUT,DELETE,HEAD,OPTION请求都通过字典树(PathTrie类)维护URL到处理类的映射,根据不同的请求找到对应的Action实例
(2)、第一次请求的URL符合/{index}/{type}/{id}模式,被分配给RestIndexAction来处理,RestIndexAction通过RestRequest获取用户参数及数据,调用TransportClient的index方法,TransportClient根据不同的Action查找到具体负责的TransportAction实例,基本上每个RestAction都有相应的TransportAction,这里就是TransportIndexAction类,调用其execute方法发起请求,因为TransportAction对于请求的处理采用责任链模式(类似JavaWeb中的FilterChain处理机制),首先要依次通过ActionFilter[]过滤器数组的一次过滤,然后才调用TransportIndexAction的doExecute(task,request,listener)方法处理请求,TransportIndexAction继承TransportSingleItemBulkWriteAction抽象类,doExecute由TransportSingleItemBulkWriteAction处理,TransportSingleItemBulkWriteAction类实际上调用TransportBulkAction批处理类来执行请求,在这里就是单个请求的批处理。TransportIndexAction和TransportSingleItemBulkWriteAction都被标记为@Deprecated,建议通过TransportBulkAction来处理
(3)、TransportBulkAction处理请求
TransportBulkAction的execute方法执行也是通过上述链式处理,每个继承TransportAction抽象类的实例都会经历这一过程,然后再处理请求。 处理的过程如下:
3.1、首先检查是否有pipeline操作,若有再查看本节点是否是IngestNode,是则处理,不是则随机选择是IngestNode的节点并将请求转发过去,处理就结束了。本示例暂不涉及pipeline操作(pipeline是指流水线式的批处理操作,IngestNode可以将用户数据进行相应转换后再存入索引)
3.2、若设置了自动创建不存在的索引(配置参数1),默认为自动创建,则遍历批处理请求,找出所有不存在的索引(IndexNameExpressionResolver类从ClusterState中可以得到所有索引的元数据信息,判断索引是否存在);对于每个需要创建的索引调用TransportCreateIndexAction的execute方法进行创建,TransportCreateIndexAction继承TransportMasterNodeAction抽象类,TransportMasterNodeAction是所有需要在主节点上进行操作的Action的基类,这里它会判断本节点是否是主节点,不是就会通过transportService.sendRequest将索引创建请求转发给主节点,如果主节点处理失败,会等待集群状态改变后再重试一次;本节点是主节点的话就会直接处理,首先要检查集群是否被阻塞,被阻塞的话还会重试,如果不能重试也创建索引失败;集群状态正常没有阻塞则将创建索引请求提交给ThreadPool线程池处理
(4)、MetaDataCreateIndexService提交任务开始索引的创建(即调用ClusterService的submitStateUpdateTask方法,Discovery模块中有过状态更新任务描述):
4.1、校验索引名称,确保没有非法字符,索引和别名确实不存在等
4.2、检索出集群中的与该索引名匹配的template索引模板(order属性值大的template会覆盖order属性值小的template,一个索引可应用多个template)
4.3、读取用户配置的mapping(与template重复以用户定义为标准)
4.4、将template模板和用户自定义mapping合并
4.5、读取Settings配置信息,比如index.number_of_shards,index.number_of_replicas等,没有提供参数则设置默认值
4.6、通过Settings和其它参数构建IndexMetaData对象,IndexMetaData存有该索引的元数据信息,包括用户提供的参数及默认的配置,这里构建的IndexMetaData对象是为了检测索引信息的正确性,正确的话后面将会应用
4.7、调用IndicesService的createIndex方法为IndexMetaData创建一个IndexService对象,IndexService主要负责一个索引的管理服务,主节点以此判断索引是否可以被成功创建,可以的话后面将会添加mapping
4.8、IndexService通过MapperService将目前集群中该索引的mapping和用户的mapping(template模板和用户自定义合并而来)合并,并根据用户参数构建新的IndexMetaData对象,IndexMetaData、当前的ClusterState形成新的ClusterState
4.9、根据新的ClusterState,调用AllocationService的reroute方法更新routing table(类似路由器的路由表功能,保存每个索引的各分片在哪个节点上的信息),AllocationService主要依赖4个服务完成分片的分配工作:AllocationDeciders(包含AllocationDecider集合,每个AllocationDecider都是一个分配策略,用于决定一个分片是否能分配到某个节点,是否可以重分配等,通过其canAllocate,canRebalance等方法即可得到Decision结果),GatewayAllocator(集群数据持久化模块的分配服务),ShardsAllocator(Shard级别的分配服务),ClusterInfoService(提供集群基本信息)
4.10、通过allocationDeciders,clusterState,routingNodes,clusterInfo初始化RoutingAllocation对象(RoutingAllocation拥有当前Shard们分配的状态以及AllocationDeciders实例),后面根据RoutingAllocation进行操作
4.11、现在要把所有未分配的Shard分配到可用的节点上,主要包括两个流程:GatewayAllocator的操作(gatewayAllocator.allocateUnassigned(routingAllocation))和ShardsAllocator的操作(shardsAllocator.allocate(routingAllocation))
GatewayAllocator对于等待分配Shard的处理主要包括主分片和副本两个处理流程,主分片分配由PrimaryShardAllocator类负责,副本分配由ReplicaShardAllocator类负责,PrimaryShardAllocator和ReplicaShardAllocator都继承自BaseGatewayShardAllocator抽象类,BaseGatewayShardAllocator会根据集群中已经存在的Shard数据将新Shard分配到相应的节点中(提供基本分配功能),子类只需要提供哪些Shard应该被分配到哪些节点的逻辑即可,下面主要说明BaseGatewayShardAllocator对于Shard的分配流程(主分片和副本Shard的分配差异就在于PrimaryShardAllocator和ReplicaShardAllocator子类中Shard到Node的映射逻辑不同,即makeAllocationDecision实现的不同,后面再单独说明):
1)、从RoutingNodes对象中拿到所有未分配的Shard信息,即ShardRouting列表
2)、为每个ShardRouting做分配决定,即决定该Shard是否可以被该ShardAllocator(PrimaryShardAllocator、ReplicaShardAllocator )分配以及分配到那个节点上
3)、分配决定是AllocationDecision.YES,则将其从未分配列表删除,初始化该Shard:如果没有则分配新的allocation id(Base64编码的字符串),通知RoutingChangesObserver,Shard由未分配转到已经初始化状态了,这里RoutingChangesObserver主要包括RestoreInProgressUpdater和IndexMetaDataUpdater实现类,RestoreInProgressUpdater跟踪恢复过程中的数据变化并更新相关元数据信息,IndexMetaDataUpdater跟踪RoutingNodes资源的变化,分配操作完成之后更新IndexMetaData中主分片和in-sync allocation ids(每个Shard在分配时都有一个allocation id属性,与主分片数据同步的Shard会保存在in-sync allocation ids列表中)等信息;否则说明没有节点能够分配该Shard,将该ShardRouting对象删除并忽略(此时忽略,后续当新的MetaData对象被构建后还会将其加到未分配列表中再次进行分配)
PrimaryShardAllocator -> makeAllocationDecision(决定主分片的分配)
1)、判断是否负责该ShardRouting对象的分配,需满足三个条件:主分片、未分配状态、恢复源的类型是EXISTING_STORE或SNAPSHOT,不满足的话返回不接受分配的决定(AllocateUnassignedDecision.NOT_TAKEN)
2)、从其它节点异步获取相关Shard数据,主要获取NodeGatewayStartedShards列表信息
3)、从MetaData对象中获得inSyncAllocationIds,若inSyncAllocationIds为空则按照version值将NodeGatewayStartedShards对象列表倒序排列得到NodeShardsResult,NodeShardsResult存有排序后的待分配候选Shard信息和已找到待分配Shard数量的一个计数器;若inSyncAllocationIds不为空,则将NodeGatewayStartedShards对象列表按照allocation id进行排序得到NodeShardsResult
4)、NodeShardsResult中如果没有足够多的NodeGatewayStartedShards对象信息,则返回AllocateUnassignedDecision.NOT_TAKEN决定;否则从NodeShardsResult中按决定类型将Shards分组,按照决定是YES,THROTTLE,NO的顺序,再根据这三个列表获取到Shard被分配的节点信息和相应的allocation id,如果有节点可以分配则返回YES决定,节流限制的话返回THROTTLED决定,其它情况返回NO决定
ReplicaShardAllocator -> makeAllocationDecision(决定副本的分配)
1)、判断是否负责该ShardRouting对象的分配,需满足三个条件:不是主分片、未分配状态、被分配的原因不能是INDEX_CREATED,不满足的话返回不接受分配的决定(AllocateUnassignedDecision.NOT_TAKEN)
2)、通过allocation deciders检查改ShardRouting对象是否可以被至少一个节点所分配,不满足的话返回NO决定
3)、通过RoutingNodes获取到主分片ShardRouting信息,没有找到已分配主分片信息返回NO决定
4)、通过主分片ShardRouting获取主分片所在节点的存储元数据信息,没有相关信息则返回NOT_TAKEN决定
5)、找出可以分配的Node
6)、allocation type 不是 YES,返回NO决定;有最适合分配Node的话,调用allocation deciders作出决定;查看是否是延迟分配,其他条件返回NOT_TAKEN决定
4.12、GatewayAllocator分配完Shard之后,ShardsAllocator开始分配操作,ShardsAllocator的实例为BalancedShardsAllocator实现类,BalancedShardsAllocator负责集群中各节点的Shard均衡分布,下面说明ShardsAllocator对于Shard的分配,即allocate方法的执行过程,主要是Balancer平衡器的处理流程:
1)、从RoutingNodes中获取所有未分配的Shard(ShardRouting),将ShardRouting排序(主分片在前,副本在后;同一个索引的分片按id递增;索引优先级倒序;优先级相等按名称递增),计算每个Shard分配到这些节点的权重,需要将所有给定Shard分配到可以分配的最小权重值的节点上,初始化Shard,并保存到多个副本中,两个数组交换,循环,直到第一个数组为空
2)、移动那些不能驻留在原节的Shard,分配到适合的最小权重节点上:遍历所有节点上的运行的Shard(交叉遍历,每次取一个Node上的一个Shard),调用allocation decider以决定一个Shard是否可以留在原节点(不能待在原地或移动后集群状态会改善),如果不可以,则需要重新分配,更改数据结构,将Shard从原Node中删除,更新为RELOCATING状态,加入到新Node中
3)、根据权重函数平衡集群中Shard的分布,threshold阈值为最大权重节点和最小权重节点的权重差,权重值基于单个索引计算,将每个索引包含的Shard均衡分布,只有当索引分布的节点的权重差大于阈值时,平衡器才会尝试重新迁移Shard,默认情况下阈值被设置为1.0,这样能够强制Shard的重新分布,最终使得阈值接近0
首先获取当前所有Index按权重排序,并将Index所在的Node也按照权重排序(Node权重的极端考虑到了Index的因素),对于每个索引,找到包含改Index的或者可以分配该Index的Node,对于这些Node维护一个窗口,从权值最高的开始遍历,如果该Node有改Index的Shard,若窗口两端权重差值大于阈值,则将Shard从权值高的向权值低的移动,对于Index中的每个Shard,只有是STARTED状态的才能移动,这里需要allocation deciders来决定是否可以移动分配以及是否可以平衡调整,如果决定是YES或THROTTLE,则计算移动Shard之后的节点权值之差,如果差值变小了,说明是可以的,更新最小差值和改Shard信息,循环后会如果有候选Shard,则更改Node的数据结构(从原Node删除,添加到新Node),对于每个Index都会进行这样的处理,如果有移动操作的话,之后集群中Shard的分布会更加均衡
(5)、ClusterStateUpdateTask任务线程执行完成,得到了最新的ClusterState,上述创建索引并不涉及物理文件的创建,是逻辑上数据结构的改变,这些改变都被封装到了新的ClusterState对象中,下面将ClusterState发布并应用到本节点,主节点和从节点更新集群状态是同一套逻辑,只是主节点需要将新的ClusterState提交到集群中去,发布给其它从节点,每个节点收到请求后再应用新的集群状态并响应主节点。elasticsearch集群状态的更新都是采用这种ClusterService提交ClusterStateUpdate任务的机制来同步数据,主节点直接更新,从节点接收到主节点发布的ClusterState之后更新,入口是ClusterService的publishAndApplyChanges方法,这里的publish只有主节点才会进行操作,从节点只需要ApplyChanges
(6)、更新节点为最新的ClusterState状态:
6.1、生成ClusterChangedEvent
6.2、通过ClusterChangedEvent和新的ClusterState调用ClusterStateAppliers更新本节点的数据,ClusterStateApplier接口只有一个void applyClusterState(ClusterChangedEvent event)方法,实现类需要将新的集群状态应用同步到本节点中,主要有下面几个实现类,每个实现分别为集群状态做出不同的改变,下面说明每个实现的处理过程
6.2.1、IndicesClusterStateService(主要有下面八个处理流程):
updateFailedShardsCache(state):在恢复索引的过程中会缓存一份启动失败的Shard列表,防止每次ClusterState状态更新时反复恢复失败的Shard,这里是更新该缓存,删除RoutingTable中没有映射到该 节点的Shard,如果在本节点的路由表中还能找到匹配的ShardRouting信息,则通过ShardStateAction向主节点报告它没有正常的移除该Shard
deleteIndices(event):取得ClusterChangedEvent中标记为删除的Index,调用IndicesService的removeIndex方法删除索引,包括从索引数据结构中删除及清除索引的物理文件夹
removeUnallocatedIndices(event):调用IndicesService的removeIndex方法删除那些本节点RoutingNode中没有相应Shard的Index,但这里跟deleteIndices操作不同,并不清除物理数据,直到相应Index的 足够多的Shard出现在集群中时才由IndicesStore触发删除物理文件操作
failMissingShards(state):通知主节点那些本该在本节点启动然而并不存在于本节点的Shard的失败信息
removeShards(state):删除那些存在于IndicesService信息中,但是已经不在本节点RoutingTable中的Shard,要按照主节点指定的RoutingTable信息走,所以调用IndexService的removeShard方法删除, 并不涉及物理文件的删除,同样由后续IndicesStore完成文件清理
上面五个操作涉及删除Index及Shard的类型主要总结为:应该删除的、不再归本节点管理的、启动失败的、路由信息不对的。
下面三个是更新和创建操作。
updateIndices(event):遍历本节点IndicesService中存储的索引信息,查看现有的IndexMetaData对象是否等于新的ClusterState中的IndexMetaData信息,如果不相等说明索引信息有变,通过 IndexService的updateMeta(newIndexMetaData)更新索引元数据信息,并通过MapperService检查mapping是否需要更新,新的mapping与原有mapping不同则调用NodeMappingRefreshAction的 nodeMappingRefresh方法向主节点发送refresh mapping请求,主节点收到请求后调用MetaDataMappingService发起ClusterStateUpdateTask更新集群状态,刷新同步mapping,如果更新mapping出错则调 用IndicesService的removeIndex方法删除索引,向主节点报告Shard失败信息,失败的Shard会在createOrUpdateShards操作中被创建或更新
createIndices(state):为有已分配信息的Shard存在的Index创建IndexService对象,将本节点需要创建的Shard按照索引分组,每个Index有对应的IndexMetaData和IndexService对象,现在通过IndicesServive为每个Index的IndexMetaData创建相应的IndexService对象,并跟updateIndices操作中类似,通过IndexService的updateMapping方法更新mapping信息,失败同样需要删除索引并向主节点汇报情况
到现在为止包含索引信息的物理文件夹还没有被创建出来,下面createOrUpdadeShards方法主要负责文件操作。
createOrUpdateShards(state):遍历ClusterState中本节点需要负责的Shard列表,对于每个ShardRouting,如果没有被之前标记失败的话,从IndicesService中查找Index索引对应的IndexService,根据Shard的id从IndexService中检索出该Shard信息,有则更新,没有则创建:
1)、创建Shard的过程(createShard方法):
说明: RecoverySource类表示创建Shard时的数据来源,主要有四种:StoreRecoverySource(从本地存储仓库Store中恢复,存储仓库是空的或者已有数据)、PeerRecoverySource(从位于其它节点的主分片上获取数据恢复)、SnapshotRecoverySource(从快照数据恢复)、LocalShardsRecoverySource(从本节点其它Index的Shard获取数据恢复),RecoverySource内部枚举类Type用于表示这几种情况,分别有EMPTY_STORE, EXISTING_STORE, PEER, SNAPSHOT, LOCAL_SHARDS方式
Shard在恢复过程中由RecoveryState对象表示其恢复状态,内部枚举类Stage将状态分为六个阶段:INIT(初始化阶段)->INDEX(恢复Lucene文件,重用本地节点或从其它节点获取数据)->VERIFY_INDEX(索引信息检查)->TRANSLOG(重放translog文件中的操作)->FINALIZE(translog执行完之后的一些结尾工作)->DONE(Shard恢复完成)
NodeEnvironment类存有本节点所有的数据文件路径信息,Store类提供了直接读写Lucene索引文件的功能,同时可以获取文件的元数据信息
1.1)、查看该ShardRouting的恢复源RecoverySource的类型,如果是PEER,即从位于其它节点的主分片上获取数据来创建Shard,则从RoutingTable中存储的路由信息得到该索引主分片所在的节点对象DiscoveryNode,调用IndicesService的createShard方法开始创建Shard
1.2)、IndexEventListener beforeIndexShardCreated处理操作
1.3)、从数据目录和状态目录中加载Shard数据,如果有多个文件夹都存有有效的Shard状态信息则使用最大version值那个,如果加载失败则将残余的Shard数据删除再重新加载,残余Shard数据与新Shard的UUID不同而索引名相同会阻碍新Shard的创建
1.4)、如果没有相关文件信息,则为Shard选择新的文件目录,同时考虑到用户提供的自定义目录参数,优先选择空间最大的目录
1.5)、为Shard创建Store对象,每个Shard都有一个专用的Store,用于文件的读写,构建IndexShard对象,IndexShard存有Shard相关的各种信息,并将ShardRouting元数据信息写入文件,接下来按照恢复源开始恢复Shard,现在就是一个空的Shard,不同的恢复源有不同的流程:
EMPTY_STORE/EXISTING_STORE恢复方式:
只有主分片而且是INITIALIZING状态才能从Store恢复,IndexShard通过StoreRecovery的internalRecoverFromStore方法从仓库中恢复Shard数据,首先读取最后一次提交的segments信息,但是如果恢复类型是EXISTING_STORE并且没有读到相关segments信息则会抛出异常,将获取到的segments的version信息更新,调用StoreRecovery的internalRecoverFromStore方法,接下来调用IndexShard的performTranslogRecovery方法执行恢复,RecoveryState更新为VERIFY_INDEX阶段,校验Index文件的数据信息后转到TRANSLOG阶段,开始重放translog操作,具体操作模式分为三种:CREATE_INDEX_AND_TRANSLOG(索引不存在的情况,创建索引并回放translog)、OPEN_INDEX_CREATE_TRANSLOG(索引存在但忽略translog重放)、OPEN_INDEX_AND_TRANSLOG(索引存在,打开索引并回放translog),为索引打开引擎Engine构建配置信息EngineConfig,EngineConfig持有上述操作模式,Shard信息,索引配置信息,存储,合并策略,分析器,similarity等等用于构建Engine的所有信息,Engine实例被创建出来之后,修改EngineConfig会影响到关联的Engine实例,具体的实例是InternalEngine类。InternalEngine调用recoverFromTranslog方法从translog恢复数据,如果失败Engine将会被关闭。
获取translog的generation信息,给translog创建快照,调用TranslogRecoveryPerformer的recoveryFromSnapshot方法从快照开始恢复,根据translog中Operation(INDEX or DELETE)做不同处理,如果是INDEX操作,从Translog.Operation中获取该文档信息,需要的话更新mapping,通过InternalEngine索引文档,最终是调用Lucene的IndexWriter类的addDocuments进行索引操作,如果是DELETE操作则调用IndexWriter的deleteDocuments删除文档
PEER恢复方式:
PeerRecoveryTargetService类的startRecovery方法执行恢复,随后在一个线程中执行其doRecovery方法,通过transportService.submitRequest向拥有主分片的节点source node发送start recovery请求,该请求被source node的PeerRecoverySourceService接收到,后面主要由该类负责后续恢复工作,PeerRecoverySourceService调用StartRecoveryTransportRequestHandler进行处理,主要分为三个阶段:phase1-传送索引文件(不阻塞请求,不允许flush操作),phase2-传送translog文件(不阻塞请求,不允许flush操作),phase3-传送在phase2的过程中的tranlog新加数据(阻塞请求)
SNAPSHOT恢复方式:
从快照中恢复,获取到Shard对应的Repository,调用StoreRecovery的recoverFromRepository方法进行恢复,将之前创建的索引快照恢复到现在正在初始化的Shard上,BlobStoreRepository负责数据文件的读写操作
LOCAL_SHARDS恢复方式:
这种方式只适用于主分片的恢复,调用StoreRecovery的recoverFromLocalShards进行恢复,更新mapping,通过IndexWriter添加索引,调用StoreRecovery的internalRecoverFromStore方法,后面与EMPTY_STORE和EXISTING_STORE恢复逻辑类似
Shard创建完成之后回调ShardStateAction的shardStarted方法,向主节点报告Shard已就绪,主节点回应后就激活该Shard。
1.6)、IndexEventListener indexShardStateChanged处理操作
1.7)、IndexEventListener afterIndexShardCreated处理操作
如果创建失败则将其删除并向主节点汇报。
2)、更新Shard的过程(updateShard方法):
主要更新Shard的ShardRouting路由信息,并通过ShardStateAction向主节点汇报Shard已启动消息。
6.2.2、PipelineStore:更新pipeline IngestMetadata元数据信息
6.2.3、PipelineExecutionService:更新pipeline统计相关信息
6.2.4、RepositoriesService:检查新的集群元数据信息,查看是否有新的repository出现或删除并更新当前repository列表信息
6.2.5、RestoreService:清理Shard恢复过程快照、状态等信息,如果是主节点需要同步ClusterState中的恢复状态信息(restore state)
6.2.6、IngestActionForwarder:将ingest请求转发给ingest nodes,采取轮询方式的均衡转发
6.2.7、GatewayAllocator类中匿名内部类:从ClusterChangedEvent中获取本节点的对象DiscoveryNode,如果本节点没有对应的对象或者本节点是候选主节点但不是主节点则清理asyncFetchStarted获取的其它节点Shard的数据缓存
6.2.8、TaskManager:删除被节点注册但不存在与集群状态中Task,取消那些已不存在于集群中的节点提交的支持被取消的Task
6.2.9、SnapshotsService:如果本节点是主节点,清除已经被删除节点存有的Shard快照状态信息,RoutingTable路由信息有变更则更新Shard的快照状态
6.2.10、SnapshotShardsService:检查本节点是否有新的Shard需要创建快照信息,并将Shard最新状态通知主节点
6.2.11、Gateway:更新MetaData集群元数据信息
ClusterState更新完成。索引已经有了物理文件夹,里面包含state、translog、segments等文件,现在只有一些元数据信息,还没有索引数据。
(7)、物理索引创建完成。每次索引创建成功或失败,TransportBulkAction中维护的计数器减1,为0后即所有索引创建完成,可能有创建失败的索引,对于失败的索引构建错误信息,不涉及后续的写数据操作,成功的索引会交由TransportBulkAction继续处理
(8)、TransportBulkAction调用其executeBulk方法执行继续批处理操作,executeBulk操作首先会遍历所有的请求,构建每个分片到请求列表的映射关系,即按照Shard将请求分组,然后对于每个Shard的批处理请求就交给TransportShardBulkAction实例处理,TransportShardBulkAction负责单个Shard的批处理操作
(9)接下来是对请求的路由处理(找到主节点所在Node,索引数据),路由处理被封装到了ReroutePhase线程中执行,该操作为同步操作,直接调用ReroutePhase类的run方法,ReroutePhase主要负责查找路由表找出主Shard所在的Node,通过transportService.sendRequest将请求发送出去,如果本节点拥有主Shard,则自己接收到请求后处理,实际的主分片上的操作是在拥有主分片的节点上执行的。
PrimaryOperationTransportHandler会接收到这个请求,每个内部协议action name都有对应的Handler处理类,Handler是通过transportService.registerRequestHandler方法进行注册绑定的,这里的action name为indices:data/write/bulk[s][p],处理类为PrimaryOperationTransportHandler。收到索引请求后,直接调用AsyncPrimaryAction线程的run方法处理,这里也是一个同步操作。
首先找出Shard对应的IndexShard实例,获取IndexShard上主分片操作需要的锁,实际上是获取IndexShardOperationsLock类中Semaphore信号量的一个许可(主副Shard的操作都完成后才释放许可),获取许可之后回调AsyncPrimaryActioin的onResponse方法,并将存有IndexShard和锁信息的PrimaryShardReference作为参数,后面通过PrimaryShardReference进行主分片上的操作
判断主分片是否是已迁移状态,是的话说明主分片被迁移到其它Node了,需要通过ClusterState查找主分片现在所在的Node,将索引请求发送出去,否则调用ReplicationOperation的execute方法
active shard count检查,查看目前运行中的Shard数量是否满足要求,默认路由表中运行中Shard数量至少有1个,Shard数量不足的话不能执行写数据操作,满足的话调用PrimaryShardReference的perform方法,然后又回到了TransportShardBulkAction类中,执行其shardOperationOnPrimary方法,对于Shard上的每个索引请求由executeBulkItemRequest方法来处理,executeBulkItemRequest通过IndexShard的index(Engine.Index)方法处理索引操作,每次执行完会返回Translog的Location信息(这次写数据所对应的Translog的位置),下一次数据写操作从上次位置再开始写入
IndexShard的index(Engine.Index)方法处理一个写请求的过程:
1)、解析请求得到ParsedDocument对象,ParsedDocument存储解析后的数据信息,包括mapping,field,source,routing等,另外构造Term对象(Term是Lucene类,代表文本中的一个单词,是最小的搜 索单元),Term中包含type和id信息,内部用_uid字段表示,是文档存在与否的标志,用于定位文档,存在的话需要删除再进行索引
2)、如果现有mapping与source不匹配,则通过MappingUpdatedAction给主节点发送更新mapping请求,走一遍更新集群状态的流程,所有节点会收到带有mapping更新信息的新的ClusterState对象,从节点 们包括本节点都会更新,之后再检查mapping是否更新,没有成功更新则报错结束
3)、mapping更新成功后,则调用IndexShard的index的方法索引数据,IndexShard调用InternalEngine的index方法进行处理,InternalEngine是Shard的索引引擎,负责底层的数据处理工作,InternalEngine 索引数据之前需要获取它的读锁,然后调用innerIndex方法,这里真正执行写文件处理,首先需要获取该文档_uid上的锁,如果该文档存在,释放锁之前其他线程不能对其修改:
说明一下Lucene的索引和更新操作,主要涉及Lucene的IndexWriter类,索引文档(添加文档)、更新文档、删除文档等操作都是由该类负责,IndexWriter的addDocuments和addDocument方法是索引文档操作,同一份文档索引多次后都会存在索引中,并不会覆盖;IndexWriter的updateDocuments和updateDocument方法是更新文档操作,Lucene通过Term类来定位文档,确定原文档是否存在,存在则删除后再索引,不存在则正常索引,Lucene并不支持局部字段的更新,Elasticsearch通过请求体中添加doc或script方式提供局部字段更新功能
Elasticsearch的version类型主要有INTERNAL(只有原文档和当前文档version值相等时才进行索引),EXTERNAL(只有当前文档大于原文档的version值才进行索引),EXTERNAL_GTE(只有当前文档大于或等于原文档的version值才进行索引)三种
Elasticsearch的索引和更新操作最终是同一个函数逻辑,这就需要能根据请求判断是调用Lucene的索引还是更新方法:
当文档ID是Elasticsearch自动生成的并且索引类型不是从本地translog恢复时,如果当前文档的ID不大于maxUnsafeAutoIdTimestamp(每次保存最后一次文档的ID),这里将原文档version置为NOT_FOUND,需要采用强制更新操作(强制更新不进行version冲突检查,直接调用IndexWriter的updateDocument方法);其他情况需要进行version冲突检查,没有version冲突才会进行更新操作
只有原文档不存在或已被删除并且不需要强制更新,则进行索引操作,其他情况进行更新操作;Job刷索引没有提供version值和类型,默认类型是INTERNAL,默认version值为MATCH_ANY,所以不会冲突,都是调用的更新操作,即IndexWriter.updateDocument
调用Translog的add方法把该请求写入translog,写入后会返回当前写入的偏移量,下一次写入将从该位置开始
返回索引操作的结果,接着处理批处理中的下一个请求(上面的1,2,3操作)
所有主分片索引请求处理完成之后,从request中得到批处理响应,new WritePrimaryResult返回,在WritePrimaryResult的构造函数中,调用AsyncAfterWriteAction线程的run方法进行一些写索引后处理操作
AsyncAfterWriteAction run方法的后处理过程:
根据请求中的RefreshPolicy检查是否需要refresh操作,RefreshPolicy枚举类有三个类型:NONE(默认,这次索引请求处理后不进行refresh,索引一个文档后不能立即被搜到,有延迟),IMMEDIATE(处理完索引请求后需要立即refresh,实时搜索),WAIT_UNTIL(等待例行的刷新操作,默认1秒1次refresh,有延迟),只有refresh操作之后文档才能被搜索到
刷新策略是IMMEDIATE的话调用IndexShard的refresh方法,然后调用InternalEngine的refresh方法,最终交由Lucene进行处理,调用SearcherManager的maybeRefreshBlocking方法,SearcherManager是Lucene的搜索管理类,maybeRefreshBlocking可能会因当前已有refresh操作而阻塞,等待当前refresh操作完成之后再进行refresh
检查是否需要flush操作,当translog文件的大小超过阈值时执行flush,阈值通过index.translog.flush_threshold_size配置,默认512MB,满足的话进行flush操作
InternalEngine调用flush方法执行flush操作,主要包括以下几个步骤:
a、依次获取InternalEngine的读锁和flush锁
b、获取translog的写锁,复制一份当前的检查点文件translog.ckp,名称为translog-{generation}.ckp,其中{generation}为当前要提交的translog的generation(比如translog-13.ckp),通过fsync系统操作将文件translog-13.ckp同步到磁盘,并将当前目录数据同步到磁盘,因为当前translog文件中的操作对应的内存中的段数据要持久化到磁盘上,所以此时translog需要加锁,不能让数据再写入,所以需要创建新的TranslogWriter对象,用于对新的translog执行写操作,其generation为当前generation+1,即14,新的translog文件为translog-14.tlog,目前文件为[translog-13.tlog,translog-14.tlog,translog.ckp,translog-13.ckp]
c、提交translog-14.tlog对应的IndexWriter(Lucene类)的数据,包括translog_generation和translog_uuid数据
调用translog-13.tlog对应IndexWriter(Lucene类)的commit方法,所有索引相关的增加或删除的文档、合并的段、新加的索引等信息都会被持久化到磁盘上,这样就保证了即使宕机数据也可以被恢复,不会造成数据丢失
d、refresh操作、清除老版本数据,之后文件为[translog-14.tlog,translog.ckp]
e、每次flush操作都会生成CommitId对象,CommitId存有此次flush的uuid(字节数组)信息,此次没有commit操作的话将上次CommitId返回主分片操作完成后,返回ReplicationOperation的execute方法中,然后调用performOnReplicas方法进行副本的索引操作,后续通过transportService.sendRequest将请求发送出去,TransportReplicationAction类中的ReplicaOperationTransportHandler负责副本索引请求的处理,调用AsyncReplicaAction线程的run方法,然后又回到了TransportShardBulkAction类中,执行其shardOperationOnReplica方法,最终也是调用IndexShard的index(Engine.Index)方法处理一个写请求的过程,与上面1,2,3,步骤一致,只是这里IndexShard是副本Shard对应的对象
主副分片数据都写入完成后给客户端返回响应。
附录
官方博客
每个Shard都该有个家:https://www.elastic.co/blog/every-shard-deserves-a-home