This is an automated email from the ASF dual-hosted git repository.

terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 8b8b88d6f93 Refactor coordinator registry center creation with SPI 
(#39771)
8b8b88d6f93 is described below

commit 8b8b88d6f932f7d311a3f477c4d08fdda61a7789
Author: Liang Zhang <[email protected]>
AuthorDate: Wed Sep 9 00:55:49 2026 +0800

    Refactor coordinator registry center creation with SPI (#39771)
    
    * Simplify altered encrypt rule cleanup
    
    * Refactor coordinator registry center creation with SPI
---
 kernel/data-pipeline/core/pom.xml                  |   5 +-
 .../pipeline/core/job/api/PipelineAPIFactory.java  |  29 +++-
 .../PipelineContextManagerLifecycleListener.java   |  16 +-
 .../core/job/api/PipelineAPIFactoryTest.java       | 172 +++++++++++++++++++
 ...ipelineContextManagerLifecycleListenerTest.java | 191 +++++++++++++++++++++
 .../collect/StatisticsCollectJobWorker.java        |  84 ++++-----
 .../spi/CoordinatorRegistryCenterProvider.java     |  43 +++++
 .../collect/StatisticsCollectJobWorkerTest.java    | 120 +++++++++----
 .../cluster/repository/provider/zookeeper/pom.xml  |  10 ++
 ...ZookeeperCoordinatorRegistryCenterProvider.java |  52 +++---
 ....schedule.spi.CoordinatorRegistryCenterProvider |  18 ++
 ...eeperCoordinatorRegistryCenterProviderTest.java | 131 ++++++++++++++
 pom.xml                                            |   5 +
 13 files changed, 760 insertions(+), 116 deletions(-)

diff --git a/kernel/data-pipeline/core/pom.xml 
b/kernel/data-pipeline/core/pom.xml
index ce1149c12dd..3c343a381e4 100644
--- a/kernel/data-pipeline/core/pom.xml
+++ b/kernel/data-pipeline/core/pom.xml
@@ -34,13 +34,12 @@
         </dependency>
         <dependency>
             <groupId>org.apache.shardingsphere</groupId>
-            
<artifactId>shardingsphere-standalone-mode-repository-memory</artifactId>
+            <artifactId>shardingsphere-schedule-core</artifactId>
             <version>${project.version}</version>
         </dependency>
-        <!-- TODO remove cluster mode dependency after 3 modes have equivalent 
features -->
         <dependency>
             <groupId>org.apache.shardingsphere</groupId>
-            
<artifactId>shardingsphere-cluster-mode-repository-zookeeper</artifactId>
+            
<artifactId>shardingsphere-standalone-mode-repository-memory</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
diff --git 
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/job/api/PipelineAPIFactory.java
 
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/job/api/PipelineAPIFactory.java
index 47d1659dee2..19b5999e52d 100644
--- 
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/job/api/PipelineAPIFactory.java
+++ 
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/job/api/PipelineAPIFactory.java
@@ -25,7 +25,6 @@ import org.apache.commons.lang3.concurrent.LazyInitializer;
 import org.apache.shardingsphere.data.pipeline.core.context.PipelineContextKey;
 import 
org.apache.shardingsphere.data.pipeline.core.context.PipelineContextManager;
 import 
org.apache.shardingsphere.data.pipeline.core.metadata.node.PipelineMetaDataNode;
-import 
org.apache.shardingsphere.data.pipeline.core.registrycenter.elasticjob.CoordinatorRegistryCenterInitializer;
 import 
org.apache.shardingsphere.data.pipeline.core.registrycenter.repository.PipelineGovernanceFacade;
 import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.api.JobConfigurationAPI;
 import org.apache.shardingsphere.elasticjob.lite.lifecycle.api.JobOperateAPI;
@@ -36,9 +35,10 @@ import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.settings.Job
 import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.statistics.JobStatisticsAPIImpl;
 import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.statistics.ShardingStatisticsAPIImpl;
 import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
-import org.apache.shardingsphere.infra.config.mode.ModeConfiguration;
-import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepository;
+import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepositoryConfiguration;
+import 
org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider;
 
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -118,6 +118,20 @@ public final class PipelineAPIFactory {
         return RegistryCenterHolder.getInstance(contextKey).registryCenter;
     }
     
+    /**
+     * Close pipeline APIs.
+     *
+     * @param contextKey context key
+     */
+    public static void close(final PipelineContextKey contextKey) {
+        ElasticJobAPIHolder.INSTANCE_MAP.remove(contextKey);
+        GOVERNANCE_FACADE_MAP.remove(contextKey);
+        RegistryCenterHolder registryCenterHolder = 
RegistryCenterHolder.INSTANCE_MAP.remove(contextKey);
+        if (null != registryCenterHolder) {
+            registryCenterHolder.registryCenter.close();
+        }
+    }
+    
     private static final class ElasticJobAPIHolder {
         
         private static final Map<PipelineContextKey, ElasticJobAPIHolder> 
INSTANCE_MAP = new ConcurrentHashMap<>();
@@ -154,12 +168,9 @@ public final class PipelineAPIFactory {
         }
         
         private CoordinatorRegistryCenter createRegistryCenter(final 
PipelineContextKey contextKey) {
-            CoordinatorRegistryCenterInitializer registryCenterInitializer = 
new CoordinatorRegistryCenterInitializer();
-            ModeConfiguration modeConfig = 
PipelineContextManager.getContext(contextKey).getComputeNodeInstanceContext().getModeConfiguration();
-            String elasticJobNamespace = 
PipelineMetaDataNode.getElasticJobNamespace();
-            String clusterType = modeConfig.getRepository().getType();
-            
ShardingSpherePreconditions.checkState("ZooKeeper".equals(clusterType), () -> 
new IllegalArgumentException("Unsupported cluster type: " + clusterType));
-            return 
registryCenterInitializer.createZookeeperRegistryCenter(modeConfig, 
elasticJobNamespace);
+            ClusterPersistRepositoryConfiguration repositoryConfig = 
(ClusterPersistRepositoryConfiguration) 
PipelineContextManager.getContext(contextKey)
+                    
.getComputeNodeInstanceContext().getModeConfiguration().getRepository();
+            return 
TypedSPILoader.getService(CoordinatorRegistryCenterProvider.class, 
repositoryConfig.getType()).create(repositoryConfig, 
PipelineMetaDataNode.getElasticJobNamespace());
         }
         
         public static RegistryCenterHolder getInstance(final 
PipelineContextKey contextKey) {
diff --git 
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/listener/PipelineContextManagerLifecycleListener.java
 
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/listener/PipelineContextManagerLifecycleListener.java
index 52c7963b453..0a40a189441 100644
--- 
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/listener/PipelineContextManagerLifecycleListener.java
+++ 
b/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/listener/PipelineContextManagerLifecycleListener.java
@@ -32,9 +32,11 @@ import 
org.apache.shardingsphere.elasticjob.infra.pojo.JobConfigurationPOJO;
 import org.apache.shardingsphere.elasticjob.infra.spi.ElasticJobServiceLoader;
 import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.api.JobConfigurationAPI;
 import org.apache.shardingsphere.elasticjob.lite.lifecycle.domain.JobBriefInfo;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.mode.manager.ContextManager;
 import 
org.apache.shardingsphere.mode.manager.listener.ContextManagerLifecycleListener;
 import 
org.apache.shardingsphere.mode.manager.listener.ContextManagerLifecycleListenerModeRequired;
+import 
org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider;
 
 import java.util.List;
 import java.util.stream.Collectors;
@@ -54,6 +56,11 @@ public final class PipelineContextManagerLifecycleListener 
implements ContextMan
         }
         PipelineContextKey contextKey = new 
PipelineContextKey(preSelectedDatabaseName, 
contextManager.getComputeNodeInstanceContext().getInstance().getMetaData().getType());
         PipelineContextManager.putContext(contextKey, contextManager);
+        String repositoryType = 
contextManager.getComputeNodeInstanceContext().getModeConfiguration().getRepository().getType();
+        if 
(!TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
repositoryType).isPresent()) {
+            log.warn("Can not resume pipeline jobs because coordinator 
registry center provider is unavailable for repository type: {}", 
repositoryType);
+            return;
+        }
         PipelineMetaDataNodeWatcher.init(contextKey);
         ElasticJobServiceLoader.registerTypedService(ElasticJobListener.class);
         try {
@@ -98,7 +105,12 @@ public final class PipelineContextManagerLifecycleListener 
implements ContextMan
     
     @Override
     public void onDestroyed(final ContextManager contextManager) {
-        PipelineContextManager.removeContext(
-                new 
PipelineContextKey(contextManager.getPreSelectedDatabaseName(), 
contextManager.getComputeNodeInstanceContext().getInstance().getMetaData().getType()));
+        PipelineContextKey contextKey = new PipelineContextKey(
+                contextManager.getPreSelectedDatabaseName(), 
contextManager.getComputeNodeInstanceContext().getInstance().getMetaData().getType());
+        try {
+            PipelineAPIFactory.close(contextKey);
+        } finally {
+            PipelineContextManager.removeContext(contextKey);
+        }
     }
 }
diff --git 
a/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/job/api/PipelineAPIFactoryTest.java
 
b/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/job/api/PipelineAPIFactoryTest.java
new file mode 100644
index 00000000000..bdcda1c865c
--- /dev/null
+++ 
b/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/job/api/PipelineAPIFactoryTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.shardingsphere.data.pipeline.core.job.api;
+
+import lombok.SneakyThrows;
+import org.apache.commons.lang3.concurrent.ConcurrentException;
+import org.apache.shardingsphere.data.pipeline.core.context.PipelineContextKey;
+import 
org.apache.shardingsphere.data.pipeline.core.context.PipelineContextManager;
+import 
org.apache.shardingsphere.data.pipeline.core.metadata.node.PipelineMetaDataNode;
+import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
+import org.apache.shardingsphere.infra.config.mode.ModeConfiguration;
+import org.apache.shardingsphere.infra.instance.metadata.InstanceType;
+import 
org.apache.shardingsphere.infra.spi.exception.ServiceProviderNotFoundException;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.mode.manager.ContextManager;
+import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepository;
+import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepositoryConfiguration;
+import 
org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.internal.configuration.plugins.Plugins;
+
+import java.util.Map;
+import java.util.Properties;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class PipelineAPIFactoryTest {
+    
+    @Test
+    void assertGetRegistryCenterViaProvider() {
+        PipelineContextKey contextKey = new PipelineContextKey("foo_db", 
InstanceType.JDBC);
+        CoordinatorRegistryCenter registryCenter = 
mock(CoordinatorRegistryCenter.class);
+        CoordinatorRegistryCenterProvider provider = 
mock(CoordinatorRegistryCenterProvider.class);
+        try (
+                MockedStatic<PipelineContextManager> contextManager = 
mockStatic(PipelineContextManager.class);
+                MockedStatic<TypedSPILoader> serviceLoader = 
mockStatic(TypedSPILoader.class)) {
+            ClusterPersistRepositoryConfiguration repositoryConfig = 
mockContext(contextKey, contextManager);
+            serviceLoader.when(() -> 
TypedSPILoader.getService(CoordinatorRegistryCenterProvider.class, 
repositoryConfig.getType())).thenReturn(provider);
+            when(provider.create(repositoryConfig, 
PipelineMetaDataNode.getElasticJobNamespace())).thenReturn(registryCenter);
+            assertThat(PipelineAPIFactory.getRegistryCenter(contextKey), 
sameInstance(registryCenter));
+            assertThat(PipelineAPIFactory.getRegistryCenter(contextKey), 
sameInstance(registryCenter));
+            verify(provider).create(repositoryConfig, 
PipelineMetaDataNode.getElasticJobNamespace());
+        } finally {
+            PipelineAPIFactory.close(contextKey);
+        }
+        verify(registryCenter).close();
+    }
+    
+    @Test
+    void assertClose() {
+        PipelineContextKey contextKey = new PipelineContextKey("bar_db", 
InstanceType.JDBC);
+        CoordinatorRegistryCenter registryCenter = 
mock(CoordinatorRegistryCenter.class);
+        CoordinatorRegistryCenterProvider provider = 
mock(CoordinatorRegistryCenterProvider.class);
+        try (
+                MockedStatic<PipelineContextManager> contextManager = 
mockStatic(PipelineContextManager.class);
+                MockedStatic<TypedSPILoader> serviceLoader = 
mockStatic(TypedSPILoader.class)) {
+            ClusterPersistRepositoryConfiguration repositoryConfig = 
mockContext(contextKey, contextManager);
+            serviceLoader.when(() -> 
TypedSPILoader.getService(CoordinatorRegistryCenterProvider.class, 
repositoryConfig.getType())).thenReturn(provider);
+            when(provider.create(repositoryConfig, 
PipelineMetaDataNode.getElasticJobNamespace())).thenReturn(registryCenter);
+            PipelineAPIFactory.getJobStatisticsAPI(contextKey);
+            PipelineAPIFactory.getJobConfigurationAPI(contextKey);
+            PipelineAPIFactory.getJobOperateAPI(contextKey);
+            PipelineAPIFactory.getShardingStatisticsAPI(contextKey);
+            PipelineAPIFactory.getPipelineGovernanceFacade(contextKey);
+            assertTrue(getMap(PipelineAPIFactory.class, 
"GOVERNANCE_FACADE_MAP").containsKey(contextKey));
+            assertTrue(getMap(getHolderClass("ElasticJobAPIHolder"), 
"INSTANCE_MAP").containsKey(contextKey));
+            assertTrue(getMap(getHolderClass("RegistryCenterHolder"), 
"INSTANCE_MAP").containsKey(contextKey));
+            PipelineAPIFactory.close(contextKey);
+            assertFalse(getMap(PipelineAPIFactory.class, 
"GOVERNANCE_FACADE_MAP").containsKey(contextKey));
+            assertFalse(getMap(getHolderClass("ElasticJobAPIHolder"), 
"INSTANCE_MAP").containsKey(contextKey));
+            assertFalse(getMap(getHolderClass("RegistryCenterHolder"), 
"INSTANCE_MAP").containsKey(contextKey));
+            PipelineAPIFactory.close(contextKey);
+        }
+        verify(registryCenter, times(1)).close();
+    }
+    
+    @Test
+    void assertCloseWithoutCreatingResources() {
+        PipelineContextKey contextKey = new PipelineContextKey("empty_db", 
InstanceType.JDBC);
+        try (MockedStatic<TypedSPILoader> serviceLoader = 
mockStatic(TypedSPILoader.class)) {
+            PipelineAPIFactory.close(contextKey);
+            serviceLoader.verifyNoInteractions();
+        }
+        assertFalse(getMap(PipelineAPIFactory.class, 
"GOVERNANCE_FACADE_MAP").containsKey(contextKey));
+        assertFalse(getMap(getHolderClass("ElasticJobAPIHolder"), 
"INSTANCE_MAP").containsKey(contextKey));
+        assertFalse(getMap(getHolderClass("RegistryCenterHolder"), 
"INSTANCE_MAP").containsKey(contextKey));
+    }
+    
+    @Test
+    void assertGetRegistryCenterWithUnsupportedProvider() {
+        PipelineContextKey contextKey = new 
PipelineContextKey("unsupported_db", InstanceType.JDBC);
+        try (MockedStatic<PipelineContextManager> contextManager = 
mockStatic(PipelineContextManager.class)) {
+            ClusterPersistRepositoryConfiguration repositoryConfig = new 
ClusterPersistRepositoryConfiguration("NOT_REGISTERED", "namespace", 
"serverLists", new Properties());
+            ContextManager contextManagerInstance = mock(ContextManager.class, 
RETURNS_DEEP_STUBS);
+            contextManager.when(() -> 
PipelineContextManager.getContext(contextKey)).thenReturn(contextManagerInstance);
+            whenModeConfiguration(contextManagerInstance, repositoryConfig);
+            ServiceProviderNotFoundException actual = 
assertThrows(ServiceProviderNotFoundException.class, () -> 
PipelineAPIFactory.getRegistryCenter(contextKey));
+            assertThat(actual.getMessage(), containsString("NOT_REGISTERED"));
+        } finally {
+            PipelineAPIFactory.close(contextKey);
+        }
+    }
+    
+    @Test
+    void assertGetPipelineGovernanceFacadePropagatesInitializationFailure() {
+        PipelineContextKey contextKey = new PipelineContextKey("error_db", 
InstanceType.JDBC);
+        ConcurrentException expected = new ConcurrentException(new 
Exception("expected"));
+        try (MockedStatic<PipelineContextManager> contextManager = 
mockStatic(PipelineContextManager.class)) {
+            contextManager.when(() -> 
PipelineContextManager.getContext(contextKey)).thenAnswer(invocation -> {
+                throw expected;
+            });
+            assertThat(assertThrows(ConcurrentException.class, () -> 
PipelineAPIFactory.getPipelineGovernanceFacade(contextKey)), 
sameInstance(expected));
+        } finally {
+            PipelineAPIFactory.close(contextKey);
+        }
+    }
+    
+    private ClusterPersistRepositoryConfiguration mockContext(final 
PipelineContextKey contextKey, final MockedStatic<PipelineContextManager> 
pipelineContextManager) {
+        ClusterPersistRepositoryConfiguration result = new 
ClusterPersistRepositoryConfiguration("FIXTURE", "namespace", "serverLists", 
new Properties());
+        ContextManager contextManager = mock(ContextManager.class, 
RETURNS_DEEP_STUBS);
+        pipelineContextManager.when(() -> 
PipelineContextManager.getContext(contextKey)).thenReturn(contextManager);
+        whenModeConfiguration(contextManager, result);
+        whenRepository(contextManager);
+        return result;
+    }
+    
+    private void whenModeConfiguration(final ContextManager contextManager, 
final ClusterPersistRepositoryConfiguration repositoryConfig) {
+        
when(contextManager.getComputeNodeInstanceContext().getModeConfiguration()).thenReturn(new
 ModeConfiguration("Cluster", repositoryConfig));
+    }
+    
+    private void whenRepository(final ContextManager contextManager) {
+        
when(contextManager.getPersistServiceFacade().getRepository()).thenReturn(mock(ClusterPersistRepository.class));
+    }
+    
+    @SneakyThrows(ClassNotFoundException.class)
+    private Class<?> getHolderClass(final String simpleName) {
+        return Class.forName(PipelineAPIFactory.class.getName() + "$" + 
simpleName);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @SneakyThrows(ReflectiveOperationException.class)
+    private Map<PipelineContextKey, ?> getMap(final Class<?> owner, final 
String fieldName) {
+        return (Map<PipelineContextKey, ?>) 
Plugins.getMemberAccessor().get(owner.getDeclaredField(fieldName), owner);
+    }
+}
diff --git 
a/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/listener/PipelineContextManagerLifecycleListenerTest.java
 
b/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/listener/PipelineContextManagerLifecycleListenerTest.java
new file mode 100644
index 00000000000..7cf492c470b
--- /dev/null
+++ 
b/kernel/data-pipeline/core/src/test/java/org/apache/shardingsphere/data/pipeline/core/listener/PipelineContextManagerLifecycleListenerTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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.shardingsphere.data.pipeline.core.listener;
+
+import org.apache.shardingsphere.data.pipeline.core.context.PipelineContextKey;
+import 
org.apache.shardingsphere.data.pipeline.core.context.PipelineContextManager;
+import 
org.apache.shardingsphere.data.pipeline.core.exception.job.PipelineJobNotFoundException;
+import org.apache.shardingsphere.data.pipeline.core.job.api.PipelineAPIFactory;
+import org.apache.shardingsphere.data.pipeline.core.job.id.PipelineJobIdUtils;
+import 
org.apache.shardingsphere.data.pipeline.core.job.service.PipelineJobManager;
+import org.apache.shardingsphere.data.pipeline.core.job.type.PipelineJobType;
+import 
org.apache.shardingsphere.data.pipeline.core.metadata.node.PipelineMetaDataNodeWatcher;
+import org.apache.shardingsphere.database.connector.core.DefaultDatabase;
+import org.apache.shardingsphere.elasticjob.infra.listener.ElasticJobListener;
+import org.apache.shardingsphere.elasticjob.infra.pojo.JobConfigurationPOJO;
+import org.apache.shardingsphere.elasticjob.infra.spi.ElasticJobServiceLoader;
+import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.api.JobConfigurationAPI;
+import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.api.JobStatisticsAPI;
+import org.apache.shardingsphere.elasticjob.lite.lifecycle.domain.JobBriefInfo;
+import org.apache.shardingsphere.infra.instance.metadata.InstanceType;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.mode.manager.ContextManager;
+import 
org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class PipelineContextManagerLifecycleListenerTest {
+    
+    @Test
+    void assertInitializeWithDefaultDatabase() {
+        ContextManager contextManager = mock(ContextManager.class);
+        
when(contextManager.getPreSelectedDatabaseName()).thenReturn(DefaultDatabase.LOGIC_NAME);
+        new 
PipelineContextManagerLifecycleListener().onInitialized(contextManager);
+    }
+    
+    @Test
+    void assertInitializeAndResumeEnabledJobs() {
+        JobConfigurationAPI jobConfigAPI = mock(JobConfigurationAPI.class);
+        JobStatisticsAPI jobStatisticsAPI = mock(JobStatisticsAPI.class);
+        
when(jobStatisticsAPI.getAllJobsBriefInfo()).thenReturn(Arrays.asList(createJobBriefInfo("_internal"),
 createJobBriefInfo("invalid"),
+                createJobBriefInfo("consistency"), 
createJobBriefInfo("missing"), createJobBriefInfo("disabled"), 
createJobBriefInfo("enabled")));
+        PipelineJobType<?> consistencyCheckJobType = 
mock(PipelineJobType.class);
+        
when(consistencyCheckJobType.getType()).thenReturn("CONSISTENCY_CHECK");
+        PipelineJobType<?> jobType = mock(PipelineJobType.class);
+        when(jobType.getType()).thenReturn("FIXTURE");
+        when(jobConfigAPI.getJobConfiguration("missing")).thenThrow(new 
PipelineJobNotFoundException("missing"));
+        JobConfigurationPOJO disabledJobConfig = 
mock(JobConfigurationPOJO.class);
+        when(disabledJobConfig.isDisabled()).thenReturn(true);
+        
when(jobConfigAPI.getJobConfiguration("disabled")).thenReturn(disabledJobConfig);
+        
when(jobConfigAPI.getJobConfiguration("enabled")).thenReturn(mock(JobConfigurationPOJO.class));
+        ContextManager contextManager = mockContextManager();
+        PipelineContextKey contextKey = new PipelineContextKey("foo_db", 
InstanceType.JDBC);
+        try (
+                MockedStatic<PipelineContextManager> pipelineContextManager = 
mockStatic(PipelineContextManager.class);
+                MockedStatic<PipelineMetaDataNodeWatcher> watcher = 
mockStatic(PipelineMetaDataNodeWatcher.class);
+                MockedStatic<ElasticJobServiceLoader> serviceLoader = 
mockStatic(ElasticJobServiceLoader.class);
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<PipelineAPIFactory> pipelineAPIFactory = 
mockStatic(PipelineAPIFactory.class);
+                MockedStatic<PipelineJobIdUtils> jobIdUtils = 
mockStatic(PipelineJobIdUtils.class);
+                MockedConstruction<PipelineJobManager> jobManagerConstruction 
= mockConstruction(PipelineJobManager.class)) {
+            typedSPILoader.when(() -> 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"FIXTURE")).thenReturn(Optional.of(mock(CoordinatorRegistryCenterProvider.class)));
+            pipelineAPIFactory.when(() -> 
PipelineAPIFactory.getJobConfigurationAPI(contextKey)).thenReturn(jobConfigAPI);
+            pipelineAPIFactory.when(() -> 
PipelineAPIFactory.getJobStatisticsAPI(contextKey)).thenReturn(jobStatisticsAPI);
+            jobIdUtils.when(() -> 
PipelineJobIdUtils.parseJobType("invalid")).thenThrow(new 
IllegalArgumentException("expected"));
+            jobIdUtils.when(() -> 
PipelineJobIdUtils.parseJobType("consistency")).thenReturn(consistencyCheckJobType);
+            jobIdUtils.when(() -> 
PipelineJobIdUtils.parseJobType("missing")).thenReturn(jobType);
+            jobIdUtils.when(() -> 
PipelineJobIdUtils.parseJobType("disabled")).thenReturn(jobType);
+            jobIdUtils.when(() -> 
PipelineJobIdUtils.parseJobType("enabled")).thenReturn(jobType);
+            new 
PipelineContextManagerLifecycleListener().onInitialized(contextManager);
+            pipelineContextManager.verify(() -> 
PipelineContextManager.putContext(contextKey, contextManager));
+            watcher.verify(() -> PipelineMetaDataNodeWatcher.init(contextKey));
+            serviceLoader.verify(() -> 
ElasticJobServiceLoader.registerTypedService(ElasticJobListener.class));
+            assertThat(jobManagerConstruction.constructed().size(), is(1));
+            
verify(jobManagerConstruction.constructed().get(0)).resume("enabled");
+        }
+    }
+    
+    @Test
+    void assertInitializeWithoutCoordinatorRegistryCenterProvider() {
+        ContextManager contextManager = mockContextManager();
+        PipelineContextKey contextKey = new PipelineContextKey("foo_db", 
InstanceType.JDBC);
+        try (
+                MockedStatic<PipelineContextManager> pipelineContextManager = 
mockStatic(PipelineContextManager.class);
+                MockedStatic<PipelineMetaDataNodeWatcher> watcher = 
mockStatic(PipelineMetaDataNodeWatcher.class);
+                MockedStatic<ElasticJobServiceLoader> serviceLoader = 
mockStatic(ElasticJobServiceLoader.class);
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<PipelineAPIFactory> pipelineAPIFactory = 
mockStatic(PipelineAPIFactory.class)) {
+            typedSPILoader.when(() -> 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"FIXTURE")).thenReturn(Optional.empty());
+            new 
PipelineContextManagerLifecycleListener().onInitialized(contextManager);
+            pipelineContextManager.verify(() -> 
PipelineContextManager.putContext(contextKey, contextManager));
+            watcher.verifyNoInteractions();
+            serviceLoader.verifyNoInteractions();
+            pipelineAPIFactory.verifyNoInteractions();
+        }
+    }
+    
+    @Test
+    void assertInitializeIgnoresDispatchFailure() {
+        ContextManager contextManager = mockContextManager();
+        PipelineContextKey contextKey = new PipelineContextKey("foo_db", 
InstanceType.JDBC);
+        try (
+                MockedStatic<PipelineContextManager> ignoredContextManager = 
mockStatic(PipelineContextManager.class);
+                MockedStatic<PipelineMetaDataNodeWatcher> ignoredWatcher = 
mockStatic(PipelineMetaDataNodeWatcher.class);
+                MockedStatic<ElasticJobServiceLoader> ignoredServiceLoader = 
mockStatic(ElasticJobServiceLoader.class);
+                MockedStatic<TypedSPILoader> typedSPILoader = 
mockStatic(TypedSPILoader.class);
+                MockedStatic<PipelineAPIFactory> pipelineAPIFactory = 
mockStatic(PipelineAPIFactory.class)) {
+            typedSPILoader.when(() -> 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"FIXTURE")).thenReturn(Optional.of(mock(CoordinatorRegistryCenterProvider.class)));
+            pipelineAPIFactory.when(() -> 
PipelineAPIFactory.getJobConfigurationAPI(contextKey)).thenThrow(new 
RuntimeException("expected"));
+            assertDoesNotThrow(() -> new 
PipelineContextManagerLifecycleListener().onInitialized(contextManager));
+        }
+    }
+    
+    @Test
+    void assertDestroyReleasesPipelineResourcesBeforeRemovingContext() {
+        ContextManager contextManager = mock(ContextManager.class, 
RETURNS_DEEP_STUBS);
+        when(contextManager.getPreSelectedDatabaseName()).thenReturn("foo_db");
+        
when(contextManager.getComputeNodeInstanceContext().getInstance().getMetaData().getType()).thenReturn(InstanceType.JDBC);
+        PipelineContextKey contextKey = new PipelineContextKey("foo_db", 
InstanceType.JDBC);
+        List<String> invocations = new LinkedList<>();
+        try (
+                MockedStatic<PipelineAPIFactory> pipelineAPIFactory = 
mockStatic(PipelineAPIFactory.class);
+                MockedStatic<PipelineContextManager> pipelineContextManager = 
mockStatic(PipelineContextManager.class)) {
+            pipelineAPIFactory.when(() -> 
PipelineAPIFactory.close(contextKey)).thenAnswer(invocation -> 
invocations.add("close"));
+            pipelineContextManager.when(() -> 
PipelineContextManager.removeContext(contextKey)).thenAnswer(invocation -> 
invocations.add("remove"));
+            new 
PipelineContextManagerLifecycleListener().onDestroyed(contextManager);
+        }
+        assertThat(invocations, contains("close", "remove"));
+    }
+    
+    @Test
+    void assertDestroyRemovesContextWhenResourceReleaseFailed() {
+        ContextManager contextManager = mockContextManager();
+        PipelineContextKey contextKey = new PipelineContextKey("foo_db", 
InstanceType.JDBC);
+        RuntimeException expected = new RuntimeException("expected");
+        try (
+                MockedStatic<PipelineAPIFactory> pipelineAPIFactory = 
mockStatic(PipelineAPIFactory.class);
+                MockedStatic<PipelineContextManager> pipelineContextManager = 
mockStatic(PipelineContextManager.class)) {
+            pipelineAPIFactory.when(() -> 
PipelineAPIFactory.close(contextKey)).thenThrow(expected);
+            assertThat(assertThrows(RuntimeException.class, () -> new 
PipelineContextManagerLifecycleListener().onDestroyed(contextManager)), 
sameInstance(expected));
+            pipelineContextManager.verify(() -> 
PipelineContextManager.removeContext(contextKey));
+        }
+    }
+    
+    private ContextManager mockContextManager() {
+        ContextManager result = mock(ContextManager.class, RETURNS_DEEP_STUBS);
+        when(result.getPreSelectedDatabaseName()).thenReturn("foo_db");
+        
when(result.getComputeNodeInstanceContext().getInstance().getMetaData().getType()).thenReturn(InstanceType.JDBC);
+        
when(result.getComputeNodeInstanceContext().getModeConfiguration().getRepository().getType()).thenReturn("FIXTURE");
+        return result;
+    }
+    
+    private JobBriefInfo createJobBriefInfo(final String jobName) {
+        JobBriefInfo result = new JobBriefInfo();
+        result.setJobName(jobName);
+        return result;
+    }
+}
diff --git 
a/kernel/schedule/core/src/main/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorker.java
 
