Elasticsearch-CCR简介
Elasticsearch CCR是 Elastic 提供的跨集群数据同步的能力。官方在 6.7 版本以上提供了此功能。
此功能以 x-pack 插件的形式提供,遵循Elasticsearch自定义的开源协议,为商业付费功能。关于官方的介绍,可以参考 追随领导者:Elasticsearch 中的跨集群复制简介 来查看。
ES-CCR模型简介
Elasticsearch CCR中,设置了 Leader 和 Follower 的角色。Follower 角色通过 Pull 的方式从 Leader 获取更新的变更,主要有如下两个过程:
- 历史数据全量同步:Follower通过远程构造snapshot方式,从Leader同步全量数据
- 增量数据:Follower不断检查Leader是否有更新,并以Translog Snapshot的形式同步过来,实现同步更新
ES-CCR周边功能
CCR模块中,主要是完成了 Follower-Leader 的 Pull 模型。除此之外,还提供了其他周边的功能:自动发现前缀匹配、自动更新mapping、查看同步状态等功能。这些功能作为生态的一部分,增强了CCR的易用性、实用性。
Elasticsearch-CCR源码解析
本文基于Elasticsearch 7.10分支,在 6.7 到 7.10 的迭代中,CCR核心代码有部分实现进行了变更,但是核心思路是统一的,读者可根据手头的代码版本,酌情进行适当的思考。
如上文所说,Elasticsearch CCR 除实现了最简单的 Pull 模型外,还实现了提升易用性的监控、自动发现等功能。本文将主要聚焦于 Pull 模型的实现,而暂时忽略周边功能的设计。
最初的起点
从配置获取信息
Elasticsearch CCR 属于 x-pack 的扩展功能,在工程结构上,位于x-pack/plugin/ccr module 下。
在打包后,该 module 的产物是一个 名为 x-pack-ccr-7.10.0 的jar 和zip,其中包含了 es 针对于插件的规范定义文件 plugin-descriptor.properties
。CCR模块的插件定义文件具体如下:
# Elasticsearch plugin descriptor file
# This file must exist as 'plugin-descriptor.properties' inside a plugin.
#
### example plugin for "foo"
#
# foo.zip <-- zip file for the plugin, with this structure:
# |____ <arbitrary name1>.jar <-- classes, resources, dependencies
# |____ <arbitrary nameN>.jar <-- any number of jars
# |____ plugin-descriptor.properties <-- example contents below:ssname=foo.bar.BazPlugin
description=Elasticsearch Expanded Pack Plugin - CCR
version=7.10.0-SNAPSHOT
name=x-pack-ccr
classname=org.elasticsearch.xpack.ccr.Ccr
java.version=1.8
elasticsearch.version=7.10.0
### optional elements for plugins:
#
# 'extended.plugins': other plugins this plugin extends through SPI
extended.plugins=x-pack-core
#
# 'has.native.controller': whether or not the plugin has a native controller
has.native.controller=false
以上便是CCR的插件定义,由上面可以看出,该 module 最终以 x-pack-ccr
为名的插件提供服务,入口类是 org.elasticsearch.xpack.ccr.Ccr
。
探究主类加载过程
本小节中,主要介绍 Ccr 启动时主类都做了什么,并针对于CCR核心链路的加载发现机制进行说明。
Ccr类作为 x-pack-ccr 模块的主入口,遵从 ES 插件开发规范,继承了 Plugin,同时实现了多个扩展Plugin的接口,用来被这些特定场景通过接口加载。
Ccr类中,实现了包括组件注册(CCR监控组件、Licence检测组件等)、Handler注册(注册CCR相关的请求和handler之间的关系)以及Task注册(注册任务用来CCR增量同步)等。
为了聚焦于核心模型的实现,我们首先把关注点放在 Ccr 的 Ccr#getActions
和 Ccr#getPersistentTasksExecutor
方法。其实现具体如下:
@Override
public List<PersistentTasksExecutor<?>> getPersistentTasksExecutor(ClusterService clusterService,
ThreadPool threadPool,
Client client,
SettingsModule settingsModule,
IndexNameExpressionResolver expressionResolver) {
return Collections.singletonList(new ShardFollowTasksExecutor(client, threadPool, clusterService, settingsModule));
}
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
if (enabled == false) {
return emptyList();
}
return Arrays.asList(
// internal actions
new ActionHandler<>(BulkShardOperationsAction.INSTANCE, TransportBulkShardOperationsAction.class),
new ActionHandler<>(ShardChangesAction.INSTANCE, ShardChangesAction.TransportAction.class),
new ActionHandler<>(PutInternalCcrRepositoryAction.INSTANCE,
PutInternalCcrRepositoryAction.TransportPutInternalRepositoryAction.class),
new ActionHandler<>(DeleteInternalCcrRepositoryAction.INSTANCE,
DeleteInternalCcrRepositoryAction.TransportDeleteInternalRepositoryAction.class),
new ActionHandler<>(PutCcrRestoreSessionAction.INSTANCE,
PutCcrRestoreSessionAction.TransportPutCcrRestoreSessionAction.class),
new ActionHandler<>(ClearCcrRestoreSessionAction.INSTANCE,
ClearCcrRestoreSessionAction.TransportDeleteCcrRestoreSessionAction.class),
new ActionHandler<>(GetCcrRestoreFileChunkAction.INSTANCE,
GetCcrRestoreFileChunkAction.TransportGetCcrRestoreFileChunkAction.class),
// stats action
new ActionHandler<>(FollowStatsAction.INSTANCE, TransportFollowStatsAction.class),
new ActionHandler<>(CcrStatsAction.INSTANCE, TransportCcrStatsAction.class),
new ActionHandler<>(FollowInfoAction.INSTANCE, TransportFollowInfoAction.class),
// follow actions
new ActionHandler<>(PutFollowAction.INSTANCE, TransportPutFollowAction.class),
new ActionHandler<>(ResumeFollowAction.INSTANCE, TransportResumeFollowAction.class),
new ActionHandler<>(PauseFollowAction.INSTANCE, TransportPauseFollowAction.class),
new ActionHandler<>(UnfollowAction.INSTANCE, TransportUnfollowAction.class),
// auto-follow actions
new ActionHandler<>(DeleteAutoFollowPatternAction.INSTANCE, TransportDeleteAutoFollowPatternAction.class),
new ActionHandler<>(PutAutoFollowPatternAction.INSTANCE, TransportPutAutoFollowPatternAction.class),
new ActionHandler<>(GetAutoFollowPatternAction.INSTANCE, TransportGetAutoFollowPatternAction.class),
new ActionHandler<>(ActivateAutoFollowPatternAction.INSTANCE, TransportActivateAutoFollowPatternAction.class),
// forget follower action
new ActionHandler<>(ForgetFollowerAction.INSTANCE, TransportForgetFollowerAction.class));
}
上文代码是Ccr类中关于主流程的最核心部分。
其中,getAction 方法的作用是声明了Ccr相关请求和Handler的映射关系,以用来实现相关Action的处理。在Ccr的设计中,所有的 变化 都会被抽象成一种 Action,并后面被Handler进行捕获和处理。如注释中所描述,Ccr相关的Action主要分为以下五类:
- Internal Action:内部行为,主要包括分片变化进行处理的行为、有需要写数据的行为等,这一部分承载了Ccr内部实现的核心逻辑,属于核心实现;
- Stats Action:状态行为,该部分主要是提供获取follow ccr的状态的行为;
- Follow Action:跟随者行为,该部分为follow相关的入口,提供控制follow开启同步、结束同步等操作的行为;
- Auto-Follow Action:自动跟随行为,该部分同跟随者行为部分,主要提供自动follow等操作的行为;
- Forget Follower Action:忘记跟随者行为
ShardFollowTasksExecutor方法则是完成了 ShardFollowTasksExecutor 的注册,用来执行 ShardFollowTask。
此外,我们关注 Ccr 的 getInternalRepositories
和 getEngineFactory
方法,其定义如下:
public Optional<EngineFactory> getEngineFactory(final IndexSettings indexSettings) {
if (CCR_FOLLOWING_INDEX_SETTING.get(indexSettings.getSettings())) {
return Optional.of(new FollowingEngineFactory());
} else {
return Optional.empty();
}
}
@Override
public Map<String, Repository.Factory> getInternalRepositories(Environment env, NamedXContentRegistry namedXContentRegistry,
ClusterService clusterService, RecoverySettings recoverySettings) {
Repository.Factory repositoryFactory =
(metadata) -> new CcrRepository(metadata, client, ccrLicenseChecker, settings, ccrSettings.get(),
clusterService.getClusterApplierService().threadPool());
return Collections.singletonMap(CcrRepository.TYPE, repositoryFactory);
}
getEngineFactory方法的作用是:在indexSettings中存在ccr配置的时候,该方法会被Node类加载,从而在有ccr配置的索引创建被提交的时候,将该索引的Engine设置为 FollowEngine,FollowEngine中的实现有别于默认的InternalEngine,以更好的实现 CCR 的需要,此部分在此不进行深入解析。
同理,getInternalRepositories 方法用于提供CcrRepository用来替代默认Repository实现,该实现中主要提供了从远程集群拉取snapshot的实现。
除以上几个核心方法外,Ccr类中还实现了一些rest handler注册、参数转换等其他方法,这部分在此不详细展开。
提交一个 Follow 请求
在官网的示例中,我们可以通过如下方式来提交一个follow请求:
curl -XPUT "localhost:9200/server-metrics-copy/_ccr/follow?wait_for_active_shards=1&pretty" -H 'Content-Type: application/json' -d'
{
"remote_cluster" : "leader",
"leader_index" : "server-metrics"
}
'
首先,要明白这个请求是如何接受的,就需要找到代码里处理该请求的RestHandler。从代码中可以定位到是 RestPutFollowAction 处理了这一个请求,而 RestPutFollowAction 中,是通过提交 PutFollowAction 来实现的,PutFollowAction 在上文中,已经给出了是通过 TransportPutFollowAction 来监听并处理的。所以我们也很清晰的找到,TransportPutFollowAction是 CCR 处理的入口。
小结
Elasticsearch CCR 源码的阅读,需要一些 Elasticsearch 开源设计的基础,本篇作为系列的第一篇,主要是介绍了CCR模块是如何加载并且工作起来的,同时也涉及到了一些框架加载上设计的了解,最后成功的了解到了一个follow请求是如何被接受并执行的。
本文碍于篇幅,仅完成了Ccr主类加载相关的代码解析,并没有进行全量同步/增量同步的分析,有关于 Pull 模型的具体分析,和一个 PutFollowAction 的具体响应流程,将会在后续的文章中一一揭秘。
希望阅读完本文,能让大家对ES插件机制、和插件开发的方式以及整体工作模式,有一个最基本的认识。