You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampark.apache.org by be...@apache.org on 2024/10/29 04:22:16 UTC
(incubator-streampark) branch revert-4122-remove-registry created (now 8f2a45d2b)
This is an automated email from the ASF dual-hosted git repository.
benjobs pushed a change to branch revert-4122-remove-registry
in repository https://gitbox.apache.org/repos/asf/incubator-streampark.git
at 8f2a45d2b Revert "[Improve] remove registry module (#4122)"
This branch includes the following new commits:
new 8f2a45d2b Revert "[Improve] remove registry module (#4122)"
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(incubator-streampark) 01/01: Revert "[Improve] remove registry module (#4122)"
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
benjobs pushed a commit to branch revert-4122-remove-registry
in repository https://gitbox.apache.org/repos/asf/incubator-streampark.git
commit 8f2a45d2bbcb40ac7a2adb3f76e19a3296a8cd83
Author: benjobs <be...@apache.org>
AuthorDate: Tue Oct 29 12:22:09 2024 +0800
Revert "[Improve] remove registry module (#4122)"
This reverts commit 3cb63e1f29b674b134ee0b036f7c608139df4351.
---
.github/workflows/e2e.yml | 6 +
pom.xml | 40 ++
streampark-common/pom.xml | 91 +++++
.../streampark/common/CommonConfiguration.java | 26 ++
.../org/apache/streampark/common/IStoppable.java | 31 ++
.../apache/streampark/common/utils/JSONUtils.java | 429 +++++++++++++++++++++
.../streampark/common/utils/NetworkUtils.java | 395 +++++++++++++++++++
.../apache/streampark/common/utils/OSUtils.java | 49 +++
.../apache/streampark/common/utils/UUIDUtils.java | 34 ++
.../common/utils/NetworkUtilsTestCase.java | 43 +++
.../streampark/common/utils/UUIDUtilsTestCase.java | 70 ++++
streampark-console/pom.xml | 1 +
.../streampark-console-registry/README.md | 37 ++
.../streampark-console-registry/pom.xml | 92 +++++
.../streampark-registry-api/pom.xml | 39 ++
.../streampark/registry/api/ConnectStrategy.java | 31 ++
.../registry/api/ConnectStrategyProperties.java | 33 ++
.../registry/api/ConnectionListener.java | 28 ++
.../streampark/registry/api/ConnectionState.java | 28 ++
.../org/apache/streampark/registry/api/Event.java | 131 +++++++
.../apache/streampark/registry/api/Registry.java | 112 ++++++
.../streampark/registry/api/RegistryClient.java | 214 ++++++++++
.../registry/api/RegistryConfiguration.java | 33 ++
.../streampark/registry/api/RegistryException.java | 29 ++
.../streampark/registry/api/StrategyType.java | 25 ++
.../streampark/registry/api/SubscribeListener.java | 23 ++
.../registry/api/enums/RegistryNodeType.java | 35 ++
.../registry/api/enums/ServerStatusEnum.java | 25 ++
.../registry/api/lifecycle/ServerLifeCycle.java | 40 ++
.../api/lifecycle/ServerLifeCycleException.java | 29 ++
.../api/lifecycle/ServerLifeCycleManager.java | 83 ++++
.../registry/api/model/BaseHeartBeat.java | 43 +++
.../registry/api/model/BaseHeartBeatTask.java | 95 +++++
.../registry/api/model/ConsoleHeartBeat.java | 27 ++
.../streampark/registry/api/model/HeartBeat.java | 26 ++
.../streampark/registry/api/model/Server.java | 46 +++
.../registry/api/sql/ClasspathSqlScriptParser.java | 147 +++++++
.../registry/api/sql/SqlScriptParser.java | 27 ++
.../registry/api/sql/SqlScriptRunner.java | 65 ++++
.../registry/api/thread/BaseDaemonThread.java | 38 ++
.../thread/DefaultUncaughtExceptionHandler.java | 47 +++
.../registry/api/thread/ThreadUtils.java | 64 +++
.../streampark-registry-core/README.md | 83 ++++
.../streampark-registry-core/pom.xml | 113 ++++++
.../streampark/registry/core/JdbcRegistry.java | 272 +++++++++++++
.../core/JdbcRegistryAutoConfiguration.java | 120 ++++++
.../registry/core/JdbcRegistryProperties.java | 97 +++++
.../registry/core/JdbcRegistryThreadFactory.java | 31 ++
.../apache/streampark/registry/core/LockUtils.java | 34 ++
.../registry/core/client/IJdbcRegistryClient.java | 106 +++++
.../registry/core/client/JdbcRegistryClient.java | 129 +++++++
.../core/client/JdbcRegistryClientIdentify.java | 33 ++
.../mapper/JdbcRegistryClientHeartbeatMapper.java | 33 ++
.../mapper/JdbcRegistryDataChanceEventMapper.java | 41 ++
.../core/mapper/JdbcRegistryDataMapper.java | 51 +++
.../core/mapper/JdbcRegistryLockMapper.java | 39 ++
.../core/model/DO/JdbcRegistryClientHeartbeat.java | 46 +++
.../registry/core/model/DO/JdbcRegistryData.java | 48 +++
.../core/model/DO/JdbcRegistryDataChanceEvent.java | 48 +++
.../registry/core/model/DO/JdbcRegistryLock.java | 45 +++
.../registry/core/model/DTO/DataType.java | 25 ++
.../model/DTO/JdbcRegistryClientHeartbeatDTO.java | 100 +++++
.../model/DTO/JdbcRegistryDataChanceEventDTO.java | 78 ++++
.../core/model/DTO/JdbcRegistryDataDTO.java | 69 ++++
.../core/model/DTO/JdbcRegistryLockDTO.java | 63 +++
.../repository/JdbcRegistryClientRepository.java | 66 ++++
.../JdbcRegistryDataChanceEventRepository.java | 65 ++++
.../repository/JdbcRegistryDataRepository.java | 72 ++++
.../repository/JdbcRegistryLockRepository.java | 55 +++
.../core/server/ConnectionStateListener.java | 27 ++
.../core/server/IJdbcRegistryDataManager.java | 55 +++
.../core/server/IJdbcRegistryLockManager.java | 37 ++
.../registry/core/server/IJdbcRegistryServer.java | 111 ++++++
.../core/server/IRegistryRowChangeNotifier.java | 35 ++
.../server/JdbcRegistryDataChangeListener.java | 28 ++
.../core/server/JdbcRegistryDataManager.java | 264 +++++++++++++
.../core/server/JdbcRegistryLockManager.java | 150 +++++++
.../registry/core/server/JdbcRegistryServer.java | 386 ++++++++++++++++++
.../core/server/JdbcRegistryServerState.java | 46 +++
.../src/main/resources/META-INF/spring.factories | 19 +
.../src/main/resources/mysql_registry_init.sql | 69 ++++
.../main/resources/postgresql_registry_init.sql | 65 ++++
.../registry/core/JdbcRegistryTestCase.java | 64 +++
.../core/JdbcRegistryThreadFactoryTest.java | 43 +++
.../streampark/registry/core/LockUtilsTest.java | 38 ++
.../registry/core/MysqlJdbcRegistryTestCase.java | 77 ++++
.../core/PostgresqlJdbcRegistryTestCase.java | 73 ++++
.../src/test/resources/application-mysql.yaml | 38 ++
.../src/test/resources/application-postgresql.yaml | 36 ++
.../src/test/resources/application.yaml | 24 ++
.../src/test/resources/logback.xml} | 30 +-
.../streampark-registry-it}/pom.xml | 49 ++-
.../plugin/registry/RegistryTestCase.java | 260 +++++++++++++
.../streampark-console-service/pom.xml | 49 ++-
.../main/assembly/script/schema/mysql-schema.sql | 55 +++
.../main/assembly/script/schema/pgsql-schema.sql | 52 +++
.../main/assembly/script/upgrade/mysql/2.2.0.sql | 51 +++
.../main/assembly/script/upgrade/pgsql/2.2.0.sql | 52 +++
.../console/StreamParkConsoleBootstrap.java | 67 +++-
.../console/core/config/ConsoleConfig.java | 104 +++++
.../core/registry/ConsoleConnectStrategy.java | 24 ++
.../registry/ConsoleConnectionStateListener.java | 54 +++
.../core/registry/ConsoleRegistryClient.java | 203 ++++++++++
.../core/registry/ConsoleRegistryDataListener.java | 66 ++++
.../console/core/registry/ConsoleStopStrategy.java | 52 +++
.../console/core/task/ConsoleHeartBeatTask.java | 72 ++++
.../src/main/resources/db/schema-h2.sql | 55 +++
.../console/core/service/DatabaseServiceTest.java | 9 +-
.../console/core/service/TableServiceTest.java | 35 +-
tools/dependencies/known-dependencies.txt | 10 +-
110 files changed, 7823 insertions(+), 80 deletions(-)
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 9ff7011f9..d16a7b966 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -146,6 +146,12 @@ jobs:
class: org.apache.streampark.e2e.cases.Flink117OnYarnClusterDeployTest
- name: Flink118OnYarnClusterDeployTest
class: org.apache.streampark.e2e.cases.Flink118OnYarnClusterDeployTest
+ - name: FlinkSQL116OnYarnTest
+ class: org.apache.streampark.e2e.cases.FlinkSQL116OnYarnTest
+ - name: FlinkSQL117OnYarnTest
+ class: org.apache.streampark.e2e.cases.FlinkSQL117OnYarnTest
+ - name: FlinkSQL118OnYarnTest
+ class: org.apache.streampark.e2e.cases.FlinkSQL118OnYarnTest
env:
RECORDING_PATH: /tmp/recording-${{ matrix.case.name }}
steps:
diff --git a/pom.xml b/pom.xml
index 0c9fdbc15..937357f6f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -146,6 +146,12 @@
<mockito-scala.version>1.17.14</mockito-scala.version>
<assertj.version>3.23.1</assertj.version>
<testcontainer.version>1.19.8</testcontainer.version>
+ <curator.version>5.3.0</curator.version>
+ <snappy.version>1.1.10.1</snappy.version>
+ <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
+ <oshi-core.version>6.1.1</oshi-core.version>
+ <commons-collections4.version>4.3</commons-collections4.version>
+ <hibernate-validator.version>6.2.2.Final</hibernate-validator.version>
<maven.scaladoc.skip>false</maven.scaladoc.skip>
<maven.scalastyle.skip>false</maven.scalastyle.skip>
@@ -158,6 +164,21 @@
<dependencyManagement>
<dependencies>
+ <!-- spring -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>2.7.11</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hibernate.validator</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ <version>${hibernate-validator.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>force-shading</artifactId>
@@ -278,6 +299,25 @@
</exclusions>
</dependency>
+ <!-- mybatis-plus -->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-boot-starter</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-annotation</artifactId>
+ <version>${mybatis-plus.version}</version>
+ </dependency>
+
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
diff --git a/streampark-common/pom.xml b/streampark-common/pom.xml
index 115477f4a..da9631b39 100644
--- a/streampark-common/pom.xml
+++ b/streampark-common/pom.xml
@@ -29,6 +29,40 @@
<dependencies>
+ <!--spring-->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-logging</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.github.oshi</groupId>
+ <artifactId>oshi-core</artifactId>
+ <version>${oshi-core.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ <version>3.2.2</version>
+ </dependency>
+
<!-- test -->
<dependency>
<groupId>org.mockito</groupId>
@@ -58,6 +92,12 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+
<!-- enumeratum -->
<dependency>
<groupId>com.beachape</groupId>
@@ -96,6 +136,12 @@
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<optional>true</optional>
+ <exclusions>
+ <exclusion>
+ <groupId>commons-collections</groupId>
+ <artifactId>commons-collections</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
@@ -141,6 +187,51 @@
<artifactId>streampark-shaded-jackson</artifactId>
<version>${streampark.shaded.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ </dependency>
+
+ <!-- test -->
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/streampark-common/src/main/java/org/apache/streampark/common/CommonConfiguration.java b/streampark-common/src/main/java/org/apache/streampark/common/CommonConfiguration.java
new file mode 100644
index 000000000..1da7ba6e4
--- /dev/null
+++ b/streampark-common/src/main/java/org/apache/streampark/common/CommonConfiguration.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.common;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan("org.apache.streampark.common")
+public class CommonConfiguration {
+}
diff --git a/streampark-common/src/main/java/org/apache/streampark/common/IStoppable.java b/streampark-common/src/main/java/org/apache/streampark/common/IStoppable.java
new file mode 100644
index 000000000..7c0761183
--- /dev/null
+++ b/streampark-common/src/main/java/org/apache/streampark/common/IStoppable.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.common;
+
+/**
+ * server stop interface.
+ */
+public interface IStoppable {
+
+ /**
+ * Stop this service.
+ * @param cause why stopping
+ */
+ void stop(String cause);
+
+}
diff --git a/streampark-common/src/main/java/org/apache/streampark/common/utils/JSONUtils.java b/streampark-common/src/main/java/org/apache/streampark/common/utils/JSONUtils.java
new file mode 100644
index 000000000..1a9152988
--- /dev/null
+++ b/streampark-common/src/main/java/org/apache/streampark/common/utils/JSONUtils.java
@@ -0,0 +1,429 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.common.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.google.common.base.Strings;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TimeZone;
+
+import static com.fasterxml.jackson.databind.DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT;
+import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
+import static com.fasterxml.jackson.databind.DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL;
+import static com.fasterxml.jackson.databind.MapperFeature.REQUIRE_SETTERS_FOR_GETTERS;
+import static com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.streampark.common.constants.DateConstants.YYYY_MM_DD_HH_MM_SS;
+
+/**
+ * json utils
+ */
+@Slf4j
+public final class JSONUtils {
+
+ static {
+ log.info("init timezone: {}", TimeZone.getDefault());
+ }
+
+ private static final ObjectMapper objectMapper = JsonMapper.builder()
+ .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true)
+ .configure(READ_UNKNOWN_ENUM_VALUES_AS_NULL, true)
+ .configure(REQUIRE_SETTERS_FOR_GETTERS, true)
+ .configure(FAIL_ON_EMPTY_BEANS, false)
+ .addModule(new JavaTimeModule())
+ .addModule(new SimpleModule()
+ .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer())
+ .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer()))
+ .defaultTimeZone(TimeZone.getDefault())
+ .defaultDateFormat(new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS))
+ .build();
+
+ private JSONUtils() {
+ throw new UnsupportedOperationException("Construct JSONUtils");
+ }
+
+ public static synchronized void setTimeZone(TimeZone timeZone) {
+ objectMapper.setTimeZone(timeZone);
+ }
+
+ public static ArrayNode createArrayNode() {
+ return objectMapper.createArrayNode();
+ }
+
+ public static ObjectNode createObjectNode() {
+ return objectMapper.createObjectNode();
+ }
+
+ public static JsonNode toJsonNode(Object obj) {
+ return objectMapper.valueToTree(obj);
+ }
+
+ /**
+ * json representation of object
+ *
+ * @param object object
+ * @param feature feature
+ * @return object to json string
+ */
+ public static String toJsonString(Object object, SerializationFeature feature) {
+ try {
+ ObjectWriter writer = objectMapper.writer(feature);
+ return writer.writeValueAsString(object);
+ } catch (Exception e) {
+ log.error("object to json exception!", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * This method deserializes the specified Json into an object of the specified class. It is not
+ * suitable to use if the specified class is a generic type since it will not have the generic
+ * type information because of the Type Erasure feature of Java. Therefore, this method should not
+ * be used if the desired type is a generic type. Note that this method works fine if the any of
+ * the fields of the specified object are generics, just the object itself should not be a
+ * generic type.
+ *
+ * @param json the string from which the object is to be deserialized
+ * @param clazz the class of T
+ * @param <T> T
+ * @return an object of type T from the string
+ * classOfT
+ */
+ public static @Nullable <T> T parseObject(String json, Class<T> clazz) {
+ if (Strings.isNullOrEmpty(json)) {
+ return null;
+ }
+
+ try {
+ return objectMapper.readValue(json, clazz);
+ } catch (Exception e) {
+ log.error("Parse object exception, jsonStr: {}, class: {}", json, clazz, e);
+ }
+ return null;
+ }
+
+ /**
+ * deserialize
+ *
+ * @param src byte array
+ * @param clazz class
+ * @param <T> deserialize type
+ * @return deserialize type
+ */
+ public static <T> T parseObject(byte[] src, Class<T> clazz) {
+ if (src == null) {
+ return null;
+ }
+ String json = new String(src, UTF_8);
+ return parseObject(json, clazz);
+ }
+
+ /**
+ * json to list
+ *
+ * @param json json string
+ * @param clazz class
+ * @param <T> T
+ * @return list
+ */
+ public static <T> List<T> toList(String json, Class<T> clazz) {
+ if (Strings.isNullOrEmpty(json)) {
+ return Collections.emptyList();
+ }
+
+ try {
+ CollectionType listType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, clazz);
+ return objectMapper.readValue(json, listType);
+ } catch (Exception e) {
+ log.error("parse list exception!", e);
+ }
+
+ return Collections.emptyList();
+ }
+
+ /**
+ * check json object valid
+ *
+ * @param json json
+ * @return true if valid
+ */
+ public static boolean checkJsonValid(String json) {
+ return checkJsonValid(json, true);
+ }
+
+ public static boolean checkJsonValid(String json, Boolean logFlag) {
+ if (Strings.isNullOrEmpty(json)) {
+ return false;
+ }
+
+ try {
+ objectMapper.readTree(json);
+ return true;
+ } catch (IOException e) {
+ if (logFlag)
+ log.error("check json object valid exception!", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Method for finding a JSON Object field with specified name in this
+ * node or its child nodes, and returning value it has.
+ * If no matching field is found in this node or its descendants, returns null.
+ *
+ * @param jsonNode json node
+ * @param fieldName Name of field to look for
+ * @return Value of first matching node found, if any; null if none
+ */
+ public static String findValue(JsonNode jsonNode, String fieldName) {
+ JsonNode node = jsonNode.findValue(fieldName);
+
+ if (node == null) {
+ return null;
+ }
+
+ return node.asText();
+ }
+
+ /**
+ * json to map
+ * {@link #toMap(String, Class, Class)}
+ *
+ * @param json json
+ * @return json to map
+ */
+ public static Map<String, String> toMap(String json) {
+ return parseObject(json, new TypeReference<Map<String, String>>() {
+ });
+ }
+
+ /**
+ * json to map
+ *
+ * @param json json
+ * @param classK classK
+ * @param classV classV
+ * @param <K> K
+ * @param <V> V
+ * @return to map
+ */
+ public static <K, V> Map<K, V> toMap(String json, Class<K> classK, Class<V> classV) {
+ if (Strings.isNullOrEmpty(json)) {
+ return Collections.emptyMap();
+ }
+
+ try {
+ return objectMapper.readValue(json, new TypeReference<Map<K, V>>() {
+ });
+ } catch (Exception e) {
+ log.error("json to map exception!", e);
+ }
+
+ return Collections.emptyMap();
+ }
+
+ /**
+ * from the key-value generated json to get the str value no matter the real type of value
+ * @param json the json str
+ * @param nodeName key
+ * @return the str value of key
+ */
+ public static String getNodeString(String json, String nodeName) {
+ try {
+ JsonNode rootNode = objectMapper.readTree(json);
+ JsonNode jsonNode = rootNode.findValue(nodeName);
+ if (Objects.isNull(jsonNode)) {
+ return "";
+ }
+ return jsonNode.isTextual() ? jsonNode.asText() : jsonNode.toString();
+ } catch (JsonProcessingException e) {
+ return "";
+ }
+ }
+
+ /**
+ * json to object
+ *
+ * @param json json string
+ * @param type type reference
+ * @param <T>
+ * @return return parse object
+ */
+ public static <T> T parseObject(String json, TypeReference<T> type) {
+ if (Strings.isNullOrEmpty(json)) {
+ return null;
+ }
+
+ try {
+ return objectMapper.readValue(json, type);
+ } catch (Exception e) {
+ log.error("json to map exception!", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * object to json string
+ *
+ * @param object object
+ * @return json string
+ */
+ public static String toJsonString(Object object) {
+ try {
+ return objectMapper.writeValueAsString(object);
+ } catch (Exception e) {
+ throw new RuntimeException("Object json deserialization exception.", e);
+ }
+ }
+
+ public static String toPrettyJsonString(Object object) {
+ try {
+ return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
+ } catch (Exception e) {
+ throw new RuntimeException("Object json deserialization exception.", e);
+ }
+ }
+
+ /**
+ * serialize to json byte
+ *
+ * @param obj object
+ * @param <T> object type
+ * @return byte array
+ */
+ public static <T> byte[] toJsonByteArray(T obj) {
+ if (obj == null) {
+ return null;
+ }
+ String json = "";
+ try {
+ json = toJsonString(obj);
+ } catch (Exception e) {
+ log.error("json serialize exception.", e);
+ }
+
+ return json.getBytes(UTF_8);
+ }
+
+ public static ObjectNode parseObject(String text) {
+ try {
+ if (StringUtils.isEmpty(text)) {
+ return parseObject(text, ObjectNode.class);
+ } else {
+ return (ObjectNode) objectMapper.readTree(text);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("String json deserialization exception.", e);
+ }
+ }
+
+ public static ArrayNode parseArray(String text) {
+ try {
+ return (ArrayNode) objectMapper.readTree(text);
+ } catch (Exception e) {
+ throw new RuntimeException("Json deserialization exception.", e);
+ }
+ }
+
+ /**
+ * json serializer
+ */
+ public static class JsonDataSerializer extends JsonSerializer<String> {
+
+ @Override
+ public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+ gen.writeRawValue(value);
+ }
+
+ }
+
+ /**
+ * json data deserializer
+ */
+ public static class JsonDataDeserializer extends JsonDeserializer<String> {
+
+ @Override
+ public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+ JsonNode node = p.getCodec().readTree(p);
+ if (node instanceof TextNode) {
+ return node.asText();
+ } else {
+ return node.toString();
+ }
+ }
+
+ }
+
+ public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
+
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);
+
+ @Override
+ public void serialize(LocalDateTime value,
+ JsonGenerator gen,
+ SerializerProvider serializers) throws IOException {
+ gen.writeString(value.format(formatter));
+ }
+ }
+
+ public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
+
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);
+
+ @Override
+ public LocalDateTime deserialize(JsonParser p, DeserializationContext context) throws IOException {
+ return LocalDateTime.parse(p.getValueAsString(), formatter);
+ }
+ }
+}
diff --git a/streampark-common/src/main/java/org/apache/streampark/common/utils/NetworkUtils.java b/streampark-common/src/main/java/org/apache/streampark/common/utils/NetworkUtils.java
new file mode 100644
index 000000000..289495976
--- /dev/null
+++ b/streampark-common/src/main/java/org/apache/streampark/common/utils/NetworkUtils.java
@@ -0,0 +1,395 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.common.utils;
+
+import org.apache.streampark.common.constants.Constants;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.core5.net.InetAddressUtils;
+
+import com.google.common.collect.Sets;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+public class NetworkUtils {
+
+ private static InetAddress LOCAL_ADDRESS = null;
+ private static volatile String HOST_ADDRESS;
+ private static String preferredInterface;
+ private static String restrictInterface;
+ private static String priorityStrategy;
+
+ @Value("${network.preferred-interface:null}")
+ public void setPreferredInterface(String PreferredInterface) {
+ preferredInterface = PreferredInterface;
+ }
+
+ @Value("${network.restrict-interface:docker0}")
+ public void setRestrictInterface(String RestrictInterface) {
+ restrictInterface = RestrictInterface;
+ }
+
+ @Value("${network.priority-strategy:default}")
+ public void setPriorityStrategy(String PriorityStrategy) {
+ priorityStrategy = PriorityStrategy;
+ }
+
+ /**
+ * get addr like host:port
+ * @return addr
+ */
+ public static String getAddr(String host, int port) {
+ return String.format("%s:%d", host, port);
+ }
+
+ /**
+ * get addr like host:port
+ * @return addr
+ */
+ public static String getAddr(int port) {
+ return getAddr(getHost(), port);
+ }
+
+ /**
+ * get host
+ * @return host
+ */
+ public static String getHost(InetAddress inetAddress) {
+ if (inetAddress != null) {
+ return inetAddress.getHostAddress();
+ }
+ return null;
+ }
+
+ public static String getHost() {
+ if (HOST_ADDRESS != null) {
+ return HOST_ADDRESS;
+ }
+
+ InetAddress address = getLocalAddress();
+ if (address != null) {
+ HOST_ADDRESS = getHost(address);
+ return HOST_ADDRESS;
+ }
+ return Constants.LOCAL_HOST;
+ }
+
+ private static InetAddress getLocalAddress() {
+ if (null != LOCAL_ADDRESS) {
+ return LOCAL_ADDRESS;
+ }
+ LOCAL_ADDRESS = getLocalAddress0();
+ return LOCAL_ADDRESS;
+ }
+
+ /**
+ * Find first valid IP from local network card
+ *
+ * @return first valid local IP
+ */
+ private static synchronized InetAddress getLocalAddress0() {
+ List<NetworkInterface> suitableNetworkInterface = findSuitableNetworkInterface();
+ List<InetAddress> suitableInetAddress = findSuitableInetAddress(suitableNetworkInterface);
+ if (CollectionUtils.isEmpty(suitableInetAddress)) {
+ return null;
+ }
+ return suitableInetAddress.get(0);
+ }
+
+ private static InetAddress normalizeV6Address(Inet6Address address) {
+ String addr = address.getHostAddress();
+ int i = addr.lastIndexOf('%');
+ if (i > 0) {
+ try {
+ return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId());
+ } catch (UnknownHostException e) {
+ log.debug("Unknown IPV6 address: ", e);
+ }
+ }
+ return address;
+ }
+
+ protected static boolean isValidV4Address(InetAddress address) {
+ if (!(address instanceof Inet4Address)) {
+ return false;
+ }
+ String name = address.getHostAddress();
+ return (name != null
+ && InetAddressUtils.isIPv4Address(name)
+ && !address.isAnyLocalAddress()
+ && !address.isLoopbackAddress());
+ }
+
+ protected static boolean isValidV6Address(InetAddress address) {
+ if (!(address instanceof Inet6Address)) {
+ return false;
+ }
+ String name = address.getHostAddress();
+ return (name != null
+ && InetAddressUtils.isIPv6Address(name)
+ && !address.isAnyLocalAddress()
+ && !address.isLoopbackAddress());
+ }
+
+ /**
+ * Check if an ipv6 address
+ *
+ * @return true if it is reachable
+ */
+ private static boolean isPreferIPV6Address() {
+ return Boolean.getBoolean("java.net.preferIPv6Addresses");
+ }
+
+ private static boolean isPreferIPV4Address() {
+ return Boolean.getBoolean("java.net.preferIPv4Addresses");
+ }
+
+ /**
+ * Get the suitable {@link NetworkInterface}
+ */
+ private static List<NetworkInterface> findSuitableNetworkInterface() {
+
+ // Find all network interfaces
+ List<NetworkInterface> networkInterfaces = Collections.emptyList();
+ try {
+ networkInterfaces = getAllNetworkInterfaces();
+ } catch (SocketException e) {
+ log.warn("ValidNetworkInterfaces exception", e);
+ }
+
+ // Filter the loopback/virtual/ network interfaces
+ List<NetworkInterface> validNetworkInterfaces = networkInterfaces
+ .stream()
+ .filter(networkInterface -> {
+ try {
+ return !(networkInterface == null
+ || networkInterface.isLoopback()
+ || networkInterface.isVirtual()
+ || !networkInterface.isUp());
+ } catch (SocketException e) {
+ log.warn("ValidNetworkInterfaces exception", e);
+ return false;
+ }
+ })
+ .collect(Collectors.toList());
+
+ // Use the specified network interface if set
+ String specifiedNetworkInterfaceName = specifyNetworkInterfaceName();
+ if (StringUtils.isNotBlank(specifiedNetworkInterfaceName)) {
+ validNetworkInterfaces = validNetworkInterfaces.stream()
+ .filter(networkInterface -> specifiedNetworkInterfaceName.equals(networkInterface.getDisplayName()))
+ .collect(Collectors.toList());
+ if (CollectionUtils.isEmpty(validNetworkInterfaces)) {
+ throw new IllegalArgumentException(
+ "The specified network interface: " + specifiedNetworkInterfaceName + " is not found");
+ }
+ log.info("Use the specified network interface: {} -> {}", specifiedNetworkInterfaceName,
+ validNetworkInterfaces);
+ }
+
+ Set<String> restrictNetworkInterfaceName = restrictNetworkInterfaceName();
+ if (CollectionUtils.isNotEmpty(restrictNetworkInterfaceName)) {
+ validNetworkInterfaces = validNetworkInterfaces.stream()
+ .filter(validNetworkInterface -> !restrictNetworkInterfaceName
+ .contains(validNetworkInterface.getDisplayName()))
+ .collect(Collectors.toList());
+ }
+ return filterByNetworkPriority(validNetworkInterfaces);
+ }
+
+ /**
+ * Get the suitable {@link InetAddress}
+ */
+ private static List<InetAddress> findSuitableInetAddress(List<NetworkInterface> networkInterfaces) {
+ if (CollectionUtils.isEmpty(networkInterfaces)) {
+ return Collections.emptyList();
+ }
+ List<InetAddress> allInetAddresses = new LinkedList<>();
+ for (NetworkInterface networkInterface : networkInterfaces) {
+ Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ allInetAddresses.add(addresses.nextElement());
+ }
+ }
+ // Get prefer addresses
+ List<InetAddress> preferInetAddress = new ArrayList<>();
+ if (!isPreferIPV6Address() && !isPreferIPV4Address()) {
+ // no prefer, will use all addresses
+ preferInetAddress.addAll(getIpv4Addresses(allInetAddresses));
+ preferInetAddress.addAll(getIpv6Addresses(allInetAddresses));
+ }
+ if (isPreferIPV4Address()) {
+ preferInetAddress.addAll(getIpv4Addresses(allInetAddresses));
+ }
+ if (isPreferIPV6Address()) {
+ preferInetAddress.addAll(getIpv6Addresses(allInetAddresses));
+ }
+ // Get reachable addresses
+ return preferInetAddress.stream()
+ .filter(inetAddress -> {
+ try {
+ return inetAddress.isReachable(100);
+ } catch (IOException e) {
+ log.warn("InetAddress isReachable exception", e);
+ return false;
+ }
+ }).collect(Collectors.toList());
+ }
+
+ private static List<InetAddress> getIpv4Addresses(List<InetAddress> allInetAddresses) {
+ if (CollectionUtils.isEmpty(allInetAddresses)) {
+ return Collections.emptyList();
+ }
+ List<InetAddress> validIpv4Addresses = new ArrayList<>();
+ for (InetAddress inetAddress : allInetAddresses) {
+ if (isValidV4Address(inetAddress)) {
+ validIpv4Addresses.add(inetAddress);
+ }
+ }
+ return validIpv4Addresses;
+ }
+
+ private static List<InetAddress> getIpv6Addresses(List<InetAddress> allInetAddresses) {
+ if (CollectionUtils.isEmpty(allInetAddresses)) {
+ return Collections.emptyList();
+ }
+ List<InetAddress> validIpv6Addresses = new ArrayList<>();
+ for (InetAddress inetAddress : allInetAddresses) {
+ if (!isValidV6Address(inetAddress)) {
+ continue;
+ }
+ Inet6Address v6Address = (Inet6Address) inetAddress;
+ InetAddress normalizedV6Address = normalizeV6Address(v6Address);
+ validIpv6Addresses.add(normalizedV6Address);
+ }
+ return validIpv6Addresses;
+ }
+
+ private static List<NetworkInterface> getAllNetworkInterfaces() throws SocketException {
+ List<NetworkInterface> validNetworkInterfaces = new LinkedList<>();
+ Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+ while (interfaces.hasMoreElements()) {
+ NetworkInterface networkInterface = interfaces.nextElement();
+ log.debug("Found NetworkInterface: {}", networkInterface);
+ validNetworkInterfaces.add(networkInterface);
+ }
+ log.info("Get all NetworkInterfaces: {}", validNetworkInterfaces);
+ return validNetworkInterfaces;
+ }
+
+ private static String specifyNetworkInterfaceName() {
+ return preferredInterface;
+ }
+
+ private static Set<String> restrictNetworkInterfaceName() {
+ if (StringUtils.isBlank(restrictInterface)) {
+ return Sets.newHashSet("docker0");
+ } else {
+ return Arrays.stream(preferredInterface.split(",")).map(String::trim).collect(Collectors.toSet());
+ }
+ }
+
+ private static List<NetworkInterface> filterByNetworkPriority(List<NetworkInterface> validNetworkInterfaces) {
+ if (CollectionUtils.isEmpty(validNetworkInterfaces)) {
+ return Collections.emptyList();
+ }
+
+ switch (priorityStrategy) {
+ case "default":
+ log.debug("Use default NetworkInterface acquisition policy");
+ return findAddressByDefaultPolicy(validNetworkInterfaces);
+ case "inner":
+ log.debug("Use inner NetworkInterface acquisition policy");
+ return findInnerAddressNetWorkInterface(validNetworkInterfaces);
+ case "outer":
+ log.debug("Use outer NetworkInterface acquisition policy");
+ return findOuterAddressNetworkInterface(validNetworkInterfaces);
+ default:
+ log.error("There is no matching network card acquisition policy!");
+ return Collections.emptyList();
+ }
+ }
+
+ private static List<NetworkInterface> findAddressByDefaultPolicy(List<NetworkInterface> validNetworkInterfaces) {
+ List<NetworkInterface> allAddress = new ArrayList<>();
+ allAddress.addAll(findInnerAddressNetWorkInterface(validNetworkInterfaces));
+ allAddress.addAll(findOuterAddressNetworkInterface(validNetworkInterfaces));
+ return allAddress;
+ }
+
+ /**
+ * Get the Intranet IP
+ *
+ * @return If no {@link NetworkInterface} is available , return <code>null</code>
+ */
+ private static List<NetworkInterface> findInnerAddressNetWorkInterface(List<NetworkInterface> validNetworkInterfaces) {
+ if (CollectionUtils.isEmpty(validNetworkInterfaces)) {
+ return Collections.emptyList();
+ }
+
+ List<NetworkInterface> innerNetworkInterfaces = new ArrayList<>();
+ for (NetworkInterface ni : validNetworkInterfaces) {
+ Enumeration<InetAddress> address = ni.getInetAddresses();
+ while (address.hasMoreElements()) {
+ InetAddress ip = address.nextElement();
+ if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress()) {
+ innerNetworkInterfaces.add(ni);
+ }
+ }
+ }
+ return innerNetworkInterfaces;
+ }
+
+ private static List<NetworkInterface> findOuterAddressNetworkInterface(List<NetworkInterface> validNetworkInterfaces) {
+ if (CollectionUtils.isEmpty(validNetworkInterfaces)) {
+ return Collections.emptyList();
+ }
+
+ List<NetworkInterface> outerNetworkInterfaces = new ArrayList<>();
+ for (NetworkInterface ni : validNetworkInterfaces) {
+ Enumeration<InetAddress> address = ni.getInetAddresses();
+ while (address.hasMoreElements()) {
+ InetAddress ip = address.nextElement();
+ if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress()) {
+ outerNetworkInterfaces.add(ni);
+ }
+ }
+ }
+ return outerNetworkInterfaces;
+ }
+}
diff --git a/streampark-common/src/main/java/org/apache/streampark/common/utils/OSUtils.java b/streampark-common/src/main/java/org/apache/streampark/common/utils/OSUtils.java
new file mode 100644
index 000000000..d5d0f7682
--- /dev/null
+++ b/streampark-common/src/main/java/org/apache/streampark/common/utils/OSUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.common.utils;
+
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import oshi.SystemInfo;
+import oshi.hardware.HardwareAbstractionLayer;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+
+@Slf4j
+@UtilityClass
+public class OSUtils {
+
+ private static final SystemInfo SI = new SystemInfo();
+
+ private static final HardwareAbstractionLayer hal = SI.getHardware();
+
+ public static long getTotalSystemMemory() {
+ return hal.getMemory().getTotal();
+ }
+
+ public static long getSystemAvailableMemoryUsed() {
+ return hal.getMemory().getAvailable();
+ }
+
+ public static int getProcessID() {
+ RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
+ return Integer.parseInt(runtimeMXBean.getName().split("@")[0]);
+ }
+
+}
diff --git a/streampark-common/src/main/java/org/apache/streampark/common/utils/UUIDUtils.java b/streampark-common/src/main/java/org/apache/streampark/common/utils/UUIDUtils.java
new file mode 100644
index 000000000..03418b95a
--- /dev/null
+++ b/streampark-common/src/main/java/org/apache/streampark/common/utils/UUIDUtils.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.common.utils;
+
+import lombok.experimental.UtilityClass;
+
+import java.util.UUID;
+
+@UtilityClass
+public class UUIDUtils {
+
+ public static long generateUUID() {
+ String uuidOrigin = Long.toString(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+ if (uuidOrigin.length() > 14) {
+ return Long.parseLong(uuidOrigin.substring(0, 14));
+ }
+ return Long.parseLong(uuidOrigin);
+ }
+}
diff --git a/streampark-common/src/test/java/org/apache/streampark/common/utils/NetworkUtilsTestCase.java b/streampark-common/src/test/java/org/apache/streampark/common/utils/NetworkUtilsTestCase.java
new file mode 100644
index 000000000..f9a37f8b3
--- /dev/null
+++ b/streampark-common/src/test/java/org/apache/streampark/common/utils/NetworkUtilsTestCase.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.common.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(classes = NetworkUtils.class)
+@TestPropertySource(locations = "classpath:application.yaml")
+@Slf4j
+public class NetworkUtilsTestCase {
+
+ @Test
+ void testGetHost() {
+ String host = NetworkUtils.getHost();
+ assertThat(host).isNotNull();
+ }
+
+ @Test
+ void testGetAddr() {
+ String addr = NetworkUtils.getAddr(10000);
+ assertThat(addr).isNotNull();
+ }
+}
diff --git a/streampark-common/src/test/java/org/apache/streampark/common/utils/UUIDUtilsTestCase.java b/streampark-common/src/test/java/org/apache/streampark/common/utils/UUIDUtilsTestCase.java
new file mode 100644
index 000000000..441013471
--- /dev/null
+++ b/streampark-common/src/test/java/org/apache/streampark/common/utils/UUIDUtilsTestCase.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.common.utils;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Slf4j
+class UUIDUtilsTestCase {
+
+ @Test
+ void testGenerateUUID() {
+ long uuid = UUIDUtils.generateUUID();
+ assertThat(String.valueOf(uuid).length()).isEqualTo(14);
+ }
+
+ @Test
+ @SneakyThrows
+ void testNoDuplicateGenerateUUID() {
+ int threadNum = 10;
+ int uuidNum = 10000;
+
+ CountDownLatch countDownLatch = new CountDownLatch(threadNum);
+ Map<String, List<Long>> nodeUUIDs = new ConcurrentHashMap<>();
+
+ for (int i = 0; i < threadNum; i++) {
+ new Thread(() -> {
+ List<Long> uuids = new ArrayList<>(uuidNum);
+ for (int j = 0; j < uuidNum; j++) {
+ uuids.add(UUIDUtils.generateUUID());
+ }
+ nodeUUIDs.put(Thread.currentThread().getName(), uuids);
+ countDownLatch.countDown();
+ }).start();
+ }
+
+ countDownLatch.await();
+ Set<Long> totalUUIDs = new HashSet<>();
+ nodeUUIDs.values().forEach(totalUUIDs::addAll);
+
+ assertEquals(uuidNum * threadNum, totalUUIDs.size());
+ }
+}
diff --git a/streampark-console/pom.xml b/streampark-console/pom.xml
index 59488748e..8fdb2a4fc 100644
--- a/streampark-console/pom.xml
+++ b/streampark-console/pom.xml
@@ -31,6 +31,7 @@
<modules>
<module>streampark-console-service</module>
+ <module>streampark-console-registry</module>
</modules>
<profiles>
diff --git a/streampark-console/streampark-console-registry/README.md b/streampark-console/streampark-console-registry/README.md
new file mode 100644
index 000000000..1545f5f41
--- /dev/null
+++ b/streampark-console/streampark-console-registry/README.md
@@ -0,0 +1,37 @@
+## Function
+
+SP use registry to do the below three things:
+
+1. Store the metadata of master/worker so that it can get notify when nodes up and down.
+2. Store the metadata of worker to do load balance.
+3. Acquire a global lock when do failover.
+
+So for SP, the registry need to notify the server when the server subscribe data have added/deleted/updated, support a way to create/release a global lock,
+delete the server's metadata when server down.
+
+## How to use
+
+At present, we have implements three registry: JDBC(Default, Support MySql and PostgreSQL),Zookeeper. If you
+want to use them, you should config it at resource/application.yaml. The configuration details
+can be viewed in the README of plugin under Module streampark-registry-plugins
+
+## Module
+
+### streampark-registry-all
+
+This module is used for exporting the implementation of registry.
+If you want to add new registry,you should add the dependency in the pom.xml
+
+### streampark-registry-api
+
+This module contains the relevant interfaces involved in the use of the registry.
+The following are several important interfaces
+1. Registry Interface: If you want to implement your own registry, you just need to implement this interface
+2. ConnectionListener Interface: This interface is responsible for the connection status between the client and the registry,
+The connection state can be viewed in ConnectionState.java
+3. SubscribeListener Interface: This interface is responsible for monitoring the state changes of child nodes under the specified prefix.
+Event content can be viewed in event.java
+
+### streampark-registry-plugins
+
+This module contains all registry implementations in SP
diff --git a/streampark-console/streampark-console-registry/pom.xml b/streampark-console/streampark-console-registry/pom.xml
new file mode 100644
index 000000000..c29bb0154
--- /dev/null
+++ b/streampark-console/streampark-console-registry/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-console</artifactId>
+ <version>2.2.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>streampark-console-registry</artifactId>
+ <packaging>pom</packaging>
+ <name>StreamPark : Registry</name>
+
+ <modules>
+ <module>streampark-registry-api</module>
+ <module>streampark-registry-core</module>
+ <module>streampark-registry-it</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-autoconfigure</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-collections4</artifactId>
+ </dependency>
+
+ <!-- test dependencies on TestContainers -->
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>mysql</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>postgresql</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/pom.xml b/streampark-console/streampark-console-registry/streampark-registry-api/pom.xml
new file mode 100644
index 000000000..e4de468e4
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to Apache Software Foundation (ASF) under one or more contributor
+ ~ license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright
+ ~ ownership. Apache Software Foundation (ASF) licenses this file to you under
+ ~ the Apache License, Version 2.0 (the "License"); you may
+ ~ not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-console-registry</artifactId>
+ <version>2.2.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>streampark-registry-api</artifactId>
+ <name>StreamPark : Console Registry API</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-common_${scala.binary.version}</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectStrategy.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectStrategy.java
new file mode 100644
index 000000000..7aeaac4bc
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectStrategy.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+/**
+ * This interface defined a method to be executed when the server disconnected from registry.
+ */
+public interface ConnectStrategy {
+
+ void disconnect();
+
+ void reconnect();
+
+ StrategyType getStrategyType();
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectStrategyProperties.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectStrategyProperties.java
new file mode 100644
index 000000000..30a384a78
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectStrategyProperties.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.Duration;
+
+@Getter
+@Setter
+public class ConnectStrategyProperties {
+
+ private StrategyType strategy = StrategyType.STOP;
+
+ private Duration maxWaitingTime = Duration.ofSeconds(0);
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectionListener.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectionListener.java
new file mode 100644
index 000000000..ebdc56eb3
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectionListener.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+/**
+ * when the connect state between client and registry center changed,
+ * the {@code onUpdate} function is triggered
+ */
+@FunctionalInterface
+public interface ConnectionListener {
+
+ void onUpdate(ConnectionState newState);
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectionState.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectionState.java
new file mode 100644
index 000000000..dfcdbc10f
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/ConnectionState.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+/**
+ * Connection state between client and registry center(Etcd, MySql, Zookeeper)
+ */
+public enum ConnectionState {
+ CONNECTED,
+ RECONNECTED,
+ SUSPENDED,
+ DISCONNECTED
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/Event.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/Event.java
new file mode 100644
index 000000000..deb9d5f7b
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/Event.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+public class Event {
+
+ // The prefix which is watched
+ private String key;
+ // The full path where the event was generated
+ private String path;
+ // The value corresponding to the path
+ private String data;
+ // The event type {ADD, REMOVE, UPDATE}
+ private Type type;
+
+ public Event(String key, String path, String data, Type type) {
+ this.key = key;
+ this.path = path;
+ this.data = data;
+ this.type = type;
+ }
+
+ public Event() {
+ }
+
+ public static EventBuilder builder() {
+ return new EventBuilder();
+ }
+
+ public String key() {
+ return this.key;
+ }
+
+ public String path() {
+ return this.path;
+ }
+
+ public String data() {
+ return this.data;
+ }
+
+ public Type type() {
+ return this.type;
+ }
+
+ public Event key(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public Event path(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public Event data(String data) {
+ this.data = data;
+ return this;
+ }
+
+ public Event type(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ public String toString() {
+ return "Event(key=" + this.key() + ", path=" + this.path() + ", data=" + this.data() + ", type=" + this.type()
+ + ")";
+ }
+
+ public enum Type {
+ ADD,
+ REMOVE,
+ UPDATE
+ }
+
+ public static class EventBuilder {
+
+ private String key;
+ private String path;
+ private String data;
+ private Type type;
+
+ EventBuilder() {
+ }
+
+ public EventBuilder key(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public EventBuilder path(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public EventBuilder data(String data) {
+ this.data = data;
+ return this;
+ }
+
+ public EventBuilder type(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ public Event build() {
+ return new Event(key, path, data, type);
+ }
+
+ public String toString() {
+ return "Event.EventBuilder(key=" + this.key + ", path=" + this.path + ", data=" + this.data + ", type="
+ + this.type + ")";
+ }
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/Registry.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/Registry.java
new file mode 100644
index 000000000..2f349be1d
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/Registry.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+import lombok.NonNull;
+
+import java.io.Closeable;
+import java.time.Duration;
+import java.util.Collection;
+
+/**
+ * The SPI interface for registry center, each registry plugin should implement this interface.
+ */
+public interface Registry extends Closeable {
+
+ /**
+ * Start the registry, once started, the registry will connect to the registry center.
+ */
+ void start();
+
+ /**
+ * Whether the registry is connected
+ *
+ * @return true if connected, false otherwise.
+ */
+ boolean isConnected();
+
+ /**
+ * Connect to the registry, will wait in the given timeout
+ *
+ * @param timeout max timeout, if timeout <= 0 will wait indefinitely.
+ * @throws RegistryException cannot connect in the given timeout
+ */
+ void connectUntilTimeout(@NonNull Duration timeout) throws RegistryException;
+
+ /**
+ * Subscribe the path, when the path has expose {@link Event}, the listener will be triggered.
+ * <p>
+ * The sub path will also be watched, if the sub path has event, the listener will be triggered.
+ *
+ * @param path the path to subscribe
+ * @param listener the listener to be triggered
+ */
+ void subscribe(String path, SubscribeListener listener);
+
+ /**
+ * Add a connection listener to collection.
+ */
+ void addConnectionStateListener(ConnectionListener listener);
+
+ /**
+ * Get the value of the key, if key not exist will throw {@link RegistryException}
+ */
+ String get(String key) throws RegistryException;
+
+ /**
+ * Put the key-value pair into the registry
+ *
+ * @param key the key, cannot be null
+ * @param value the value, cannot be null
+ * @param deleteOnDisconnect if true, when the connection state is disconnected, the key will be deleted
+ */
+ void put(String key, String value, boolean deleteOnDisconnect);
+
+ /**
+ * Delete the key from the registry
+ */
+ void delete(String key);
+
+ /**
+ * Return the children of the key
+ */
+ Collection<String> children(String key);
+
+ /**
+ * Check if the key exists
+ *
+ * @param key the key to check
+ * @return true if the key exists
+ */
+ boolean exists(String key);
+
+ /**
+ * Acquire the lock of the prefix {@param key}
+ */
+ boolean acquireLock(String key);
+
+ /**
+ * Acquire the lock of the prefix {@param key}, if acquire in the given timeout return true, else return false.
+ */
+ boolean acquireLock(String key, long timeout);
+
+ /**
+ * Release the lock of the prefix {@param key}
+ */
+ boolean releaseLock(String key);
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryClient.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryClient.java
new file mode 100644
index 000000000..0dbd96adc
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryClient.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+import org.apache.streampark.common.IStoppable;
+import org.apache.streampark.common.constants.Constants;
+import org.apache.streampark.common.utils.JSONUtils;
+import org.apache.streampark.registry.api.enums.RegistryNodeType;
+import org.apache.streampark.registry.api.model.ConsoleHeartBeat;
+import org.apache.streampark.registry.api.model.Server;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.google.common.base.Strings;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@Component
+@Slf4j
+public class RegistryClient {
+
+ private IStoppable stoppable;
+
+ private static final String EMPTY = "";
+
+ private final Registry registry;
+
+ public RegistryClient(Registry registry) {
+ this.registry = registry;
+ if (!registry.exists(RegistryNodeType.CONSOLE_SERVER.getRegistryPath())) {
+ registry.put(RegistryNodeType.CONSOLE_SERVER.getRegistryPath(), EMPTY, false);
+ }
+ }
+
+ public boolean isConnected() {
+ return registry.isConnected();
+
+ }
+
+ public void connectUntilTimeout(@NonNull Duration duration) throws RegistryException {
+ registry.connectUntilTimeout(duration);
+ }
+
+ public List<Server> getServerList(RegistryNodeType registryNodeType) {
+ Map<String, String> serverMaps = getServerMaps(registryNodeType);
+
+ List<Server> serverList = new ArrayList<>();
+ for (Map.Entry<String, String> entry : serverMaps.entrySet()) {
+ String serverPath = entry.getKey();
+ String heartBeatJson = entry.getValue();
+ if (StringUtils.isEmpty(heartBeatJson)) {
+ log.error("The heartBeatJson is empty, serverPath: {}", serverPath);
+ continue;
+ }
+ Server server = new Server();
+ switch (registryNodeType) {
+ case CONSOLE_SERVER:
+ ConsoleHeartBeat consoleHeartBeat =
+ JSONUtils.parseObject(heartBeatJson, ConsoleHeartBeat.class);
+ server.setCreateTime(new Date(consoleHeartBeat.getStartupTime()));
+ server.setLastHeartbeatTime(new Date(consoleHeartBeat.getReportTime()));
+ server.setId(consoleHeartBeat.getProcessId());
+ server.setHost(consoleHeartBeat.getHost());
+ server.setPort(consoleHeartBeat.getPort());
+ break;
+ default:
+ log.warn("unknown registry node type: {}", registryNodeType);
+ }
+
+ server.setResInfo(heartBeatJson);
+ // todo: add host, port in heartBeat Info, so that we don't need to parse this again
+ server.setZkDirectory(registryNodeType.getRegistryPath() + "/" + serverPath);
+ serverList.add(server);
+ }
+ return serverList;
+ }
+
+ /**
+ * Return server host:port -> value
+ */
+ public Map<String, String> getServerMaps(RegistryNodeType nodeType) {
+ Map<String, String> serverMap = new HashMap<>();
+ try {
+ Collection<String> serverList = getServerNodes(nodeType);
+ for (String server : serverList) {
+ serverMap.putIfAbsent(server, get(nodeType.getRegistryPath() + Constants.SINGLE_SLASH + server));
+ }
+ } catch (Exception e) {
+ log.error("get server list failed", e);
+ }
+
+ return serverMap;
+ }
+
+ public boolean checkNodeExists(String host, RegistryNodeType nodeType) {
+ return getServerMaps(nodeType).keySet()
+ .stream()
+ .anyMatch(it -> it.contains(host));
+ }
+
+ public Collection<String> getConsoleNodesDirectly() {
+ return getChildrenKeys(RegistryNodeType.CONSOLE_SERVER.getRegistryPath());
+ }
+
+ /**
+ * get host ip:port, path format: parentPath/ip:port
+ *
+ * @param path path
+ * @return host ip:port, string format: parentPath/ip:port
+ */
+ public String getHostByEventDataPath(String path) {
+ checkArgument(!Strings.isNullOrEmpty(path), "path cannot be null or empty");
+
+ final String[] pathArray = path.split(Constants.SINGLE_SLASH);
+
+ checkArgument(pathArray.length >= 1, "cannot parse path: %s", path);
+
+ return pathArray[pathArray.length - 1];
+ }
+
+ public void close() throws IOException {
+ registry.close();
+ }
+
+ public void persistEphemeral(String key, String value) {
+ registry.put(key, value, true);
+ }
+
+ public void remove(String key) {
+ registry.delete(key);
+ }
+
+ public String get(String key) {
+ return registry.get(key);
+ }
+
+ public void subscribe(String path, SubscribeListener listener) {
+ registry.subscribe(path, listener);
+ }
+
+ public void addConnectionStateListener(ConnectionListener listener) {
+ registry.addConnectionStateListener(listener);
+ }
+
+ public boolean exists(String key) {
+ return registry.exists(key);
+ }
+
+ public boolean getLock(String key) {
+ return registry.acquireLock(key);
+ }
+
+ public boolean releaseLock(String key) {
+ return registry.releaseLock(key);
+ }
+
+ public void setStoppable(IStoppable stoppable) {
+ this.stoppable = stoppable;
+ }
+
+ public IStoppable getStoppable() {
+ return stoppable;
+ }
+
+ public boolean isConsolePath(String path) {
+ return path != null
+ && path.startsWith(RegistryNodeType.CONSOLE_SERVER.getRegistryPath() + Constants.SINGLE_SLASH);
+ }
+
+ public Collection<String> getChildrenKeys(final String key) {
+ return registry.children(key);
+ }
+
+ public Set<String> getServerNodeSet(RegistryNodeType nodeType) {
+ try {
+ return new HashSet<>(getServerNodes(nodeType));
+ } catch (Exception e) {
+ throw new RegistryException("Failed to get server node: " + nodeType, e);
+ }
+ }
+
+ private Collection<String> getServerNodes(RegistryNodeType nodeType) {
+ return getChildrenKeys(nodeType.getRegistryPath());
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryConfiguration.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryConfiguration.java
new file mode 100644
index 000000000..629b148cc
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RegistryConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public RegistryClient registryClient(Registry registry) {
+ return new RegistryClient(registry);
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryException.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryException.java
new file mode 100644
index 000000000..8611f102c
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/RegistryException.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+public final class RegistryException extends RuntimeException {
+
+ public RegistryException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public RegistryException(String message) {
+ super(message);
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/StrategyType.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/StrategyType.java
new file mode 100644
index 000000000..450a4a272
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/StrategyType.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+public enum StrategyType {
+
+ STOP,
+ WAITING,
+ ;
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/SubscribeListener.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/SubscribeListener.java
new file mode 100644
index 000000000..9c3ab224e
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/SubscribeListener.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api;
+
+public interface SubscribeListener {
+
+ void notify(Event event);
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/enums/RegistryNodeType.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/enums/RegistryNodeType.java
new file mode 100644
index 000000000..8d7086334
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/enums/RegistryNodeType.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum RegistryNodeType {
+
+ ALL_SERVERS("nodes", "/nodes"),
+ CONSOLE_SERVER("ConsoleServer", "/nodes/console-server"),
+ CONSOLE_NODE_LOCK("ConsoleNodeLock", "/lock/console"),
+ ;
+
+ private final String name;
+
+ private final String registryPath;
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/enums/ServerStatusEnum.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/enums/ServerStatusEnum.java
new file mode 100644
index 000000000..def84ca4a
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/enums/ServerStatusEnum.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.enums;
+
+public enum ServerStatusEnum {
+
+ NORMAL,
+ BUSY,
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycle.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycle.java
new file mode 100644
index 000000000..3ad4b0fc7
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycle.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.lifecycle;
+
+import lombok.Getter;
+
+/**
+ * This enum is used to represent the server status, include master/worker.
+ */
+@Getter
+public enum ServerLifeCycle {
+
+ RUNNING(0, "The current server is running"),
+ WAITING(1, "The current server is waiting, this means it cannot work"),
+ STOPPED(2, "The current server is stopped"),
+ ;
+
+ private final int code;
+ private final String desc;
+
+ ServerLifeCycle(int code, String desc) {
+ this.code = code;
+ this.desc = desc;
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycleException.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycleException.java
new file mode 100644
index 000000000..8fcbf8279
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycleException.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.lifecycle;
+
+public class ServerLifeCycleException extends Exception {
+
+ public ServerLifeCycleException(String message) {
+ super(message);
+ }
+
+ public ServerLifeCycleException(String message, Throwable throwable) {
+ super(message, throwable);
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycleManager.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycleManager.java
new file mode 100644
index 000000000..d70133617
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/lifecycle/ServerLifeCycleManager.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.lifecycle;
+
+import lombok.Getter;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@UtilityClass
+public class ServerLifeCycleManager {
+
+ @Getter
+ private static volatile ServerLifeCycle serverLifeCycle = ServerLifeCycle.RUNNING;
+
+ @Getter
+ private static long serverStartupTime = System.currentTimeMillis();
+
+ public static boolean isRunning() {
+ return serverLifeCycle == ServerLifeCycle.RUNNING;
+ }
+
+ public static boolean isStopped() {
+ return serverLifeCycle == ServerLifeCycle.STOPPED;
+ }
+
+ /**
+ * Change the current server state to {@link ServerLifeCycle#WAITING}, only {@link ServerLifeCycle#RUNNING} can change to {@link ServerLifeCycle#WAITING}.
+ *
+ * @throws ServerLifeCycleException if change failed.
+ */
+ public static synchronized void toWaiting() throws ServerLifeCycleException {
+ if (isStopped()) {
+ throw new ServerLifeCycleException("The current server is already stopped, cannot change to waiting");
+ }
+
+ if (serverLifeCycle == ServerLifeCycle.WAITING) {
+ log.warn("The current server is already at waiting status, cannot change to waiting");
+ return;
+ }
+ serverLifeCycle = ServerLifeCycle.WAITING;
+ }
+
+ /**
+ * Recover from {@link ServerLifeCycle#WAITING} to {@link ServerLifeCycle#RUNNING}.
+ */
+ public static synchronized void recoverFromWaiting() throws ServerLifeCycleException {
+ if (isStopped()) {
+ throw new ServerLifeCycleException("The current server is already stopped, cannot recovery");
+ }
+
+ if (serverLifeCycle == ServerLifeCycle.RUNNING) {
+ log.warn("The current server status is already running, cannot recover form waiting");
+ return;
+ }
+ serverStartupTime = System.currentTimeMillis();
+ serverLifeCycle = ServerLifeCycle.RUNNING;
+ }
+
+ public static synchronized boolean toStopped() {
+ if (serverLifeCycle == ServerLifeCycle.STOPPED) {
+ return false;
+ }
+ serverLifeCycle = ServerLifeCycle.STOPPED;
+ return true;
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/BaseHeartBeat.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/BaseHeartBeat.java
new file mode 100644
index 000000000..ca9c98fab
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/BaseHeartBeat.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.model;
+
+import org.apache.streampark.registry.api.enums.ServerStatusEnum;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseHeartBeat implements HeartBeat {
+
+ protected int processId;
+ protected long startupTime;
+ protected long reportTime;
+ protected ServerStatusEnum serverStatusEnum;
+
+ protected String host;
+ protected int port;
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/BaseHeartBeatTask.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/BaseHeartBeatTask.java
new file mode 100644
index 000000000..0585bc83a
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/BaseHeartBeatTask.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.model;
+
+import org.apache.streampark.registry.api.lifecycle.ServerLifeCycleManager;
+import org.apache.streampark.registry.api.thread.BaseDaemonThread;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public abstract class BaseHeartBeatTask<T extends HeartBeat> extends BaseDaemonThread {
+
+ private static final long DEFAULT_HEARTBEAT_SCAN_INTERVAL = 1_000L;
+
+ private final String threadName;
+ private final long heartBeatInterval;
+
+ protected boolean runningFlag;
+
+ protected long lastWriteTime = 0L;
+
+ protected T lastHeartBeat = null;
+
+ public BaseHeartBeatTask(String threadName, long heartBeatInterval) {
+ super(threadName);
+ this.threadName = threadName;
+ this.heartBeatInterval = heartBeatInterval;
+ this.runningFlag = true;
+ }
+
+ @Override
+ public synchronized void start() {
+ log.info("Starting {}...", threadName);
+ super.start();
+ log.info("Started {}, heartBeatInterval: {}...", threadName, heartBeatInterval);
+ }
+
+ @Override
+ public void run() {
+ while (runningFlag) {
+ try {
+ if (!ServerLifeCycleManager.isRunning()) {
+ log.info("The current server status is {}, will not write heartBeatInfo into registry",
+ ServerLifeCycleManager.getServerLifeCycle());
+ continue;
+ }
+ T heartBeat = getHeartBeat();
+ // if first time or heartBeat status changed, write heartBeatInfo into registry
+ if (System.currentTimeMillis() - lastWriteTime >= heartBeatInterval
+ || !lastHeartBeat.getServerStatusEnum().equals(heartBeat.getServerStatusEnum())) {
+ lastHeartBeat = heartBeat;
+ writeHeartBeat(heartBeat);
+ lastWriteTime = System.currentTimeMillis();
+ }
+ } catch (Exception ex) {
+ log.error("{} task execute failed", threadName, ex);
+ } finally {
+ try {
+ Thread.sleep(DEFAULT_HEARTBEAT_SCAN_INTERVAL);
+ } catch (InterruptedException e) {
+ handleInterruptException(e);
+ }
+ }
+ }
+ }
+
+ public void shutdown() {
+ runningFlag = false;
+ log.warn("{} finished...", threadName);
+ }
+
+ private void handleInterruptException(InterruptedException ex) {
+ log.warn("{} has been interrupted", threadName, ex);
+ Thread.currentThread().interrupt();
+ }
+
+ public abstract T getHeartBeat();
+
+ public abstract void writeHeartBeat(T heartBeat);
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/ConsoleHeartBeat.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/ConsoleHeartBeat.java
new file mode 100644
index 000000000..8f6a9529b
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/ConsoleHeartBeat.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.model;
+
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+
+@SuperBuilder
+@NoArgsConstructor
+public class ConsoleHeartBeat extends BaseHeartBeat implements HeartBeat {
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/HeartBeat.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/HeartBeat.java
new file mode 100644
index 000000000..490ac12ed
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/HeartBeat.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.model;
+
+import org.apache.streampark.registry.api.enums.ServerStatusEnum;
+
+public interface HeartBeat {
+
+ ServerStatusEnum getServerStatusEnum();
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/Server.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/Server.java
new file mode 100644
index 000000000..2571169fa
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/model/Server.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+@Getter
+@Setter
+public class Server {
+
+ private int id;
+
+ private String host;
+
+ private int port;
+
+ private String zkDirectory;
+
+ /**
+ * resource info: CPU and memory
+ */
+ private String resInfo;
+
+ private Date createTime;
+
+ private Date lastHeartbeatTime;
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/ClasspathSqlScriptParser.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/ClasspathSqlScriptParser.java
new file mode 100644
index 000000000..3b414802d
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/ClasspathSqlScriptParser.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.sql;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Slf4j
+public class ClasspathSqlScriptParser implements SqlScriptParser {
+
+ private final String sqlScriptPath;
+
+ private final Charset charset;
+
+ public ClasspathSqlScriptParser(String sqlScriptPath) {
+ this.sqlScriptPath = sqlScriptPath;
+ this.charset = StandardCharsets.UTF_8;
+ }
+
+ @Override
+ public List<String> getAllSql() throws IOException {
+ Resource sqlScriptResource = new ClassPathResource(sqlScriptPath);
+ if (!sqlScriptResource.exists()) {
+ log.warn("The sql script file {} doesn't exist", sqlScriptPath);
+ return Collections.emptyList();
+ }
+ List<String> result = new ArrayList<>();
+ try (
+ InputStream inputStream = sqlScriptResource.getInputStream();
+ Reader sqlScriptReader = new InputStreamReader(inputStream, charset);
+ LineNumberReader lineNumberReader = new LineNumberReader(sqlScriptReader)) {
+ String sql;
+ do {
+ sql = parseNextSql(lineNumberReader);
+ if (StringUtils.isNotBlank(sql)) {
+ result.add(sql);
+ }
+ } while (StringUtils.isNotBlank(sql));
+ }
+ return result;
+ }
+
+ private String parseNextSql(LineNumberReader lineNumberReader) throws IOException {
+ String line;
+ while ((line = lineNumberReader.readLine()) != null) {
+ String trimLine = line.trim();
+ if (StringUtils.isEmpty(trimLine) || isComment(trimLine)) {
+ // Skip the empty line, comment line
+ continue;
+ }
+ if (trimLine.startsWith("/*")) {
+ skipLicenseHeader(lineNumberReader);
+ continue;
+ }
+ if (trimLine.startsWith("delimiter")) {
+ // begin to parse processor, until delimiter ;
+ String[] split = trimLine.split(" ");
+ if (split[1].equals(";")) {
+ continue;
+ }
+ return parseProcedure(lineNumberReader, split[1]);
+ }
+ // begin to parse sql until;
+ List<String> sqlLines = new ArrayList<>();
+ sqlLines.add(line);
+ while (!line.endsWith(";")) {
+ line = lineNumberReader.readLine();
+ if (line == null) {
+ break;
+ }
+ if (StringUtils.isBlank(line)) {
+ continue;
+ }
+ sqlLines.add(line);
+ }
+ return String.join("\n", sqlLines);
+ }
+ return null;
+ }
+
+ private void skipLicenseHeader(LineNumberReader lineNumberReader) throws IOException {
+ String line;
+ while ((line = lineNumberReader.readLine()) != null) {
+ String trimLine = line.trim();
+ if (StringUtils.isEmpty(trimLine) || isComment(trimLine)) {
+ // Skip the empty line, comment line
+ continue;
+ }
+ if (line.startsWith("*/")) {
+ break;
+ }
+ }
+ }
+
+ private String parseProcedure(LineNumberReader lineNumberReader, String delimiter) throws IOException {
+ List<String> sqlLines = new ArrayList<>();
+ // begin to parse processor, until delimiter ;
+ String line;
+ while (true) {
+ line = lineNumberReader.readLine();
+ if (line == null) {
+ break;
+ }
+ if (StringUtils.isBlank(line)) {
+ continue;
+ }
+ if (line.trim().startsWith(delimiter)) {
+ break;
+ }
+ sqlLines.add(line);
+ }
+ return String.join("\n", sqlLines);
+ }
+
+ private boolean isComment(String line) {
+ return line.startsWith("--") || line.startsWith("//");
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/SqlScriptParser.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/SqlScriptParser.java
new file mode 100644
index 000000000..bcf43710f
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/SqlScriptParser.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.sql;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface SqlScriptParser {
+
+ List<String> getAllSql() throws IOException;
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/SqlScriptRunner.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/SqlScriptRunner.java
new file mode 100644
index 000000000..450e4e428
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/sql/SqlScriptRunner.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.sql;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.sql.DataSource;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+
+@Slf4j
+public class SqlScriptRunner {
+
+ private final DataSource dataSource;
+
+ private final SqlScriptParser sqlScriptParser;
+
+ /**
+ * @param dataSource DataSource which used to execute the sql script.
+ * @param sqlScriptFilePath Sqk script file path, the path should under classpath.
+ */
+ public SqlScriptRunner(DataSource dataSource, String sqlScriptFilePath) {
+ this.dataSource = dataSource;
+ this.sqlScriptParser = new ClasspathSqlScriptParser(sqlScriptFilePath);
+ }
+
+ public void execute() throws SQLException, IOException {
+ List<String> allSql = sqlScriptParser.getAllSql();
+ try (Connection connection = dataSource.getConnection()) {
+ for (String sql : allSql) {
+ if (StringUtils.isBlank(sql)) {
+ continue;
+ }
+ try (Statement statement = connection.createStatement()) {
+ // Since some sql doesn't have result so we believe if there is no exception then we think the sql
+ // execute success.
+ statement.execute(sql);
+ log.info("Execute sql: {} success", sql);
+ }
+ }
+ }
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/BaseDaemonThread.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/BaseDaemonThread.java
new file mode 100644
index 000000000..d156273a8
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/BaseDaemonThread.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.thread;
+
+/**
+ * All thread used in StreamPark should extend with this class to avoid the server hang issue.
+ */
+public abstract class BaseDaemonThread extends Thread {
+
+ protected BaseDaemonThread(Runnable runnable) {
+ super(runnable);
+ this.setDaemon(true);
+ this.setUncaughtExceptionHandler(DefaultUncaughtExceptionHandler.getInstance());
+ }
+
+ protected BaseDaemonThread(String threadName) {
+ super();
+ this.setName(threadName);
+ this.setDaemon(true);
+ this.setUncaughtExceptionHandler(DefaultUncaughtExceptionHandler.getInstance());
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/DefaultUncaughtExceptionHandler.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/DefaultUncaughtExceptionHandler.java
new file mode 100644
index 000000000..79df8d8ce
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/DefaultUncaughtExceptionHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.thread;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.atomic.LongAdder;
+
+@Slf4j
+public class DefaultUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
+
+ private static final DefaultUncaughtExceptionHandler INSTANCE = new DefaultUncaughtExceptionHandler();
+
+ private static final LongAdder uncaughtExceptionCount = new LongAdder();
+
+ private DefaultUncaughtExceptionHandler() {
+ }
+
+ public static DefaultUncaughtExceptionHandler getInstance() {
+ return INSTANCE;
+ }
+
+ public static long getUncaughtExceptionCount() {
+ return uncaughtExceptionCount.longValue();
+ }
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ uncaughtExceptionCount.add(1);
+ log.error("Caught an exception in {}.", t, e);
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/ThreadUtils.java b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/ThreadUtils.java
new file mode 100644
index 000000000..9e92fdb96
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-api/src/main/java/org/apache/streampark/registry/api/thread/ThreadUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.api.thread;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@UtilityClass
+@Slf4j
+public class ThreadUtils {
+
+ public static ThreadPoolExecutor newDaemonFixedThreadExecutor(String threadName, int threadsNum) {
+ return (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsNum, newDaemonThreadFactory(threadName));
+ }
+
+ public static ScheduledExecutorService newSingleDaemonScheduledExecutorService(String threadName) {
+ return Executors.newSingleThreadScheduledExecutor(newDaemonThreadFactory(threadName));
+ }
+
+ public static ScheduledExecutorService newDaemonScheduledExecutorService(String threadName, int threadsNum) {
+ return Executors.newScheduledThreadPool(threadsNum, newDaemonThreadFactory(threadName));
+ }
+
+ public static ThreadFactory newDaemonThreadFactory(String threadName) {
+ return new ThreadFactoryBuilder()
+ .setDaemon(true)
+ .setNameFormat(threadName)
+ .setUncaughtExceptionHandler(DefaultUncaughtExceptionHandler.getInstance())
+ .build();
+ }
+
+ /**
+ * Sleep in given mills, this is not accuracy.
+ */
+ public static void sleep(final long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (final InterruptedException interruptedException) {
+ Thread.currentThread().interrupt();
+ log.error("Current thread sleep error", interruptedException);
+ }
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/README.md b/streampark-console/streampark-console-registry/streampark-registry-core/README.md
new file mode 100644
index 000000000..a9bfc05b9
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/README.md
@@ -0,0 +1,83 @@
+# Introduction
+
+This module is the jdbc registry plugin module, this plugin will use jdbc as the registry center. Will use the database
+configuration same as streampark in config.yaml default.
+
+# How to use
+
+1. Initialize the database table
+
+- If you use Mysql you can directly execute the sql script `src/main/resources/mysql_registry_init.sql`.
+
+- If you use Postgresql you can directly execute the sql script `src/main/resources/postgresql_registry_init.sql`.
+
+2. Change the config
+
+You need to set the registry properties in master/worker/api's application.yml
+
+```yaml
+registry:
+ type: jdbc
+```
+
+After do this two steps, you can start your streampark cluster, your cluster will use mysql as registry center to
+store server metadata.
+
+NOTE: You need to add `mysql-connector-java.jar` into SP classpath if you use mysql database, since this plugin will not
+bundle this driver in distribution.
+
+## Optional configuration
+
+```yaml
+registry:
+ type: jdbc
+ # Used to schedule refresh the heartbeat.
+ heartbeat-refresh-interval: 3s
+ # Once the client's heartbeat is not refresh in this time, the server will consider the client is offline.
+ session-timeout: 60s
+ # The hikari configuration, default will use the same datasource pool as streampark.
+ hikari-config:
+ jdbc-url: jdbc:mysql://127.0.0.1:3306/streampark
+ username: root
+ password: root
+ maximum-pool-size: 5
+ connection-timeout: 9000
+ idle-timeout: 600000
+```
+
+## Use different database configuration for jdbc registry center
+
+You need to set the registry properties in console config.yaml
+
+### Use MySql as registry center
+
+```yaml
+registry:
+ type: jdbc
+ heartbeat-refresh-interval: 3s
+ session-timeout: 60s
+ hikari-config:
+ jdbc-url: jdbc:mysql://127.0.0.1:3306/streampark
+ username: root
+ password: root
+ maximum-pool-size: 5
+ connection-timeout: 9000
+ idle-timeout: 600000
+```
+
+### Use PostgreSQL as registry center
+
+```yaml
+registry:
+ type: jdbc
+ heartbeat-refresh-interval: 3s
+ session-timeout: 60s
+ hikari-config:
+ jdbc-url: jdbc:postgresql://localhost:5432/streampark
+ username: root
+ password: root
+ maximum-pool-size: 5
+ connection-timeout: 9000
+ idle-timeout: 600000
+```
+
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/pom.xml b/streampark-console/streampark-console-registry/streampark-registry-core/pom.xml
new file mode 100644
index 000000000..a4877f699
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to Apache Software Foundation (ASF) under one or more contributor
+ ~ license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright
+ ~ ownership. Apache Software Foundation (ASF) licenses this file to you under
+ ~ the Apache License, Version 2.0 (the "License"); you may
+ ~ not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-console-registry</artifactId>
+ <version>2.2.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>streampark-registry-core</artifactId>
+ <name>StreamPark : Console Registry Core</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-registry-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-common_${scala.binary.version}</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.zaxxer</groupId>
+ <artifactId>HikariCP</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.postgresql</groupId>
+ <artifactId>postgresql</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-boot-starter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-to-slf4j</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.hibernate.validator</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-registry-it</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>mysql</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>postgresql</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.httpcomponents.client5</groupId>
+ <artifactId>httpclient5</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistry.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistry.java
new file mode 100644
index 000000000..58f5f972e
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistry.java
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.registry.api.ConnectionListener;
+import org.apache.streampark.registry.api.ConnectionState;
+import org.apache.streampark.registry.api.Event;
+import org.apache.streampark.registry.api.Registry;
+import org.apache.streampark.registry.api.RegistryException;
+import org.apache.streampark.registry.api.SubscribeListener;
+import org.apache.streampark.registry.core.client.JdbcRegistryClient;
+import org.apache.streampark.registry.core.model.DTO.DataType;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataDTO;
+import org.apache.streampark.registry.core.server.ConnectionStateListener;
+import org.apache.streampark.registry.core.server.IJdbcRegistryServer;
+import org.apache.streampark.registry.core.server.JdbcRegistryDataChangeListener;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * This is one of the implementation of {@link Registry}, with this implementation, you need to rely on mysql database to
+ * store the streampark master/worker's metadata and do the server registry/unRegistry.
+ */
+@Slf4j
+public final class JdbcRegistry implements Registry {
+
+ private final JdbcRegistryProperties jdbcRegistryProperties;
+ private final JdbcRegistryClient jdbcRegistryClient;
+
+ private final IJdbcRegistryServer jdbcRegistryServer;
+
+ JdbcRegistry(JdbcRegistryProperties jdbcRegistryProperties, IJdbcRegistryServer jdbcRegistryServer) {
+ this.jdbcRegistryProperties = jdbcRegistryProperties;
+ this.jdbcRegistryServer = jdbcRegistryServer;
+ this.jdbcRegistryClient = new JdbcRegistryClient(jdbcRegistryProperties, jdbcRegistryServer);
+ log.info("Initialize Jdbc Registry...");
+ }
+
+ @Override
+ public void start() {
+ log.info("Starting Jdbc Registry...");
+ jdbcRegistryServer.start();
+ jdbcRegistryClient.start();
+ log.info("Started Jdbc Registry...");
+ }
+
+ @Override
+ public boolean isConnected() {
+ return jdbcRegistryClient.isConnectivity();
+ }
+
+ @Override
+ public void connectUntilTimeout(@NonNull Duration timeout) throws RegistryException {
+ long beginTimeMillis = System.currentTimeMillis();
+ long endTimeMills = timeout.getSeconds() <= 0 ? Long.MAX_VALUE : beginTimeMillis + timeout.toMillis();
+ while (true) {
+ if (System.currentTimeMillis() > endTimeMills) {
+ throw new RegistryException(
+ String.format("Cannot connect to jdbc registry in %s s", timeout.getSeconds()));
+ }
+ if (jdbcRegistryClient.isConnectivity()) {
+ return;
+ }
+ try {
+ Thread.sleep(jdbcRegistryProperties.getHeartbeatRefreshInterval().toMillis());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RegistryException("Cannot connect to jdbc registry due to interrupted exception", e);
+ }
+ }
+ }
+
+ @Override
+ public void subscribe(String path, SubscribeListener listener) {
+ checkNotNull(path);
+ checkNotNull(listener);
+ jdbcRegistryClient.subscribeJdbcRegistryDataChange(new JdbcRegistryDataChangeListener() {
+
+ @Override
+ public void onJdbcRegistryDataChanged(String key, String value) {
+ if (!key.startsWith(path)) {
+ return;
+ }
+ Event event = Event.builder()
+ .key(key)
+ .path(path)
+ .data(value)
+ .type(Event.Type.UPDATE)
+ .build();
+ listener.notify(event);
+ }
+
+ @Override
+ public void onJdbcRegistryDataDeleted(String key) {
+ if (!key.startsWith(path)) {
+ return;
+ }
+ Event event = Event.builder()
+ .key(key)
+ .path(key)
+ .type(Event.Type.REMOVE)
+ .build();
+ listener.notify(event);
+ }
+
+ @Override
+ public void onJdbcRegistryDataAdded(String key, String value) {
+ if (!key.startsWith(path)) {
+ return;
+ }
+ Event event = Event.builder()
+ .key(key)
+ .path(key)
+ .data(value)
+ .type(Event.Type.ADD)
+ .build();
+ listener.notify(event);
+ }
+ });
+ }
+
+ @Override
+ public void addConnectionStateListener(ConnectionListener listener) {
+ checkNotNull(listener);
+ jdbcRegistryClient.subscribeConnectionStateChange(new ConnectionStateListener() {
+
+ @Override
+ public void onConnected() {
+ listener.onUpdate(ConnectionState.CONNECTED);
+ }
+
+ @Override
+ public void onDisConnected() {
+ listener.onUpdate(ConnectionState.DISCONNECTED);
+ }
+
+ @Override
+ public void onReconnected() {
+ listener.onUpdate(ConnectionState.RECONNECTED);
+ }
+ });
+ }
+
+ @Override
+ public String get(String key) {
+ try {
+ // get the key value
+ // Directly get from the db?
+ Optional<JdbcRegistryDataDTO> jdbcRegistryDataOptional = jdbcRegistryClient.getJdbcRegistryDataByKey(key);
+ if (!jdbcRegistryDataOptional.isPresent()) {
+ throw new RegistryException("key: " + key + " not exist");
+ }
+ return jdbcRegistryDataOptional.get().getDataValue();
+ } catch (RegistryException registryException) {
+ throw registryException;
+ } catch (Exception e) {
+ throw new RegistryException(String.format("Get key: %s error", key), e);
+ }
+ }
+
+ @Override
+ public void put(String key, String value, boolean deleteOnDisconnect) {
+ try {
+ DataType dataType = deleteOnDisconnect ? DataType.EPHEMERAL : DataType.PERSISTENT;
+ jdbcRegistryClient.putJdbcRegistryData(key, value, dataType);
+ } catch (Exception ex) {
+ throw new RegistryException(String.format("put key:%s, value:%s error", key, value), ex);
+ }
+ }
+
+ @Override
+ public void delete(String key) {
+ try {
+ jdbcRegistryClient.deleteJdbcRegistryDataByKey(key);
+ } catch (Exception e) {
+ throw new RegistryException(String.format("Delete key: %s error", key), e);
+ }
+ }
+
+ @Override
+ public Collection<String> children(String key) {
+ try {
+ List<JdbcRegistryDataDTO> children = jdbcRegistryClient.listJdbcRegistryDataChildren(key);
+ return children
+ .stream()
+ .map(JdbcRegistryDataDTO::getDataKey)
+ .filter(fullPath -> fullPath.length() > key.length())
+ .map(fullPath -> StringUtils.substringBefore(fullPath.substring(key.length() + 1), "/"))
+ .distinct()
+ .collect(Collectors.toList());
+ } catch (Exception e) {
+ throw new RegistryException(String.format("Get key: %s children error", key), e);
+ }
+ }
+
+ @Override
+ public boolean exists(String key) {
+ try {
+ return jdbcRegistryClient.existJdbcRegistryDataKey(key);
+ } catch (Exception e) {
+ throw new RegistryException(String.format("Check key: %s exist error", key), e);
+ }
+ }
+
+ @Override
+ public boolean acquireLock(String key) {
+ try {
+ jdbcRegistryClient.acquireJdbcRegistryLock(key);
+ return true;
+ } catch (RegistryException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RegistryException(String.format("Acquire lock: %s error", key), e);
+ }
+ }
+
+ @Override
+ public boolean acquireLock(String key, long timeout) {
+ try {
+ return jdbcRegistryClient.acquireJdbcRegistryLock(key, timeout);
+ } catch (RegistryException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RegistryException(String.format("Acquire lock: %s error", key), e);
+ }
+ }
+
+ @Override
+ public boolean releaseLock(String key) {
+ jdbcRegistryClient.releaseJdbcRegistryLock(key);
+ return true;
+ }
+
+ @Override
+ public void close() {
+ log.info("Closing Jdbc Registry...");
+ // remove the current Ephemeral node, if can connect to jdbc
+ try {
+ jdbcRegistryServer.close();
+ jdbcRegistryClient.close();
+ } catch (Exception e) {
+ log.error("Close Jdbc Registry error", e);
+ }
+ log.info("Closed Jdbc Registry...");
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryAutoConfiguration.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryAutoConfiguration.java
new file mode 100644
index 000000000..bb6799055
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryAutoConfiguration.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.registry.core.mapper.JdbcRegistryClientHeartbeatMapper;
+import org.apache.streampark.registry.core.mapper.JdbcRegistryDataChanceEventMapper;
+import org.apache.streampark.registry.core.mapper.JdbcRegistryDataMapper;
+import org.apache.streampark.registry.core.mapper.JdbcRegistryLockMapper;
+import org.apache.streampark.registry.core.repository.JdbcRegistryClientRepository;
+import org.apache.streampark.registry.core.repository.JdbcRegistryDataChanceEventRepository;
+import org.apache.streampark.registry.core.repository.JdbcRegistryDataRepository;
+import org.apache.streampark.registry.core.repository.JdbcRegistryLockRepository;
+import org.apache.streampark.registry.core.server.IJdbcRegistryServer;
+import org.apache.streampark.registry.core.server.JdbcRegistryServer;
+
+import org.apache.ibatis.session.SqlSessionFactory;
+
+import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
+import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
+import com.zaxxer.hikari.HikariDataSource;
+import lombok.extern.slf4j.Slf4j;
+import org.mybatis.spring.SqlSessionTemplate;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Slf4j
+@ComponentScan
+@Configuration(proxyBeanMethods = false)
+@MapperScan("org.apache.streampark.registry.core.mapper")
+@ConditionalOnProperty(prefix = "registry", name = "type", havingValue = "jdbc")
+@AutoConfigureAfter(MybatisPlusAutoConfiguration.class)
+public class JdbcRegistryAutoConfiguration {
+
+ public JdbcRegistryAutoConfiguration() {
+ log.info("Load JdbcRegistryAutoConfiguration");
+ }
+
+ @Bean
+ public IJdbcRegistryServer jdbcRegistryServer(JdbcRegistryDataRepository jdbcRegistryDataRepository,
+ JdbcRegistryLockRepository jdbcRegistryLockRepository,
+ JdbcRegistryClientRepository jdbcRegistryClientRepository,
+ JdbcRegistryDataChanceEventRepository jdbcRegistryDataChanceEventRepository,
+ JdbcRegistryProperties jdbcRegistryProperties) {
+ return new JdbcRegistryServer(
+ jdbcRegistryDataRepository,
+ jdbcRegistryLockRepository,
+ jdbcRegistryClientRepository,
+ jdbcRegistryDataChanceEventRepository,
+ jdbcRegistryProperties);
+ }
+
+ @Bean
+ public JdbcRegistry jdbcRegistry(JdbcRegistryProperties jdbcRegistryProperties,
+ IJdbcRegistryServer jdbcRegistryServer) {
+ JdbcRegistry jdbcRegistry = new JdbcRegistry(jdbcRegistryProperties, jdbcRegistryServer);
+ jdbcRegistry.start();
+ return jdbcRegistry;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SqlSessionFactory sqlSessionFactory(JdbcRegistryProperties jdbcRegistryProperties) throws Exception {
+ log.info("Initialize jdbcRegistrySqlSessionFactory");
+ MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
+ sqlSessionFactoryBean.setDataSource(new HikariDataSource(jdbcRegistryProperties.getHikariConfig()));
+ return sqlSessionFactoryBean.getObject();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory jdbcRegistrySqlSessionFactory) {
+ log.info("Initialize jdbcRegistrySqlSessionTemplate");
+ return new SqlSessionTemplate(jdbcRegistrySqlSessionFactory);
+ }
+
+ @Bean
+ public JdbcRegistryDataMapper jdbcRegistryDataMapper(SqlSessionTemplate jdbcRegistrySqlSessionTemplate) {
+ jdbcRegistrySqlSessionTemplate.getConfiguration().addMapper(JdbcRegistryDataMapper.class);
+ return jdbcRegistrySqlSessionTemplate.getMapper(JdbcRegistryDataMapper.class);
+ }
+
+ @Bean
+ public JdbcRegistryLockMapper jdbcRegistryLockMapper(SqlSessionTemplate jdbcRegistrySqlSessionTemplate) {
+ jdbcRegistrySqlSessionTemplate.getConfiguration().addMapper(JdbcRegistryLockMapper.class);
+ return jdbcRegistrySqlSessionTemplate.getMapper(JdbcRegistryLockMapper.class);
+ }
+
+ @Bean
+ public JdbcRegistryDataChanceEventMapper jdbcRegistryDataChanceEventMapper(SqlSessionTemplate jdbcRegistrySqlSessionTemplate) {
+ jdbcRegistrySqlSessionTemplate.getConfiguration().addMapper(JdbcRegistryDataChanceEventMapper.class);
+ return jdbcRegistrySqlSessionTemplate.getMapper(JdbcRegistryDataChanceEventMapper.class);
+ }
+
+ @Bean
+ public JdbcRegistryClientHeartbeatMapper jdbcRegistryClientHeartbeatMapper(SqlSessionTemplate jdbcRegistrySqlSessionTemplate) {
+ jdbcRegistrySqlSessionTemplate.getConfiguration().addMapper(JdbcRegistryClientHeartbeatMapper.class);
+ return jdbcRegistrySqlSessionTemplate.getMapper(JdbcRegistryClientHeartbeatMapper.class);
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryProperties.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryProperties.java
new file mode 100644
index 000000000..16bd4de88
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryProperties.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.common.utils.NetworkUtils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.zaxxer.hikari.HikariConfig;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.Errors;
+import org.springframework.validation.Validator;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.Duration;
+
+@Getter
+@Setter
+@Slf4j
+@Validated
+@Configuration
+@ConditionalOnProperty(prefix = "registry", name = "type", havingValue = "jdbc")
+@ConfigurationProperties(prefix = "registry")
+public class JdbcRegistryProperties implements Validator {
+
+ @Autowired
+ private NetworkUtils networkUtils;
+
+ private static final Duration MIN_HEARTBEAT_REFRESH_INTERVAL = Duration.ofSeconds(1);
+
+ @Value("${server.port:10000}")
+ private int serverPort;
+
+ private String jdbcRegistryClientName;
+
+ private Duration heartbeatRefreshInterval = Duration.ofSeconds(3);
+ private Duration sessionTimeout = Duration.ofSeconds(60);
+ private HikariConfig hikariConfig;
+
+ @Override
+ public boolean supports(Class<?> clazz) {
+ return JdbcRegistryProperties.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ public void validate(Object target, Errors errors) {
+ JdbcRegistryProperties jdbcRegistryProperties = (JdbcRegistryProperties) target;
+ if (jdbcRegistryProperties.getHeartbeatRefreshInterval().compareTo(MIN_HEARTBEAT_REFRESH_INTERVAL) < 0) {
+ errors.rejectValue("heartbeatRefreshInterval", "heartbeatRefreshInterval",
+ "heartbeatRefreshInterval must be greater than 1s");
+ }
+
+ if (jdbcRegistryProperties.getSessionTimeout().toMillis() < 3
+ * jdbcRegistryProperties.getHeartbeatRefreshInterval().toMillis()) {
+ errors.rejectValue("sessionTimeout", "sessionTimeout",
+ "sessionTimeout must be greater than 3 * heartbeatRefreshInterval");
+ }
+ if (StringUtils.isEmpty(jdbcRegistryClientName)) {
+ jdbcRegistryClientName = NetworkUtils.getHost() + ":" + serverPort;
+ }
+
+ print();
+ }
+
+ private void print() {
+ String config =
+ "\n****************************JdbcRegistryProperties**************************************" +
+ "\n jdbcRegistryClientName -> " + jdbcRegistryClientName +
+ "\n heartbeatRefreshInterval -> " + heartbeatRefreshInterval +
+ "\n sessionTimeout -> " + sessionTimeout +
+ "\n hikariConfig -> " + hikariConfig +
+ "\n****************************JdbcRegistryProperties**************************************";
+ log.info(config);
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryThreadFactory.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryThreadFactory.java
new file mode 100644
index 000000000..8b004c640
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/JdbcRegistryThreadFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.registry.api.thread.ThreadUtils;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+public class JdbcRegistryThreadFactory {
+
+ public static ScheduledExecutorService getDefaultSchedulerThreadExecutor() {
+ return ThreadUtils.newDaemonScheduledExecutorService("jdbc-registry-default-scheduler-thread-pool",
+ Runtime.getRuntime().availableProcessors());
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/LockUtils.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/LockUtils.java
new file mode 100644
index 000000000..0f4f86c18
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/LockUtils.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.common.utils.NetworkUtils;
+import org.apache.streampark.common.utils.OSUtils;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class LockUtils {
+
+ private static final String LOCK_OWNER_PREFIX = NetworkUtils.getHost() + "_" + OSUtils.getProcessID() + "_";
+
+ public static String getLockOwner() {
+ return LOCK_OWNER_PREFIX + Thread.currentThread().getName();
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/IJdbcRegistryClient.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/IJdbcRegistryClient.java
new file mode 100644
index 000000000..b6f487090
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/IJdbcRegistryClient.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.client;
+
+import org.apache.streampark.registry.core.model.DTO.DataType;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataDTO;
+import org.apache.streampark.registry.core.server.ConnectionStateListener;
+import org.apache.streampark.registry.core.server.JdbcRegistryDataChangeListener;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface IJdbcRegistryClient extends AutoCloseable {
+
+ /**
+ * Start the jdbc registry client, once started, the client will connect to the jdbc registry server, and then it can be used.
+ */
+ void start();
+
+ /**
+ * Get identify of the client.
+ */
+ JdbcRegistryClientIdentify getJdbcRegistryClientIdentify();
+
+ /**
+ * Check the connectivity of the client.
+ */
+ boolean isConnectivity();
+
+ /**
+ * Subscribe the jdbc registry connection state change event.
+ */
+ void subscribeConnectionStateChange(ConnectionStateListener connectionStateListener);
+
+ /**
+ * Subscribe the {@link JdbcRegistryDataDTO} change event.
+ */
+ void subscribeJdbcRegistryDataChange(JdbcRegistryDataChangeListener jdbcRegistryDataChangeListener);
+
+ /**
+ * Get the {@link JdbcRegistryDataDTO} by key.
+ */
+ Optional<JdbcRegistryDataDTO> getJdbcRegistryDataByKey(String key);
+
+ /**
+ * Put the {@link JdbcRegistryDataDTO} to the jdbc registry server.
+ * <p>
+ * If the key is already exist, then update the {@link JdbcRegistryDataDTO}. If the key is not exist, then insert a new {@link JdbcRegistryDataDTO}.
+ */
+ void putJdbcRegistryData(String key, String value, DataType dataType);
+
+ /**
+ * Delete the {@link JdbcRegistryDataDTO} by key.
+ */
+ void deleteJdbcRegistryDataByKey(String key);
+
+ /**
+ * List all the {@link JdbcRegistryDataDTO} children by key.
+ * <p>
+ * e.g. key = "/streampark/master", and data exist in db is "/streampark/master/master1", "/streampark/master/master2"
+ * <p>
+ * then the return value will be ["master1", "master2"]
+ */
+ List<JdbcRegistryDataDTO> listJdbcRegistryDataChildren(String key);
+
+ /**
+ * Check the key exist in the jdbc registry server.
+ */
+ boolean existJdbcRegistryDataKey(String key);
+
+ /**
+ * Acquire the jdbc registry lock by key. this is a blocking method. if you want to stop the blocking, you can use interrupt the thread.
+ */
+ void acquireJdbcRegistryLock(String lockKey) throws IllegalArgumentException;
+
+ /**
+ * Acquire the jdbc registry lock by key until timeout.
+ */
+ boolean acquireJdbcRegistryLock(String lockKey, long timeout);
+
+ /**
+ * Release the jdbc registry lock by key, if the lockKey is not exist will do nothing.
+ */
+ void releaseJdbcRegistryLock(String lockKey);
+
+ /**
+ * Close the jdbc registry client, once the client been closed, it cannot work anymore.
+ */
+ @Override
+ void close();
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/JdbcRegistryClient.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/JdbcRegistryClient.java
new file mode 100644
index 000000000..2775953e7
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/JdbcRegistryClient.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.client;
+
+import org.apache.streampark.common.utils.NetworkUtils;
+import org.apache.streampark.common.utils.OSUtils;
+import org.apache.streampark.common.utils.UUIDUtils;
+import org.apache.streampark.registry.core.JdbcRegistryProperties;
+import org.apache.streampark.registry.core.model.DTO.DataType;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataDTO;
+import org.apache.streampark.registry.core.server.ConnectionStateListener;
+import org.apache.streampark.registry.core.server.IJdbcRegistryServer;
+import org.apache.streampark.registry.core.server.JdbcRegistryDataChangeListener;
+import org.apache.streampark.registry.core.server.JdbcRegistryServer;
+import org.apache.streampark.registry.core.server.JdbcRegistryServerState;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * The client of jdbc registry, used to interact with the {@link JdbcRegistryServer}.
+ */
+@Slf4j
+public class JdbcRegistryClient implements IJdbcRegistryClient {
+
+ private static final String DEFAULT_CLIENT_NAME = NetworkUtils.getHost() + "_" + OSUtils.getProcessID();
+
+ private final JdbcRegistryProperties jdbcRegistryProperties;
+
+ private final JdbcRegistryClientIdentify jdbcRegistryClientIdentify;
+
+ private final IJdbcRegistryServer jdbcRegistryServer;
+
+ public JdbcRegistryClient(JdbcRegistryProperties jdbcRegistryProperties, IJdbcRegistryServer jdbcRegistryServer) {
+ this.jdbcRegistryProperties = jdbcRegistryProperties;
+ this.jdbcRegistryServer = jdbcRegistryServer;
+ this.jdbcRegistryClientIdentify =
+ new JdbcRegistryClientIdentify(UUIDUtils.generateUUID(), DEFAULT_CLIENT_NAME);
+ }
+
+ @Override
+ public void start() {
+ jdbcRegistryServer.registerClient(this);
+ }
+
+ @Override
+ public JdbcRegistryClientIdentify getJdbcRegistryClientIdentify() {
+ return jdbcRegistryClientIdentify;
+ }
+
+ @Override
+ public void subscribeConnectionStateChange(ConnectionStateListener connectionStateListener) {
+ jdbcRegistryServer.subscribeConnectionStateChange(connectionStateListener);
+ }
+
+ @Override
+ public void subscribeJdbcRegistryDataChange(JdbcRegistryDataChangeListener jdbcRegistryDataChangeListener) {
+ jdbcRegistryServer.subscribeJdbcRegistryDataChange(jdbcRegistryDataChangeListener);
+ }
+
+ @Override
+ public Optional<JdbcRegistryDataDTO> getJdbcRegistryDataByKey(String key) {
+ return jdbcRegistryServer.getJdbcRegistryDataByKey(key);
+ }
+
+ @Override
+ public void putJdbcRegistryData(String key, String value, DataType dataType) {
+ jdbcRegistryServer.putJdbcRegistryData(jdbcRegistryClientIdentify.getClientId(), key, value, dataType);
+ }
+
+ @Override
+ public void deleteJdbcRegistryDataByKey(String key) {
+ jdbcRegistryServer.deleteJdbcRegistryDataByKey(key);
+ }
+
+ @Override
+ public List<JdbcRegistryDataDTO> listJdbcRegistryDataChildren(String key) {
+ return jdbcRegistryServer.listJdbcRegistryDataChildren(key);
+ }
+
+ @Override
+ public boolean existJdbcRegistryDataKey(String key) {
+ return jdbcRegistryServer.existJdbcRegistryDataKey(key);
+ }
+
+ @Override
+ public void acquireJdbcRegistryLock(String key) {
+ jdbcRegistryServer.acquireJdbcRegistryLock(jdbcRegistryClientIdentify.getClientId(), key);
+ }
+
+ @Override
+ public boolean acquireJdbcRegistryLock(String key, long timeout) {
+ return jdbcRegistryServer.acquireJdbcRegistryLock(jdbcRegistryClientIdentify.getClientId(), key, timeout);
+ }
+
+ @Override
+ public void releaseJdbcRegistryLock(String key) {
+ jdbcRegistryServer.releaseJdbcRegistryLock(jdbcRegistryClientIdentify.getClientId(), key);
+ }
+
+ @Override
+ public void close() {
+ jdbcRegistryServer.deregisterClient(this);
+ log.info("Closed JdbcRegistryClient: {}", jdbcRegistryClientIdentify);
+ }
+
+ @Override
+ public boolean isConnectivity() {
+ return jdbcRegistryServer.getServerState() == JdbcRegistryServerState.STARTED;
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/JdbcRegistryClientIdentify.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/JdbcRegistryClientIdentify.java
new file mode 100644
index 000000000..7481c3a30
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/client/JdbcRegistryClientIdentify.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.client;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
+
+@ToString
+@Getter
+@AllArgsConstructor
+public class JdbcRegistryClientIdentify {
+
+ private final Long clientId;
+
+ private final String clientName;
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryClientHeartbeatMapper.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryClientHeartbeatMapper.java
new file mode 100644
index 000000000..f1a27c299
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryClientHeartbeatMapper.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.mapper;
+
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryClientHeartbeat;
+
+import org.apache.ibatis.annotations.Select;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+public interface JdbcRegistryClientHeartbeatMapper extends BaseMapper<JdbcRegistryClientHeartbeat> {
+
+ @Select("select * from t_jdbc_registry_client_heartbeat")
+ List<JdbcRegistryClientHeartbeat> selectAll();
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryDataChanceEventMapper.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryDataChanceEventMapper.java
new file mode 100644
index 000000000..b66982ed9
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryDataChanceEventMapper.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.mapper;
+
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryDataChanceEvent;
+
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.Date;
+import java.util.List;
+
+public interface JdbcRegistryDataChanceEventMapper extends BaseMapper<JdbcRegistryDataChanceEvent> {
+
+ @Select("select max(id) from t_jdbc_registry_data_change_event")
+ Long getMaxId();
+
+ @Select("select * from t_jdbc_registry_data_change_event where id > #{id} order by id asc limit 1000")
+ List<JdbcRegistryDataChanceEvent> selectJdbcRegistryDataChangeEventWhereIdAfter(@Param("id") long id);
+
+ @Delete("delete from t_jdbc_registry_data_change_event where create_time > #{createTime}")
+ void deleteJdbcRegistryDataChangeEventBeforeCreateTime(@Param("createTime") Date createTime);
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryDataMapper.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryDataMapper.java
new file mode 100644
index 000000000..c0eb45dff
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryDataMapper.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.mapper;
+
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryData;
+
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+public interface JdbcRegistryDataMapper extends BaseMapper<JdbcRegistryData> {
+
+ @Select("select * from t_jdbc_registry_data")
+ List<JdbcRegistryData> selectAll();
+
+ @Select("select * from t_jdbc_registry_data where data_key = #{key}")
+ JdbcRegistryData selectByKey(@Param("key") String key);
+
+ @Delete("delete from t_jdbc_registry_data where data_key = #{key}")
+ void deleteByKey(@Param("key") String key);
+
+ @Delete({"<script>",
+ "delete from t_jdbc_registry_data",
+ "where client_id IN ",
+ "<foreach item='clientId' index='index' collection='clientIds' open='(' separator=',' close=')'>",
+ " #{clientId}",
+ "</foreach>",
+ "and data_type = #{dataType}",
+ "</script>"})
+ void deleteByClientIds(@Param("clientIds") List<Long> clientIds, @Param("dataType") String dataType);
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryLockMapper.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryLockMapper.java
new file mode 100644
index 000000000..84f8d7e45
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/mapper/JdbcRegistryLockMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.mapper;
+
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryLock;
+
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Param;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+public interface JdbcRegistryLockMapper extends BaseMapper<JdbcRegistryLock> {
+
+ @Delete({"<script>",
+ "delete from t_jdbc_registry_lock",
+ "where client_id IN ",
+ "<foreach item='clientId' index='index' collection='clientIds' open='(' separator=',' close=')'>",
+ " #{clientId}",
+ "</foreach>",
+ "</script>"})
+ void deleteByClientIds(@Param("clientIds") List<Long> clientIds);
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryClientHeartbeat.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryClientHeartbeat.java
new file mode 100644
index 000000000..3875acab8
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryClientHeartbeat.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DO;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+
+@TableName(value = "t_jdbc_registry_client_heartbeat")
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JdbcRegistryClientHeartbeat {
+
+ @TableId(value = "id", type = IdType.INPUT)
+ private Long id;
+ private String clientName;
+ private Long lastHeartbeatTime;
+ private String connectionConfig;
+ private Date createTime;
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryData.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryData.java
new file mode 100644
index 000000000..29e3c8810
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryData.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DO;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+
+@TableName(value = "t_jdbc_registry_data")
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JdbcRegistryData {
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+ private String dataKey;
+ private String dataValue;
+ private String dataType;
+ private long clientId;
+ private Date createTime;
+ private Date lastUpdateTime;
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryDataChanceEvent.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryDataChanceEvent.java
new file mode 100644
index 000000000..8088a3bb4
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryDataChanceEvent.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DO;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+
+@TableName(value = "t_jdbc_registry_data_change_event")
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JdbcRegistryDataChanceEvent {
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ private String eventType;
+
+ private String jdbcRegistryData;
+
+ private Date createTime;
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryLock.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryLock.java
new file mode 100644
index 000000000..f77783c01
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DO/JdbcRegistryLock.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DO;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+
+@TableName(value = "t_jdbc_registry_lock")
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JdbcRegistryLock {
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+ private String lockKey;
+ private String lockOwner;
+ private Long clientId;
+ private Date createTime;
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/DataType.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/DataType.java
new file mode 100644
index 000000000..cd33dc186
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/DataType.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DTO;
+
+public enum DataType {
+
+ EPHEMERAL(),
+ PERSISTENT(),
+ ;
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryClientHeartbeatDTO.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryClientHeartbeatDTO.java
new file mode 100644
index 000000000..462ba92f9
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryClientHeartbeatDTO.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DTO;
+
+import org.apache.streampark.common.utils.JSONUtils;
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryClientHeartbeat;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.SneakyThrows;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JdbcRegistryClientHeartbeatDTO {
+
+ private Long id;
+
+ // clientName
+ private String clientName;
+
+ private Long lastHeartbeatTime;
+
+ private ClientConfig clientConfig;
+
+ private Date createTime;
+
+ public static JdbcRegistryClientHeartbeatDTO fromJdbcRegistryClientHeartbeat(JdbcRegistryClientHeartbeat jdbcRegistryClientHeartbeat) {
+ return JdbcRegistryClientHeartbeatDTO.builder()
+ .id(jdbcRegistryClientHeartbeat.getId())
+ .clientName(jdbcRegistryClientHeartbeat.getClientName())
+ .lastHeartbeatTime(jdbcRegistryClientHeartbeat.getLastHeartbeatTime())
+ .clientConfig(
+ JSONUtils.parseObject(jdbcRegistryClientHeartbeat.getConnectionConfig(), ClientConfig.class))
+ .createTime(jdbcRegistryClientHeartbeat.getCreateTime())
+ .build();
+ }
+
+ public static JdbcRegistryClientHeartbeat toJdbcRegistryClientHeartbeat(JdbcRegistryClientHeartbeatDTO jdbcRegistryClientHeartbeatDTO) {
+ return JdbcRegistryClientHeartbeat.builder()
+ .id(jdbcRegistryClientHeartbeatDTO.getId())
+ .clientName(jdbcRegistryClientHeartbeatDTO.getClientName())
+ .lastHeartbeatTime(jdbcRegistryClientHeartbeatDTO.getLastHeartbeatTime())
+ .connectionConfig(JSONUtils.toJsonString(jdbcRegistryClientHeartbeatDTO.getClientConfig()))
+ .createTime(jdbcRegistryClientHeartbeatDTO.getCreateTime())
+ .build();
+ }
+
+ public boolean isDead() {
+ // check if the client connection is expired.
+ return System.currentTimeMillis() - lastHeartbeatTime > clientConfig.getSessionTimeout();
+ }
+
+ @SneakyThrows
+ @Override
+ public JdbcRegistryClientHeartbeatDTO clone() {
+ return JdbcRegistryClientHeartbeatDTO.builder()
+ .id(id)
+ .clientName(clientName)
+ .lastHeartbeatTime(lastHeartbeatTime)
+ .clientConfig(clientConfig)
+ .createTime(createTime)
+ .build();
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class ClientConfig {
+
+ @Builder.Default
+ private long sessionTimeout = 60 * 1000L;
+
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryDataChanceEventDTO.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryDataChanceEventDTO.java
new file mode 100644
index 000000000..8803e83fc
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryDataChanceEventDTO.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DTO;
+
+import org.apache.streampark.common.utils.JSONUtils;
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryData;
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryDataChanceEvent;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JdbcRegistryDataChanceEventDTO {
+
+ private Long id;
+
+ private EventType eventType;
+
+ private JdbcRegistryDataDTO jdbcRegistryData;
+
+ private Date createTime;
+
+ public enum EventType {
+ ADD,
+ UPDATE,
+ DELETE;
+
+ }
+
+ public static JdbcRegistryDataChanceEventDTO fromJdbcRegistryDataChanceEvent(JdbcRegistryDataChanceEvent jdbcRegistryDataChanceEvent) {
+ JdbcRegistryData jdbcRegistryData =
+ JSONUtils.parseObject(jdbcRegistryDataChanceEvent.getJdbcRegistryData(), JdbcRegistryData.class);
+ if (jdbcRegistryData == null) {
+ throw new IllegalArgumentException(
+ "jdbcRegistryData: " + jdbcRegistryDataChanceEvent.getJdbcRegistryData() + " is invalidated");
+ }
+ return JdbcRegistryDataChanceEventDTO.builder()
+ .id(jdbcRegistryDataChanceEvent.getId())
+ .jdbcRegistryData(JdbcRegistryDataDTO.fromJdbcRegistryData(jdbcRegistryData))
+ .eventType(EventType.valueOf(jdbcRegistryDataChanceEvent.getEventType()))
+ .createTime(jdbcRegistryDataChanceEvent.getCreateTime())
+ .build();
+ }
+
+ public static JdbcRegistryDataChanceEvent toJdbcRegistryDataChanceEvent(JdbcRegistryDataChanceEventDTO jdbcRegistryDataChanceEvent) {
+ return JdbcRegistryDataChanceEvent.builder()
+ .id(jdbcRegistryDataChanceEvent.getId())
+ .jdbcRegistryData(JSONUtils.toJsonString(jdbcRegistryDataChanceEvent.getJdbcRegistryData()))
+ .eventType(jdbcRegistryDataChanceEvent.getEventType().name())
+ .createTime(jdbcRegistryDataChanceEvent.getCreateTime())
+ .build();
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryDataDTO.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryDataDTO.java
new file mode 100644
index 000000000..06df011a7
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryDataDTO.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DTO;
+
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryData;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JdbcRegistryDataDTO {
+
+ private Long id;
+ private String dataKey;
+ private String dataValue;
+ private String dataType;
+ private Long clientId;
+ private Date createTime;
+ private Date lastUpdateTime;
+
+ public static JdbcRegistryDataDTO fromJdbcRegistryData(JdbcRegistryData jdbcRegistryData) {
+ return JdbcRegistryDataDTO.builder()
+ .id(jdbcRegistryData.getId())
+ .dataKey(jdbcRegistryData.getDataKey())
+ .dataValue(jdbcRegistryData.getDataValue())
+ .dataType(jdbcRegistryData.getDataType())
+ .clientId(jdbcRegistryData.getClientId())
+ .createTime(jdbcRegistryData.getCreateTime())
+ .lastUpdateTime(jdbcRegistryData.getLastUpdateTime())
+ .build();
+ }
+
+ public static JdbcRegistryData toJdbcRegistryData(JdbcRegistryDataDTO jdbcRegistryData) {
+ return JdbcRegistryData.builder()
+ .id(jdbcRegistryData.getId())
+ .dataKey(jdbcRegistryData.getDataKey())
+ .dataValue(jdbcRegistryData.getDataValue())
+ .dataType(jdbcRegistryData.getDataType())
+ .clientId(jdbcRegistryData.getClientId())
+ .createTime(jdbcRegistryData.getCreateTime())
+ .lastUpdateTime(jdbcRegistryData.getLastUpdateTime())
+ .build();
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryLockDTO.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryLockDTO.java
new file mode 100644
index 000000000..e274f52ec
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/model/DTO/JdbcRegistryLockDTO.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.model.DTO;
+
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryLock;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+
+@Getter
+@Setter
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JdbcRegistryLockDTO {
+
+ private Long id;
+ private String lockKey;
+ private String lockOwner;
+ private Long clientId;
+ private Date createTime;
+
+ public static JdbcRegistryLockDTO fromJdbcRegistryLock(JdbcRegistryLock jdbcRegistryLock) {
+ return JdbcRegistryLockDTO.builder()
+ .id(jdbcRegistryLock.getId())
+ .lockKey(jdbcRegistryLock.getLockKey())
+ .lockOwner(jdbcRegistryLock.getLockOwner())
+ .clientId(jdbcRegistryLock.getClientId())
+ .createTime(jdbcRegistryLock.getCreateTime())
+ .build();
+ }
+
+ public static JdbcRegistryLock toJdbcRegistryLock(JdbcRegistryLockDTO jdbcRegistryLock) {
+ return JdbcRegistryLock.builder()
+ .id(jdbcRegistryLock.getId())
+ .lockKey(jdbcRegistryLock.getLockKey())
+ .lockOwner(jdbcRegistryLock.getLockOwner())
+ .clientId(jdbcRegistryLock.getClientId())
+ .createTime(jdbcRegistryLock.getCreateTime())
+ .build();
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryClientRepository.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryClientRepository.java
new file mode 100644
index 000000000..c3ae1797b
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryClientRepository.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.repository;
+
+import org.apache.streampark.registry.core.mapper.JdbcRegistryClientHeartbeatMapper;
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryClientHeartbeat;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryClientHeartbeatDTO;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Repository
+public class JdbcRegistryClientRepository {
+
+ @Autowired
+ private JdbcRegistryClientHeartbeatMapper jdbcRegistryClientHeartbeatMapper;
+
+ public List<JdbcRegistryClientHeartbeatDTO> queryAll() {
+ return jdbcRegistryClientHeartbeatMapper.selectAll()
+ .stream()
+ .map(JdbcRegistryClientHeartbeatDTO::fromJdbcRegistryClientHeartbeat)
+ .collect(Collectors.toList());
+ }
+
+ public void deleteByIds(List<Long> clientIds) {
+ if (CollectionUtils.isEmpty(clientIds)) {
+ return;
+ }
+ jdbcRegistryClientHeartbeatMapper.deleteBatchIds(clientIds);
+ }
+
+ public boolean updateById(JdbcRegistryClientHeartbeatDTO jdbcRegistryClientHeartbeatDTO) {
+ JdbcRegistryClientHeartbeat jdbcRegistryClientHeartbeat =
+ JdbcRegistryClientHeartbeatDTO.toJdbcRegistryClientHeartbeat(jdbcRegistryClientHeartbeatDTO);
+ return jdbcRegistryClientHeartbeatMapper.updateById(jdbcRegistryClientHeartbeat) == 1;
+ }
+
+ public void insert(JdbcRegistryClientHeartbeatDTO jdbcRegistryClient) {
+ checkNotNull(jdbcRegistryClient.getId());
+ JdbcRegistryClientHeartbeat jdbcRegistryClientHeartbeat =
+ JdbcRegistryClientHeartbeatDTO.toJdbcRegistryClientHeartbeat(jdbcRegistryClient);
+ jdbcRegistryClientHeartbeatMapper.insert(jdbcRegistryClientHeartbeat);
+
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryDataChanceEventRepository.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryDataChanceEventRepository.java
new file mode 100644
index 000000000..489443827
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryDataChanceEventRepository.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.repository;
+
+import org.apache.streampark.registry.core.mapper.JdbcRegistryDataChanceEventMapper;
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryDataChanceEvent;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataChanceEventDTO;
+
+import org.springframework.stereotype.Repository;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Repository
+public class JdbcRegistryDataChanceEventRepository {
+
+ private final JdbcRegistryDataChanceEventMapper jdbcRegistryDataChanceEventMapper;
+
+ public JdbcRegistryDataChanceEventRepository(JdbcRegistryDataChanceEventMapper jdbcRegistryDataChanceEventMapper) {
+ this.jdbcRegistryDataChanceEventMapper = jdbcRegistryDataChanceEventMapper;
+ }
+
+ public long getMaxJdbcRegistryDataChanceEventId() {
+ Long maxId = jdbcRegistryDataChanceEventMapper.getMaxId();
+ if (maxId == null) {
+ return -1;
+ } else {
+ return maxId;
+ }
+ }
+
+ public List<JdbcRegistryDataChanceEventDTO> selectJdbcRegistryDataChangeEventWhereIdAfter(long id) {
+ return jdbcRegistryDataChanceEventMapper.selectJdbcRegistryDataChangeEventWhereIdAfter(id)
+ .stream()
+ .map(JdbcRegistryDataChanceEventDTO::fromJdbcRegistryDataChanceEvent)
+ .collect(Collectors.toList());
+ }
+
+ public void insert(JdbcRegistryDataChanceEventDTO registryDataChanceEvent) {
+ JdbcRegistryDataChanceEvent jdbcRegistryDataChanceEvent =
+ JdbcRegistryDataChanceEventDTO.toJdbcRegistryDataChanceEvent(registryDataChanceEvent);
+ jdbcRegistryDataChanceEventMapper.insert(jdbcRegistryDataChanceEvent);
+ registryDataChanceEvent.setId(jdbcRegistryDataChanceEvent.getId());
+ }
+
+ public void deleteJdbcRegistryDataChangeEventBeforeCreateTime(Date createTime) {
+ jdbcRegistryDataChanceEventMapper.deleteJdbcRegistryDataChangeEventBeforeCreateTime(createTime);
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryDataRepository.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryDataRepository.java
new file mode 100644
index 000000000..0ceedafb1
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryDataRepository.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.repository;
+
+import org.apache.streampark.registry.core.mapper.JdbcRegistryDataMapper;
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryData;
+import org.apache.streampark.registry.core.model.DTO.DataType;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataDTO;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Repository
+public class JdbcRegistryDataRepository {
+
+ @Autowired
+ private JdbcRegistryDataMapper jdbcRegistryDataMapper;
+
+ public List<JdbcRegistryDataDTO> selectAll() {
+ return jdbcRegistryDataMapper
+ .selectAll()
+ .stream()
+ .map(JdbcRegistryDataDTO::fromJdbcRegistryData)
+ .collect(Collectors.toList());
+ }
+
+ public Optional<JdbcRegistryDataDTO> selectByKey(String key) {
+ return Optional.ofNullable(jdbcRegistryDataMapper.selectByKey(key))
+ .map(JdbcRegistryDataDTO::fromJdbcRegistryData);
+ }
+
+ public void deleteEphemeralDateByClientIds(List<Long> clientIds) {
+ if (CollectionUtils.isEmpty(clientIds)) {
+ return;
+ }
+ jdbcRegistryDataMapper.deleteByClientIds(clientIds, DataType.EPHEMERAL.name());
+ }
+
+ public void deleteByKey(String key) {
+ jdbcRegistryDataMapper.deleteByKey(key);
+ }
+
+ public void insert(JdbcRegistryDataDTO jdbcRegistryData) {
+ JdbcRegistryData jdbcRegistryDataDO = JdbcRegistryDataDTO.toJdbcRegistryData(jdbcRegistryData);
+ jdbcRegistryDataMapper.insert(jdbcRegistryDataDO);
+ jdbcRegistryData.setId(jdbcRegistryDataDO.getId());
+ }
+
+ public void updateById(JdbcRegistryDataDTO jdbcRegistryDataDTO) {
+ jdbcRegistryDataMapper.updateById(JdbcRegistryDataDTO.toJdbcRegistryData(jdbcRegistryDataDTO));
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryLockRepository.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryLockRepository.java
new file mode 100644
index 000000000..b9771bc44
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/repository/JdbcRegistryLockRepository.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.repository;
+
+import org.apache.streampark.registry.core.mapper.JdbcRegistryLockMapper;
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryLock;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryLockDTO;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Repository
+public class JdbcRegistryLockRepository {
+
+ @Autowired
+ private JdbcRegistryLockMapper jdbcRegistryLockMapper;
+
+ public void deleteByClientIds(List<Long> clientIds) {
+ if (CollectionUtils.isEmpty(clientIds)) {
+ return;
+ }
+ jdbcRegistryLockMapper.deleteByClientIds(clientIds);
+ }
+
+ public void insert(JdbcRegistryLockDTO jdbcRegistryLock) {
+ checkNotNull(jdbcRegistryLock);
+ JdbcRegistryLock jdbcRegistryLockDO = JdbcRegistryLockDTO.toJdbcRegistryLock(jdbcRegistryLock);
+ jdbcRegistryLockMapper.insert(jdbcRegistryLockDO);
+ jdbcRegistryLock.setId(jdbcRegistryLockDO.getId());
+ }
+
+ public void deleteById(Long id) {
+ jdbcRegistryLockMapper.deleteById(id);
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/ConnectionStateListener.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/ConnectionStateListener.java
new file mode 100644
index 000000000..e0053c797
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/ConnectionStateListener.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+public interface ConnectionStateListener {
+
+ void onConnected();
+
+ void onDisConnected();
+
+ void onReconnected();
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryDataManager.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryDataManager.java
new file mode 100644
index 000000000..4ecca0738
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryDataManager.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+import org.apache.streampark.registry.core.model.DTO.DataType;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataDTO;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface IJdbcRegistryDataManager {
+
+ boolean existKey(String key);
+
+ /**
+ * Get the {@link JdbcRegistryDataDTO} by key.
+ */
+ Optional<JdbcRegistryDataDTO> getRegistryDataByKey(String key);
+
+ /**
+ * List all the {@link JdbcRegistryDataDTO} children by key.
+ * <p>
+ * e.g. key = "/streampark/master", and data exist in db is "/streampark/master/master1", "/streampark/master/master2"
+ * <p>
+ * then the return value will be ["master1", "master2"]
+ */
+ List<JdbcRegistryDataDTO> listJdbcRegistryDataChildren(String key);
+
+ /**
+ * Put the {@link JdbcRegistryDataDTO} to the jdbc registry server.
+ * <p>
+ * If the key is already exist, then update the {@link JdbcRegistryDataDTO}. If the key is not exist, then insert a new {@link JdbcRegistryDataDTO}.
+ */
+ void putJdbcRegistryData(Long clientId, String key, String value, DataType dataType);
+
+ /**
+ * Delete the {@link JdbcRegistryDataDTO} by key.
+ */
+ void deleteJdbcRegistryDataByKey(String key);
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryLockManager.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryLockManager.java
new file mode 100644
index 000000000..7d8f9d291
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryLockManager.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+public interface IJdbcRegistryLockManager {
+
+ /**
+ * Acquire the jdbc registry lock by key. this is a blocking method. if you want to stop the blocking, you can use interrupt the thread.
+ */
+ void acquireJdbcRegistryLock(Long clientId, String lockKey) throws InterruptedException;
+
+ /**
+ * Acquire the jdbc registry lock by key until timeout.
+ */
+ boolean acquireJdbcRegistryLock(Long clientId, String lockKey, long timeout);
+
+ /**
+ * Release the jdbc registry lock by key, if the lockKey is not exist will do nothing.
+ */
+ void releaseJdbcRegistryLock(Long clientId, String lockKey);
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryServer.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryServer.java
new file mode 100644
index 000000000..3f03e7460
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IJdbcRegistryServer.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+import org.apache.streampark.registry.core.client.IJdbcRegistryClient;
+import org.apache.streampark.registry.core.model.DO.JdbcRegistryData;
+import org.apache.streampark.registry.core.model.DTO.DataType;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataDTO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * The JdbcRegistryServer is represent the server side of the jdbc registry, it can be thought as db server.
+ */
+public interface IJdbcRegistryServer extends AutoCloseable {
+
+ void start();
+
+ /**
+ * Register a client to the server, once the client connect to the server then the server will refresh the client's term interval.
+ */
+ void registerClient(IJdbcRegistryClient jdbcRegistryClient);
+
+ /**
+ * Deregister a client to the server, once the client id deregister, then the server will deleted the data related to the client and stop refresh the client's term.
+ */
+ void deregisterClient(IJdbcRegistryClient jdbcRegistryClient);
+
+ /**
+ * Get the {@link JdbcRegistryServerState}
+ */
+ JdbcRegistryServerState getServerState();
+
+ /**
+ * Subscribe the jdbc registry connection state change
+ */
+ void subscribeConnectionStateChange(ConnectionStateListener connectionStateListener);
+
+ /**
+ * Subscribe the {@link JdbcRegistryData} change.
+ */
+ void subscribeJdbcRegistryDataChange(JdbcRegistryDataChangeListener jdbcRegistryDataChangeListener);
+
+ /**
+ * Check the jdbc registry data key is exist or not.
+ */
+ boolean existJdbcRegistryDataKey(String key);
+
+ /**
+ * Get the {@link JdbcRegistryDataDTO} by key.
+ */
+ Optional<JdbcRegistryDataDTO> getJdbcRegistryDataByKey(String key);
+
+ /**
+ * List all the {@link JdbcRegistryDataDTO} children by key.
+ * <p>
+ * e.g. key = "/streampark/master", and data exist in db is "/streampark/master/master1", "/streampark/master/master2"
+ * <p>
+ * then the return value will be ["master1", "master2"]
+ */
+ List<JdbcRegistryDataDTO> listJdbcRegistryDataChildren(String key);
+
+ /**
+ * Put the {@link JdbcRegistryDataDTO} to the jdbc registry server.
+ * <p>
+ * If the key is already exist, then update the {@link JdbcRegistryDataDTO}. If the key is not exist, then insert a new {@link JdbcRegistryDataDTO}.
+ */
+ void putJdbcRegistryData(Long clientId, String key, String value, DataType dataType);
+
+ /**
+ * Delete the {@link JdbcRegistryDataDTO} by key.
+ */
+ void deleteJdbcRegistryDataByKey(String key);
+
+ /**
+ * Acquire the jdbc registry lock by key. this is a blocking method. if you want to stop the blocking, you can use interrupt the thread.
+ */
+ void acquireJdbcRegistryLock(Long clientId, String key);
+
+ /**
+ * Acquire the jdbc registry lock by key until timeout.
+ */
+ boolean acquireJdbcRegistryLock(Long clientId, String key, long timeout);
+
+ /**
+ * Release the jdbc registry lock by key, if the lockKey is not exist will do nothing.
+ */
+ void releaseJdbcRegistryLock(Long clientId, String key);
+
+ /**
+ * Close the server, once the server been closed, it cannot work anymore.
+ */
+ @Override
+ void close();
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IRegistryRowChangeNotifier.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IRegistryRowChangeNotifier.java
new file mode 100644
index 000000000..723f63103
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/IRegistryRowChangeNotifier.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+public interface IRegistryRowChangeNotifier<T> {
+
+ void start();
+
+ void subscribeRegistryRowChange(RegistryRowChangeListener<T> registryRowChangeListener);
+
+ interface RegistryRowChangeListener<T> {
+
+ void onRegistryRowUpdated(T data);
+
+ void onRegistryRowAdded(T data);
+
+ void onRegistryRowDeleted(T data);
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryDataChangeListener.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryDataChangeListener.java
new file mode 100644
index 000000000..015219ea8
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryDataChangeListener.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+public interface JdbcRegistryDataChangeListener {
+
+ void onJdbcRegistryDataChanged(String key, String value);
+
+ void onJdbcRegistryDataDeleted(String key);
+
+ void onJdbcRegistryDataAdded(String key, String value);
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryDataManager.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryDataManager.java
new file mode 100644
index 000000000..1b2ef8c49
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryDataManager.java
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+import org.apache.streampark.registry.core.JdbcRegistryProperties;
+import org.apache.streampark.registry.core.JdbcRegistryThreadFactory;
+import org.apache.streampark.registry.core.model.DTO.DataType;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataChanceEventDTO;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataDTO;
+import org.apache.streampark.registry.core.repository.JdbcRegistryDataChanceEventRepository;
+import org.apache.streampark.registry.core.repository.JdbcRegistryDataRepository;
+
+import org.apache.commons.lang3.time.DateUtils;
+
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.CollectionUtils;
+
+import java.time.Duration;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+@Slf4j
+public class JdbcRegistryDataManager
+ implements
+ IRegistryRowChangeNotifier<JdbcRegistryDataDTO>,
+ IJdbcRegistryDataManager {
+
+ private final Integer keepJdbcRegistryDataChanceEventHours = 2;
+
+ private final JdbcRegistryProperties registryProperties;
+
+ private final JdbcRegistryDataRepository jdbcRegistryDataRepository;
+
+ private final JdbcRegistryDataChanceEventRepository jdbcRegistryDataChanceEventRepository;
+
+ private final List<RegistryRowChangeListener<JdbcRegistryDataDTO>> registryRowChangeListeners;
+
+ private long lastDetectedJdbcRegistryDataChangeEventId = -1;
+
+ public JdbcRegistryDataManager(JdbcRegistryProperties registryProperties,
+ JdbcRegistryDataRepository jdbcRegistryDataRepository,
+ JdbcRegistryDataChanceEventRepository jdbcRegistryDataChanceEventRepository) {
+ this.registryProperties = registryProperties;
+ this.jdbcRegistryDataChanceEventRepository = jdbcRegistryDataChanceEventRepository;
+ this.jdbcRegistryDataRepository = jdbcRegistryDataRepository;
+ this.registryRowChangeListeners = new CopyOnWriteArrayList<>();
+ this.lastDetectedJdbcRegistryDataChangeEventId =
+ jdbcRegistryDataChanceEventRepository.getMaxJdbcRegistryDataChanceEventId();
+ }
+
+ @Override
+ public void start() {
+ JdbcRegistryThreadFactory.getDefaultSchedulerThreadExecutor().scheduleWithFixedDelay(
+ this::detectJdbcRegistryDataChangeEvent,
+ registryProperties.getHeartbeatRefreshInterval().toMillis(),
+ registryProperties.getHeartbeatRefreshInterval().toMillis(),
+ TimeUnit.MILLISECONDS);
+
+ JdbcRegistryThreadFactory.getDefaultSchedulerThreadExecutor().scheduleWithFixedDelay(
+ this::purgeHistoryJdbcRegistryDataChangeEvent,
+ 0,
+ Duration.ofHours(keepJdbcRegistryDataChanceEventHours).toHours(),
+ TimeUnit.HOURS);
+ }
+
+ private void detectJdbcRegistryDataChangeEvent() {
+ final List<JdbcRegistryDataChanceEventDTO> jdbcRegistryDataChanceEvents = jdbcRegistryDataChanceEventRepository
+ .selectJdbcRegistryDataChangeEventWhereIdAfter(lastDetectedJdbcRegistryDataChangeEventId);
+ if (CollectionUtils.isEmpty(jdbcRegistryDataChanceEvents)) {
+ return;
+ }
+ for (JdbcRegistryDataChanceEventDTO jdbcRegistryDataChanceEvent : jdbcRegistryDataChanceEvents) {
+ log.debug("Detect JdbcRegistryDataChangeEvent: {}", jdbcRegistryDataChanceEvent);
+ switch (jdbcRegistryDataChanceEvent.getEventType()) {
+ case ADD:
+ doTriggerJdbcRegistryDataAddedListener(
+ Lists.newArrayList(jdbcRegistryDataChanceEvent.getJdbcRegistryData()));
+ break;
+ case UPDATE:
+ doTriggerJdbcRegistryDataUpdatedListener(
+ Lists.newArrayList(jdbcRegistryDataChanceEvent.getJdbcRegistryData()));
+ break;
+ case DELETE:
+ doTriggerJdbcRegistryDataRemovedListener(
+ Lists.newArrayList(jdbcRegistryDataChanceEvent.getJdbcRegistryData()));
+ break;
+ default:
+ log.error("Unknown event type: {}", jdbcRegistryDataChanceEvent.getEventType());
+ break;
+ }
+ if (jdbcRegistryDataChanceEvent.getId() > lastDetectedJdbcRegistryDataChangeEventId) {
+ lastDetectedJdbcRegistryDataChangeEventId = jdbcRegistryDataChanceEvent.getId();
+ }
+ }
+ }
+
+ private void purgeHistoryJdbcRegistryDataChangeEvent() {
+ log.info("Purge JdbcRegistryDataChanceEvent which createTime is before: {} hours",
+ keepJdbcRegistryDataChanceEventHours);
+ jdbcRegistryDataChanceEventRepository.deleteJdbcRegistryDataChangeEventBeforeCreateTime(
+ DateUtils.addHours(new Date(), -keepJdbcRegistryDataChanceEventHours));
+ }
+
+ @Override
+ public void subscribeRegistryRowChange(RegistryRowChangeListener<JdbcRegistryDataDTO> registryRowChangeListener) {
+ registryRowChangeListeners.add(checkNotNull(registryRowChangeListener));
+ }
+
+ @Override
+ public boolean existKey(String key) {
+ checkNotNull(key);
+ return jdbcRegistryDataRepository.selectByKey(key).isPresent();
+ }
+
+ @Override
+ public Optional<JdbcRegistryDataDTO> getRegistryDataByKey(String key) {
+ checkNotNull(key);
+ return jdbcRegistryDataRepository.selectByKey(key);
+ }
+
+ @Override
+ public List<JdbcRegistryDataDTO> listJdbcRegistryDataChildren(String key) {
+ checkNotNull(key);
+ return jdbcRegistryDataRepository.selectAll()
+ .stream()
+ .filter(jdbcRegistryDataDTO -> jdbcRegistryDataDTO.getDataKey().startsWith(key)
+ && !jdbcRegistryDataDTO.getDataKey().equals(key))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void putJdbcRegistryData(Long clientId, String key, String value, DataType dataType) {
+ checkNotNull(clientId);
+ checkNotNull(key);
+ checkNotNull(dataType);
+
+ Optional<JdbcRegistryDataDTO> jdbcRegistryDataOptional = jdbcRegistryDataRepository.selectByKey(key);
+ if (jdbcRegistryDataOptional.isPresent()) {
+ JdbcRegistryDataDTO jdbcRegistryData = jdbcRegistryDataOptional.get();
+ if (!dataType.name().equals(jdbcRegistryData.getDataType())) {
+ throw new UnsupportedOperationException("The data type: " + jdbcRegistryData.getDataType()
+ + " of the key: " + key + " cannot be updated");
+ }
+
+ if (DataType.EPHEMERAL.name().equals(jdbcRegistryData.getDataType())) {
+ if (!jdbcRegistryData.getClientId().equals(clientId)) {
+ throw new UnsupportedOperationException(
+ "The EPHEMERAL data: " + key + " can only be updated by its owner: "
+ + jdbcRegistryData.getClientId() + " but not: " + clientId);
+ }
+ }
+
+ jdbcRegistryData.setDataValue(value);
+ jdbcRegistryData.setLastUpdateTime(new Date());
+ jdbcRegistryDataRepository.updateById(jdbcRegistryData);
+
+ JdbcRegistryDataChanceEventDTO jdbcRegistryDataChanceEvent = JdbcRegistryDataChanceEventDTO.builder()
+ .jdbcRegistryData(jdbcRegistryData)
+ .eventType(JdbcRegistryDataChanceEventDTO.EventType.UPDATE)
+ .createTime(new Date())
+ .build();
+ jdbcRegistryDataChanceEventRepository.insert(jdbcRegistryDataChanceEvent);
+ } else {
+ JdbcRegistryDataDTO jdbcRegistryDataDTO = JdbcRegistryDataDTO.builder()
+ .clientId(clientId)
+ .dataKey(key)
+ .dataValue(value)
+ .dataType(dataType.name())
+ .createTime(new Date())
+ .lastUpdateTime(new Date())
+ .build();
+ jdbcRegistryDataRepository.insert(jdbcRegistryDataDTO);
+ JdbcRegistryDataChanceEventDTO registryDataChanceEvent = JdbcRegistryDataChanceEventDTO.builder()
+ .jdbcRegistryData(jdbcRegistryDataDTO)
+ .eventType(JdbcRegistryDataChanceEventDTO.EventType.ADD)
+ .createTime(new Date())
+ .build();
+ jdbcRegistryDataChanceEventRepository.insert(registryDataChanceEvent);
+ }
+
+ }
+
+ @Override
+ public void deleteJdbcRegistryDataByKey(String key) {
+ checkNotNull(key);
+ // todo: this is not atomic, need to be improved
+ Optional<JdbcRegistryDataDTO> jdbcRegistryDataOptional = jdbcRegistryDataRepository.selectByKey(key);
+ if (!jdbcRegistryDataOptional.isPresent()) {
+ return;
+ }
+ jdbcRegistryDataRepository.deleteByKey(key);
+ JdbcRegistryDataChanceEventDTO registryDataChanceEvent = JdbcRegistryDataChanceEventDTO.builder()
+ .jdbcRegistryData(jdbcRegistryDataOptional.get())
+ .eventType(JdbcRegistryDataChanceEventDTO.EventType.DELETE)
+ .createTime(new Date())
+ .build();
+ jdbcRegistryDataChanceEventRepository.insert(registryDataChanceEvent);
+ }
+
+ private void doTriggerJdbcRegistryDataAddedListener(List<JdbcRegistryDataDTO> valuesToAdd) {
+ if (CollectionUtils.isEmpty(valuesToAdd)) {
+ return;
+ }
+ log.debug("Trigger:onJdbcRegistryDataAdded: {}", valuesToAdd);
+ valuesToAdd.forEach(jdbcRegistryData -> {
+ try {
+ registryRowChangeListeners.forEach(listener -> listener.onRegistryRowAdded(jdbcRegistryData));
+ } catch (Exception ex) {
+ log.error("Trigger:onRegistryRowAdded: {} failed", jdbcRegistryData, ex);
+ }
+ });
+ }
+
+ private void doTriggerJdbcRegistryDataRemovedListener(List<JdbcRegistryDataDTO> valuesToRemoved) {
+ if (CollectionUtils.isEmpty(valuesToRemoved)) {
+ return;
+ }
+ log.debug("Trigger:onJdbcRegistryDataDeleted: {}", valuesToRemoved);
+ valuesToRemoved.forEach(jdbcRegistryData -> {
+ try {
+ registryRowChangeListeners.forEach(listener -> listener.onRegistryRowDeleted(jdbcRegistryData));
+ } catch (Exception ex) {
+ log.error("Trigger:onRegistryRowAdded: {} failed", jdbcRegistryData, ex);
+ }
+ });
+ }
+
+ private void doTriggerJdbcRegistryDataUpdatedListener(List<JdbcRegistryDataDTO> valuesToUpdated) {
+ if (CollectionUtils.isEmpty(valuesToUpdated)) {
+ return;
+ }
+ log.debug("Trigger:onJdbcRegistryDataUpdated: {}", valuesToUpdated);
+ valuesToUpdated.forEach(jdbcRegistryData -> {
+ try {
+ registryRowChangeListeners.forEach(listener -> listener.onRegistryRowUpdated(jdbcRegistryData));
+ } catch (Exception ex) {
+ log.error("Trigger:onRegistryRowAdded: {} failed", jdbcRegistryData, ex);
+ }
+ });
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryLockManager.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryLockManager.java
new file mode 100644
index 000000000..ec0392f3a
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryLockManager.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+import org.apache.streampark.registry.api.thread.ThreadUtils;
+import org.apache.streampark.registry.core.JdbcRegistryProperties;
+import org.apache.streampark.registry.core.LockUtils;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryLockDTO;
+import org.apache.streampark.registry.core.repository.JdbcRegistryLockRepository;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DuplicateKeyException;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+public class JdbcRegistryLockManager implements IJdbcRegistryLockManager {
+
+ private final JdbcRegistryProperties jdbcRegistryProperties;
+ private final JdbcRegistryLockRepository jdbcRegistryLockRepository;
+
+ // lockKey -> LockEntry
+ private final Map<String, LockEntry> jdbcRegistryLockHolderMap = new HashMap<>();
+
+ public JdbcRegistryLockManager(JdbcRegistryProperties jdbcRegistryProperties,
+ JdbcRegistryLockRepository jdbcRegistryLockRepository) {
+ this.jdbcRegistryProperties = jdbcRegistryProperties;
+ this.jdbcRegistryLockRepository = jdbcRegistryLockRepository;
+ }
+
+ @Override
+ public void acquireJdbcRegistryLock(Long clientId, String lockKey) {
+ String lockOwner = LockUtils.getLockOwner();
+ while (true) {
+ LockEntry lockEntry = jdbcRegistryLockHolderMap.get(lockKey);
+ if (lockEntry != null && lockOwner.equals(lockEntry.getLockOwner())) {
+ return;
+ }
+ JdbcRegistryLockDTO jdbcRegistryLock = JdbcRegistryLockDTO.builder()
+ .lockKey(lockKey)
+ .clientId(clientId)
+ .lockOwner(lockOwner)
+ .createTime(new Date())
+ .build();
+ try {
+ jdbcRegistryLockRepository.insert(jdbcRegistryLock);
+ if (jdbcRegistryLock != null) {
+ jdbcRegistryLockHolderMap.put(lockKey, LockEntry.builder()
+ .lockKey(lockKey)
+ .lockOwner(lockOwner)
+ .jdbcRegistryLock(jdbcRegistryLock)
+ .build());
+ return;
+ }
+ log.debug("{} acquire the lock {} success", lockOwner, lockKey);
+ } catch (DuplicateKeyException duplicateKeyException) {
+ // The lock is already exist, wait it release.
+ continue;
+ }
+ log.debug("Acquire the lock {} failed try again", lockKey);
+ // acquire failed, wait and try again
+ ThreadUtils.sleep(jdbcRegistryProperties.getHeartbeatRefreshInterval().toMillis());
+ }
+ }
+
+ @Override
+ public boolean acquireJdbcRegistryLock(Long clientId, String lockKey, long timeout) {
+ String lockOwner = LockUtils.getLockOwner();
+ long start = System.currentTimeMillis();
+ while (System.currentTimeMillis() - start <= timeout) {
+ LockEntry lockEntry = jdbcRegistryLockHolderMap.get(lockKey);
+ if (lockEntry != null && lockOwner.equals(lockEntry.getLockOwner())) {
+ return true;
+ }
+ JdbcRegistryLockDTO jdbcRegistryLock = JdbcRegistryLockDTO.builder()
+ .lockKey(lockKey)
+ .clientId(clientId)
+ .lockOwner(lockOwner)
+ .createTime(new Date())
+ .build();
+ try {
+ jdbcRegistryLockRepository.insert(jdbcRegistryLock);
+ if (jdbcRegistryLock != null) {
+ jdbcRegistryLockHolderMap.put(lockKey, LockEntry.builder()
+ .lockKey(lockKey)
+ .lockOwner(lockOwner)
+ .jdbcRegistryLock(jdbcRegistryLock)
+ .build());
+ return true;
+ }
+ log.debug("{} acquire the lock {} success", lockOwner, lockKey);
+ } catch (DuplicateKeyException duplicateKeyException) {
+ // The lock is already exist, wait it release.
+ continue;
+ }
+ log.debug("Acquire the lock {} failed try again", lockKey);
+ // acquire failed, wait and try again
+ ThreadUtils.sleep(jdbcRegistryProperties.getHeartbeatRefreshInterval().toMillis());
+ }
+ return false;
+ }
+
+ @Override
+ public void releaseJdbcRegistryLock(Long clientId, String lockKey) {
+ LockEntry lockEntry = jdbcRegistryLockHolderMap.get(lockKey);
+ if (lockEntry == null) {
+ return;
+ }
+ if (!clientId.equals(lockEntry.getJdbcRegistryLock().getClientId())) {
+ throw new UnsupportedOperationException(
+ "The client " + clientId + " is not the lock owner of the lock: " + lockKey);
+ }
+ jdbcRegistryLockRepository.deleteById(lockEntry.getJdbcRegistryLock().getId());
+ jdbcRegistryLockHolderMap.remove(lockKey);
+ }
+
+ @Getter
+ @Setter
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class LockEntry {
+
+ private String lockKey;
+ private String lockOwner;
+ private JdbcRegistryLockDTO jdbcRegistryLock;
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryServer.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryServer.java
new file mode 100644
index 000000000..596cbe58e
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryServer.java
@@ -0,0 +1,386 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+import org.apache.streampark.registry.api.RegistryException;
+import org.apache.streampark.registry.core.JdbcRegistryProperties;
+import org.apache.streampark.registry.core.JdbcRegistryThreadFactory;
+import org.apache.streampark.registry.core.client.IJdbcRegistryClient;
+import org.apache.streampark.registry.core.client.JdbcRegistryClientIdentify;
+import org.apache.streampark.registry.core.model.DTO.DataType;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryClientHeartbeatDTO;
+import org.apache.streampark.registry.core.model.DTO.JdbcRegistryDataDTO;
+import org.apache.streampark.registry.core.repository.JdbcRegistryClientRepository;
+import org.apache.streampark.registry.core.repository.JdbcRegistryDataChanceEventRepository;
+import org.apache.streampark.registry.core.repository.JdbcRegistryDataRepository;
+import org.apache.streampark.registry.core.repository.JdbcRegistryLockRepository;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+import com.google.common.collect.Lists;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * The JdbcRegistryServer will manage the client, once a client is disconnected, the server will remove the client from the registry, and remove it's related data and lock.
+ */
+@Slf4j
+public class JdbcRegistryServer implements IJdbcRegistryServer {
+
+ private final JdbcRegistryProperties jdbcRegistryProperties;
+
+ private final JdbcRegistryDataRepository jdbcRegistryDataRepository;
+
+ private final JdbcRegistryLockRepository jdbcRegistryLockRepository;
+
+ private final JdbcRegistryClientRepository jdbcRegistryClientRepository;
+
+ private final JdbcRegistryDataManager jdbcRegistryDataManager;
+
+ private final JdbcRegistryLockManager jdbcRegistryLockManager;
+
+ private JdbcRegistryServerState jdbcRegistryServerState;
+
+ private final List<IJdbcRegistryClient> jdbcRegistryClients = new CopyOnWriteArrayList<>();
+
+ private final List<ConnectionStateListener> connectionStateListeners = new CopyOnWriteArrayList<>();
+
+ private final Map<JdbcRegistryClientIdentify, JdbcRegistryClientHeartbeatDTO> jdbcRegistryClientDTOMap =
+ new ConcurrentHashMap<>();
+
+ private Long lastSuccessHeartbeat;
+
+ public JdbcRegistryServer(JdbcRegistryDataRepository jdbcRegistryDataRepository,
+ JdbcRegistryLockRepository jdbcRegistryLockRepository,
+ JdbcRegistryClientRepository jdbcRegistryClientRepository,
+ JdbcRegistryDataChanceEventRepository jdbcRegistryDataChanceEventRepository,
+ JdbcRegistryProperties jdbcRegistryProperties) {
+ this.jdbcRegistryDataRepository = checkNotNull(jdbcRegistryDataRepository);
+ this.jdbcRegistryLockRepository = checkNotNull(jdbcRegistryLockRepository);
+ this.jdbcRegistryClientRepository = checkNotNull(jdbcRegistryClientRepository);
+ this.jdbcRegistryProperties = checkNotNull(jdbcRegistryProperties);
+ this.jdbcRegistryDataManager = new JdbcRegistryDataManager(
+ jdbcRegistryProperties, jdbcRegistryDataRepository, jdbcRegistryDataChanceEventRepository);
+ this.jdbcRegistryLockManager = new JdbcRegistryLockManager(
+ jdbcRegistryProperties, jdbcRegistryLockRepository);
+ this.jdbcRegistryServerState = JdbcRegistryServerState.INIT;
+ lastSuccessHeartbeat = System.currentTimeMillis();
+ }
+
+ @Override
+ public void start() {
+ if (jdbcRegistryServerState != JdbcRegistryServerState.INIT) {
+ // The server is already started or stopped, will not start again.
+ return;
+ }
+ // Purge the previous client to avoid the client is still in the registry.
+ purgePreviousJdbcRegistryClient();
+ // Start the Purge thread
+ // The Purge thread will remove the client from the registry, and remove it's related data and lock.
+ // Connect to the database, load the data and lock.
+ purgeDeadJdbcRegistryClient();
+ JdbcRegistryThreadFactory.getDefaultSchedulerThreadExecutor()
+ .scheduleWithFixedDelay(this::purgeDeadJdbcRegistryClient,
+ jdbcRegistryProperties.getHeartbeatRefreshInterval().toMillis(),
+ jdbcRegistryProperties.getHeartbeatRefreshInterval().toMillis(),
+ TimeUnit.MILLISECONDS);
+ jdbcRegistryDataManager.start();
+ jdbcRegistryServerState = JdbcRegistryServerState.STARTED;
+ doTriggerOnConnectedListener();
+ JdbcRegistryThreadFactory.getDefaultSchedulerThreadExecutor()
+ .scheduleWithFixedDelay(this::refreshClientsHeartbeat,
+ 0,
+ jdbcRegistryProperties.getHeartbeatRefreshInterval().toMillis(),
+ TimeUnit.MILLISECONDS);
+ }
+
+ @SneakyThrows
+ @Override
+ public void registerClient(IJdbcRegistryClient jdbcRegistryClient) {
+ checkNotNull(jdbcRegistryClient);
+
+ JdbcRegistryClientIdentify jdbcRegistryClientIdentify = jdbcRegistryClient.getJdbcRegistryClientIdentify();
+ checkNotNull(jdbcRegistryClientIdentify);
+
+ JdbcRegistryClientHeartbeatDTO registryClientDTO = JdbcRegistryClientHeartbeatDTO.builder()
+ .id(jdbcRegistryClientIdentify.getClientId())
+ .clientName(jdbcRegistryClientIdentify.getClientName())
+ .clientConfig(
+ new JdbcRegistryClientHeartbeatDTO.ClientConfig(
+ jdbcRegistryProperties.getSessionTimeout().toMillis()))
+ .createTime(new Date())
+ .lastHeartbeatTime(System.currentTimeMillis())
+ .build();
+
+ while (jdbcRegistryClientDTOMap.containsKey(jdbcRegistryClientIdentify)) {
+ log.warn("The client {} is already exist the registry.", jdbcRegistryClientIdentify.getClientId());
+ Thread.sleep(jdbcRegistryProperties.getHeartbeatRefreshInterval().toMillis());
+ }
+ jdbcRegistryClientRepository.insert(registryClientDTO);
+ jdbcRegistryClients.add(jdbcRegistryClient);
+ jdbcRegistryClientDTOMap.put(jdbcRegistryClientIdentify, registryClientDTO);
+ }
+
+ @Override
+ public void deregisterClient(IJdbcRegistryClient jdbcRegistryClient) {
+ checkNotNull(jdbcRegistryClient);
+ jdbcRegistryClients.remove(jdbcRegistryClient);
+ jdbcRegistryClientDTOMap.remove(jdbcRegistryClient.getJdbcRegistryClientIdentify());
+
+ JdbcRegistryClientIdentify jdbcRegistryClientIdentify = jdbcRegistryClient.getJdbcRegistryClientIdentify();
+ checkNotNull(jdbcRegistryClientIdentify);
+
+ doPurgeJdbcRegistryClientInDB(Lists.newArrayList(jdbcRegistryClientIdentify.getClientId()));
+ }
+
+ @Override
+ public JdbcRegistryServerState getServerState() {
+ return jdbcRegistryServerState;
+ }
+
+ @Override
+ public void subscribeConnectionStateChange(ConnectionStateListener connectionStateListener) {
+ checkNotNull(connectionStateListener);
+ connectionStateListeners.add(connectionStateListener);
+ }
+
+ @Override
+ public void subscribeJdbcRegistryDataChange(JdbcRegistryDataChangeListener jdbcRegistryDataChangeListener) {
+ checkNotNull(jdbcRegistryDataChangeListener);
+ jdbcRegistryDataManager.subscribeRegistryRowChange(
+ new IRegistryRowChangeNotifier.RegistryRowChangeListener<JdbcRegistryDataDTO>() {
+
+ @Override
+ public void onRegistryRowUpdated(JdbcRegistryDataDTO data) {
+ jdbcRegistryDataChangeListener.onJdbcRegistryDataChanged(data.getDataKey(),
+ data.getDataValue());
+ }
+
+ @Override
+ public void onRegistryRowAdded(JdbcRegistryDataDTO data) {
+ jdbcRegistryDataChangeListener.onJdbcRegistryDataAdded(data.getDataKey(), data.getDataValue());
+ }
+
+ @Override
+ public void onRegistryRowDeleted(JdbcRegistryDataDTO data) {
+ jdbcRegistryDataChangeListener.onJdbcRegistryDataDeleted(data.getDataKey());
+ }
+ });
+ }
+
+ @Override
+ public boolean existJdbcRegistryDataKey(String key) {
+ return jdbcRegistryDataManager.existKey(key);
+ }
+
+ @Override
+ public Optional<JdbcRegistryDataDTO> getJdbcRegistryDataByKey(String key) {
+ return jdbcRegistryDataManager.getRegistryDataByKey(key);
+ }
+
+ @Override
+ public List<JdbcRegistryDataDTO> listJdbcRegistryDataChildren(String key) {
+ return jdbcRegistryDataManager.listJdbcRegistryDataChildren(key);
+ }
+
+ @Override
+ public void putJdbcRegistryData(Long clientId, String key, String value, DataType dataType) {
+ jdbcRegistryDataManager.putJdbcRegistryData(clientId, key, value, dataType);
+ }
+
+ @Override
+ public void deleteJdbcRegistryDataByKey(String key) {
+ jdbcRegistryDataManager.deleteJdbcRegistryDataByKey(key);
+ }
+
+ @Override
+ public void acquireJdbcRegistryLock(Long clientId, String lockKey) {
+ try {
+ jdbcRegistryLockManager.acquireJdbcRegistryLock(clientId, lockKey);
+ } catch (Exception ex) {
+ throw new RegistryException("Acquire the lock: " + lockKey + " error", ex);
+ }
+ }
+
+ @Override
+ public boolean acquireJdbcRegistryLock(Long clientId, String lockKey, long timeout) {
+ try {
+ return jdbcRegistryLockManager.acquireJdbcRegistryLock(clientId, lockKey, timeout);
+ } catch (Exception ex) {
+ throw new RegistryException("Acquire the lock: " + lockKey + " error", ex);
+ }
+ }
+
+ @Override
+ public void releaseJdbcRegistryLock(Long clientId, String lockKey) {
+ try {
+ jdbcRegistryLockManager.releaseJdbcRegistryLock(clientId, lockKey);
+ } catch (Exception ex) {
+ throw new RegistryException("Release the lock: " + lockKey + " error", ex);
+ }
+ }
+
+ @Override
+ public void close() {
+ jdbcRegistryServerState = JdbcRegistryServerState.STOPPED;
+ JdbcRegistryThreadFactory.getDefaultSchedulerThreadExecutor().shutdown();
+ List<Long> clientIds = jdbcRegistryClients.stream()
+ .map(IJdbcRegistryClient::getJdbcRegistryClientIdentify)
+ .map(JdbcRegistryClientIdentify::getClientId)
+ .collect(Collectors.toList());
+ doPurgeJdbcRegistryClientInDB(clientIds);
+ jdbcRegistryClients.clear();
+ jdbcRegistryClientDTOMap.clear();
+ }
+
+ private void purgePreviousJdbcRegistryClient() {
+ if (jdbcRegistryServerState == JdbcRegistryServerState.STOPPED) {
+ return;
+ }
+ List<Long> previousJdbcRegistryClientIds = jdbcRegistryClientRepository.queryAll()
+ .stream()
+ .filter(jdbcRegistryClientHeartbeat -> jdbcRegistryClientHeartbeat.getClientName()
+ .equals(jdbcRegistryProperties.getJdbcRegistryClientName()))
+ .map(JdbcRegistryClientHeartbeatDTO::getId)
+ .collect(Collectors.toList());
+ doPurgeJdbcRegistryClientInDB(previousJdbcRegistryClientIds);
+
+ }
+
+ private void purgeDeadJdbcRegistryClient() {
+ if (jdbcRegistryServerState == JdbcRegistryServerState.STOPPED) {
+ return;
+ }
+ List<Long> deadJdbcRegistryClientIds = jdbcRegistryClientRepository.queryAll()
+ .stream()
+ .filter(JdbcRegistryClientHeartbeatDTO::isDead)
+ .map(JdbcRegistryClientHeartbeatDTO::getId)
+ .collect(Collectors.toList());
+ doPurgeJdbcRegistryClientInDB(deadJdbcRegistryClientIds);
+
+ }
+
+ private void doPurgeJdbcRegistryClientInDB(List<Long> jdbcRegistryClientIds) {
+ if (CollectionUtils.isEmpty(jdbcRegistryClientIds)) {
+ return;
+ }
+ log.info("Begin to delete dead jdbcRegistryClient: {}", jdbcRegistryClientIds);
+ jdbcRegistryDataRepository.deleteEphemeralDateByClientIds(jdbcRegistryClientIds);
+ jdbcRegistryLockRepository.deleteByClientIds(jdbcRegistryClientIds);
+ jdbcRegistryClientRepository.deleteByIds(jdbcRegistryClientIds);
+ log.info("Success delete dead jdbcRegistryClient: {}", jdbcRegistryClientIds);
+ }
+
+ private void refreshClientsHeartbeat() {
+ if (CollectionUtils.isEmpty(jdbcRegistryClients)) {
+ return;
+ }
+ if (jdbcRegistryServerState == JdbcRegistryServerState.STOPPED) {
+ log.warn("The JdbcRegistryServer is STOPPED, will not refresh clients: {} heartbeat.",
+ CollectionUtils.collect(jdbcRegistryClients, IJdbcRegistryClient::getJdbcRegistryClientIdentify));
+ return;
+ }
+ // Refresh the client's term
+ try {
+ long now = System.currentTimeMillis();
+ for (IJdbcRegistryClient jdbcRegistryClient : jdbcRegistryClients) {
+ JdbcRegistryClientHeartbeatDTO jdbcRegistryClientHeartbeatDTO =
+ jdbcRegistryClientDTOMap.get(jdbcRegistryClient.getJdbcRegistryClientIdentify());
+ if (jdbcRegistryClientHeartbeatDTO == null) {
+ // This may occur when the data in db has been deleted, but the client is still alive.
+ log.error(
+ "The client {} is not found in the registry, will not refresh it's term. (This may happen when the client is removed from the db)",
+ jdbcRegistryClient.getJdbcRegistryClientIdentify().getClientId());
+ continue;
+ }
+ JdbcRegistryClientHeartbeatDTO clone = jdbcRegistryClientHeartbeatDTO.clone();
+ clone.setLastHeartbeatTime(now);
+ jdbcRegistryClientRepository.updateById(jdbcRegistryClientHeartbeatDTO);
+ jdbcRegistryClientHeartbeatDTO.setLastHeartbeatTime(clone.getLastHeartbeatTime());
+ }
+ if (jdbcRegistryServerState == JdbcRegistryServerState.SUSPENDED) {
+ jdbcRegistryServerState = JdbcRegistryServerState.STARTED;
+ doTriggerReconnectedListener();
+ }
+ lastSuccessHeartbeat = now;
+ log.debug("Success refresh clients: {} heartbeat.",
+ CollectionUtils.collect(jdbcRegistryClients, IJdbcRegistryClient::getJdbcRegistryClientIdentify));
+ } catch (Exception ex) {
+ log.error("Failed to refresh the client's term", ex);
+ switch (jdbcRegistryServerState) {
+ case STARTED:
+ jdbcRegistryServerState = JdbcRegistryServerState.SUSPENDED;
+ break;
+ case SUSPENDED:
+ if (System.currentTimeMillis() - lastSuccessHeartbeat > jdbcRegistryProperties.getSessionTimeout()
+ .toMillis()) {
+ jdbcRegistryServerState = JdbcRegistryServerState.DISCONNECTED;
+ doTriggerOnDisConnectedListener();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private void doTriggerReconnectedListener() {
+ log.info("Trigger:onReconnected listener.");
+ connectionStateListeners.forEach(listener -> {
+ try {
+ listener.onReconnected();
+ } catch (Exception ex) {
+ log.error("Trigger:onReconnected failed", ex);
+ }
+ });
+ }
+
+ private void doTriggerOnConnectedListener() {
+ log.info("Trigger:onConnected listener.");
+ connectionStateListeners.forEach(listener -> {
+ try {
+ listener.onConnected();
+ } catch (Exception ex) {
+ log.error("Trigger:onConnected failed", ex);
+ }
+ });
+ }
+
+ private void doTriggerOnDisConnectedListener() {
+ log.info("Trigger:onDisConnected listener.");
+ connectionStateListeners.forEach(listener -> {
+ try {
+ listener.onDisConnected();
+ } catch (Exception ex) {
+ log.error("Trigger:onDisConnected failed", ex);
+ }
+ });
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryServerState.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryServerState.java
new file mode 100644
index 000000000..e568e7d8a
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/java/org/apache/streampark/registry/core/server/JdbcRegistryServerState.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core.server;
+
+import org.apache.streampark.registry.core.JdbcRegistryProperties;
+
+public enum JdbcRegistryServerState {
+ /**
+ * Once the {@link JdbcRegistryServer} created, it will be in the INIT state.
+ */
+ INIT,
+ /**
+ * After the {@link JdbcRegistryServer} started, it will be in the STARTED state.
+ */
+ STARTED,
+ /**
+ * Once the {@link JdbcRegistryServer} cannot connect to DB, it will be in the SUSPENDED state, and if it can reconnect to DB in {@link JdbcRegistryProperties#getSessionTimeout()} ()},
+ * it will be changed to the STARTED state again.
+ */
+ SUSPENDED,
+ /**
+ * If the {@link JdbcRegistryServer} cannot connected to DB in {@link JdbcRegistryProperties#getSessionTimeout()}, it will be in the DISCONNECTED state.
+ */
+ DISCONNECTED,
+ /**
+ * If the {@link JdbcRegistryServer} closed, it will be in the STOPPED state.
+ */
+ STOPPED,
+ ;
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/META-INF/spring.factories b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..a4f56a18a
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ org.apache.streampark.registry.core.JdbcRegistryAutoConfiguration
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/mysql_registry_init.sql b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/mysql_registry_init.sql
new file mode 100644
index 000000000..4ee6c47a5
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/mysql_registry_init.sql
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+
+DROP TABLE IF EXISTS `t_jdbc_registry_data`;
+CREATE TABLE `t_jdbc_registry_data`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `data_key` varchar(256) NOT NULL COMMENT 'key, like zookeeper node path',
+ `data_value` text NOT NULL COMMENT 'data, like zookeeper node value',
+ `data_type` varchar(64) NOT NULL COMMENT 'EPHEMERAL, PERSISTENT',
+ `client_id` bigint(11) NOT NULL COMMENT 'client id',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `last_update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'last update time',
+ PRIMARY KEY (`id`),
+ unique Key `uk_t_jdbc_registry_dataKey` (`data_key`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+
+DROP TABLE IF EXISTS `t_jdbc_registry_lock`;
+CREATE TABLE `t_jdbc_registry_lock`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `lock_key` varchar(256) NOT NULL COMMENT 'lock path',
+ `lock_owner` varchar(256) NOT NULL COMMENT 'the lock owner, ip_processId',
+ `client_id` bigint(11) NOT NULL COMMENT 'client id',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`),
+ unique Key `uk_t_jdbc_registry_lockKey` (`lock_key`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `t_jdbc_registry_client_heartbeat`;
+CREATE TABLE `t_jdbc_registry_client_heartbeat`
+(
+ `id` bigint(11) NOT NULL COMMENT 'primary key',
+ `client_name` varchar(256) NOT NULL COMMENT 'client name, ip_processId',
+ `last_heartbeat_time` bigint(11) NOT NULL COMMENT 'last heartbeat timestamp',
+ `connection_config` text NOT NULL COMMENT 'connection config',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `t_jdbc_registry_data_change_event`;
+CREATE TABLE `t_jdbc_registry_data_change_event`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `event_type` varchar(64) NOT NULL COMMENT 'ADD, UPDATE, DELETE',
+ `jdbc_registry_data` text NOT NULL COMMENT 'jdbc registry data',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/postgresql_registry_init.sql b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/postgresql_registry_init.sql
new file mode 100644
index 000000000..dcfd82074
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/main/resources/postgresql_registry_init.sql
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+DROP TABLE IF EXISTS t_jdbc_registry_data;
+create table t_jdbc_registry_data
+(
+ id bigserial not null,
+ data_key varchar not null,
+ data_value text not null,
+ data_type varchar not null,
+ client_id bigint not null,
+ create_time timestamp not null default current_timestamp,
+ last_update_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+create unique index uk_t_jdbc_registry_dataKey on t_jdbc_registry_data (data_key);
+
+
+DROP TABLE IF EXISTS t_jdbc_registry_lock;
+create table t_jdbc_registry_lock
+(
+ id bigserial not null,
+ lock_key varchar not null,
+ lock_owner varchar not null,
+ client_id bigint not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+create unique index uk_t_jdbc_registry_lockKey on t_jdbc_registry_lock (lock_key);
+
+
+DROP TABLE IF EXISTS t_jdbc_registry_client_heartbeat;
+create table t_jdbc_registry_client_heartbeat
+(
+ id bigint not null,
+ client_name varchar not null,
+ last_heartbeat_time bigint not null,
+ connection_config text not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+
+DROP TABLE IF EXISTS t_jdbc_registry_data_change_event;
+create table t_jdbc_registry_data_change_event
+(
+ id bigserial not null,
+ event_type varchar not null,
+ jdbc_registry_data text not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/JdbcRegistryTestCase.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/JdbcRegistryTestCase.java
new file mode 100644
index 000000000..75b876d38
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/JdbcRegistryTestCase.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.common.CommonConfiguration;
+import org.apache.streampark.registry.RegistryTestCase;
+import org.apache.streampark.registry.api.ConnectionState;
+import org.apache.streampark.registry.core.server.IJdbcRegistryServer;
+
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.DirtiesContext;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+@SpringBootTest(classes = {CommonConfiguration.class, JdbcRegistryProperties.class})
+@SpringBootApplication(scanBasePackageClasses = {CommonConfiguration.class, JdbcRegistryProperties.class})
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+public abstract class JdbcRegistryTestCase extends RegistryTestCase<JdbcRegistry> {
+
+ @Autowired
+ private JdbcRegistryProperties jdbcRegistryProperties;
+
+ @Autowired
+ private IJdbcRegistryServer jdbcRegistryServer;
+
+ @SneakyThrows
+ @Test
+ public void testAddConnectionStateListener() {
+
+ AtomicReference<ConnectionState> connectionState = new AtomicReference<>();
+ registry.addConnectionStateListener(connectionState::set);
+
+ // todo: since the jdbc registry is started at the auto configuration, the stateListener is added after the
+ // registry is started.
+ assertThat(connectionState.get()).isEqualTo(null);
+ }
+
+ @Override
+ public JdbcRegistry createRegistry() {
+ return new JdbcRegistry(jdbcRegistryProperties, jdbcRegistryServer);
+ }
+
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/JdbcRegistryThreadFactoryTest.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/JdbcRegistryThreadFactoryTest.java
new file mode 100644
index 000000000..6686d3987
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/JdbcRegistryThreadFactoryTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+class JdbcRegistryThreadFactoryTest {
+
+ @Test
+ void getDefaultSchedulerThreadExecutor() {
+ ScheduledExecutorService schedulerThreadExecutor =
+ JdbcRegistryThreadFactory.getDefaultSchedulerThreadExecutor();
+ AtomicInteger atomicInteger = new AtomicInteger(0);
+ for (int i = 0; i < 100; i++) {
+ schedulerThreadExecutor.scheduleWithFixedDelay(atomicInteger::incrementAndGet, 0, 1, TimeUnit.SECONDS);
+ }
+ await()
+ .atMost(10, TimeUnit.SECONDS)
+ .untilAsserted(() -> assertThat(atomicInteger.get()).isEqualTo(100));
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/LockUtilsTest.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/LockUtilsTest.java
new file mode 100644
index 000000000..7d2b98e46
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/LockUtilsTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.common.utils.NetworkUtils;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+@SpringBootTest
+@ContextConfiguration(classes = {NetworkUtils.class})
+@TestPropertySource(locations = "classpath:application.yaml")
+class LockUtilsTest {
+
+ @Test
+ void getLockOwner() {
+ assertThat(LockUtils.getLockOwner()).isNotNull();
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/MysqlJdbcRegistryTestCase.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/MysqlJdbcRegistryTestCase.java
new file mode 100644
index 000000000..262df31d4
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/MysqlJdbcRegistryTestCase.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.registry.api.sql.SqlScriptRunner;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.springframework.test.context.ActiveProfiles;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.MySQLContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.lifecycle.Startables;
+import org.testcontainers.utility.DockerImageName;
+
+import java.time.Duration;
+import java.util.stream.Stream;
+
+@ActiveProfiles("mysql")
+class MysqlJdbcRegistryTestCase extends JdbcRegistryTestCase {
+
+ private static GenericContainer<?> mysqlContainer;
+
+ @SneakyThrows
+ @BeforeAll
+ public static void setUpTestingServer() {
+ mysqlContainer = new MySQLContainer(DockerImageName.parse("mysql:8.0"))
+ .withUsername("root")
+ .withPassword("root")
+ .withDatabaseName("streampark")
+ .withNetwork(Network.newNetwork())
+ .withExposedPorts(3306)
+ .waitingFor(Wait.forHealthcheck().withStartupTimeout(Duration.ofSeconds(300)));
+
+ Startables.deepStart(Stream.of(mysqlContainer)).join();
+
+ String jdbcUrl = "jdbc:mysql://localhost:" + mysqlContainer.getMappedPort(3306)
+ + "/streampark?useSSL=false&serverTimezone=UTC";
+ System.clearProperty("spring.datasource.url");
+ System.setProperty("spring.datasource.url", jdbcUrl);
+
+ HikariConfig config = new HikariConfig();
+ config.setJdbcUrl(jdbcUrl);
+ config.setUsername("root");
+ config.setPassword("root");
+
+ try (HikariDataSource dataSource = new HikariDataSource(config)) {
+ SqlScriptRunner sqlScriptRunner = new SqlScriptRunner(dataSource, "mysql_registry_init.sql");
+ sqlScriptRunner.execute();
+ }
+ }
+
+ @SneakyThrows
+ @AfterAll
+ public static void tearDownTestingServer() {
+ mysqlContainer.close();
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/PostgresqlJdbcRegistryTestCase.java b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/PostgresqlJdbcRegistryTestCase.java
new file mode 100644
index 000000000..1e87d284c
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/java/org/apache/streampark/registry/core/PostgresqlJdbcRegistryTestCase.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry.core;
+
+import org.apache.streampark.registry.api.sql.SqlScriptRunner;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.springframework.test.context.ActiveProfiles;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.lifecycle.Startables;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.stream.Stream;
+
+@ActiveProfiles("postgresql")
+public class PostgresqlJdbcRegistryTestCase extends JdbcRegistryTestCase {
+
+ private static GenericContainer<?> postgresqlContainer;
+
+ @SneakyThrows
+ @BeforeAll
+ public static void setUpTestingServer() {
+ postgresqlContainer = new PostgreSQLContainer(DockerImageName.parse("postgres:16.0"))
+ .withUsername("root")
+ .withPassword("root")
+ .withDatabaseName("streampark")
+ .withNetwork(Network.newNetwork())
+ .withExposedPorts(5432);
+
+ Startables.deepStart(Stream.of(postgresqlContainer)).join();
+
+ String jdbcUrl = "jdbc:postgresql://localhost:" + postgresqlContainer.getMappedPort(5432) + "/streampark";
+ System.clearProperty("spring.datasource.url");
+ System.setProperty("spring.datasource.url", jdbcUrl);
+
+ HikariConfig config = new HikariConfig();
+ config.setJdbcUrl(jdbcUrl);
+ config.setUsername("root");
+ config.setPassword("root");
+
+ try (HikariDataSource dataSource = new HikariDataSource(config)) {
+ SqlScriptRunner sqlScriptRunner = new SqlScriptRunner(dataSource, "postgresql_registry_init.sql");
+ sqlScriptRunner.execute();
+ }
+ }
+
+ @SneakyThrows
+ @AfterAll
+ public static void tearDownTestingServer() {
+ postgresqlContainer.close();
+ }
+}
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application-mysql.yaml b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application-mysql.yaml
new file mode 100644
index 000000000..779b43a2c
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application-mysql.yaml
@@ -0,0 +1,38 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+spring:
+ sql:
+ init:
+ schema-locations: classpath:mysql_registry_init.sql
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/streampark?useUnicode=true&characterEncoding=UTF-8
+ username: root
+ password: root
+
+registry:
+ type: jdbc
+ heartbeat-refresh-interval: 1s
+ session-timeout: 3s
+
+network:
+ # network interface preferred like eth0, default: empty
+ preferred-interface: ""
+ # network interface restricted like docker0, default: empty
+ restrict-interface: docker0
+ # network IP gets priority, default inner outer
+ priority-strategy: default
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application-postgresql.yaml b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application-postgresql.yaml
new file mode 100644
index 000000000..e5fbb5a56
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application-postgresql.yaml
@@ -0,0 +1,36 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+spring:
+ datasource:
+ driver-class-name: org.postgresql.Driver
+ url: jdbc:postgresql://localhost:5432/streampark
+ username: root
+ password: root
+
+registry:
+ type: jdbc
+ heartbeat-refresh-interval: 1s
+ session-timeout: 3s
+
+network:
+ # network interface preferred like eth0, default: empty
+ preferred-interface: ""
+ # network interface restricted like docker0, default: empty
+ restrict-interface: docker0
+ # network IP gets priority, default inner outer
+ priority-strategy: default
+
diff --git a/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application.yaml b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application.yaml
new file mode 100644
index 000000000..7f828bf58
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/application.yaml
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+network:
+ # network interface preferred like eth0, default: empty
+ preferred-interface: ""
+ # network interface restricted like docker0, default: empty
+ restrict-interface: docker0
+ # network IP gets priority, default inner outer
+ priority-strategy: default
diff --git a/streampark-console/pom.xml b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/logback.xml
similarity index 50%
copy from streampark-console/pom.xml
copy to streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/logback.xml
index 59488748e..6f211959c 100644
--- a/streampark-console/pom.xml
+++ b/streampark-console/streampark-console-registry/streampark-registry-core/src/test/resources/logback.xml
@@ -15,31 +15,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.apache.streampark</groupId>
- <artifactId>streampark</artifactId>
- <version>2.2.0-SNAPSHOT</version>
- </parent>
-
- <artifactId>streampark-console</artifactId>
- <packaging>pom</packaging>
- <name>StreamPark : Console Parent</name>
-
- <modules>
- <module>streampark-console-service</module>
- </modules>
-
- <profiles>
- <profile>
- <id>apache-release</id>
- <properties>
- <maven.deploy.skip>true</maven.deploy.skip>
- </properties>
- </profile>
- </profiles>
-
-</project>
+<configuration>
+ <logger name="*" level="ERROR"/>
+</configuration>
diff --git a/streampark-console/pom.xml b/streampark-console/streampark-console-registry/streampark-registry-it/pom.xml
similarity index 51%
copy from streampark-console/pom.xml
copy to streampark-console/streampark-console-registry/streampark-registry-it/pom.xml
index 59488748e..6fce49e99 100644
--- a/streampark-console/pom.xml
+++ b/streampark-console/streampark-console-registry/streampark-registry-it/pom.xml
@@ -17,29 +17,46 @@
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.streampark</groupId>
- <artifactId>streampark</artifactId>
+ <artifactId>streampark-console-registry</artifactId>
<version>2.2.0-SNAPSHOT</version>
</parent>
- <artifactId>streampark-console</artifactId>
- <packaging>pom</packaging>
- <name>StreamPark : Console Parent</name>
+ <artifactId>streampark-registry-it</artifactId>
+ <name>StreamPark : Console Registry Testcase</name>
- <modules>
- <module>streampark-console-service</module>
- </modules>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-registry-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
- <profiles>
- <profile>
- <id>apache-release</id>
- <properties>
- <maven.deploy.skip>true</maven.deploy.skip>
- </properties>
- </profile>
- </profiles>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
</project>
diff --git a/streampark-console/streampark-console-registry/streampark-registry-it/src/test/java/org/apache/streampark/plugin/registry/RegistryTestCase.java b/streampark-console/streampark-console-registry/streampark-registry-it/src/test/java/org/apache/streampark/plugin/registry/RegistryTestCase.java
new file mode 100644
index 000000000..63ec42936
--- /dev/null
+++ b/streampark-console/streampark-console-registry/streampark-registry-it/src/test/java/org/apache/streampark/plugin/registry/RegistryTestCase.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.registry;
+
+import org.apache.streampark.common.utils.NetworkUtils;
+import org.apache.streampark.registry.api.ConnectionState;
+import org.apache.streampark.registry.api.Event;
+import org.apache.streampark.registry.api.Registry;
+import org.apache.streampark.registry.api.RegistryException;
+import org.apache.streampark.registry.api.SubscribeListener;
+
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
+
+public abstract class RegistryTestCase<R extends Registry> {
+
+ protected R registry;
+
+ @BeforeAll
+ public static void init() {
+ ReflectionTestUtils.setField(NetworkUtils.class, "priorityStrategy", "default");
+ }
+
+ @BeforeEach
+ public void setupRegistry() {
+ registry = createRegistry();
+ }
+
+ @SneakyThrows
+ @AfterEach
+ public void tearDownRegistry() {
+ this.registry.close();
+ }
+
+ @Test
+ public void testIsConnected() {
+ registry.start();
+ assertThat(registry.isConnected()).isTrue();
+ }
+
+ @Test
+ public void testConnectUntilTimeout() {
+ registry.start();
+ await().atMost(Duration.ofSeconds(10))
+ .untilAsserted(() -> registry.connectUntilTimeout(Duration.ofSeconds(3)));
+
+ }
+
+ @SneakyThrows
+ @Test
+ public void testSubscribe() {
+ registry.start();
+
+ final AtomicBoolean subscribeAdded = new AtomicBoolean(false);
+ final AtomicBoolean subscribeRemoved = new AtomicBoolean(false);
+ final AtomicBoolean subscribeUpdated = new AtomicBoolean(false);
+
+ SubscribeListener subscribeListener = event -> {
+ System.out.println("Receive event: " + event);
+ if (event.type() == Event.Type.ADD) {
+ subscribeAdded.compareAndSet(false, true);
+ }
+ if (event.type() == Event.Type.REMOVE) {
+ subscribeRemoved.compareAndSet(false, true);
+ }
+ if (event.type() == Event.Type.UPDATE) {
+ subscribeUpdated.compareAndSet(false, true);
+ }
+ };
+ String key = "/nodes/console-server" + System.nanoTime();
+ registry.subscribe(key, subscribeListener);
+ registry.put(key, String.valueOf(System.nanoTime()), true);
+ // Sleep 3 seconds here since in mysql jdbc registry
+ // If multiple event occurs in a refresh time, only the last event will be triggered
+ Thread.sleep(3000);
+ registry.put(key, String.valueOf(System.nanoTime()), true);
+ Thread.sleep(3000);
+ registry.delete(key);
+
+ await().atMost(Duration.ofSeconds(10))
+ .untilAsserted(() -> {
+ Assertions.assertTrue(subscribeAdded.get());
+ Assertions.assertTrue(subscribeUpdated.get());
+ Assertions.assertTrue(subscribeRemoved.get());
+ });
+ }
+
+ @SneakyThrows
+ @Test
+ public void testAddConnectionStateListener() {
+
+ AtomicReference<ConnectionState> connectionState = new AtomicReference<>();
+ registry.addConnectionStateListener(connectionState::set);
+
+ assertThat(connectionState.get()).isNull();
+ registry.start();
+
+ await().atMost(Duration.ofSeconds(2))
+ .until(() -> ConnectionState.CONNECTED == connectionState.get());
+
+ }
+
+ @Test
+ public void testGet() {
+ registry.start();
+ String key = "/nodes/console-server" + System.nanoTime();
+ String value = "127.0.0.1:8080";
+ assertThrows(RegistryException.class, () -> registry.get(key));
+ registry.put(key, value, true);
+ assertThat(registry.get(key)).isEqualTo(value);
+ }
+
+ @Test
+ public void testPut() {
+ registry.start();
+ String key = "/nodes/console-server" + System.nanoTime();
+ String value = "127.0.0.1:8080";
+ registry.put(key, value, true);
+ assertThat(registry.get(key)).isEqualTo(value);
+
+ // Update the value
+ registry.put(key, "123", true);
+ assertThat(registry.get(key)).isEqualTo("123");
+ }
+
+ @Test
+ public void testDelete() {
+ registry.start();
+ String key = "/nodes/console-server" + System.nanoTime();
+ String value = "127.0.0.1:8080";
+ // Delete a non-existent key
+ registry.delete(key);
+
+ registry.put(key, value, true);
+ assertThat(registry.get(key)).isEqualTo(value);
+ registry.delete(key);
+ assertThat(registry.exists(key)).isFalse();
+
+ }
+
+ @Test
+ public void testChildren() {
+ registry.start();
+ String master1 = "/nodes/children/childGroup1/127.0.0.1:8080";
+ String master2 = "/nodes/children/childGroup1/127.0.0.2:8080";
+ String value = "123";
+ registry.put(master1, value, true);
+ registry.put(master2, value, true);
+ assertThat(registry.children("/nodes/children")).contains("childGroup1");
+ assertThat(registry.children("/nodes/children/childGroup1")).contains("127.0.0.1:8080",
+ "127.0.0.2:8080");
+ }
+
+ @Test
+ public void testExists() {
+ registry.start();
+ String key = "/nodes/console-server" + System.nanoTime();
+ String value = "123";
+ assertThat(registry.exists(key)).isFalse();
+ registry.put(key, value, true);
+ assertThat(registry.exists(key)).isTrue();
+
+ }
+
+ @SneakyThrows
+ @Test
+ public void testAcquireLock() {
+ registry.start();
+ String lockKey = "/lock" + System.nanoTime();
+
+ // 1. Acquire the lock at the main thread
+ assertThat(registry.acquireLock(lockKey)).isTrue();
+ // Acquire the lock at the main thread again
+ // It should acquire success
+ assertThat(registry.acquireLock(lockKey)).isTrue();
+
+ // Acquire the lock at another thread
+ // It should acquire failed
+ CompletableFuture<Boolean> acquireResult = CompletableFuture.supplyAsync(() -> registry.acquireLock(lockKey));
+ assertThrows(TimeoutException.class, () -> acquireResult.get(3000, TimeUnit.MILLISECONDS));
+
+ }
+
+ @SneakyThrows
+ @Test
+ public void testAcquireLock_withTimeout() {
+ registry.start();
+ String lockKey = "/lock" + System.nanoTime();
+ // 1. Acquire the lock in the main thread
+ assertThat(registry.acquireLock(lockKey, 3000)).isTrue();
+
+ // Acquire the lock in the main thread
+ // It should acquire success
+ assertThat(registry.acquireLock(lockKey, 3000)).isTrue();
+
+ // Acquire the lock at another thread
+ // It should acquire failed
+ CompletableFuture<Boolean> acquireResult =
+ CompletableFuture.supplyAsync(() -> registry.acquireLock(lockKey, 3000));
+ assertThat(acquireResult.get()).isFalse();
+
+ }
+
+ @SneakyThrows
+ @Test
+ public void testReleaseLock() {
+ registry.start();
+ String lockKey = "/lock" + System.nanoTime();
+ // 1. Acquire the lock in the main thread
+ assertThat(registry.acquireLock(lockKey, 3000)).isTrue();
+
+ // Acquire the lock at another thread
+ // It should acquire failed
+ CompletableFuture<Boolean> acquireResult =
+ CompletableFuture.supplyAsync(() -> registry.acquireLock(lockKey, 3000));
+ assertThat(acquireResult.get()).isFalse();
+
+ // 2. Release the lock in the main thread
+ assertThat(registry.releaseLock(lockKey)).isTrue();
+
+ // Acquire the lock at another thread
+ // It should acquire success
+ acquireResult = CompletableFuture.supplyAsync(() -> registry.acquireLock(lockKey, 3000));
+ assertThat(acquireResult.get()).isTrue();
+ }
+
+ public abstract R createRegistry();
+
+}
diff --git a/streampark-console/streampark-console-service/pom.xml b/streampark-console/streampark-console-service/pom.xml
index fd7eb9c5d..2372f65e6 100644
--- a/streampark-console/streampark-console-service/pom.xml
+++ b/streampark-console/streampark-console-service/pom.xml
@@ -34,7 +34,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<postgresql.version>42.5.1</postgresql.version>
- <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<streampark.flink.shims.version>1.14</streampark.flink.shims.version>
<frontend.project.name>streampark-console-webapp</frontend.project.name>
<PermGen>64m</PermGen>
@@ -61,18 +60,6 @@
<eclipse.jgit.version>5.13.1.202206130422-r</eclipse.jgit.version>
</properties>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-dependencies</artifactId>
- <version>2.7.11</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
-
<dependencies>
<!--scala-->
<dependency>
@@ -126,6 +113,20 @@
</dependency>
<!--spring-->
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-autoconfigure</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ </dependency>
+
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -242,7 +243,16 @@
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
- <version>${mybatis-plus.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-annotation</artifactId>
</dependency>
<!-- sql print -->
@@ -461,6 +471,17 @@
<version>${flink.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.hibernate.validator</groupId>
+ <artifactId>hibernate-validator</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.streampark</groupId>
+ <artifactId>streampark-registry-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-java</artifactId>
diff --git a/streampark-console/streampark-console-service/src/main/assembly/script/schema/mysql-schema.sql b/streampark-console/streampark-console-service/src/main/assembly/script/schema/mysql-schema.sql
index b76f90b5c..19feaf77d 100644
--- a/streampark-console/streampark-console-service/src/main/assembly/script/schema/mysql-schema.sql
+++ b/streampark-console/streampark-console-service/src/main/assembly/script/schema/mysql-schema.sql
@@ -682,6 +682,61 @@ CREATE TABLE `t_flink_catalog` (
UNIQUE KEY uniq_catalog_name (`catalog_name`)
) ENGINE=InnoDB auto_increment=100000 default charset=utf8mb4 collate=utf8mb4_general_ci;
+-- ----------------------------
+-- Table structure for jdbc registry
+-- ----------------------------
+DROP TABLE IF EXISTS `t_jdbc_registry_data`;
+CREATE TABLE `t_jdbc_registry_data`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `data_key` varchar(256) NOT NULL COMMENT 'key, like zookeeper node path',
+ `data_value` text NOT NULL COMMENT 'data, like zookeeper node value',
+ `data_type` varchar(64) NOT NULL COMMENT 'EPHEMERAL, PERSISTENT',
+ `client_id` bigint(11) NOT NULL COMMENT 'client id',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `last_update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'last update time',
+ PRIMARY KEY (`id`),
+ unique Key `uk_t_jdbc_registry_dataKey` (`data_key`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+
+DROP TABLE IF EXISTS `t_jdbc_registry_lock`;
+CREATE TABLE `t_jdbc_registry_lock`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `lock_key` varchar(256) NOT NULL COMMENT 'lock path',
+ `lock_owner` varchar(256) NOT NULL COMMENT 'the lock owner, ip_processId',
+ `client_id` bigint(11) NOT NULL COMMENT 'client id',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`),
+ unique Key `uk_t_jdbc_registry_lockKey` (`lock_key`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `t_jdbc_registry_client_heartbeat`;
+CREATE TABLE `t_jdbc_registry_client_heartbeat`
+(
+ `id` bigint(11) NOT NULL COMMENT 'primary key',
+ `client_name` varchar(256) NOT NULL COMMENT 'client name, ip_processId',
+ `last_heartbeat_time` bigint(11) NOT NULL COMMENT 'last heartbeat timestamp',
+ `connection_config` text NOT NULL COMMENT 'connection config',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `t_jdbc_registry_data_change_event`;
+CREATE TABLE `t_jdbc_registry_data_change_event`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `event_type` varchar(64) NOT NULL COMMENT 'ADD, UPDATE, DELETE',
+ `jdbc_registry_data` text NOT NULL COMMENT 'jdbc registry data',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
-- ----------------------------
-- table structure for t_spark_effective
-- ----------------------------
diff --git a/streampark-console/streampark-console-service/src/main/assembly/script/schema/pgsql-schema.sql b/streampark-console/streampark-console-service/src/main/assembly/script/schema/pgsql-schema.sql
index 4462bd207..91eb75188 100644
--- a/streampark-console/streampark-console-service/src/main/assembly/script/schema/pgsql-schema.sql
+++ b/streampark-console/streampark-console-service/src/main/assembly/script/schema/pgsql-schema.sql
@@ -883,3 +883,55 @@ create trigger "streampark_t_flink_project_modify_time_tri" before update on "pu
create trigger "streampark_t_menu_modify_time_tri" before update on "public"."t_menu" for each row execute procedure "public"."update_modify_time"();
create trigger "streampark_t_role_modify_time_tri" before update on "public"."t_role" for each row execute procedure "public"."update_modify_time"();
create trigger "streampark_t_user_modify_time_tri" before update on "public"."t_user" for each row execute procedure "public"."update_modify_time"();
+
+-- ----------------------------
+-- Table structure for jdbc registry
+-- ----------------------------
+DROP TABLE IF EXISTS t_jdbc_registry_data;
+create table t_jdbc_registry_data
+(
+ id bigserial not null,
+ data_key varchar not null,
+ data_value text not null,
+ data_type varchar not null,
+ client_id bigint not null,
+ create_time timestamp not null default current_timestamp,
+ last_update_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+create unique index uk_t_jdbc_registry_dataKey on t_jdbc_registry_data (data_key);
+
+
+DROP TABLE IF EXISTS t_jdbc_registry_lock;
+create table t_jdbc_registry_lock
+(
+ id bigserial not null,
+ lock_key varchar not null,
+ lock_owner varchar not null,
+ client_id bigint not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+create unique index uk_t_jdbc_registry_lockKey on t_jdbc_registry_lock (lock_key);
+
+
+DROP TABLE IF EXISTS t_jdbc_registry_client_heartbeat;
+create table t_jdbc_registry_client_heartbeat
+(
+ id bigint not null,
+ client_name varchar not null,
+ last_heartbeat_time bigint not null,
+ connection_config text not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+
+DROP TABLE IF EXISTS t_jdbc_registry_data_change_event;
+create table t_jdbc_registry_data_change_event
+(
+ id bigserial not null,
+ event_type varchar not null,
+ jdbc_registry_data text not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
diff --git a/streampark-console/streampark-console-service/src/main/assembly/script/upgrade/mysql/2.2.0.sql b/streampark-console/streampark-console-service/src/main/assembly/script/upgrade/mysql/2.2.0.sql
index 13f86f0ed..91b116763 100644
--- a/streampark-console/streampark-console-service/src/main/assembly/script/upgrade/mysql/2.2.0.sql
+++ b/streampark-console/streampark-console-service/src/main/assembly/script/upgrade/mysql/2.2.0.sql
@@ -104,6 +104,57 @@ insert into `t_role_menu` (role_id, menu_id) values (100002, 120403);
alter table `t_user` modify column `password` varchar(64) collate utf8mb4_general_ci default null comment 'password';
alter table `t_user` modify column `login_type` tinyint default 0 comment 'login type 0:password 1:ldap 2:sso';
+-- ----------------------------
+-- Table structure for jdbc registry
+-- ----------------------------
+DROP TABLE IF EXISTS `t_jdbc_registry_data`;
+CREATE TABLE `t_jdbc_registry_data` (
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `data_key` varchar(256) NOT NULL COMMENT 'key, like zookeeper node path',
+ `data_value` text NOT NULL COMMENT 'data, like zookeeper node value',
+ `data_type` varchar(64) NOT NULL COMMENT 'EPHEMERAL, PERSISTENT',
+ `client_id` bigint(11) NOT NULL COMMENT 'client id',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ `last_update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'last update time',
+ PRIMARY KEY (`id`),
+ unique Key `uk_t_jdbc_registry_dataKey` (`data_key`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+
+DROP TABLE IF EXISTS `t_jdbc_registry_lock`;
+CREATE TABLE `t_jdbc_registry_lock` (
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `lock_key` varchar(256) NOT NULL COMMENT 'lock path',
+ `lock_owner` varchar(256) NOT NULL COMMENT 'the lock owner, ip_processId',
+ `client_id` bigint(11) NOT NULL COMMENT 'client id',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`),
+ unique Key `uk_t_jdbc_registry_lockKey` (`lock_key`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `t_jdbc_registry_client_heartbeat`;
+CREATE TABLE `t_jdbc_registry_client_heartbeat` (
+ `id` bigint(11) NOT NULL COMMENT 'primary key',
+ `client_name` varchar(256) NOT NULL COMMENT 'client name, ip_processId',
+ `last_heartbeat_time` bigint(11) NOT NULL COMMENT 'last heartbeat timestamp',
+ `connection_config` text NOT NULL COMMENT 'connection config',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `t_jdbc_registry_data_change_event`;
+CREATE TABLE `t_jdbc_registry_data_change_event` (
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `event_type` varchar(64) NOT NULL COMMENT 'ADD, UPDATE, DELETE',
+ `jdbc_registry_data` text NOT NULL COMMENT 'jdbc registry data',
+ `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
-- ----------------------------
-- table structure for t_flink_catalog
-- ----------------------------
diff --git a/streampark-console/streampark-console-service/src/main/assembly/script/upgrade/pgsql/2.2.0.sql b/streampark-console/streampark-console-service/src/main/assembly/script/upgrade/pgsql/2.2.0.sql
index 61b8a22dc..0230c8273 100644
--- a/streampark-console/streampark-console-service/src/main/assembly/script/upgrade/pgsql/2.2.0.sql
+++ b/streampark-console/streampark-console-service/src/main/assembly/script/upgrade/pgsql/2.2.0.sql
@@ -99,6 +99,58 @@ insert into "public"."t_role_menu" (role_id, menu_id) values (100002, 120501);
insert into "public"."t_role_menu" (role_id, menu_id) values (100002, 120502);
insert into "public"."t_role_menu" (role_id, menu_id) values (100002, 120503);
+-- ----------------------------
+-- Table structure for jdbc registry
+-- ----------------------------
+DROP TABLE IF EXISTS t_jdbc_registry_data;
+create table t_jdbc_registry_data
+(
+ id bigserial not null,
+ data_key varchar not null,
+ data_value text not null,
+ data_type varchar not null,
+ client_id bigint not null,
+ create_time timestamp not null default current_timestamp,
+ last_update_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+create unique index uk_t_jdbc_registry_dataKey on t_jdbc_registry_data (data_key);
+
+
+DROP TABLE IF EXISTS t_jdbc_registry_lock;
+create table t_jdbc_registry_lock
+(
+ id bigserial not null,
+ lock_key varchar not null,
+ lock_owner varchar not null,
+ client_id bigint not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+create unique index uk_t_jdbc_registry_lockKey on t_jdbc_registry_lock (lock_key);
+
+
+DROP TABLE IF EXISTS t_jdbc_registry_client_heartbeat;
+create table t_jdbc_registry_client_heartbeat
+(
+ id bigint not null,
+ client_name varchar not null,
+ last_heartbeat_time bigint not null,
+ connection_config text not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+
+DROP TABLE IF EXISTS t_jdbc_registry_data_change_event;
+create table t_jdbc_registry_data_change_event
+(
+ id bigserial not null,
+ event_type varchar not null,
+ jdbc_registry_data text not null,
+ create_time timestamp not null default current_timestamp,
+ primary key (id)
+);
+
-- ----------------------------
-- table structure for t_flink_catalog
-- ----------------------------
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/StreamParkConsoleBootstrap.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/StreamParkConsoleBootstrap.java
index 601fc7f00..3dfea1344 100644
--- a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/StreamParkConsoleBootstrap.java
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/StreamParkConsoleBootstrap.java
@@ -17,13 +17,25 @@
package org.apache.streampark.console;
+import org.apache.streampark.common.CommonConfiguration;
+import org.apache.streampark.common.IStoppable;
+import org.apache.streampark.common.constants.Constants;
import org.apache.streampark.console.base.config.SpringProperties;
+import org.apache.streampark.console.base.util.SpringContextUtils;
+import org.apache.streampark.console.core.registry.ConsoleRegistryClient;
+import org.apache.streampark.registry.api.RegistryConfiguration;
+import org.apache.streampark.registry.api.lifecycle.ServerLifeCycleManager;
+import org.apache.streampark.registry.api.thread.ThreadUtils;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableScheduling;
+import javax.annotation.PostConstruct;
+
/**
*
*
@@ -46,7 +58,15 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
@SpringBootApplication
@EnableScheduling
-public class StreamParkConsoleBootstrap {
+@Import({CommonConfiguration.class,
+ RegistryConfiguration.class})
+public class StreamParkConsoleBootstrap implements IStoppable {
+
+ @Autowired
+ private ConsoleRegistryClient consoleRegistryClient;
+
+ @Autowired
+ private SpringContextUtils springContextUtils;
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder()
@@ -55,4 +75,49 @@ public class StreamParkConsoleBootstrap {
.run(args);
}
+ @PostConstruct
+ public void run() {
+ consoleRegistryClient.start();
+ consoleRegistryClient.setRegistryStoppable(this);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ if (!ServerLifeCycleManager.isStopped()) {
+ close("ConsoleServer shutdownHook");
+ }
+ }));
+ }
+
+ /**
+ * gracefully close console server
+ *
+ * @param cause close cause
+ */
+ public void close(String cause) {
+ // set stop signal is true
+ // execute only once
+ if (!ServerLifeCycleManager.toStopped()) {
+ log.warn("MasterServer is already stopped, current cause: {}", cause);
+ return;
+ }
+ // thread sleep 3 seconds for thread quietly stop
+ ThreadUtils.sleep(Constants.SERVER_CLOSE_WAIT_TIME.toMillis());
+ try (ConsoleRegistryClient closedMasterRegistryClient = consoleRegistryClient) {
+ // todo: close other resources
+ springContextUtils.close();
+ log.info("Master server is stopping, current cause : {}", cause);
+ } catch (Exception e) {
+ log.error("MasterServer stop failed, current cause: {}", cause, e);
+ return;
+ }
+ log.info("MasterServer stopped, current cause: {}", cause);
+ }
+
+ @Override
+ public void stop(String cause) {
+ close(cause);
+
+ // make sure exit after server closed, don't call System.exit in close logic, will cause deadlock if close
+ // multiple times at the same time
+ System.exit(1);
+ }
}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/config/ConsoleConfig.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/config/ConsoleConfig.java
new file mode 100644
index 000000000..6f17c038c
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/config/ConsoleConfig.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.core.config;
+
+import org.apache.streampark.common.utils.NetworkUtils;
+import org.apache.streampark.registry.api.ConnectStrategyProperties;
+import org.apache.streampark.registry.api.enums.RegistryNodeType;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.Errors;
+import org.springframework.validation.Validator;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.Duration;
+
+@Getter
+@Setter
+@Validated
+@Configuration
+@ConfigurationProperties(prefix = "server")
+@Slf4j
+public class ConsoleConfig implements Validator {
+
+ /**
+ * The console server listen port.
+ */
+ @Value("${server.port:10000}")
+ private int listenPort = 10000;
+
+ /**
+ * Console heart beat task execute interval.
+ */
+ @Value("${streampark.max-heartbeat-interval:10}")
+ private Duration maxHeartbeatInterval = Duration.ofSeconds(10);
+
+ private ConnectStrategyProperties registryDisconnectStrategy = new ConnectStrategyProperties();
+
+ /**
+ * The IP address and listening port of the console server in the format 'ip:listenPort'.
+ */
+ private String consoleAddress;
+
+ /**
+ * The registry path for the console server in the format '/nodes/console/ip:listenPort'.
+ */
+ private String consoleRegistryPath;
+
+ @Override
+ public boolean supports(Class<?> clazz) {
+ return ConsoleConfig.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ public void validate(Object target, Errors errors) {
+ ConsoleConfig consoleConfig = (ConsoleConfig) target;
+ if (consoleConfig.getListenPort() <= 0) {
+ errors.rejectValue("listen-port", null, "is invalidated");
+ }
+ if (consoleConfig.getMaxHeartbeatInterval().toMillis() < 0) {
+ errors.rejectValue("max-heartbeat-interval", null, "should be a valid duration");
+ }
+ if (StringUtils.isEmpty(consoleConfig.getConsoleAddress())) {
+ consoleConfig.setConsoleAddress(NetworkUtils.getAddr(consoleConfig.getListenPort()));
+ }
+
+ consoleConfig.setConsoleRegistryPath(
+ RegistryNodeType.CONSOLE_SERVER.getRegistryPath() + "/" + consoleConfig.getConsoleAddress());
+
+ printConfig();
+ }
+
+ private void printConfig() {
+ String config =
+ "\n****************************Console Configuration**************************************" +
+ "\n listen-port -> " + listenPort +
+ "\n registry-disconnect-strategy -> " + registryDisconnectStrategy +
+ "\n console-address -> " + consoleAddress +
+ "\n console-registry-path: " + consoleRegistryPath +
+ "\n****************************Master Configuration**************************************";
+ log.info(config);
+ }
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleConnectStrategy.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleConnectStrategy.java
new file mode 100644
index 000000000..97bb3aa6b
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleConnectStrategy.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.core.registry;
+
+import org.apache.streampark.registry.api.ConnectStrategy;
+
+public interface ConsoleConnectStrategy extends ConnectStrategy {
+
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleConnectionStateListener.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleConnectionStateListener.java
new file mode 100644
index 000000000..0e44e1c50
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleConnectionStateListener.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.core.registry;
+
+import org.apache.streampark.registry.api.ConnectionListener;
+import org.apache.streampark.registry.api.ConnectionState;
+import org.apache.streampark.registry.api.lifecycle.ServerLifeCycleManager;
+
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ConsoleConnectionStateListener implements ConnectionListener {
+
+ private final ConsoleConnectStrategy consoleConnectStrategy;
+
+ public ConsoleConnectionStateListener(@NonNull ConsoleConnectStrategy consoleConnectStrategy) {
+ this.consoleConnectStrategy = consoleConnectStrategy;
+ }
+
+ @Override
+ public void onUpdate(ConnectionState state) {
+ log.info("Master received a {} event from registry, the current server state is {}", state,
+ ServerLifeCycleManager.getServerLifeCycle());
+ switch (state) {
+ case CONNECTED:
+ break;
+ case SUSPENDED:
+ break;
+ case RECONNECTED:
+ consoleConnectStrategy.reconnect();
+ break;
+ case DISCONNECTED:
+ consoleConnectStrategy.disconnect();
+ break;
+ default:
+ }
+ }
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleRegistryClient.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleRegistryClient.java
new file mode 100644
index 000000000..b9c70be51
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleRegistryClient.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.core.registry;
+
+import org.apache.streampark.common.IStoppable;
+import org.apache.streampark.common.utils.JSONUtils;
+import org.apache.streampark.common.utils.NetworkUtils;
+import org.apache.streampark.console.core.config.ConsoleConfig;
+import org.apache.streampark.console.core.service.DistributedTaskService;
+import org.apache.streampark.console.core.task.ConsoleHeartBeatTask;
+import org.apache.streampark.registry.api.RegistryClient;
+import org.apache.streampark.registry.api.RegistryException;
+import org.apache.streampark.registry.api.enums.RegistryNodeType;
+import org.apache.streampark.registry.api.enums.ServerStatusEnum;
+import org.apache.streampark.registry.api.model.ConsoleHeartBeat;
+import org.apache.streampark.registry.api.thread.ThreadUtils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Set;
+
+import static org.apache.streampark.common.constants.Constants.SLEEP_TIME_MILLIS;
+
+/**
+ * <p>streampark console register client, used to connect to registry and hand the registry events.
+ * <p>When the console node startup, it will register in registry center. And start a {@link ConsoleHeartBeatTask} to update its metadata in registry.
+ */
+@Component
+@Slf4j
+public class ConsoleRegistryClient implements AutoCloseable {
+
+ @Autowired
+ private RegistryClient registryClient;
+
+ @Autowired
+ private ConsoleConfig consoleConfig;
+
+ @Autowired
+ private ConsoleConnectStrategy consoleConnectStrategy;
+
+ @Autowired
+ private DistributedTaskService distributedTaskService;
+
+ private ConsoleHeartBeatTask consoleHeartBeatTask;
+
+ public void start() {
+ try {
+ log.info("consoleConfig: {}", consoleConfig);
+ this.consoleHeartBeatTask = new ConsoleHeartBeatTask(consoleConfig, registryClient);
+ // console registry
+ registry();
+ registryClient.addConnectionStateListener(new ConsoleConnectionStateListener(consoleConnectStrategy));
+ registryClient.subscribe(RegistryNodeType.ALL_SERVERS.getRegistryPath(), new ConsoleRegistryDataListener());
+ } catch (Exception e) {
+ throw new RegistryException("Console registry client start up error", e);
+ }
+ }
+
+ public void setRegistryStoppable(IStoppable stoppable) {
+ registryClient.setStoppable(stoppable);
+ }
+
+ @Override
+ public void close() {
+ // TODO unsubscribe ConsoleRegistryDataListener
+ deregister();
+ }
+
+ /**
+ * add console node path
+ *
+ * @param path node path
+ * @param nodeType node type
+ */
+ public void addConsoleNodePath(String path, RegistryNodeType nodeType) {
+ log.info("{} node added : {}", nodeType, path);
+
+ if (StringUtils.isEmpty(path)) {
+ log.error("server start error: empty path: {}, nodeType:{}", path, nodeType);
+ return;
+ }
+
+ String serverHost = registryClient.getHostByEventDataPath(path);
+ if (StringUtils.isEmpty(serverHost)) {
+ log.error("server start error: unknown path: {}, nodeType:{}", path, nodeType);
+ return;
+ }
+
+ try {
+ if (!registryClient.exists(path)) {
+ log.info("path: {} not exists", path);
+ }
+ distributedTaskService.addServer(serverHost);
+ } catch (Exception e) {
+ log.error("{} server failover failed, host:{}", nodeType, serverHost, e);
+ }
+ }
+
+ /**
+ * remove console node path
+ *
+ * @param path node path
+ * @param nodeType node type
+ * @param failover is failover
+ */
+ public void removeConsoleNodePath(String path, RegistryNodeType nodeType, boolean failover) {
+ log.info("{} node deleted : {}", nodeType, path);
+
+ if (StringUtils.isEmpty(path)) {
+ log.error("server down error: empty path: {}, nodeType:{}", path, nodeType);
+ return;
+ }
+
+ String serverHost = registryClient.getHostByEventDataPath(path);
+ if (StringUtils.isEmpty(serverHost)) {
+ log.error("server down error: unknown path: {}, nodeType:{}", path, nodeType);
+ return;
+ }
+
+ try {
+ if (!registryClient.exists(path)) {
+ log.info("path: {} not exists", path);
+ }
+ // todo: add failover logic here
+ } catch (Exception e) {
+ log.error("{} server failover failed, host:{}", nodeType, serverHost, e);
+ }
+ }
+
+ /**
+ * Registry the current console server itself to registry.
+ */
+ void registry() {
+ log.info("Console node : {} registering to registry center", consoleConfig.getConsoleAddress());
+ String consoleRegistryPath = consoleConfig.getConsoleRegistryPath();
+
+ ConsoleHeartBeat heartBeat = consoleHeartBeatTask.getHeartBeat();
+ while (ServerStatusEnum.BUSY.equals(heartBeat.getServerStatusEnum())) {
+ log.warn("Console node is BUSY: {}", heartBeat);
+ ThreadUtils.sleep(SLEEP_TIME_MILLIS);
+ heartBeat = consoleHeartBeatTask.getHeartBeat();
+ }
+
+ // remove before persist
+ registryClient.remove(consoleRegistryPath);
+ registryClient.persistEphemeral(consoleRegistryPath,
+ JSONUtils.toJsonString(consoleHeartBeatTask.getHeartBeat()));
+
+ while (!registryClient.checkNodeExists(NetworkUtils.getHost(), RegistryNodeType.CONSOLE_SERVER)) {
+ log.warn("The current console server node:{} cannot find in registry", NetworkUtils.getHost());
+ ThreadUtils.sleep(SLEEP_TIME_MILLIS);
+ }
+
+ // sleep 1s, waiting console failover remove
+ ThreadUtils.sleep(SLEEP_TIME_MILLIS);
+
+ consoleHeartBeatTask.start();
+ Set<String> allServers = getServerNodes(RegistryNodeType.CONSOLE_SERVER);
+ distributedTaskService.init(allServers, consoleConfig.getConsoleAddress());
+ log.info("Console node : {} registered to registry center successfully", consoleConfig.getConsoleAddress());
+
+ }
+
+ public void deregister() {
+ try {
+ registryClient.remove(consoleConfig.getConsoleRegistryPath());
+ log.info("Console node : {} unRegistry to register center.", consoleConfig.getConsoleAddress());
+ if (consoleHeartBeatTask != null) {
+ consoleHeartBeatTask.shutdown();
+ }
+ registryClient.close();
+ } catch (Exception e) {
+ log.error("ConsoleServer remove registry path exception ", e);
+ }
+ }
+
+ public boolean isAvailable() {
+ return registryClient.isConnected();
+ }
+
+ public Set<String> getServerNodes(RegistryNodeType nodeType) {
+ return registryClient.getServerNodeSet(nodeType);
+ }
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleRegistryDataListener.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleRegistryDataListener.java
new file mode 100644
index 000000000..cb40aa072
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleRegistryDataListener.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.core.registry;
+
+import org.apache.streampark.common.constants.Constants;
+import org.apache.streampark.console.base.util.SpringContextUtils;
+import org.apache.streampark.registry.api.Event;
+import org.apache.streampark.registry.api.SubscribeListener;
+import org.apache.streampark.registry.api.enums.RegistryNodeType;
+
+import com.google.common.base.Strings;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ConsoleRegistryDataListener implements SubscribeListener {
+
+ private final ConsoleRegistryClient consoleRegistryClient;
+
+ public ConsoleRegistryDataListener() {
+ consoleRegistryClient = SpringContextUtils.getBean(ConsoleRegistryClient.class);
+ }
+
+ @Override
+ public void notify(Event event) {
+ final String path = event.path();
+ if (Strings.isNullOrEmpty(path)) {
+ return;
+ }
+ // monitor console
+ if (path.startsWith(RegistryNodeType.CONSOLE_SERVER.getRegistryPath() + Constants.SINGLE_SLASH)) {
+ handleConsoleEvent(event);
+ }
+ }
+
+ private void handleConsoleEvent(Event event) {
+ final String path = event.path();
+ switch (event.type()) {
+ case ADD:
+ log.info("console node added : {}", path);
+ consoleRegistryClient.addConsoleNodePath(path, RegistryNodeType.CONSOLE_SERVER);
+ break;
+ case REMOVE:
+ log.info("console node deleted : {}", path);
+ consoleRegistryClient.removeConsoleNodePath(path, RegistryNodeType.CONSOLE_SERVER, true);
+ break;
+ default:
+ break;
+ }
+ }
+
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleStopStrategy.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleStopStrategy.java
new file mode 100644
index 000000000..60034dd43
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/registry/ConsoleStopStrategy.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.core.registry;
+
+import org.apache.streampark.registry.api.RegistryClient;
+import org.apache.streampark.registry.api.StrategyType;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * This strategy will stop the console server, when disconnected from {@link org.apache.streampark.registry.api.Registry}.
+ */
+@Service
+@Slf4j
+public class ConsoleStopStrategy implements ConsoleConnectStrategy {
+
+ @Autowired
+ private RegistryClient registryClient;
+
+ @Override
+ public void disconnect() {
+ registryClient.getStoppable()
+ .stop("Console disconnected from registry, will stop myself due to the stop strategy");
+ }
+
+ @Override
+ public void reconnect() {
+ log.warn("The current connect strategy is stop, so the console will not reconnect to registry");
+ }
+
+ @Override
+ public StrategyType getStrategyType() {
+ return StrategyType.STOP;
+ }
+}
diff --git a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/task/ConsoleHeartBeatTask.java b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/task/ConsoleHeartBeatTask.java
new file mode 100644
index 000000000..1fc25a18a
--- /dev/null
+++ b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/task/ConsoleHeartBeatTask.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.streampark.console.core.task;
+
+import org.apache.streampark.common.utils.JSONUtils;
+import org.apache.streampark.common.utils.NetworkUtils;
+import org.apache.streampark.common.utils.OSUtils;
+import org.apache.streampark.console.core.config.ConsoleConfig;
+import org.apache.streampark.registry.api.RegistryClient;
+import org.apache.streampark.registry.api.enums.ServerStatusEnum;
+import org.apache.streampark.registry.api.lifecycle.ServerLifeCycleManager;
+import org.apache.streampark.registry.api.model.BaseHeartBeatTask;
+import org.apache.streampark.registry.api.model.ConsoleHeartBeat;
+
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ConsoleHeartBeatTask extends BaseHeartBeatTask<ConsoleHeartBeat> {
+
+ private final ConsoleConfig consoleConfig;
+
+ private final RegistryClient registryClient;
+
+ private final String heartBeatPath;
+
+ private final int processId;
+
+ public ConsoleHeartBeatTask(@NonNull ConsoleConfig consoleConfig,
+ @NonNull RegistryClient registryClient) {
+ super("ConsoleHeartBeatTask", consoleConfig.getMaxHeartbeatInterval().toMillis());
+ this.consoleConfig = consoleConfig;
+ this.registryClient = registryClient;
+ this.heartBeatPath = consoleConfig.getConsoleRegistryPath();
+ this.processId = OSUtils.getProcessID();
+ }
+
+ @Override
+ public ConsoleHeartBeat getHeartBeat() {
+ return ConsoleHeartBeat.builder()
+ .startupTime(ServerLifeCycleManager.getServerStartupTime())
+ .reportTime(System.currentTimeMillis())
+ .processId(processId)
+ .serverStatusEnum(ServerStatusEnum.NORMAL)
+ .host(NetworkUtils.getHost())
+ .port(consoleConfig.getListenPort())
+ .build();
+ }
+
+ @Override
+ public void writeHeartBeat(ConsoleHeartBeat consoleHeartBeat) {
+ String masterHeartBeatJson = JSONUtils.toJsonString(consoleHeartBeat);
+ registryClient.persistEphemeral(heartBeatPath, masterHeartBeatJson);
+ log.debug("Success write master heartBeatInfo into registry, masterRegistryPath: {}, heartBeatInfo: {}",
+ heartBeatPath, masterHeartBeatJson);
+ }
+}
diff --git a/streampark-console/streampark-console-service/src/main/resources/db/schema-h2.sql b/streampark-console/streampark-console-service/src/main/resources/db/schema-h2.sql
index cc44fe197..623b9a139 100644
--- a/streampark-console/streampark-console-service/src/main/resources/db/schema-h2.sql
+++ b/streampark-console/streampark-console-service/src/main/resources/db/schema-h2.sql
@@ -670,3 +670,58 @@ create table if not exists `t_spark_app_backup` (
`modify_time` datetime default null comment 'modify time',
primary key(`id`)
);
+
+-- ----------------------------
+-- Table structure for jdbc registry
+-- ----------------------------
+DROP TABLE IF EXISTS `t_jdbc_registry_data`;
+CREATE TABLE `t_jdbc_registry_data`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `data_key` varchar(256) NOT NULL COMMENT 'key, like zookeeper node path',
+ `data_value` text NOT NULL COMMENT 'data, like zookeeper node value',
+ `data_type` varchar(64) NOT NULL COMMENT 'EPHEMERAL, PERSISTENT',
+ `client_id` bigint NOT NULL COMMENT 'client id',
+ `create_time` timestamp NOT NULL default current_timestamp COMMENT 'create time',
+ `last_update_time` timestamp NOT NULL default current_timestamp COMMENT 'last update time',
+ PRIMARY KEY (`id`),
+ unique KEY `uk_t_jdbc_registry_dataKey`(`data_key`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+
+DROP TABLE IF EXISTS `t_jdbc_registry_lock`;
+CREATE TABLE `t_jdbc_registry_lock`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `lock_key` varchar(256) NOT NULL COMMENT 'lock path',
+ `lock_owner` varchar(256) NOT NULL COMMENT 'the lock owner, ip_processId',
+ `client_id` bigint NOT NULL COMMENT 'client id',
+ `create_time` timestamp NOT NULL default current_timestamp COMMENT 'create time',
+ PRIMARY KEY (`id`),
+ unique KEY `uk_t_jdbc_registry_lockKey`(`lock_key`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `t_jdbc_registry_client_heartbeat`;
+CREATE TABLE `t_jdbc_registry_client_heartbeat`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `client_name` varchar(256) NOT NULL COMMENT 'client name, ip_processId',
+ `last_heartbeat_time` bigint NOT NULL COMMENT 'last heartbeat timestamp',
+ `connection_config` text NOT NULL COMMENT 'connection config',
+ `create_time` timestamp NOT NULL default current_timestamp COMMENT 'create time',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
+
+DROP TABLE IF EXISTS `t_jdbc_registry_data_change_event`;
+CREATE TABLE `t_jdbc_registry_data_change_event`
+(
+ `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
+ `event_type` varchar(64) NOT NULL COMMENT 'ADD, UPDATE, DELETE',
+ `jdbc_registry_data` text NOT NULL COMMENT 'jdbc registry data',
+ `create_time` timestamp NOT NULL default current_timestamp COMMENT 'create time',
+ PRIMARY KEY (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8;
diff --git a/streampark-console/streampark-console-service/src/test/java/org/apache/streampark/console/core/service/DatabaseServiceTest.java b/streampark-console/streampark-console-service/src/test/java/org/apache/streampark/console/core/service/DatabaseServiceTest.java
index d86f2f8d7..16bcb3a9d 100644
--- a/streampark-console/streampark-console-service/src/test/java/org/apache/streampark/console/core/service/DatabaseServiceTest.java
+++ b/streampark-console/streampark-console-service/src/test/java/org/apache/streampark/console/core/service/DatabaseServiceTest.java
@@ -26,6 +26,7 @@ import org.apache.streampark.console.core.service.container.MysqlBaseITCASE;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@@ -79,7 +80,7 @@ public class DatabaseServiceTest extends MysqlBaseITCASE {
assertTrue(databaseService.databaseExists(dbParam));
}
- // @Test
+ @Test
public void testCreateDatabase_Negative() {
DatabaseParam dbParam = new DatabaseParam();
dbParam.setCatalogId(flinkCatalog.getId());
@@ -101,7 +102,7 @@ public class DatabaseServiceTest extends MysqlBaseITCASE {
assertNotNull(databaseParamList);
}
- // @Test
+ @Test
public void testListDatabases_Negative_NoDatabases() {
Exception exception =
@@ -115,7 +116,7 @@ public class DatabaseServiceTest extends MysqlBaseITCASE {
"The catalog can't be null. get catalog from database failed.", exception.getMessage());
}
- // @Test
+ @Test
public void testDropDatabase_Positive() {
DatabaseParam dbParam = new DatabaseParam();
dbParam.setCatalogId(flinkCatalog.getId());
@@ -132,7 +133,7 @@ public class DatabaseServiceTest extends MysqlBaseITCASE {
assertEquals(null, exception.getMessage());
}
- // @Test
+ @Test
public void testDropDatabase_Negative_NullDatabaseName() {
DatabaseParam dbParam = new DatabaseParam();
dbParam.setCatalogId(1L);
diff --git a/streampark-console/streampark-console-service/src/test/java/org/apache/streampark/console/core/service/TableServiceTest.java b/streampark-console/streampark-console-service/src/test/java/org/apache/streampark/console/core/service/TableServiceTest.java
index 38bfeb3d4..76c435f16 100644
--- a/streampark-console/streampark-console-service/src/test/java/org/apache/streampark/console/core/service/TableServiceTest.java
+++ b/streampark-console/streampark-console-service/src/test/java/org/apache/streampark/console/core/service/TableServiceTest.java
@@ -29,6 +29,7 @@ import org.apache.streampark.console.core.service.container.MysqlBaseITCASE;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
@@ -77,7 +78,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
catalogService.remove(flinkCatalog.getId());
}
- // @Test
+ @Test
@Order(1)
public void testTableExists_Positive() {
TableParams tableParams = new TableParams();
@@ -88,7 +89,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertTrue(tableService.tableExists(tableParams));
}
- // @Test
+ @Test
@Order(2)
public void testTableExists_Negative_CatalogNotFound() {
TableParams tableParams = new TableParams();
@@ -101,7 +102,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals("Catalog is not exit.", exception.getMessage());
}
- // @Test
+ @Test
@Order(3)
public void testCreateTable_Positive() {
TableParams tableParams = new TableParams();
@@ -126,7 +127,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals(null, exception.getMessage());
}
- // @Test
+ @Test
@Order(4)
public void testCreateTable_Negative_TableNameNull() {
TableParams tableParams = new TableParams();
@@ -144,7 +145,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals("Table name can not be null.", exception.getMessage());
}
- // @Test
+ @Test
@Order(5)
public void testAddColumn_Positive() {
TableParams tableParams = new TableParams();
@@ -163,7 +164,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals(null, exception.getMessage());
}
- // @Test
+ @Test
@Order(6)
public void testAddColumn_Negative_TableNameNull() {
TableParams tableParams = new TableParams();
@@ -181,7 +182,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals("Table name can not be null.", exception.getMessage());
}
- // @Test
+ @Test
@Order(7)
public void testDropColumn() {
String catalogName = flinkCatalog.getCatalogName();
@@ -195,7 +196,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals(null, exception.getMessage());
}
- // @Test
+ @Test
@Order(8)
public void testDropColumn_Negative_TableNameNull() {
String catalogName = "flink-test";
@@ -209,7 +210,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals("Table name can not be null.", exception.getMessage());
}
- // @Test
+ @Test
@Order(9)
public void testAddOption() {
TableParams tableParams = new TableParams();
@@ -226,7 +227,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
exception.getMessage());
}
- // @Test
+ @Test
@Order(10)
public void testAddOption_Negative_OptionsNull() {
TableParams tableParams = new TableParams();
@@ -239,7 +240,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals("Table options can not be null.", exception.getMessage());
}
- // @Test
+ @Test
@Order(12)
public void testDropTable_Negative_TableNameNull() {
String catalogName = "test_catalog";
@@ -251,7 +252,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals("Table name can not be null.", exception.getMessage());
}
- // @Test
+ @Test
@Order(13)
public void testRenameTable() {
String catalogName = flinkCatalog.getCatalogName();
@@ -263,7 +264,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals(null, exception.getMessage());
}
- // @Test
+ @Test
@Order(14)
public void testRenameTable_Negative_FromTableNameNull() {
String catalogName = "test_catalog";
@@ -277,7 +278,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals("From table name can not be null.", exception.getMessage());
}
- // @Test
+ @Test
@Order(15)
public void testListTables_Positive() {
TableParams tableParams = new TableParams();
@@ -291,7 +292,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals(1, result.size());
}
- // @Test
+ @Test
@Order(16)
public void testListTables_Negative_DatabaseNameNull() {
TableParams tableParams = new TableParams();
@@ -304,7 +305,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertEquals("Database name can not be null.", exception.getMessage());
}
- // @Test
+ @Test
@Order(17)
public void testListColumns_Positive() {
String catalogName = flinkCatalog.getCatalogName();
@@ -313,7 +314,7 @@ public class TableServiceTest extends MysqlBaseITCASE {
assertNotNull(result);
}
- // @Test
+ @Test
@Order(17)
public void testListColumns_Negative_DatabaseNameNull() {
String catalogName = "test_catalog";
diff --git a/tools/dependencies/known-dependencies.txt b/tools/dependencies/known-dependencies.txt
index e685de5ad..75858965c 100644
--- a/tools/dependencies/known-dependencies.txt
+++ b/tools/dependencies/known-dependencies.txt
@@ -35,6 +35,8 @@ caffeine-2.8.6.jar
checker-qual-3.5.0.jar
chill-java-0.9.5.jar
chill_2.12-0.9.5.jar
+classmate-1.5.1.jar
+commons-collections4-4.3.jar
commons-beanutils-1.9.3.jar
commons-cli-1.5.0.jar
commons-codec-1.15.jar
@@ -155,7 +157,8 @@ jersey-server-2.35.jar
jetty-servlet-9.4.51.v20230217.jar
jetty-util-9.4.51.v20230217.jar
jetty-util-ajax-9.4.51.v20230217.jar
-jna-5.13.0.jar
+jna-5.10.0.jar
+jna-platform-5.10.0.jar
jsr305-1.3.9.jar
jsch-0.2.11.jar
json-smart-2.4.10.jar
@@ -379,3 +382,8 @@ xnio-nio-3.8.7.Final.jar
xz-1.5.jar
zookeeper-3.4.14.jar
icu4j-67.1.jar
+hibernate-validator-6.2.2.Final.jar
+javax.annotation-api-1.3.2.jar
+oshi-core-6.1.1.jar
+spring-boot-starter-logging-2.7.11.jar
+spring-boot-starter-validation-2.7.11.jar
\ No newline at end of file