b/kernel/schedule/core/src/main/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorker.java
index c781e1630b0..efd7ed9472a 100644
--- 
a/kernel/schedule/core/src/main/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorker.java
+++ 
b/kernel/schedule/core/src/main/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorker.java
@@ -25,18 +25,17 @@ import 
org.apache.shardingsphere.elasticjob.lite.api.bootstrap.impl.ScheduleJobB
 import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.operate.JobOperateAPIImpl;
 import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.settings.JobConfigurationAPIImpl;
 import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
-import 
org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperConfiguration;
-import 
org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperRegistryCenter;
 import org.apache.shardingsphere.infra.config.mode.ModeConfiguration;
 import 
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationPropertyKey;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.mode.manager.ContextManager;
 import 
org.apache.shardingsphere.mode.node.path.engine.generator.NodePathGenerator;
 import 
org.apache.shardingsphere.mode.node.path.type.database.statistics.StatisticsJobNodePath;
 import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepositoryConfiguration;
+import 
org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider;
 import org.quartz.CronExpression;
 
 import java.util.Optional;
-import java.util.Properties;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -63,48 +62,35 @@ public final class StatisticsCollectJobWorker {
      */
     public void initialize(final ContextManager contextManager) {
         if (WORKER_INITIALIZED.compareAndSet(false, true)) {
-            ModeConfiguration modeConfig = 
contextManager.getComputeNodeInstanceContext().getModeConfiguration();
-            if (!"ZooKeeper".equals(modeConfig.getRepository().getType())) {
-                log.warn("Can not collect statistics because of unsupported 
cluster type: {}", modeConfig.getRepository().getType());
-                return;
+            try {
+                ModeConfiguration modeConfig = 
contextManager.getComputeNodeInstanceContext().getModeConfiguration();
+                Optional<CoordinatorRegistryCenterProvider> provider = 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
modeConfig.getRepository().getType());
+                if (!provider.isPresent()) {
+                    log.warn("Can not collect statistics because of 
unsupported cluster type: {}", modeConfig.getRepository().getType());
+                    WORKER_INITIALIZED.set(false);
+                    return;
+                }
+                StatisticsCollectJobWorker.contextManager = contextManager;
+                ClusterPersistRepositoryConfiguration repositoryConfig = 
(ClusterPersistRepositoryConfiguration) modeConfig.getRepository();
+                registryCenter = provider.get().create(repositoryConfig, 
NodePathGenerator.toPath(new StatisticsJobNodePath()));
+                scheduleJobBootstrap = new 
ScheduleJobBootstrap(registryCenter, new StatisticsCollectJob(contextManager), 
createJobConfiguration());
+                scheduleJobBootstrap.schedule();
+                new JobOperateAPIImpl(registryCenter).trigger(JOB_NAME);
+                // CHECKSTYLE:OFF
+            } catch (final RuntimeException ex) {
+                // CHECKSTYLE:ON
+                try {
+                    destroy();
+                    // CHECKSTYLE:OFF
+                } catch (final RuntimeException cleanupEx) {
+                    // CHECKSTYLE:ON
+                    ex.addSuppressed(cleanupEx);
+                }
+                throw ex;
             }
-            StatisticsCollectJobWorker.contextManager = contextManager;
-            registryCenter = createRegistryCenter(modeConfig);
-            scheduleJobBootstrap = new ScheduleJobBootstrap(registryCenter, 
new StatisticsCollectJob(contextManager), createJobConfiguration());
-            scheduleJobBootstrap.schedule();
-            new JobOperateAPIImpl(registryCenter).trigger(JOB_NAME);
         }
     }
     
-    private CoordinatorRegistryCenter createRegistryCenter(final 
ModeConfiguration modeConfig) {
-        ClusterPersistRepositoryConfiguration repositoryConfig = 
(ClusterPersistRepositoryConfiguration) modeConfig.getRepository();
-        String namespace = repositoryConfig.getNamespace() + 
NodePathGenerator.toPath(new StatisticsJobNodePath());
-        CoordinatorRegistryCenter result = new 
ZookeeperRegistryCenter(getZookeeperConfiguration(repositoryConfig, namespace));
-        result.init();
-        return result;
-    }
-    
-    private ZookeeperConfiguration getZookeeperConfiguration(final 
ClusterPersistRepositoryConfiguration repositoryConfig, final String namespace) 
{
-        // TODO Merge registry center code in ElasticJob and ShardingSphere 
mode; Use SPI to load impl
-        ZookeeperConfiguration result = new 
ZookeeperConfiguration(repositoryConfig.getServerLists(), namespace);
-        Properties props = repositoryConfig.getProps();
-        int retryIntervalMilliseconds = 
props.containsKey("retryIntervalMilliseconds") ? 
Integer.parseInt(props.get("retryIntervalMilliseconds").toString()) : 500;
-        int maxRetries = props.containsKey("maxRetries") ? 
Integer.parseInt(props.get("maxRetries").toString()) : 3;
-        result.setBaseSleepTimeMilliseconds(retryIntervalMilliseconds);
-        result.setMaxRetries(maxRetries);
-        result.setMaxSleepTimeMilliseconds(retryIntervalMilliseconds * 
maxRetries);
-        int timeToLiveSeconds = props.containsKey("timeToLiveSeconds") ? 
Integer.parseInt(props.get("timeToLiveSeconds").toString()) : 60;
-        if (0 != timeToLiveSeconds) {
-            result.setSessionTimeoutMilliseconds(timeToLiveSeconds * 1000);
-        }
-        int operationTimeoutMilliseconds = 
props.containsKey("operationTimeoutMilliseconds") ? 
Integer.parseInt(props.get("operationTimeoutMilliseconds").toString()) : 500;
-        if (0 != operationTimeoutMilliseconds) {
-            
result.setConnectionTimeoutMilliseconds(operationTimeoutMilliseconds);
-        }
-        result.setDigest(props.getProperty("digest"));
-        return result;
-    }
-    
     private JobConfiguration createJobConfiguration() {
         String jobCron = 
contextManager.getMetaDataContexts().getMetaData().getTemporaryProps().getValue(TemporaryConfigurationPropertyKey.PROXY_META_DATA_COLLECTOR_CRON);
         if (!CronExpression.isValidExpression(jobCron)) {
@@ -139,11 +125,17 @@ public final class StatisticsCollectJobWorker {
      */
     public void destroy() {
         if (WORKER_INITIALIZED.compareAndSet(true, false)) {
-            
Optional.ofNullable(scheduleJobBootstrap).ifPresent(ScheduleJobBootstrap::shutdown);
-            scheduleJobBootstrap = null;
-            
Optional.ofNullable(registryCenter).ifPresent(CoordinatorRegistryCenter::close);
-            registryCenter = null;
-            contextManager = null;
+            try {
+                
Optional.ofNullable(scheduleJobBootstrap).ifPresent(ScheduleJobBootstrap::shutdown);
+            } finally {
+                scheduleJobBootstrap = null;
+                try {
+                    
Optional.ofNullable(registryCenter).ifPresent(CoordinatorRegistryCenter::close);
+                } finally {
+                    registryCenter = null;
+                    contextManager = null;
+                }
+            }
         }
     }
 }
diff --git 
a/kernel/schedule/core/src/main/java/org/apache/shardingsphere/schedule/spi/CoordinatorRegistryCenterProvider.java
 
b/kernel/schedule/core/src/main/java/org/apache/shardingsphere/schedule/spi/CoordinatorRegistryCenterProvider.java
new file mode 100644
index 00000000000..51b6dea4b1e
--- /dev/null
+++ 
b/kernel/schedule/core/src/main/java/org/apache/shardingsphere/schedule/spi/CoordinatorRegistryCenterProvider.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.shardingsphere.schedule.spi;
+
+import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
+import org.apache.shardingsphere.infra.spi.annotation.SingletonSPI;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPI;
+import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepositoryConfiguration;
+
+/**
+ * Coordinator registry center provider.
+ *
+ * <p>Implementations must not retain invocation state or create resources in 
their constructor or SPI initialization method.</p>
+ */
+@SingletonSPI
+public interface CoordinatorRegistryCenterProvider extends TypedSPI {
+    
+    /**
+     * Create an initialized coordinator registry center.
+     *
+     * <p>The caller owns the returned registry center and must close it. If 
initialization fails, the provider must close any partially created registry 
center before propagating the failure.</p>
+     *
+     * @param repositoryConfig cluster persist repository configuration
+     * @param namespaceRelativePath namespace relative path; {@code null} 
means that no relative path is appended
+     * @return initialized coordinator registry center
+     */
+    CoordinatorRegistryCenter create(ClusterPersistRepositoryConfiguration 
repositoryConfig, String namespaceRelativePath);
+}
diff --git 
a/kernel/schedule/core/src/test/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorkerTest.java
 
b/kernel/schedule/core/src/test/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorkerTest.java
index b489fcf5f03..b3b775f2c0a 100644
--- 
a/kernel/schedule/core/src/test/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorkerTest.java
+++ 
b/kernel/schedule/core/src/test/java/org/apache/shardingsphere/schedule/core/job/statistics/collect/StatisticsCollectJobWorkerTest.java
@@ -24,14 +24,17 @@ import 
org.apache.shardingsphere.elasticjob.lite.api.bootstrap.impl.ScheduleJobB
 import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.operate.JobOperateAPIImpl;
 import 
org.apache.shardingsphere.elasticjob.lite.lifecycle.internal.settings.JobConfigurationAPIImpl;
 import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
-import 
org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperRegistryCenter;
 import org.apache.shardingsphere.infra.config.mode.ModeConfiguration;
 import 
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationProperties;
 import 
org.apache.shardingsphere.infra.config.props.temporary.TemporaryConfigurationPropertyKey;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.infra.util.props.PropertiesBuilder;
 import org.apache.shardingsphere.infra.util.props.PropertiesBuilder.Property;
 import org.apache.shardingsphere.mode.manager.ContextManager;
+import 
org.apache.shardingsphere.mode.node.path.engine.generator.NodePathGenerator;
+import 
org.apache.shardingsphere.mode.node.path.type.database.statistics.StatisticsJobNodePath;
 import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepositoryConfiguration;
+import 
org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -40,21 +43,30 @@ import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
 import org.mockito.internal.configuration.plugins.Plugins;
 import org.mockito.junit.jupiter.MockitoExtension;
 
+import java.util.Optional;
 import java.util.Properties;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.arrayContaining;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
@@ -62,18 +74,27 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 class StatisticsCollectJobWorkerTest {
     
+    private static final String STATISTICS_JOB_PATH = 
NodePathGenerator.toPath(new StatisticsJobNodePath());
+    
     private static AtomicBoolean workerInitialized = new AtomicBoolean(false);
     
     private StatisticsCollectJobWorker jobWorker;
     
+    private MockedStatic<TypedSPILoader> serviceLoader;
+    
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private ContextManager contextManager;
     
+    @Mock
+    private CoordinatorRegistryCenterProvider provider;
+    
     @BeforeEach
     @SneakyThrows(ReflectiveOperationException.class)
     void setUp() {
         workerInitialized = (AtomicBoolean) 
Plugins.getMemberAccessor().get(StatisticsCollectJobWorker.class.getDeclaredField("WORKER_INITIALIZED"),
 StatisticsCollectJobWorker.class);
+        workerInitialized.set(false);
         jobWorker = new StatisticsCollectJobWorker();
+        serviceLoader = mockStatic(TypedSPILoader.class);
     }
     
     @AfterEach
@@ -82,6 +103,7 @@ class StatisticsCollectJobWorkerTest {
         setStaticField("scheduleJobBootstrap", null);
         setStaticField("contextManager", null);
         setStaticField("registryCenter", null);
+        serviceLoader.close();
     }
     
     @SneakyThrows(ReflectiveOperationException.class)
@@ -91,66 +113,98 @@ class StatisticsCollectJobWorkerTest {
     
     @Test
     void assertInitializeTwice() {
-        jobWorker.initialize(contextManager);
-        jobWorker.initialize(contextManager);
-        
verify(contextManager.getComputeNodeInstanceContext()).getModeConfiguration();
-    }
-    
-    @Test
-    void assertInitializeWithZooKeeperRepository() {
-        Properties repositoryProps = PropertiesBuilder.build(new 
Property("retryIntervalMilliseconds", 200),
-                new Property("maxRetries", 4), new 
Property("timeToLiveSeconds", 5), new Property("operationTimeoutMilliseconds", 
800), new Property("digest", "digest"));
-        
when(contextManager.getComputeNodeInstanceContext().getModeConfiguration()).thenReturn(
-                new ModeConfiguration("Cluster", new 
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace", 
"127.0.0.1:2181", repositoryProps)));
+        ClusterPersistRepositoryConfiguration repositoryConfig = 
mockRepositoryConfiguration("FIXTURE");
+        CoordinatorRegistryCenter registryCenter = 
mock(CoordinatorRegistryCenter.class);
+        serviceLoader.when(() -> 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"FIXTURE")).thenReturn(Optional.of(provider));
+        when(provider.create(repositoryConfig, 
STATISTICS_JOB_PATH)).thenReturn(registryCenter);
         
when(contextManager.getMetaDataContexts().getMetaData().getTemporaryProps()).thenReturn(
                 new 
TemporaryConfigurationProperties(PropertiesBuilder.build(new 
Property(TemporaryConfigurationPropertyKey.PROXY_META_DATA_COLLECTOR_CRON.getKey(),
 "0 0/5 * * * ?"))));
         AtomicReference<JobConfiguration> jobConfigRef = new 
AtomicReference<>();
         try (
-                MockedConstruction<ZookeeperRegistryCenter> 
registryCenterConstruction = mockConstruction(ZookeeperRegistryCenter.class);
                 MockedConstruction<ScheduleJobBootstrap> 
scheduleJobBootstrapConstruction = mockConstruction(ScheduleJobBootstrap.class,
                         (mock, context) -> jobConfigRef.set((JobConfiguration) 
context.arguments().get(2)));
                 MockedConstruction<JobOperateAPIImpl> 
jobOperateAPIConstruction = mockConstruction(JobOperateAPIImpl.class)) {
             jobWorker.initialize(contextManager);
-            verify(registryCenterConstruction.constructed().get(0)).init();
+            jobWorker.initialize(contextManager);
+            verify(provider).create(repositoryConfig, STATISTICS_JOB_PATH);
             
verify(scheduleJobBootstrapConstruction.constructed().get(0)).schedule();
             assertThat(jobConfigRef.get().getCron(), is("0 0/5 * * * ?"));
             
verify(jobOperateAPIConstruction.constructed().get(0)).trigger("statistics-collect");
+            jobWorker.destroy();
+            
verify(scheduleJobBootstrapConstruction.constructed().get(0)).shutdown();
+            verify(registryCenter).close();
         }
     }
     
     @Test
-    void assertInitializeWithZooKeeperRepositoryUsingDefaultConfiguration() {
-        Properties repositoryProps = PropertiesBuilder.build(new 
Property("timeToLiveSeconds", 0), new Property("operationTimeoutMilliseconds", 
0));
-        
when(contextManager.getComputeNodeInstanceContext().getModeConfiguration()).thenReturn(
-                new ModeConfiguration("Cluster", new 
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace", 
"127.0.0.1:2181", repositoryProps)));
+    void assertInitializeWithUnsupportedProvider() {
+        mockRepositoryConfiguration("UNSUPPORTED");
+        serviceLoader.when(() -> 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"UNSUPPORTED")).thenReturn(Optional.empty());
+        jobWorker.initialize(contextManager);
+        jobWorker.initialize(contextManager);
+        assertFalse(workerInitialized.get());
+        serviceLoader.verify(() -> 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"UNSUPPORTED"), times(2));
+    }
+    
+    @Test
+    void assertInitializeFailureReleasesResourcesAndCanRetry() {
+        ClusterPersistRepositoryConfiguration repositoryConfig = 
mockRepositoryConfiguration("FIXTURE");
+        CoordinatorRegistryCenter firstRegistryCenter = 
mock(CoordinatorRegistryCenter.class);
+        CoordinatorRegistryCenter secondRegistryCenter = 
mock(CoordinatorRegistryCenter.class);
+        serviceLoader.when(() -> 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"FIXTURE")).thenReturn(Optional.of(provider));
+        when(provider.create(repositoryConfig, 
STATISTICS_JOB_PATH)).thenReturn(firstRegistryCenter, secondRegistryCenter);
         
when(contextManager.getMetaDataContexts().getMetaData().getTemporaryProps()).thenReturn(new
 TemporaryConfigurationProperties(new Properties()));
+        RuntimeException expected = new RuntimeException("expected");
+        AtomicBoolean failOnSchedule = new AtomicBoolean(true);
         try (
-                MockedConstruction<ZookeeperRegistryCenter> 
registryCenterConstruction = mockConstruction(ZookeeperRegistryCenter.class);
-                MockedConstruction<ScheduleJobBootstrap> 
scheduleJobBootstrapConstruction = mockConstruction(ScheduleJobBootstrap.class);
+                MockedConstruction<ScheduleJobBootstrap> 
scheduleJobBootstrapConstruction = mockConstruction(ScheduleJobBootstrap.class, 
(mock, context) -> {
+                    if (failOnSchedule.compareAndSet(true, false)) {
+                        doThrow(expected).when(mock).schedule();
+                    }
+                });
                 MockedConstruction<JobOperateAPIImpl> 
jobOperateAPIConstruction = mockConstruction(JobOperateAPIImpl.class)) {
+            assertThat(assertThrows(RuntimeException.class, () -> 
jobWorker.initialize(contextManager)), sameInstance(expected));
+            assertFalse(workerInitialized.get());
+            
verify(scheduleJobBootstrapConstruction.constructed().get(0)).shutdown();
+            verify(firstRegistryCenter).close();
             jobWorker.initialize(contextManager);
-            verify(registryCenterConstruction.constructed().get(0)).init();
-            
verify(scheduleJobBootstrapConstruction.constructed().get(0)).schedule();
+            assertTrue(workerInitialized.get());
+            
verify(scheduleJobBootstrapConstruction.constructed().get(1)).schedule();
             
verify(jobOperateAPIConstruction.constructed().get(0)).trigger("statistics-collect");
+            jobWorker.destroy();
+            
verify(scheduleJobBootstrapConstruction.constructed().get(1)).shutdown();
+            verify(secondRegistryCenter).close();
         }
     }
     
     @Test
-    void assertInitializeWithZooKeeperRepositoryUsingDefaultValues() {
-        
when(contextManager.getComputeNodeInstanceContext().getModeConfiguration()).thenReturn(
-                new ModeConfiguration("Cluster", new 
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace", 
"127.0.0.1:2181", new Properties())));
+    void assertPreserveInitializationFailureWhenCleanupFailed() {
+        ClusterPersistRepositoryConfiguration repositoryConfig = 
mockRepositoryConfiguration("FIXTURE");
+        CoordinatorRegistryCenter registryCenter = 
mock(CoordinatorRegistryCenter.class);
+        serviceLoader.when(() -> 
TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"FIXTURE")).thenReturn(Optional.of(provider));
+        when(provider.create(repositoryConfig, 
STATISTICS_JOB_PATH)).thenReturn(registryCenter);
         
when(contextManager.getMetaDataContexts().getMetaData().getTemporaryProps()).thenReturn(new
 TemporaryConfigurationProperties(new Properties()));
-        try (
-                MockedConstruction<ZookeeperRegistryCenter> 
registryCenterConstruction = mockConstruction(ZookeeperRegistryCenter.class);
-                MockedConstruction<ScheduleJobBootstrap> 
scheduleJobBootstrapConstruction = mockConstruction(ScheduleJobBootstrap.class);
-                MockedConstruction<JobOperateAPIImpl> 
jobOperateAPIConstruction = mockConstruction(JobOperateAPIImpl.class)) {
-            jobWorker.initialize(contextManager);
-            verify(registryCenterConstruction.constructed().get(0)).init();
-            
verify(scheduleJobBootstrapConstruction.constructed().get(0)).schedule();
-            
verify(jobOperateAPIConstruction.constructed().get(0)).trigger("statistics-collect");
+        RuntimeException expected = new RuntimeException("expected");
+        RuntimeException cleanupException = new RuntimeException("cleanup 
failed");
+        try (MockedConstruction<ScheduleJobBootstrap> 
scheduleJobBootstrapConstruction = mockConstruction(ScheduleJobBootstrap.class, 
(mock, context) -> {
+            doThrow(expected).when(mock).schedule();
+            doThrow(cleanupException).when(mock).shutdown();
+        })) {
+            RuntimeException actual = assertThrows(RuntimeException.class, () 
-> jobWorker.initialize(contextManager));
+            assertThat(actual, sameInstance(expected));
+            assertThat(actual.getSuppressed(), 
arrayContaining(cleanupException));
+            assertFalse(workerInitialized.get());
+            
verify(scheduleJobBootstrapConstruction.constructed().get(0)).shutdown();
+            verify(registryCenter).close();
         }
     }
     
+    private ClusterPersistRepositoryConfiguration 
mockRepositoryConfiguration(final String type) {
+        ClusterPersistRepositoryConfiguration result = new 
ClusterPersistRepositoryConfiguration(type, "namespace", "127.0.0.1:2181", new 
Properties());
+        
when(contextManager.getComputeNodeInstanceContext().getModeConfiguration()).thenReturn(new
 ModeConfiguration("Cluster", result));
+        return result;
+    }
+    
     @Test
     void assertUpdateJobConfigurationWithNullContextManager() {
         assertDoesNotThrow(() -> jobWorker.updateJobConfiguration());
diff --git a/mode/type/cluster/repository/provider/zookeeper/pom.xml 
b/mode/type/cluster/repository/provider/zookeeper/pom.xml
index 338b8f6a4f7..17356601395 100644
--- a/mode/type/cluster/repository/provider/zookeeper/pom.xml
+++ b/mode/type/cluster/repository/provider/zookeeper/pom.xml
@@ -37,6 +37,16 @@
             <artifactId>shardingsphere-cluster-mode-core</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shardingsphere</groupId>
+            <artifactId>shardingsphere-schedule-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.apache.shardingsphere.elasticjob</groupId>
+            
<artifactId>elasticjob-registry-center-zookeeper-curator</artifactId>
+        </dependency>
         
         <dependency>
             <groupId>org.apache.curator</groupId>
diff --git 
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/registrycenter/elasticjob/CoordinatorRegistryCenterInitializer.java
 
b/mode/type/cluster/repository/provider/zookeeper/src/main/java/org/apache/shardingsphere/mode/repository/cluster/zookeeper/ZookeeperCoordinatorRegistryCenterProvider.java
similarity index 67%
rename from 
kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/registrycenter/elasticjob/CoordinatorRegistryCenterInitializer.java
rename to 
mode/type/cluster/repository/provider/zookeeper/src/main/java/org/apache/shardingsphere/mode/repository/cluster/zookeeper/ZookeeperCoordinatorRegistryCenterProvider.java
index 92b8c584fb2..d8be149740c 100644
--- 
a/kernel/data-pipeline/core/src/main/java/org/apache/shardingsphere/data/pipeline/core/registrycenter/elasticjob/CoordinatorRegistryCenterInitializer.java
+++ 
b/mode/type/cluster/repository/provider/zookeeper/src/main/java/org/apache/shardingsphere/mode/repository/cluster/zookeeper/ZookeeperCoordinatorRegistryCenterProvider.java
@@ -15,42 +15,43 @@
  * limitations under the License.
  */
 
-package org.apache.shardingsphere.data.pipeline.core.registrycenter.elasticjob;
+package org.apache.shardingsphere.mode.repository.cluster.zookeeper;
 
 import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
 import 
org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperConfiguration;
 import 
org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperRegistryCenter;
-import org.apache.shardingsphere.infra.config.mode.ModeConfiguration;
 import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepositoryConfiguration;
 import 
org.apache.shardingsphere.mode.repository.cluster.zookeeper.props.ZookeeperProperties;
 import 
org.apache.shardingsphere.mode.repository.cluster.zookeeper.props.ZookeeperPropertyKey;
-
-import java.util.Properties;
+import 
org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider;
 
 /**
- * {@linkplain CoordinatorRegistryCenter} initializer.
+ * ZooKeeper coordinator registry center provider.
  */
-public final class CoordinatorRegistryCenterInitializer {
+public final class ZookeeperCoordinatorRegistryCenterProvider implements 
CoordinatorRegistryCenterProvider {
     
-    /**
-     * Create ZooKeeper registry center instance.
-     *
-     * @param modeConfig mode configuration
-     * @param namespaceRelativePath namespace relative path
-     * @return registry center instance
-     */
-    public CoordinatorRegistryCenter createZookeeperRegistryCenter(final 
ModeConfiguration modeConfig, final String namespaceRelativePath) {
-        ClusterPersistRepositoryConfiguration repositoryConfig = 
(ClusterPersistRepositoryConfiguration) modeConfig.getRepository();
-        // TODO Add registry center cache. Refer to 
RegistryCenterFactory.createCoordinatorRegistryCenter
-        CoordinatorRegistryCenter result = new 
ZookeeperRegistryCenter(getZookeeperConfig(repositoryConfig, 
namespaceRelativePath));
-        result.init();
-        return result;
+    @Override
+    public CoordinatorRegistryCenter create(final 
ClusterPersistRepositoryConfiguration repositoryConfig, final String 
namespaceRelativePath) {
+        CoordinatorRegistryCenter result = new 
ZookeeperRegistryCenter(createConfiguration(repositoryConfig, 
namespaceRelativePath));
+        try {
+            result.init();
+            return result;
+            // CHECKSTYLE:OFF
+        } catch (final RuntimeException ex) {
+            // CHECKSTYLE:ON
+            try {
+                result.close();
+                // CHECKSTYLE:OFF
+            } catch (final RuntimeException closeEx) {
+                // CHECKSTYLE:ON
+                ex.addSuppressed(closeEx);
+            }
+            throw ex;
+        }
     }
     
-    private ZookeeperConfiguration getZookeeperConfig(final 
ClusterPersistRepositoryConfiguration repositoryConfig, final String 
namespaceRelativePath) {
-        // TODO Merge registry center code in ElasticJob and ShardingSphere 
mode; Use SPI to load impl
-        Properties props = repositoryConfig.getProps();
-        ZookeeperProperties zookeeperProps = new ZookeeperProperties(props);
+    private ZookeeperConfiguration createConfiguration(final 
ClusterPersistRepositoryConfiguration repositoryConfig, final String 
namespaceRelativePath) {
+        ZookeeperProperties zookeeperProps = new 
ZookeeperProperties(repositoryConfig.getProps());
         String namespace = repositoryConfig.getNamespace() + (null == 
namespaceRelativePath ? "" : namespaceRelativePath);
         ZookeeperConfiguration result = new 
ZookeeperConfiguration(repositoryConfig.getServerLists(), namespace);
         int retryIntervalMilliseconds = 
zookeeperProps.getValue(ZookeeperPropertyKey.RETRY_INTERVAL_MILLISECONDS);
@@ -69,4 +70,9 @@ public final class CoordinatorRegistryCenterInitializer {
         result.setDigest(zookeeperProps.getValue(ZookeeperPropertyKey.DIGEST));
         return result;
     }
+    
+    @Override
+    public String getType() {
+        return "ZooKeeper";
+    }
 }
diff --git 
a/mode/type/cluster/repository/provider/zookeeper/src/main/resources/META-INF/services/org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider
 
b/mode/type/cluster/repository/provider/zookeeper/src/main/resources/META-INF/services/org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider
new file mode 100644
index 00000000000..6613a3d5b30
--- /dev/null
+++ 
b/mode/type/cluster/repository/provider/zookeeper/src/main/resources/META-INF/services/org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider
@@ -0,0 +1,18 @@
+#
+# 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.apache.shardingsphere.mode.repository.cluster.zookeeper.ZookeeperCoordinatorRegistryCenterProvider
diff --git 
a/mode/type/cluster/repository/provider/zookeeper/src/test/java/org/apache/shardingsphere/mode/repository/cluster/zookeeper/ZookeeperCoordinatorRegistryCenterProviderTest.java
 
b/mode/type/cluster/repository/provider/zookeeper/src/test/java/org/apache/shardingsphere/mode/repository/cluster/zookeeper/ZookeeperCoordinatorRegistryCenterProviderTest.java
new file mode 100644
index 00000000000..5a00964eda2
--- /dev/null
+++ 
b/mode/type/cluster/repository/provider/zookeeper/src/test/java/org/apache/shardingsphere/mode/repository/cluster/zookeeper/ZookeeperCoordinatorRegistryCenterProviderTest.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.shardingsphere.mode.repository.cluster.zookeeper;
+
+import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
+import 
org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperConfiguration;
+import 
org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperRegistryCenter;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import 
org.apache.shardingsphere.mode.repository.cluster.ClusterPersistRepositoryConfiguration;
+import 
org.apache.shardingsphere.schedule.spi.CoordinatorRegistryCenterProvider;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
+
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.arrayContaining;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.verify;
+
+class ZookeeperCoordinatorRegistryCenterProviderTest {
+    
+    @Test
+    void assertIsSupportedWithoutCreatingRegistryCenter() {
+        try (MockedConstruction<ZookeeperRegistryCenter> construction = 
mockConstruction(ZookeeperRegistryCenter.class)) {
+            
assertTrue(TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"ZooKeeper").isPresent());
+            
assertTrue(TypedSPILoader.findService(CoordinatorRegistryCenterProvider.class, 
"zookeeper").isPresent());
+            assertTrue(construction.constructed().isEmpty());
+        }
+    }
+    
+    @Test
+    void assertCreate() {
+        Properties props = new Properties();
+        props.setProperty("retryIntervalMilliseconds", "200");
+        props.setProperty("maxRetries", "4");
+        props.setProperty("timeToLiveSeconds", "5");
+        props.setProperty("operationTimeoutMilliseconds", "800");
+        props.setProperty("digest", "digest");
+        ClusterPersistRepositoryConfiguration repositoryConfig = new 
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace", 
"127.0.0.1:2181", props);
+        AtomicReference<ZookeeperConfiguration> configuration = new 
AtomicReference<>();
+        try (
+                MockedConstruction<ZookeeperRegistryCenter> construction = 
mockConstruction(ZookeeperRegistryCenter.class,
+                        (mock, context) -> 
configuration.set((ZookeeperConfiguration) context.arguments().get(0)))) {
+            CoordinatorRegistryCenter actual = 
TypedSPILoader.getService(CoordinatorRegistryCenterProvider.class, 
repositoryConfig.getType()).create(repositoryConfig, "/jobs");
+            assertThat(actual, 
sameInstance(construction.constructed().get(0)));
+            verify(actual).init();
+            assertThat(configuration.get().getServerLists(), 
is("127.0.0.1:2181"));
+            assertThat(configuration.get().getNamespace(), 
is("namespace/jobs"));
+            assertThat(configuration.get().getBaseSleepTimeMilliseconds(), 
is(200));
+            assertThat(configuration.get().getMaxRetries(), is(4));
+            assertThat(configuration.get().getMaxSleepTimeMilliseconds(), 
is(800));
+            assertThat(configuration.get().getSessionTimeoutMilliseconds(), 
is(5000));
+            assertThat(configuration.get().getConnectionTimeoutMilliseconds(), 
is(800));
+            assertThat(configuration.get().getDigest(), is("digest"));
+            actual.close();
+            verify(actual).close();
+        }
+    }
+    
+    @Test
+    void assertCreateWithZeroTimeoutsAndNullNamespaceRelativePath() {
+        Properties props = new Properties();
+        props.setProperty("timeToLiveSeconds", "0");
+        props.setProperty("operationTimeoutMilliseconds", "0");
+        ClusterPersistRepositoryConfiguration repositoryConfig = new 
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace", 
"127.0.0.1:2181", props);
+        AtomicReference<ZookeeperConfiguration> configuration = new 
AtomicReference<>();
+        try (
+                MockedConstruction<ZookeeperRegistryCenter> ignored = 
mockConstruction(ZookeeperRegistryCenter.class,
+                        (mock, context) -> 
configuration.set((ZookeeperConfiguration) context.arguments().get(0)))) {
+            TypedSPILoader.getService(CoordinatorRegistryCenterProvider.class, 
repositoryConfig.getType()).create(repositoryConfig, null);
+            assertThat(configuration.get().getNamespace(), is("namespace"));
+            assertThat(configuration.get().getBaseSleepTimeMilliseconds(), 
is(500));
+            assertThat(configuration.get().getMaxRetries(), is(3));
+            assertThat(configuration.get().getMaxSleepTimeMilliseconds(), 
is(1500));
+            assertThat(configuration.get().getSessionTimeoutMilliseconds(), 
is(0));
+            assertThat(configuration.get().getConnectionTimeoutMilliseconds(), 
is(0));
+            assertThat(configuration.get().getDigest(), is(""));
+        }
+    }
+    
+    @Test
+    void assertCloseRegistryCenterWhenInitializationFailed() {
+        ClusterPersistRepositoryConfiguration repositoryConfig = new 
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace", 
"127.0.0.1:2181", new Properties());
+        RuntimeException expected = new RuntimeException("expected");
+        try (
+                MockedConstruction<ZookeeperRegistryCenter> construction = 
mockConstruction(ZookeeperRegistryCenter.class,
+                        (mock, context) -> 
doThrow(expected).when(mock).init())) {
+            assertThat(assertThrows(RuntimeException.class,
+                    () -> 
TypedSPILoader.getService(CoordinatorRegistryCenterProvider.class, 
repositoryConfig.getType()).create(repositoryConfig, null)), 
sameInstance(expected));
+            verify(construction.constructed().get(0)).close();
+        }
+    }
+    
+    @Test
+    void assertPreserveInitializationFailureWhenCloseFailed() {
+        ClusterPersistRepositoryConfiguration repositoryConfig = new 
ClusterPersistRepositoryConfiguration("ZooKeeper", "namespace", 
"127.0.0.1:2181", new Properties());
+        RuntimeException expected = new RuntimeException("expected");
+        RuntimeException closeException = new RuntimeException("close failed");
+        try (MockedConstruction<ZookeeperRegistryCenter> ignored = 
mockConstruction(ZookeeperRegistryCenter.class, (mock, context) -> {
+            doThrow(expected).when(mock).init();
+            doThrow(closeException).when(mock).close();
+        })) {
+            RuntimeException actual = assertThrows(RuntimeException.class,
+                    () -> 
TypedSPILoader.getService(CoordinatorRegistryCenterProvider.class, 
repositoryConfig.getType()).create(repositoryConfig, null));
+            assertThat(actual, sameInstance(expected));
+            assertThat(actual.getSuppressed(), 
arrayContaining(closeException));
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 54b3f192dae..ab0ca82d42f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -496,6 +496,11 @@
                 <artifactId>elasticjob-lite-lifecycle</artifactId>
                 <version>${elasticjob.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.shardingsphere.elasticjob</groupId>
+                
<artifactId>elasticjob-registry-center-zookeeper-curator</artifactId>
+                <version>${elasticjob.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.quartz-scheduler</groupId>
                 <artifactId>quartz</artifactId>

Reply via email to