SeaTunnel
SeaTunnel:易用、高性能、支持实时流式和离线批处理的海量数据集成平台
简介
1. 什么是SeaTunnel
SeaTunnel是一个非常易用、高性能、支持实时流式和离线批处理的海量数据集成平台,架构于
SeaTunnel专注于数据集成和数据同步,主要解决数据集成领域的常见问题:
-
数据源多样:常用的数据源有数百种,版本不兼容。随着新技术的出现,出现了更多的数据源。用户很难找到能够全面快速支持这些数据源的工具。
-
复杂同步场景:数据同步需要支持离线-全量同步、离线-增量同步、CDC、实时同步、全库同步等多种同步场景。
-
资源需求高:现有的数据集成和数据同步工具往往需要大量的计算资源或JDBC连接资源来完成海量小表的实时同步。这在一定程度上加重了企业的负担。
-
缺乏质量和监控:数据集成和同步过程经常会丢失或重复数据。同步过程缺乏监控,无法直观了解任务过程中数据的真实情况。
-
技术栈复杂:企业使用的技术组件各不相同,用户需要针对不同的组件开发相应的同步程序来完成数据集成。
-
管理维护困难:受限于不同的底层技术组件(Flink/Spark),离线同步和实时同步往往是分开开发和管理的,增加了管理和维护的难度。
2. 系统架构与工作流程
系统架构
上图为 SeaTunnel 的整个工作流程,数据处理流水线由多个过滤器构成,以满足多种数据处理需求。如果用户习惯了 SQL,也可以直接使用 SQL 构建数据处理管道,更加简单高效。目前,SeaTunnel 支持的过滤器列表也在扩展中。
-
丰富且可扩展的Connector:SeaTunnel提供了不依赖于特定执行引擎的Connector API。基于此API开发的连接器(Source、Transform、Sink)可以运行在很多不同的引擎上,比如目前支持的SeaTunnel Engine、Flink、Spark。
-
Connector插件:插件式的设计让用户可以很方便的开发自己的Connector,并集成到SeaTunnel项目中。目前,SeaTunnel 已支持 100 多个 Connector,而且数量还在激增。
-
批流融合:基于SeaTunnel Connector API开发的Connector,完美兼容离线同步、实时同步、全量同步、增量同步等场景。大大降低了管理数据集成任务的难度。
-
支持分布式快照算法,保证数据一致性。
-
多引擎支持:SeaTunnel默认使用SeaTunnel Engine进行数据同步。同时,SeaTunnel也支持使用Flink或Spark作为Connector的执行引擎,以适配企业现有的技术组件。SeaTunnel 支持多个版本的 Spark 和 Flink。
-
JDBC多路复用,数据库日志多表解析:SeaTunnel支持多表或全库同步,解决了JDBC连接过多的问题;支持多表或全库日志读取和解析,解决了CDC多表同步场景需要重复读取和解析日志的问题。
-
高吞吐低延迟:SeaTunnel支持并行读写,提供高吞吐低延迟稳定可靠的数据同步能力。
-
完善的实时监控:SeaTunnel支持数据同步过程中每一步的详细监控信息,让用户轻松了解同步任务读写的数据量、数据大小、QPS等信息。
-
支持两种作业开发方式:编码和画布设计:提供了作业的可视化管理、调度、运行和监控能力。
工作架构
工作架构图如下:
-
用户配置作业信息,选择执行引擎提交作业。
-
Source Connector负责并行读取数据并将数据发送给下游Transform或直接发送给Sink,Sink将数据写入目的地。值得注意的是,无论是Source还是Transform和Sink,都可以很方便的自行开发扩展。
-
SeaTunnel 是一个 EL(T) 数据集成平台。因此,在SeaTunnel中,Transform只能用于对数据进行一些简单的转换,例如将某列的数据转换为大写或小写,更改列名,或者将一列拆分为多列。
-
SeaTunnel 使用的默认引擎是SeaTunnel Engine。如果您选择使用Flink或Spark引擎,SeaTunnel会将Connector打包成Flink或Spark程序提交给Flink或Spark运行。
-
Source Connectors SeaTunnel 支持从各种关系数据库、图形数据库、NoSQL 数据库、文档数据库和内存数据库中读取数据。HDFS等各种分布式文件系统。S3、OSS等多种云存储。同时我们也支持很多常见的SaaS服务的数据读取。
-
转换连接器如果源和接收器之间的架构不同,您可以使用转换连接器更改从源读取的架构,使其与接收器架构相同。
-
Sink Connector SeaTunnel 支持向各种关系数据库、图数据库、NoSQL 数据库、文档数据库和内存数据库写入数据。HDFS等各种分布式文件系统。S3、OSS等多种云存储。同时我们也支持向很多常见的SaaS服务写入数据。
初始构建
1. 安装Java
需要提供jdk1.8以上的版本环境
2. 下载SeaTunel
# 执行如下命令,速度可能比较缓慢
wget -b "https://archive.apache.org/dist/incubator/seatunnel/2.3.1/apache-seatunnel-incubating-2.3.1-bin.tar.gz"
# 下载完毕后,进行解压缩
tar -zxvf apache-seatunnel-incubating-2.3.1-bin.tar.gz
3. 安装连接器
从2.2.0-beta开始,二进制包默认不提供connector依赖,所以第一次使用时,我们需要执行如下命令安装connector:(当然你也可以手动下载connector从repo.maven.apache.org/maven2/org/…下载,然后手动移动到connectors/seatunnel目录.
# 此方法太慢了,建议从本地网上直接下载来所有的包
sh bin/install-plugin.sh 2.3.1
通常你不需要所有的连接器插件,所以你可以通过配置指定你需要的插件config/plugin_config,比如你只需要connector-console插件,那么你可以修改plugin_config为
--connectors-v2--
connector-console
--end--
如果你想让示例应用程序正常工作,你需要添加以下插件
--connectors-v2--
connector-fake
connector-console
--end--
注意点:需要在环境变量中配置seatunnel的全局环境变量。
您可以在${SEATUNNEL_HOME}/connectors/plugins-mapping.properties下找到所有支持的连接器和相应的 plugin_config 配置名称。
提示:
如果想通过手动下载connector的方式安装connector插件,需要特别注意以下几点
connectors目录包含以下子目录,如果不存在,需要手动创建
seatunnel
如果想手动安装V2 connector插件,只需要下载自己需要的V2 connector插件,放到seatunnel目录下即可。
下载完毕好的connector插件,需要复制到lib文件夹下。
4. 快速启动作业
添加作业配置文件以定义
编辑config/v2.batch.config.template,决定了seatunnel启动后数据输入、处理、输出的方式和逻辑。下面是一个配置文件的例子,和上面提到的例子应用是一样的。
env {
execution.parallelism = 1
job.mode = "BATCH"
}
source {
FakeSource {
result_table_name = "fake"
row.num = 16
schema = {
fields {
name = "string"
age = "int"
}
}
}
}
sink {
Console {}
}
运行SeaTunnel
可以通过以下命令启动应用程序
cd "apache-seatunnel-incubating-${version}"
./bin/seatunnel.sh --config ./config/v2.batch.config.template -e local
查看输出:运行命令时,您可以在控制台中看到它的输出。您可以认为这是命令运行成功与否的标志。
需要注意的是:当第一次启动的官方的例子的时候,出现错误,找不到hadoop相关的包,追踪原因,发现lib下缺失包需要下载。

5. 构建Seatunnel服务引擎
在本地模式下启动成后,检测认证seatunnel运行正常,但是只能提交一次任务执行,不符合当前使用场景,根据相关官方文档的参考,需要启动setunnel服务。
# 使用命令
# -d 后台运行
sh /bin/seatunnel-cluster.sh -d
参考日志
出现以上信息后,可以认为服务启动成功,也可以使用jps能够显示出seatunnel Server的进程。
基础概念
配置文件
在SeaTunnel中,最重要的是Config文件,通过它用户可以自定义自己的数据 同步要求,以最大限度地发挥SeaTunnel的潜力。
Config文件的主要格式是
一个完整的SeaTunnel配置文件应包含四个配置文件,分别是:
env{} source{} transform{} sink{}
env {
job.mode = "BATCH" # 设置配置可选参数
}
-- 定义了数据源信息,可以同时定义多个数据源,每一个数据源有自己的特定参数定义如何获取
source {
FakeSource {
result_table_name = "fake" -- 注冊為其他组件可识别的数据集
-- 插件内置参数
row.num = 100
schema = {
fields {
name = "string"
age = "int"
card = "int"
}
}
}
}
transform {
Filter {
source_table_name = "fake" -- 使用哪个数据集
result_table_name = "fake1"
fields = [name, card]
}
}
sink {
Clickhouse {
host = "clickhouse:8123"
database = "default"
table = "seatunnel_console"
fields = ["name", "card"]
username = "default"
password = ""
source_table_name = "fake1"
}
}
我们使用
result_table_name
source_table_name
env块
env块中可以直接写支持的配置项信息,比如并行度、检查点间隔时间等。
env {
job.mode="BATCH" #作业的运行模式,BATCH=离线批同步,STREAMING=实时同步
job.name="SeaTunnel_Job"
checkpoint.interval=10000 #每10000ms进行一次checkpoint
}
具体的详情细节可以观看ConfigKeyName类中的定义项。
Row数据结构
Row是seaTunnel中数据传递的核心数据结构。
因为DataStream可以很方便地和Table进行互转,所以将Row当作核心数据结构可以让转换插件同时具有使用代码(命令式)和sql(声明式)处理数据的能力。
Source块
source块是用来声明数据源的。source块中可以声明多个连接器。
所有的source插件中都可以声明result_table_name。如果你声明了result_table_name。SeaTunnel会将source插件输出的DataStream转换为Table并注册在Table环境中。当你指定了result_table_name,那么你还可以指定field_name,在注册时,给Table重设字段名。
TransForm块
transform{}块中可以声明多个转换插件。所有的转换插件都可以使用source_table_name,和result_table_name。同样,如果我们声明了result_table_name,那么我们就能声明field_name。
具体的使用说明见下文或官方文档
sink块
Sink块里可以声明多个sink插件,每个sink插件都可以指定source_table_name。不过因为不同Sink插件的配置差异较大,所以在实现时建议参考官方文档。
工作流程
seaTunnel-web
简介
由于SeaTunnel Web使用
初始构建
准备工作
1. 准备安装包
从apache官方中下载seatunnel.incubator.apache.org/download,
2. 修改配置文件
将script/seatunnel_server_env.sh的相关配置改为你对应的配置信息。
由于默认的变量名称容易与系统环境变量产生冲突,加上前缀STWEB_。该完毕之后,需要更改init.sql中的用到此变量的名称。
需要注意的是:我这边采用的是手动导入的方式,当前服务器并未配置mysql客户端,命令无法执行。
将seatunnel_server_mysql.sql导出,到所在的数据库客户端进行数据库初始化操作。
3. 修改后端配置
打开conf/application.yml修改端口号和数据源信息
4. 配置插件信息
复制seatunnel的配置文件,
cp work/soft/seatunnel/apache-seatunnel-2.3.3/config/hazelcast-client.yaml conf/
cp work/soft/seatunnel/apache-seatunnel-2.3.3/connnector/plugin-mapping.properties conf/
5. 安装适应jar包
-
需要下载mysql的元数据驱动包
-
需要下载相关数据源的datasource-xx.jar
可以前往maven的中央仓库下载repo1.maven.org/maven2/org/…,不过速度超级慢。可以选择阿里的国内镜像。
不过如果想实现自动下载的话,可以拉下源码然后运行。
不过可以直接构建seatunnel与web的源码,从本地maven仓库中获取到jar包。
将相关的datasource的包导入到libs中,
需要注意的是:还需要将元数据与数据源的jar包复制到work/soft/seatunnel/apache-seatunnel-2.3.3/lib,否则任务将会不执行。
启动任务
进入到安装目录下,执行./bin/
注意:一定是在安装目录下,否则启动项目后,会出现页面404情况。
登录进入到192.168.57.128:8801页面
点击数据源信息,点击创建
会出现以下我们之前放好的插件信息:
;
SET @i = @i + 1;
END WHILE:
任务执行情况:
十万条场景
2. 流式传输数据自动同步
3. 内置转化插件
copy插件
env {
execution.parallelism = 2
job.mode = "BATCH"
}
source {
Jdbc {
url = "jdbc:mysql://127.0.0.1:3306/test"
driver = "com.mysql.cj.jdbc.Driver"
connection_check_timeout_sec = 100
user = "user"
password = "password"
result_table_name = "base_region_01"
query = "select * from base_region limit 4"
}
}
transform {
Copy {
source_table_name = "base_region_01"
result_table_name = "base_region_02"
-- 映射的字段名称
fields {
id = id
region_name = region_name
region_name2 = region_name
}
}
}
sink {
jdbc {
url = "jdbc:mysql://127.0.0.1:3306/dw"
driver = "com.mysql.cj.jdbc.Driver"
user = "user"
password = "password"
source_table_name = "base_region_02"
query = "insert into base_region(id,region_name,region_name2) values(?,?,?)"
}
}
Filter插件
env {
execution.parallelism = 2
job.mode = "BATCH"
}
source {
Jdbc {
url = "jdbc:mysql://127.0.0.1:3306/test"
driver = "com.mysql.cj.jdbc.Driver"
connection_check_timeout_sec = 100
user = "user"
password = "password"
result_table_name = "t_user_01"
query = "select * from t_user"
}
}
transform {
Filter {
source_table_name = "t_user_01"
result_table_name = "t_user_02"
fields = [id, name]
}
}
sink {
jdbc {
url = "jdbc:mysql://127.0.0.1:3306/test"
driver = "com.mysql.cj.jdbc.Driver"
connection_check_timeout_sec = 100
user = "user"
password = "password"
source_table_name = "t_user_02"
query = "insert into ods_t_user(id,name) values(?,?)"
}
}
FilterSelector插件:
transform {
FieldSelector {
fields = ["id", "name", "age"] -- 只选择数据源信息中这些字段进行后续处理
}
}
FieldMapper插件:
transform {
FieldMapper {
mappings {
source_field = "source_value"
target_field = "target_value"
}
}
}
DataFilter插件
transform {
DataFilter {
condition = "age >= 18" // 只需要年龄大于18的数据
}
}
TypeConverter插件
transform {
TypeConverter {
field_conversion {
name {
from = "string"
to = "integer"
}
age {
from = "string"
to = "integer"
}
}
}
}
Replace插件
env {
execution.parallelism = 2
job.mode = "BATCH"
}
source {
Jdbc {
url = "jdbc:mysql://127.0.0.1:3306/test"
driver = "com.mysql.cj.jdbc.Driver"
connection_check_timeout_sec = 100
user = "user"
password = "password"
result_table_name = "t_user_01"
query = "select * from t_user"
}
}
transform {
Replace {
source_table_name = "t_user_01"
result_table_name = "t_user_02"
replace_field = "name"
pattern = "%"
replacement = ""
}
}
sink {
jdbc {
url = "jdbc:mysql://127.0.0.1:3306/test"
driver = "com.mysql.cj.jdbc.Driver"
connection_check_timeout_sec = 100
user = "user"
password = "password"
source_table_name = "t_user_02"
query = "insert into ods_t_user(id,name,birth,gender) values(?,?,?,?)"
}
}
Split插件
env {
execution.parallelism = 2
job.mode = "BATCH"
}
source {
Jdbc {
url = "jdbc:mysql://127.0.0.1:3306/test"
driver = "com.mysql.cj.jdbc.Driver"
connection_check_timeout_sec = 100
user = "user"
password = "password"
result_table_name = "t_user_01"
query = "select * from t_user"
}
}
transform {
Split {
source_table_name = "t_user_01"
result_table_name = "t_user_02"
separator = "-"
split_field = "birth"
output_fields = [birth_y, birth_m, birth_d]
}
}
sink {
jdbc {
url = "jdbc:mysql://127.0.0.1:3306/test"
driver = "com.mysql.cj.jdbc.Driver"
user = "user"
password = "password"
source_table_name = "t_user_02"
query = "insert into ods_t_user_y_m_d(id,name,birth,gender,birth_y,birth_m,birth_d) values(?,?,?,?,?,?,?)"
}
}
FilterRowKind插件
env {
job.mode = "BATCH"
}
source {
FakeSource {
result_table_name = "fake"
row.num = 100
schema = {
fields {
id = "int"
name = "string"
age = "int"
}
}
}
}
transform {
FilterRowKind {
source_table_name = "fake"
result_table_name = "fake1"
exclude_kinds = ["INSERT"] -- 只保留数据标记为 insert的行
}
}
sink {
Console {
source_table_name = "fake1"
}
}
SQL引擎
可以通过手写sql语句的方式,进行数据转换,可支持自定义函数。
使用sql插件来检测字段,仅保留用户名与地址字段,其他字段将被丢弃,用户信息表是前一个插件配置的结果表名称。
sql {
sql = "select username, address from user_info",
}
使用自定义函数-Use UDF
sql {
sql = "select substring(telephone, 0, 10) from user_info",
}
使用自定义聚合函数-Use UDAF
sql {
sql = "select avg(age) from user_info",
table_name = "user_info"
}
4. 自定义转化插件
以copy的插件为例:
代码结构:
copyTransformConfig类
// 保存了插件的配置项信息
@Getter
@Setter
public class CopyTransformConfig implements Serializable {
@Deprecated
public static final Option<String> SRC_FIELD =
Options.key("src_field")
.stringType()
.noDefaultValue()
.withDescription("Src field you want to copy");
@Deprecated
public static final Option<String> DEST_FIELD =
Options.key("dest_field")
.stringType()
.noDefaultValue()
.withDescription("Copy Src field to Dest field");
// 配置fields局域信息
public static final Option<Map<String, String>> FIELDS =
Options.key("fields")
.mapType()
.noDefaultValue() // 无默认信息
// 描述信息
.withDescription(
"Specify the field copy relationship between input and output");
private LinkedHashMap<String, String> fields;
public static CopyTransformConfig of(ReadonlyConfig config) {
LinkedHashMap<String, String> fields = new LinkedHashMap<>();
Optional<Map<String, String>> optional = config.getOptional(FIELDS);
if (optional.isPresent()) {
fields.putAll(config.get(FIELDS));
} else {
fields.put(config.get(DEST_FIELD), config.get(SRC_FIELD));
}
CopyTransformConfig copyTransformConfig = new CopyTransformConfig();
copyTransformConfig.setFields(fields);
return copyTransformConfig;
}
}
CopyFieldTransformFactory类
@AutoService(Factory.class)
public class CopyFieldTransformFactory implements TableTransformFactory {
@Override
// 定义身份标识
public String factoryIdentifier() {
return CopyFieldTransform.PLUGIN_NAME;
}
@Override
public OptionRule optionRule() {
return OptionRule.builder()
// bundled捆绑选项:必须同时存在或者都不存在
.bundled(CopyTransformConfig.SRC_FIELD, CopyTransformConfig.DEST_FIELD)
.bundled(CopyTransformConfig.FIELDS)
.build();
}
@Override
// 创建插件
public TableTransform createTransform(TableTransformFactoryContext context) {
CopyTransformConfig copyTransformConfig = CopyTransformConfig.of(context.getOptions());
CatalogTable catalogTable = context.getCatalogTables().get(0);
return () -> new CopyFieldTransform(copyTransformConfig, catalogTable);
}
}
CopyFieldTransform类:
// 具体的转化类:主要用于对source数据的处理
public class CopyFieldTransform extends MultipleFieldOutputTransform {
public static final String PLUGIN_NAME = "Copy";
private final CopyTransformConfig config;
private List<String> fieldNames;
private List<Integer> fieldOriginalIndexes;
private List<SeaTunnelDataType<?>> fieldTypes;
public CopyFieldTransform(CopyTransformConfig copyTransformConfig, CatalogTable catalogTable) {
super(catalogTable);
this.config = copyTransformConfig;
SeaTunnelRowType seaTunnelRowType = catalogTable.getTableSchema().toPhysicalRowDataType();
initOutputFields(seaTunnelRowType, config.getFields());
}
@Override
public String getPluginName() {
return PLUGIN_NAME;
}
// 将输入的字段域初始化进入到类型中
private void initOutputFields(
SeaTunnelRowType inputRowType, LinkedHashMap<String, String> fields) {
List<String> fieldNames = new ArrayList<>();
List<Integer> fieldOriginalIndexes = new ArrayList<>();
List<SeaTunnelDataType<?>> fieldsType = new ArrayList<>();
for (Map.Entry<String, String> field : fields.entrySet()) {
String srcField = field.getValue();
int srcFieldIndex;
try {
srcFieldIndex = inputRowType.indexOf(srcField);
} catch (IllegalArgumentException e) {
throw TransformCommonError.cannotFindInputFieldError(getPluginName(), srcField);
}
fieldNames.add(field.getKey());
fieldOriginalIndexes.add(srcFieldIndex);
fieldsType.add(inputRowType.getFieldType(srcFieldIndex));
}
this.fieldNames = fieldNames;
this.fieldOriginalIndexes = fieldOriginalIndexes;
this.fieldTypes = fieldsType;
}
@Override
protected Column[] getOutputColumns() {
if (inputCatalogTable == null) {
Column[] columns = new Column[fieldNames.size()];
for (int i = 0; i < fieldNames.size(); i++) {
columns[i] =
PhysicalColumn.of(fieldNames.get(i), fieldTypes.get(i), 200, true, "", "");
}
return columns;
}
Map<String, Column> catalogTableColumns =
inputCatalogTable.getTableSchema().getColumns().stream()
.collect(Collectors.toMap(column -> column.getName(), column -> column));
List<Column> columns = new ArrayList<>();
for (Map.Entry<String, String> copyField : config.getFields().entrySet()) {
Column srcColumn = catalogTableColumns.get(copyField.getValue());
PhysicalColumn destColumn =
PhysicalColumn.of(
copyField.getKey(),
srcColumn.getDataType(),
srcColumn.getColumnLength(),
srcColumn.isNullable(),
srcColumn.getDefaultValue(),
srcColumn.getComment());
columns.add(destColumn);
}
return columns.toArray(new Column[0]);
}
@Override
protected Object[] getOutputFieldValues(SeaTunnelRowAccessor inputRow) {
Object[] fieldValues = new Object[fieldNames.size()];
for (int i = 0; i < fieldOriginalIndexes.size(); i++) {
fieldValues[i] =
clone(
fieldNames.get(i),
fieldTypes.get(i),
inputRow.getField(fieldOriginalIndexes.get(i)));
}
return fieldValues;
}
// 核心复制方法
private Object clone(String field, SeaTunnelDataType<?> dataType, Object value) {
if (value == null) {
return null;
}
switch (dataType.getSqlType()) {
case BOOLEAN:
case STRING:
case TINYINT:
case SMALLINT:
case INT:
case BIGINT:
case FLOAT:
case DOUBLE:
case DECIMAL:
case DATE:
case TIME:
case TIMESTAMP:
return value;
case BYTES:
byte[] bytes = (byte[]) value;
byte[] newBytes = new byte[bytes.length];
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
return newBytes;
case ARRAY:
ArrayType arrayType = (ArrayType) dataType;
Object[] array = (Object[]) value;
Object newArray =
Array.newInstance(arrayType.getElementType().getTypeClass(), array.length);
for (int i = 0; i < array.length; i++) {
Array.set(newArray, i, clone(field, arrayType.getElementType(), array[i]));
}
return newArray;
case MAP:
MapType mapType = (MapType) dataType;
Map map = (Map) value;
Map<Object, Object> newMap = new HashMap<>();
for (Object key : map.keySet()) {
newMap.put(
clone(field, mapType.getKeyType(), key),
clone(field, mapType.getValueType(), map.get(key)));
}
return newMap;
case ROW:
SeaTunnelRowType rowType = (SeaTunnelRowType) dataType;
SeaTunnelRow row = (SeaTunnelRow) value;
Object[] newFields = new Object[rowType.getTotalFields()];
for (int i = 0; i < rowType.getTotalFields(); i++) {
newFields[i] =
clone(
rowType.getFieldName(i),
rowType.getFieldType(i),
row.getField(i));
}
SeaTunnelRow newRow = new SeaTunnelRow(newFields);
newRow.setRowKind(row.getRowKind());
newRow.setTableId(row.getTableId());
return newRow;
case NULL:
return null;
default:
throw CommonError.unsupportedDataType(
getPluginName(), dataType.getSqlType().toString(), field);
}
}
}
入口解析
服务端
默认使用seatunnel默认引擎,通过源码中找到初始入口。
核心启动类
new SeaTunnelNodeContext(seaTunnelConfig),这里会返回一个SeaTunnelNodeContext类,这个类是继承自Hazelcast这个组件的DefaultNodeContext类。在Hazelcast启动的过程中,会去调用DefaultNodeContext类的实现类的createNodeExtension()方法,在这里其实也就是SeaTunnelNodeContext类的createNodeExtension()方法。
实际运行中,hazelcast会调用方法中init方法。可以观测到任务执行器开始启动。
官方介绍服务类:
**TaskExecutionService **
TaskExecutionService 是一个执行任务的服务,将在每个节点上运行一个实例。它从 JobMaster 接收 TaskGroup 并在其中运行 Task。并维护TaskID->TaskContext,对Task的具体操作都封装在TaskContext中。而Task内部持有OperationService,也就是说Task可以通过OperationService远程调用其他Task或JobMaster进行通信。
**CoordinatorService **
CoordinatorService是一个充当协调器的服务,它主要负责处理客户端提交的命令以及切换master后任务的恢复。客户端在提交任务时会找到master节点并将任务提交到CoordinatorService服务上,CoordinatorService会缓存任务信息并等待任务执行结束。当任务结束后再对任务进行归档处理。
SlotService
SlotService是slot管理服务,用于管理集群的可用Slot资源。SlotService运行在所有节点上并定期向master上报资源信息。
这里新建了一个cooperativeTaskWorker类,这个类对象会被提交到executorService去执行,代码就是下一行的executorService.submit(cooperativeTaskWorker)这个代码。
然后当cooperativeTaskWorker被提交到executorService上的时候,其实是会运行cooperativeTaskWorker这个类的run方法的。
客户端