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

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-java-chassis.git


The following commit(s) were added to refs/heads/master by this push:
     new 19e26102f [SCB-2892]support Etcd as Discovery  (#4564)
19e26102f is described below

commit 19e26102f535ff3b0f54047193159c63649fffb2
Author: SweetWuXiaoMei <[email protected]>
AuthorDate: Tue Oct 29 11:23:14 2024 +0800

    [SCB-2892]support Etcd as Discovery  (#4564)
    
    Co-authored-by: chenzhida <[email protected]>
---
 demo/demo-etcd/README.md                           |   5 +
 demo/demo-etcd/consumer/pom.xml                    |  88 +++++++++
 .../samples/ClientWebsocketController.java         |  52 +++++
 .../servicecomb/samples/ConsumerController.java    |  42 ++++
 .../samples/ConsumerHeaderParamWithListSchema.java |  55 ++++++
 .../samples/ConsumerReactiveStreamController.java  |  84 ++++++++
 .../samples/EtcdConsumerApplication.java           |  33 ++++
 .../servicecomb/samples/ProviderService.java       |  24 +++
 .../consumer/src/main/resources/application.yml    |  32 ++++
 .../consumer/src/main/resources/log4j2.xml         |  41 ++++
 demo/demo-etcd/gateway/pom.xml                     |  91 +++++++++
 .../servicecomb/samples/GatewayApplication.java    |  33 ++++
 .../gateway/src/main/resources/application.yml     |  53 +++++
 .../gateway/src/main/resources/log4j2.xml          |  41 ++++
 {service-registry => demo/demo-etcd}/pom.xml       |  46 +++--
 demo/demo-etcd/provider/pom.xml                    |  97 ++++++++++
 .../samples/EtcdProviderApplication.java           |  33 ++++
 .../samples/HeaderParamWithListSchema.java         |  51 +++++
 .../servicecomb/samples/ProviderController.java    |  53 +++++
 .../samples/ReactiveStreamController.java          |  78 ++++++++
 .../servicecomb/samples/WebsocketController.java   |  67 +++++++
 .../provider/src/main/resources/application.yml    |  44 +++++
 .../provider/src/main/resources/log4j2.xml         |  41 ++++
 demo/demo-etcd/test-client/pom.xml                 | 213 +++++++++++++++++++++
 .../org/apache/servicecomb/samples/Config.java     |  22 +++
 .../samples/HeaderParamWithListSchemaIT.java       |  98 ++++++++++
 .../apache/servicecomb/samples/HelloWorldIT.java   |  68 +++++++
 .../servicecomb/samples/ReactiveStreamIT.java      | 119 ++++++++++++
 .../servicecomb/samples/TestClientApplication.java |  49 +++++
 .../servicecomb/samples/ThirdSvcConfiguration.java | 125 ++++++++++++
 .../apache/servicecomb/samples/WebsocketIT.java    |  57 ++++++
 .../test-client/src/main/resources/application.yml |  25 +++
 .../test-client/src/main/resources/log4j2.xml      |  41 ++++
 .../org/apache/servicecomb/samples/EtcdIT.java     |  53 +++++
 demo/pom.xml                                       |   1 +
 dependencies/bom/pom.xml                           |   5 +
 dependencies/default/pom.xml                       |   7 +
 service-registry/pom.xml                           |   1 +
 service-registry/{ => registry-etcd}/pom.xml       |  39 ++--
 .../registry/etcd/EtcdConfiguration.java           |  40 ++++
 .../servicecomb/registry/etcd/EtcdConst.java       |  30 +++
 .../servicecomb/registry/etcd/EtcdDiscovery.java   | 197 +++++++++++++++++++
 .../registry/etcd/EtcdDiscoveryInstance.java       |  37 ++++
 .../servicecomb/registry/etcd/EtcdInstance.java    | 208 ++++++++++++++++++++
 .../registry/etcd/EtcdRegistration.java            | 199 +++++++++++++++++++
 .../registry/etcd/EtcdRegistrationInstance.java    |  36 ++++
 .../registry/etcd/EtcdRegistryProperties.java      |  99 ++++++++++
 .../registry/etcd/MuteExceptionUtil.java           |  94 +++++++++
 ...rk.boot.autoconfigure.AutoConfiguration.imports |  18 ++
 49 files changed, 3038 insertions(+), 27 deletions(-)

diff --git a/demo/demo-etcd/README.md b/demo/demo-etcd/README.md
new file mode 100644
index 000000000..046bec6c9
--- /dev/null
+++ b/demo/demo-etcd/README.md
@@ -0,0 +1,5 @@
+# Notice
+
+This integration tests is designed for Etcd registry and configuration. And 
extra test cases include:
+
+* Test cases related to SpringMVC annotations that demo-springmvc can not 
cover.
diff --git a/demo/demo-etcd/consumer/pom.xml b/demo/demo-etcd/consumer/pom.xml
new file mode 100644
index 000000000..cbccb498a
--- /dev/null
+++ b/demo/demo-etcd/consumer/pom.xml
@@ -0,0 +1,88 @@
+<!--
+  ~ 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.servicecomb.demo</groupId>
+    <artifactId>demo-etcd</artifactId>
+    <version>3.3.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>etcd-consumer</artifactId>
+  <name>Java Chassis::Demo::Etcd::CONSUMER</name>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>java-chassis-spring-boot-starter-standalone</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.protobuf</groupId>
+      <artifactId>protobuf-java</artifactId>
+      <version>3.25.3</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>registry-etcd</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>docker</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>io.fabric8</groupId>
+            <artifactId>docker-maven-plugin</artifactId>
+          </plugin>
+          <plugin>
+            <groupId>org.commonjava.maven.plugins</groupId>
+            <artifactId>directory-maven-plugin</artifactId>
+          </plugin>
+          <plugin>
+            <groupId>com.github.odavid.maven.plugins</groupId>
+            <artifactId>mixin-maven-plugin</artifactId>
+            <configuration>
+              <mixins>
+                <mixin>
+                  <groupId>org.apache.servicecomb.demo</groupId>
+                  <artifactId>docker-build-config</artifactId>
+                  <version>${project.version}</version>
+                </mixin>
+              </mixins>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git 
a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ClientWebsocketController.java
 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ClientWebsocketController.java
new file mode 100644
index 000000000..f71738a9e
--- /dev/null
+++ 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ClientWebsocketController.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.servicecomb.samples;
+
+import org.apache.servicecomb.core.CoreConst;
+import org.apache.servicecomb.core.annotation.Transport;
+import org.apache.servicecomb.provider.pojo.RpcReference;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import io.vertx.core.http.ServerWebSocket;
+import io.vertx.core.http.WebSocket;
+
+@RestSchema(schemaId = "ClientWebsocketController")
+@RequestMapping(path = "/ws")
+public class ClientWebsocketController {
+  interface ProviderService {
+    WebSocket websocket();
+  }
+
+  @RpcReference(schemaId = "WebsocketController", microserviceName = 
"provider")
+  private ProviderService providerService;
+
+  @PostMapping("/websocket")
+  @Transport(name = CoreConst.WEBSOCKET)
+  public void websocket(ServerWebSocket serverWebsocket) {
+    WebSocket providerWebSocket = providerService.websocket();
+    providerWebSocket.closeHandler(v -> serverWebsocket.close());
+    providerWebSocket.textMessageHandler(m -> {
+      serverWebsocket.writeTextMessage(m);
+    });
+    serverWebsocket.textMessageHandler(m -> {
+      providerWebSocket.writeTextMessage(m);
+    });
+  }
+}
diff --git 
a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerController.java
 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerController.java
new file mode 100644
index 000000000..e5dd86d9d
--- /dev/null
+++ 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerController.java
@@ -0,0 +1,42 @@
+/*
+ * 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.servicecomb.samples;
+
+import org.apache.servicecomb.provider.pojo.RpcReference;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@RestSchema(schemaId = "ConsumerController")
+@RequestMapping(path = "/")
+public class ConsumerController {
+  @RpcReference(schemaId = "ProviderController", microserviceName = "provider")
+  private ProviderService providerService;
+
+  // consumer service which delegate the implementation to provider service.
+  @GetMapping("/sayHello")
+  public String sayHello(@RequestParam("name") String name) {
+    return providerService.sayHello(name);
+  }
+
+  @GetMapping("/getConfig")
+  public String getConfig(@RequestParam("key") String key) {
+    return providerService.getConfig(key);
+  }
+}
diff --git 
a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerHeaderParamWithListSchema.java
 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerHeaderParamWithListSchema.java
new file mode 100644
index 000000000..f0e894d67
--- /dev/null
+++ 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerHeaderParamWithListSchema.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.servicecomb.samples;
+
+import java.util.List;
+
+import org.apache.servicecomb.demo.api.IHeaderParamWithListSchemaSpringMvc;
+import org.apache.servicecomb.provider.pojo.RpcReference;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+
+@RestSchema(schemaId = "ConsumerHeaderParamWithListSchema", schemaInterface = 
IHeaderParamWithListSchemaSpringMvc.class)
+public class ConsumerHeaderParamWithListSchema implements 
IHeaderParamWithListSchemaSpringMvc {
+  @RpcReference(microserviceName = "provider", schemaId = 
"HeaderParamWithListSchema")
+  private IHeaderParamWithListSchemaSpringMvc provider;
+
+  @Override
+  public String headerListDefault(List<String> headerList) {
+    return provider.headerListDefault(headerList);
+  }
+
+  @Override
+  public String headerListCSV(List<String> headerList) {
+    return provider.headerListCSV(headerList);
+  }
+
+  @Override
+  public String headerListMULTI(List<String> headerList) {
+    return provider.headerListMULTI(headerList);
+  }
+
+  @Override
+  public String headerListSSV(List<String> headerList) {
+    return provider.headerListSSV(headerList);
+  }
+
+  @Override
+  public String headerListPIPES(List<String> headerList) {
+    return provider.headerListPIPES(headerList);
+  }
+}
diff --git 
a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerReactiveStreamController.java
 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerReactiveStreamController.java
new file mode 100644
index 000000000..aa169801c
--- /dev/null
+++ 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ConsumerReactiveStreamController.java
@@ -0,0 +1,84 @@
+/*
+ * 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.servicecomb.samples;
+
+
+import org.apache.servicecomb.core.CoreConst;
+import org.apache.servicecomb.core.annotation.Transport;
+import org.apache.servicecomb.provider.pojo.RpcReference;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.reactivestreams.Publisher;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@RestSchema(schemaId = "ReactiveStreamController")
+@RequestMapping(path = "/")
+public class ConsumerReactiveStreamController {
+  interface ProviderReactiveStreamController {
+    Publisher<String> sseString();
+
+    Publisher<Model> sseModel();
+  }
+
+  @RpcReference(microserviceName = "provider", schemaId = 
"ReactiveStreamController")
+  ProviderReactiveStreamController controller;
+
+  public static class Model {
+    private String name;
+
+    private int age;
+
+    public Model() {
+
+    }
+
+    public Model(String name, int age) {
+      this.name = name;
+      this.age = age;
+    }
+
+    public int getAge() {
+      return age;
+    }
+
+    public Model setAge(int age) {
+      this.age = age;
+      return this;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public Model setName(String name) {
+      this.name = name;
+      return this;
+    }
+  }
+
+  @GetMapping("/sseString")
+  @Transport(name = CoreConst.RESTFUL)
+  public Publisher<String> sseString() {
+    return controller.sseString();
+  }
+
+  @GetMapping("/sseModel")
+  @Transport(name = CoreConst.RESTFUL)
+  public Publisher<Model> sseModel() {
+    return controller.sseModel();
+  }
+}
diff --git 
a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/EtcdConsumerApplication.java
 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/EtcdConsumerApplication.java
new file mode 100644
index 000000000..2101e6512
--- /dev/null
+++ 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/EtcdConsumerApplication.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.servicecomb.samples;
+
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+@SpringBootApplication
+public class EtcdConsumerApplication {
+  public static void main(String[] args) throws Exception {
+    try {
+      new 
SpringApplicationBuilder().web(WebApplicationType.NONE).sources(EtcdConsumerApplication.class).run(args);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git 
a/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ProviderService.java
 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ProviderService.java
new file mode 100644
index 000000000..fe71314c9
--- /dev/null
+++ 
b/demo/demo-etcd/consumer/src/main/java/org/apache/servicecomb/samples/ProviderService.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.servicecomb.samples;
+
+public interface ProviderService {
+  String sayHello(String name);
+
+  String getConfig(String key);
+}
diff --git a/demo/demo-etcd/consumer/src/main/resources/application.yml 
b/demo/demo-etcd/consumer/src/main/resources/application.yml
new file mode 100644
index 000000000..a63a16518
--- /dev/null
+++ b/demo/demo-etcd/consumer/src/main/resources/application.yml
@@ -0,0 +1,32 @@
+#
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+servicecomb:
+  service:
+    application: demo-etcd
+    version: 0.0.1
+    name: consumer
+    properties:
+      group: red
+  registry:
+    etcd:
+      connectString: http://127.0.0.1:2379
+
+  rest:
+    address: 0.0.0.0:9092?websocketEnabled=true
+    server:
+      websocket-prefix: /ws
diff --git a/demo/demo-etcd/consumer/src/main/resources/log4j2.xml 
b/demo/demo-etcd/consumer/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..c51f7ad50
--- /dev/null
+++ b/demo/demo-etcd/consumer/src/main/resources/log4j2.xml
@@ -0,0 +1,41 @@
+<?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.
+  -->
+
+<!--this is sample configuration, please modify as your wish-->
+<configuration>
+  <Appenders>
+    <!-- can use MarkerFilter to separate logs with trace id
+    <Console name="Console" target="SYSTEM_OUT">
+      <MarkerFilter marker="SERVICECOMB_MARKER" onMatch="DENY" 
onMismatch="ACCEPT"/>
+      <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n"/>
+    </Console>
+    <Console name="Console-Tracing" target="SYSTEM_OUT">
+      <MarkerFilter marker="SERVICECOMB_MARKER" onMismatch="DENY" 
onMatch="ACCEPT"/>
+      <PatternLayout pattern="[%d][%t][%p][%c:%L][%X{SERVICECOMB_TRACE_ID}] 
%m%n"/>
+    </Console>
+    -->
+    <Console name="Console" target="SYSTEM_OUT">
+      <PatternLayout pattern="[%d][%t][%p][%c:%L][%X{SERVICECOMB_TRACE_ID}] 
%m%n"/>
+    </Console>
+  </Appenders>
+  <Loggers>
+    <Root level="info">
+      <AppenderRef ref="Console"/>
+    </Root>
+  </Loggers>
+</configuration>
diff --git a/demo/demo-etcd/gateway/pom.xml b/demo/demo-etcd/gateway/pom.xml
new file mode 100644
index 000000000..b2eb1e51d
--- /dev/null
+++ b/demo/demo-etcd/gateway/pom.xml
@@ -0,0 +1,91 @@
+<!--
+  ~ 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.servicecomb.demo</groupId>
+    <artifactId>demo-etcd</artifactId>
+    <version>3.3.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>etcd-gateway</artifactId>
+  <name>Java Chassis::Demo::Etcd::GATEWAY</name>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>java-chassis-spring-boot-starter-standalone</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>edge-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.protobuf</groupId>
+      <artifactId>protobuf-java</artifactId>
+      <version>3.25.3</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>registry-etcd</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>docker</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>io.fabric8</groupId>
+            <artifactId>docker-maven-plugin</artifactId>
+          </plugin>
+          <plugin>
+            <groupId>org.commonjava.maven.plugins</groupId>
+            <artifactId>directory-maven-plugin</artifactId>
+          </plugin>
+          <plugin>
+            <groupId>com.github.odavid.maven.plugins</groupId>
+            <artifactId>mixin-maven-plugin</artifactId>
+            <configuration>
+              <mixins>
+                <mixin>
+                  <groupId>org.apache.servicecomb.demo</groupId>
+                  <artifactId>docker-build-config</artifactId>
+                  <version>${project.version}</version>
+                </mixin>
+              </mixins>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git 
a/demo/demo-etcd/gateway/src/main/java/org/apache/servicecomb/samples/GatewayApplication.java
 
b/demo/demo-etcd/gateway/src/main/java/org/apache/servicecomb/samples/GatewayApplication.java
new file mode 100644
index 000000000..7d58caafd
--- /dev/null
+++ 
b/demo/demo-etcd/gateway/src/main/java/org/apache/servicecomb/samples/GatewayApplication.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.servicecomb.samples;
+
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+@SpringBootApplication
+public class GatewayApplication {
+  public static void main(String[] args) throws Exception {
+    try {
+      new 
SpringApplicationBuilder().web(WebApplicationType.NONE).sources(GatewayApplication.class).run(args);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/demo/demo-etcd/gateway/src/main/resources/application.yml 
b/demo/demo-etcd/gateway/src/main/resources/application.yml
new file mode 100644
index 000000000..f526eb3de
--- /dev/null
+++ b/demo/demo-etcd/gateway/src/main/resources/application.yml
@@ -0,0 +1,53 @@
+#
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+servicecomb:
+  service:
+    application: demo-etcd
+    version: 0.0.1
+    name: gateway
+  registry:
+    etcd:
+      enabled: true
+      connectString: http://127.0.0.1:2379
+
+  rest:
+    address: 0.0.0.0:9090?websocketEnabled=true
+    server:
+      websocket-prefix: /ws
+
+  http:
+    dispatcher:
+      edge:
+        default:
+          enabled: false
+        url:
+          enabled: true
+          pattern: /(.*)
+          mappings:
+            consumer:
+              prefixSegmentCount: 0
+              path: "/.*"
+              microserviceName: consumer
+              versionRule: 0.0.0+
+        websocket:
+          mappings:
+            consumer:
+              prefixSegmentCount: 0
+              path: "/ws/.*"
+              microserviceName: consumer
+              versionRule: 0.0.0+
diff --git a/demo/demo-etcd/gateway/src/main/resources/log4j2.xml 
b/demo/demo-etcd/gateway/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..c51f7ad50
--- /dev/null
+++ b/demo/demo-etcd/gateway/src/main/resources/log4j2.xml
@@ -0,0 +1,41 @@
+<?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.
+  -->
+
+<!--this is sample configuration, please modify as your wish-->
+<configuration>
+  <Appenders>
+    <!-- can use MarkerFilter to separate logs with trace id
+    <Console name="Console" target="SYSTEM_OUT">
+      <MarkerFilter marker="SERVICECOMB_MARKER" onMatch="DENY" 
onMismatch="ACCEPT"/>
+      <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n"/>
+    </Console>
+    <Console name="Console-Tracing" target="SYSTEM_OUT">
+      <MarkerFilter marker="SERVICECOMB_MARKER" onMismatch="DENY" 
onMatch="ACCEPT"/>
+      <PatternLayout pattern="[%d][%t][%p][%c:%L][%X{SERVICECOMB_TRACE_ID}] 
%m%n"/>
+    </Console>
+    -->
+    <Console name="Console" target="SYSTEM_OUT">
+      <PatternLayout pattern="[%d][%t][%p][%c:%L][%X{SERVICECOMB_TRACE_ID}] 
%m%n"/>
+    </Console>
+  </Appenders>
+  <Loggers>
+    <Root level="info">
+      <AppenderRef ref="Console"/>
+    </Root>
+  </Loggers>
+</configuration>
diff --git a/service-registry/pom.xml b/demo/demo-etcd/pom.xml
similarity index 55%
copy from service-registry/pom.xml
copy to demo/demo-etcd/pom.xml
index 0fb0f3475..bda7b06fe 100644
--- a/service-registry/pom.xml
+++ b/demo/demo-etcd/pom.xml
@@ -18,24 +18,44 @@
 
 <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.servicecomb</groupId>
-    <artifactId>java-chassis-parent</artifactId>
+    <groupId>org.apache.servicecomb.demo</groupId>
+    <artifactId>demo-parent</artifactId>
     <version>3.3.0-SNAPSHOT</version>
-    <relativePath>../parents/default</relativePath>
   </parent>
-  <modelVersion>4.0.0</modelVersion>
-
-  <artifactId>service-registry-parent</artifactId>
-  <name>Java Chassis::Service Registry</name>
+  <artifactId>demo-etcd</artifactId>
+  <name>Java Chassis::Demo::Etcd</name>
   <packaging>pom</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.servicecomb.demo</groupId>
+      <artifactId>demo-schema</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>solution-basic</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-slf4j-impl</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+    </dependency>
+  </dependencies>
 
   <modules>
-    <module>registry-local</module>
-    <module>registry-service-center</module>
-    <module>registry-lightweight</module>
-    <module>registry-zero-config</module>
-    <module>registry-nacos</module>
-    <module>registry-zookeeper</module>
+    <module>provider</module>
+    <module>consumer</module>
+    <module>gateway</module>
+    <module>test-client</module>
   </modules>
+
 </project>
diff --git a/demo/demo-etcd/provider/pom.xml b/demo/demo-etcd/provider/pom.xml
new file mode 100644
index 000000000..c1413de54
--- /dev/null
+++ b/demo/demo-etcd/provider/pom.xml
@@ -0,0 +1,97 @@
+<?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.servicecomb.demo</groupId>
+    <artifactId>demo-etcd</artifactId>
+    <version>3.3.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>etcd-provider</artifactId>
+  <name>Java Chassis::Demo::Etcd::PROVIDER</name>
+  <packaging>jar</packaging>
+
+  <properties>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>java-chassis-spring-boot-starter-standalone</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.protobuf</groupId>
+      <artifactId>protobuf-java</artifactId>
+      <version>3.25.3</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>registry-etcd</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>io.reactivex.rxjava3</groupId>
+      <artifactId>rxjava</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>docker</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>io.fabric8</groupId>
+            <artifactId>docker-maven-plugin</artifactId>
+          </plugin>
+          <plugin>
+            <groupId>org.commonjava.maven.plugins</groupId>
+            <artifactId>directory-maven-plugin</artifactId>
+          </plugin>
+          <plugin>
+            <groupId>com.github.odavid.maven.plugins</groupId>
+            <artifactId>mixin-maven-plugin</artifactId>
+            <configuration>
+              <mixins>
+                <mixin>
+                  <groupId>org.apache.servicecomb.demo</groupId>
+                  <artifactId>docker-build-config</artifactId>
+                  <version>${project.version}</version>
+                </mixin>
+              </mixins>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git 
a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/EtcdProviderApplication.java
 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/EtcdProviderApplication.java
new file mode 100644
index 000000000..388b962dc
--- /dev/null
+++ 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/EtcdProviderApplication.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.servicecomb.samples;
+
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+@SpringBootApplication
+public class EtcdProviderApplication {
+  public static void main(String[] args) throws Exception {
+    try {
+      new 
SpringApplicationBuilder().web(WebApplicationType.NONE).sources(EtcdProviderApplication.class).run(args);
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+}
diff --git 
a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchema.java
 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchema.java
new file mode 100644
index 000000000..8a773f91c
--- /dev/null
+++ 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchema.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.servicecomb.samples;
+
+import java.util.List;
+
+import org.apache.servicecomb.demo.api.IHeaderParamWithListSchemaSpringMvc;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+
+@RestSchema(schemaId = "HeaderParamWithListSchema", schemaInterface = 
IHeaderParamWithListSchemaSpringMvc.class)
+public class HeaderParamWithListSchema implements 
IHeaderParamWithListSchemaSpringMvc {
+  @Override
+  public String headerListDefault(List<String> headerList) {
+    return headerList == null ? "null" : headerList.size() + ":" + headerList;
+  }
+
+  @Override
+  public String headerListCSV(List<String> headerList) {
+    return headerList == null ? "null" : headerList.size() + ":" + headerList;
+  }
+
+  @Override
+  public String headerListMULTI(List<String> headerList) {
+    return headerList == null ? "null" : headerList.size() + ":" + headerList;
+  }
+
+  @Override
+  public String headerListSSV(List<String> headerList) {
+    return headerList == null ? "null" : headerList.size() + ":" + headerList;
+  }
+
+  @Override
+  public String headerListPIPES(List<String> headerList) {
+    return headerList == null ? "null" : headerList.size() + ":" + headerList;
+  }
+}
diff --git 
a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ProviderController.java
 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ProviderController.java
new file mode 100644
index 000000000..8fc333cee
--- /dev/null
+++ 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ProviderController.java
@@ -0,0 +1,53 @@
+/*
+ * 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.servicecomb.samples;
+
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@RestSchema(schemaId = "ProviderController")
+@RequestMapping(path = "/")
+public class ProviderController implements InitializingBean {
+  private Environment environment;
+
+  @Autowired
+  public void setEnvironment(Environment environment) {
+    this.environment = environment;
+  }
+
+  // a very simple service to echo the request parameter
+  @GetMapping("/sayHello")
+  public String sayHello(@RequestParam("name") String name) {
+//    return "Hello " + environment.getProperty("servicecomb.rest.address");
+    return "Hello " + name;
+  }
+
+  @GetMapping("/getConfig")
+  public String getConfig(@RequestParam("key") String key) {
+    return environment.getProperty(key);
+  }
+
+  @Override
+  public void afterPropertiesSet() throws Exception {
+  }
+}
diff --git 
a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ReactiveStreamController.java
 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ReactiveStreamController.java
new file mode 100644
index 000000000..8108d15fd
--- /dev/null
+++ 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/ReactiveStreamController.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.servicecomb.samples;
+
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.servicecomb.core.CoreConst;
+import org.apache.servicecomb.core.annotation.Transport;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.reactivestreams.Publisher;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import io.reactivex.rxjava3.core.Flowable;
+
+@RestSchema(schemaId = "ReactiveStreamController")
+@RequestMapping(path = "/")
+@Transport(name = CoreConst.RESTFUL)
+public class ReactiveStreamController {
+  public static class Model {
+    private String name;
+
+    private int age;
+
+    public Model() {
+
+    }
+
+    public Model(String name, int age) {
+      this.name = name;
+      this.age = age;
+    }
+
+    public int getAge() {
+      return age;
+    }
+
+    public Model setAge(int age) {
+      this.age = age;
+      return this;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public Model setName(String name) {
+      this.name = name;
+      return this;
+    }
+  }
+
+  @GetMapping("/sseString")
+  public Publisher<String> sseString() {
+    return Flowable.fromArray("a", "b", "c");
+  }
+
+  @GetMapping("/sseModel")
+  public Publisher<Model> sseModel() {
+    return Flowable.intervalRange(0, 5, 0, 1, TimeUnit.SECONDS)
+        .map(item -> new Model("jack", item.intValue()));
+  }
+}
diff --git 
a/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/WebsocketController.java
 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/WebsocketController.java
new file mode 100644
index 000000000..5f4f9719c
--- /dev/null
+++ 
b/demo/demo-etcd/provider/src/main/java/org/apache/servicecomb/samples/WebsocketController.java
@@ -0,0 +1,67 @@
+/*
+ * 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.servicecomb.samples;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.servicecomb.core.CoreConst;
+import org.apache.servicecomb.core.annotation.Transport;
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import io.vertx.core.http.ServerWebSocket;
+
+@RestSchema(schemaId = "WebsocketController")
+@RequestMapping(path = "/ws")
+public class WebsocketController {
+  @PostMapping("/websocket")
+  @Transport(name = CoreConst.WEBSOCKET)
+  public void websocket(ServerWebSocket serverWebsocket) {
+    // Client may have not registered message handler, and messages sent may 
get lost.
+    // So we sleep for a while to send message.
+    AtomicInteger receiveCount = new AtomicInteger(0);
+    serverWebsocket.textMessageHandler(s -> {
+      receiveCount.getAndIncrement();
+    });
+    serverWebsocket.closeHandler((v) -> System.out.println("closed"));
+
+    new Thread(() -> {
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+
+      serverWebsocket.writeTextMessage("hello", r -> {
+      });
+
+      for (int i = 0; i < 5; i++) {
+        serverWebsocket.writeTextMessage("hello " + i, r -> {
+        });
+        try {
+          Thread.sleep(500);
+        } catch (InterruptedException e) {
+          e.printStackTrace();
+        }
+      }
+      serverWebsocket.writeTextMessage("total " + receiveCount.get());
+      serverWebsocket.close();
+    }).start();
+  }
+}
diff --git a/demo/demo-etcd/provider/src/main/resources/application.yml 
b/demo/demo-etcd/provider/src/main/resources/application.yml
new file mode 100644
index 000000000..75b6dee9e
--- /dev/null
+++ b/demo/demo-etcd/provider/src/main/resources/application.yml
@@ -0,0 +1,44 @@
+#
+## ---------------------------------------------------------------------------
+## 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 boot configurations
+servicecomb:
+  service:
+    application: demo-etcd
+    version: 0.0.1
+    name: provider
+    properties:
+      group: green
+  registry:
+    etcd:
+      connectString: http://127.0.0.1:2379
+
+  rest:
+    address: 0.0.0.0:9094?websocketEnabled=true
+    server:
+      websocket-prefix: /ws
+
+  cors:
+    enabled: true
+    origin: "*"
+    allowCredentials: false
+    allowedMethod: "*"
+    maxAge: 3600
+
+key1: 1
+key2: 3
+key3: 5
diff --git a/demo/demo-etcd/provider/src/main/resources/log4j2.xml 
b/demo/demo-etcd/provider/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..c51f7ad50
--- /dev/null
+++ b/demo/demo-etcd/provider/src/main/resources/log4j2.xml
@@ -0,0 +1,41 @@
+<?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.
+  -->
+
+<!--this is sample configuration, please modify as your wish-->
+<configuration>
+  <Appenders>
+    <!-- can use MarkerFilter to separate logs with trace id
+    <Console name="Console" target="SYSTEM_OUT">
+      <MarkerFilter marker="SERVICECOMB_MARKER" onMatch="DENY" 
onMismatch="ACCEPT"/>
+      <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n"/>
+    </Console>
+    <Console name="Console-Tracing" target="SYSTEM_OUT">
+      <MarkerFilter marker="SERVICECOMB_MARKER" onMismatch="DENY" 
onMatch="ACCEPT"/>
+      <PatternLayout pattern="[%d][%t][%p][%c:%L][%X{SERVICECOMB_TRACE_ID}] 
%m%n"/>
+    </Console>
+    -->
+    <Console name="Console" target="SYSTEM_OUT">
+      <PatternLayout pattern="[%d][%t][%p][%c:%L][%X{SERVICECOMB_TRACE_ID}] 
%m%n"/>
+    </Console>
+  </Appenders>
+  <Loggers>
+    <Root level="info">
+      <AppenderRef ref="Console"/>
+    </Root>
+  </Loggers>
+</configuration>
diff --git a/demo/demo-etcd/test-client/pom.xml 
b/demo/demo-etcd/test-client/pom.xml
new file mode 100644
index 000000000..0c050adcb
--- /dev/null
+++ b/demo/demo-etcd/test-client/pom.xml
@@ -0,0 +1,213 @@
+<?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.servicecomb.demo</groupId>
+    <artifactId>demo-etcd</artifactId>
+    <version>3.3.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>etcd-test-client</artifactId>
+  <name>Java Chassis::Demo::Etcd::TEST-CLIENT</name>
+  <packaging>jar</packaging>
+
+  <properties>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>java-chassis-spring-boot-starter-standalone</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb.demo</groupId>
+      <artifactId>demo-schema</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>registry-local</artifactId>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <profile>
+      <id>docker</id>
+      <build>
+        <pluginManagement>
+          <plugins>
+            <plugin>
+              <groupId>io.fabric8</groupId>
+              <artifactId>docker-maven-plugin</artifactId>
+              <configuration>
+                <images>
+                  <image>
+                    <name>bitnami/etcd:latest</name>
+                    <alias>etcd</alias>
+                    <run>
+                      <namingStrategy>alias</namingStrategy>
+                      <wait>
+                        <log>etcdserver</log>
+                        <tcp>
+                          <ports>
+                            <port>2379</port>
+                          </ports>
+                        </tcp>
+                        <time>60000</time>
+                      </wait>
+                      <ports>
+                        <port>etcd.port:2379</port>
+                      </ports>
+                      <env>
+                        
<ALLOW_NONE_AUTHENTICATION>yes</ALLOW_NONE_AUTHENTICATION>
+                      </env>
+                    </run>
+                  </image>
+                  <image>
+                    <name>etcd-provider:${project.version}</name>
+                    <alias>etcd-provider</alias>
+                    <run>
+                      <namingStrategy>alias</namingStrategy>
+                      <env>
+                        <JAVA_OPTS>
+                          
-Dservicecomb.registry.etcd.connectString=http://etcd:2379
+                          
-Dservicecomb.config.etcd.connectString=http://etcd:2379
+                        </JAVA_OPTS>
+                        
<JAR_PATH>/maven/maven/etcd-provider-${project.version}.jar</JAR_PATH>
+                      </env>
+                      <links>
+                        <link>etcd:etcd</link>
+                      </links>
+                      <wait>
+                        <log>ServiceComb is ready</log>
+                        <tcp>
+                          <ports>
+                            <port>9094</port>
+                          </ports>
+                        </tcp>
+                        <time>120000</time>
+                      </wait>
+                      <ports>
+                        <port>9094:9094</port>
+                      </ports>
+                    </run>
+                  </image>
+                  <image>
+                    <name>etcd-consumer:${project.version}</name>
+                    <alias>etcd-consumer</alias>
+                    <run>
+                      <namingStrategy>alias</namingStrategy>
+                      <env>
+                        <JAVA_OPTS>
+                          
-Dservicecomb.registry.etcd.connectString=http://etcd:2379
+                        </JAVA_OPTS>
+                        
<JAR_PATH>/maven/maven/etcd-consumer-${project.version}.jar</JAR_PATH>
+                      </env>
+                      <links>
+                        <link>etcd:etcd</link>
+                      </links>
+                      <wait>
+                        <log>ServiceComb is ready</log>
+                        <tcp>
+                          <ports>
+                            <port>9092</port>
+                          </ports>
+                        </tcp>
+                        <time>120000</time>
+                      </wait>
+                      <ports>
+                        <port>9092:9092</port>
+                      </ports>
+                    </run>
+                  </image>
+                  <image>
+                    <name>etcd-gateway:${project.version}</name>
+                    <alias>etcd-gateway</alias>
+                    <run>
+                      <namingStrategy>alias</namingStrategy>
+                      <env>
+                        <JAVA_OPTS>
+                          
-Dservicecomb.registry.etcd.connectString=http://etcd:2379
+                        </JAVA_OPTS>
+                        
<JAR_PATH>/maven/maven/etcd-gateway-${project.version}.jar</JAR_PATH>
+                      </env>
+                      <links>
+                        <link>etcd:etcd</link>
+                      </links>
+                      <wait>
+                        <log>ServiceComb is ready</log>
+                        <tcp>
+                          <ports>
+                            <port>9090</port>
+                          </ports>
+                        </tcp>
+                        <time>120000</time>
+                      </wait>
+                      <ports>
+                        <port>9090:9090</port>
+                      </ports>
+                    </run>
+                  </image>
+                </images>
+              </configuration>
+              <executions>
+                <execution>
+                  <id>start</id>
+                  <phase>pre-integration-test</phase>
+                  <goals>
+                    <goal>start</goal>
+                  </goals>
+                </execution>
+                <execution>
+                  <id>stop</id>
+                  <phase>post-integration-test</phase>
+                  <goals>
+                    <goal>stop</goal>
+                  </goals>
+                </execution>
+              </executions>
+            </plugin>
+          </plugins>
+        </pluginManagement>
+
+        <plugins>
+          <plugin>
+            <groupId>io.fabric8</groupId>
+            <artifactId>docker-maven-plugin</artifactId>
+          </plugin>
+          <plugin>
+            <groupId>com.github.odavid.maven.plugins</groupId>
+            <artifactId>mixin-maven-plugin</artifactId>
+            <configuration>
+              <mixins>
+                <mixin>
+                  <groupId>org.apache.servicecomb.demo</groupId>
+                  <artifactId>docker-run-config</artifactId>
+                  <version>${project.version}</version>
+                </mixin>
+              </mixins>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git 
a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/Config.java
 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/Config.java
new file mode 100644
index 000000000..2e9105cdd
--- /dev/null
+++ 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/Config.java
@@ -0,0 +1,22 @@
+/*
+ * 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.servicecomb.samples;
+
+public interface Config {
+  String GATEWAY_URL = "http://localhost:9090";;
+}
diff --git 
a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchemaIT.java
 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchemaIT.java
new file mode 100644
index 000000000..1b129acc2
--- /dev/null
+++ 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HeaderParamWithListSchemaIT.java
@@ -0,0 +1,98 @@
+/*
+ * 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.servicecomb.samples;
+
+import org.apache.servicecomb.demo.CategorizedTestCase;
+import org.apache.servicecomb.demo.TestMgr;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestOperations;
+import org.springframework.web.client.RestTemplate;
+
+@Component
+public class HeaderParamWithListSchemaIT implements CategorizedTestCase {
+  RestOperations template = new RestTemplate();
+
+  @Override
+  public void testRestTransport() throws Exception {
+    testHeaderListDefault();
+    testHeaderListMulti();
+    testHeaderListCSV();
+    testHeaderListSSV();
+    testHeaderListPipes();
+  }
+
+  // default to multi
+  private void testHeaderListDefault() {
+    MultiValueMap<String, String> headers = new HttpHeaders();
+    headers.add("headerList", "a");
+    headers.add("headerList", "b");
+    headers.add("headerList", "c");
+    HttpEntity<Void> entity = new HttpEntity<>(headers);
+    String result = template
+        .exchange(Config.GATEWAY_URL + "/headerList/headerListDefault", 
HttpMethod.GET, entity, String.class).getBody();
+    TestMgr.check("3:[a, b, c]", result);
+  }
+
+  private void testHeaderListPipes() {
+    MultiValueMap<String, String> headers = new HttpHeaders();
+    headers.add("headerList", "a|b|c");
+    HttpEntity<Void> entity = new HttpEntity<>(headers);
+    String result = template
+        .exchange(Config.GATEWAY_URL + "/headerList/headerListPIPES", 
HttpMethod.GET, entity, String.class).getBody();
+    TestMgr.check("3:[a, b, c]", result);
+  }
+
+  private void testHeaderListSSV() {
+    MultiValueMap<String, String> headers = new HttpHeaders();
+    headers.add("headerList", "a b c");
+    HttpEntity<Void> entity = new HttpEntity<>(headers);
+    String result = template
+        .exchange(Config.GATEWAY_URL + "/headerList/headerListSSV", 
HttpMethod.GET, entity, String.class).getBody();
+    TestMgr.check("3:[a, b, c]", result);
+  }
+
+  private void testHeaderListCSV() {
+    MultiValueMap<String, String> headers = new HttpHeaders();
+    headers.add("headerList", "a,b,c");
+    HttpEntity<Void> entity = new HttpEntity<>(headers);
+    String result = template
+        .exchange(Config.GATEWAY_URL + "/headerList/headerListCSV", 
HttpMethod.GET, entity, String.class).getBody();
+    TestMgr.check("3:[a, b, c]", result);
+
+    headers.add("headerList", "a, b, c");
+    entity = new HttpEntity<>(headers);
+    result = template
+        .exchange(Config.GATEWAY_URL + "/headerList/headerListCSV", 
HttpMethod.GET, entity, String.class).getBody();
+    TestMgr.check("3:[a, b, c]", result);
+  }
+
+  private void testHeaderListMulti() {
+    MultiValueMap<String, String> headers = new HttpHeaders();
+    headers.add("headerList", "a");
+    headers.add("headerList", "b");
+    headers.add("headerList", "c");
+    HttpEntity<Void> entity = new HttpEntity<>(headers);
+    String result = template
+        .exchange(Config.GATEWAY_URL + "/headerList/headerListMULTI", 
HttpMethod.GET, entity, String.class).getBody();
+    TestMgr.check("3:[a, b, c]", result);
+  }
+}
diff --git 
a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java
 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java
new file mode 100644
index 000000000..97e883fb4
--- /dev/null
+++ 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/HelloWorldIT.java
@@ -0,0 +1,68 @@
+/*
+ * 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.servicecomb.samples;
+
+import org.apache.servicecomb.demo.CategorizedTestCase;
+import org.apache.servicecomb.demo.TestMgr;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestOperations;
+import org.springframework.web.client.RestTemplate;
+
+@Component
+public class HelloWorldIT implements CategorizedTestCase {
+  RestOperations template = new RestTemplate();
+
+  @Override
+  public void testRestTransport() throws Exception {
+    testHelloWorld();
+    testGetConfig();
+  }
+
+  private void testGetConfig() {
+    String result = template
+        .getForObject(Config.GATEWAY_URL + "/getConfig?key=key1", 
String.class);
+    TestMgr.check("1", result);
+    result = template
+        .getForObject(Config.GATEWAY_URL + "/getConfig?key=key2", 
String.class);
+    TestMgr.check("3", result);
+    result = template
+        .getForObject(Config.GATEWAY_URL + "/getConfig?key=key3", 
String.class);
+    TestMgr.check("5", result);
+  }
+
+  private void testHelloWorld() {
+    String result = template
+        .getForObject(Config.GATEWAY_URL + "/sayHello?name=World", 
String.class);
+    TestMgr.check("Hello World", result);
+
+    // test trace id added
+    MultiValueMap<String, String> headers = new HttpHeaders();
+    headers.add("X-B3-TraceId", "81de2eb7691c2bbb");
+    HttpEntity<Object> entity = new HttpEntity(headers);
+    ResponseEntity<String> response =
+        template.exchange(Config.GATEWAY_URL + "/sayHello?name=World", 
HttpMethod.GET, entity, String.class);
+    TestMgr.check(1, response.getHeaders().get("X-B3-TraceId").size());
+    TestMgr.check("81de2eb7691c2bbb", 
response.getHeaders().getFirst("X-B3-TraceId"));
+    TestMgr.check("Hello World", response.getBody());
+  }
+}
diff --git 
a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ReactiveStreamIT.java
 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ReactiveStreamIT.java
new file mode 100644
index 000000000..488a6cf71
--- /dev/null
+++ 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ReactiveStreamIT.java
@@ -0,0 +1,119 @@
+/*
+ * 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.servicecomb.samples;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.servicecomb.demo.CategorizedTestCase;
+import org.apache.servicecomb.demo.TestMgr;
+import 
org.apache.servicecomb.samples.ThirdSvcConfiguration.ReactiveStreamClient;
+import 
org.apache.servicecomb.samples.ThirdSvcConfiguration.ReactiveStreamClient.Model;
+import org.reactivestreams.Publisher;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ReactiveStreamIT implements CategorizedTestCase {
+  @Autowired
+  @Qualifier("reactiveStreamProvider")
+  ReactiveStreamClient reactiveStreamProvider;
+
+  @Autowired
+  @Qualifier("reactiveStreamGateway")
+  ReactiveStreamClient reactiveStreamGateway;
+
+  @Override
+  public void testRestTransport() throws Exception {
+    testSseString(reactiveStreamProvider);
+    testSseModel(reactiveStreamProvider);
+    testSseString(reactiveStreamGateway);
+    testSseModel(reactiveStreamGateway);
+  }
+
+  private void testSseModel(ReactiveStreamClient client) throws Exception {
+    Publisher<Model> result = client.sseModel();
+    StringBuilder buffer = new StringBuilder();
+    CountDownLatch countDownLatch = new CountDownLatch(1);
+    result.subscribe(new Subscriber<>() {
+      Subscription subscription;
+
+      @Override
+      public void onSubscribe(Subscription s) {
+        subscription = s;
+        subscription.request(1);
+      }
+
+      @Override
+      public void onNext(Model s) {
+        buffer.append(s.getName()).append(s.getAge());
+        subscription.request(1);
+      }
+
+      @Override
+      public void onError(Throwable t) {
+        subscription.cancel();
+        countDownLatch.countDown();
+      }
+
+      @Override
+      public void onComplete() {
+        countDownLatch.countDown();
+      }
+    });
+    countDownLatch.await(10, TimeUnit.SECONDS);
+    TestMgr.check("jack0jack1jack2jack3jack4", buffer.toString());
+  }
+
+  private void testSseString(ReactiveStreamClient client) throws Exception {
+    Publisher<String> result = client.sseString();
+    StringBuilder buffer = new StringBuilder();
+    CountDownLatch countDownLatch = new CountDownLatch(1);
+    result.subscribe(new Subscriber<>() {
+      Subscription subscription;
+
+      @Override
+      public void onSubscribe(Subscription s) {
+        subscription = s;
+        subscription.request(1);
+      }
+
+      @Override
+      public void onNext(String s) {
+        buffer.append(s);
+        subscription.request(1);
+      }
+
+      @Override
+      public void onError(Throwable t) {
+        subscription.cancel();
+        countDownLatch.countDown();
+      }
+
+      @Override
+      public void onComplete() {
+        countDownLatch.countDown();
+      }
+    });
+    countDownLatch.await(10, TimeUnit.SECONDS);
+    TestMgr.check("abc", buffer.toString());
+  }
+}
diff --git 
a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/TestClientApplication.java
 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/TestClientApplication.java
new file mode 100644
index 000000000..26a2a491b
--- /dev/null
+++ 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/TestClientApplication.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.servicecomb.samples;
+
+import org.apache.servicecomb.demo.CategorizedTestCaseRunner;
+import org.apache.servicecomb.demo.TestMgr;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+
+@SpringBootApplication
+public class TestClientApplication {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(TestClientApplication.class);
+
+  public static void main(String[] args) throws Exception {
+    try {
+      new 
SpringApplicationBuilder().web(WebApplicationType.NONE).sources(TestClientApplication.class).run(args);
+
+      run();
+    } catch (Exception e) {
+      TestMgr.failed("test case run failed", e);
+      LOGGER.error("-------------- test failed -------------");
+      LOGGER.error("", e);
+      LOGGER.error("-------------- test failed -------------");
+    }
+    TestMgr.summary();
+  }
+
+  public static void run() throws Exception {
+    CategorizedTestCaseRunner.runCategorizedTestCase("consumer");
+  }
+}
diff --git 
a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ThirdSvcConfiguration.java
 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ThirdSvcConfiguration.java
new file mode 100644
index 000000000..3ee5db8c3
--- /dev/null
+++ 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/ThirdSvcConfiguration.java
@@ -0,0 +1,125 @@
+/*
+ * 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.servicecomb.samples;
+
+import java.util.List;
+
+import org.apache.servicecomb.core.CoreConst;
+import org.apache.servicecomb.core.annotation.Transport;
+import org.apache.servicecomb.localregistry.RegistryBean;
+import org.apache.servicecomb.localregistry.RegistryBean.Instance;
+import org.apache.servicecomb.localregistry.RegistryBean.Instances;
+import org.apache.servicecomb.provider.pojo.Invoker;
+import org.reactivestreams.Publisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import io.vertx.core.http.WebSocket;
+
+@Configuration
+public class ThirdSvcConfiguration {
+  @RequestMapping(path = "/ws")
+  public interface WebsocketClient {
+    @PostMapping("/websocket")
+    @Transport(name = CoreConst.WEBSOCKET)
+    WebSocket websocket();
+  }
+
+  @RequestMapping(path = "/")
+  public interface ReactiveStreamClient {
+    class Model {
+      private String name;
+
+      private int age;
+
+      public Model() {
+
+      }
+
+      public Model(String name, int age) {
+        this.name = name;
+        this.age = age;
+      }
+
+      public int getAge() {
+        return age;
+      }
+
+      public Model setAge(int age) {
+        this.age = age;
+        return this;
+      }
+
+      public String getName() {
+        return name;
+      }
+
+      public Model setName(String name) {
+        this.name = name;
+        return this;
+      }
+    }
+
+    @GetMapping("/sseString")
+    Publisher<String> sseString();
+
+    @GetMapping("/sseModel")
+    Publisher<Model> sseModel();
+  }
+
+  @Bean
+  public RegistryBean providerServiceBean() {
+    return new RegistryBean()
+        .addSchemaInterface("ReactiveStreamController", 
ReactiveStreamClient.class)
+        .setAppId("demo-etcd")
+        .setServiceName("provider")
+        .setVersion("0.0.1")
+        .setInstances(new Instances().setInstances(List.of(
+            new Instance().setEndpoints(List.of("rest://localhost:9094")))));
+  }
+
+  @Bean
+  public RegistryBean gatewayServiceBean() {
+    return new RegistryBean()
+        .addSchemaInterface("ReactiveStreamController", 
ReactiveStreamClient.class)
+        .addSchemaInterface("WebsocketController", WebsocketClient.class)
+        .setAppId("demo-etcd")
+        .setServiceName("gateway")
+        .setVersion("0.0.1")
+        .setInstances(new Instances().setInstances(List.of(
+            new 
Instance().setEndpoints(List.of("rest://localhost:9090?websocketEnabled=true")))));
+  }
+
+  @Bean("reactiveStreamProvider")
+  public ReactiveStreamClient reactiveStreamProvider() {
+    return Invoker.createProxy("provider", "ReactiveStreamController", 
ReactiveStreamClient.class);
+  }
+
+  @Bean("reactiveStreamGateway")
+  public ReactiveStreamClient reactiveStreamGateway() {
+    return Invoker.createProxy("gateway", "ReactiveStreamController", 
ReactiveStreamClient.class);
+  }
+
+  @Bean
+  public WebsocketClient gatewayWebsocketClient() {
+    return Invoker.createProxy("gateway", "WebsocketController", 
WebsocketClient.class);
+  }
+}
diff --git 
a/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/WebsocketIT.java
 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/WebsocketIT.java
new file mode 100644
index 000000000..ea7a7f45b
--- /dev/null
+++ 
b/demo/demo-etcd/test-client/src/main/java/org/apache/servicecomb/samples/WebsocketIT.java
@@ -0,0 +1,57 @@
+/*
+ * 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.servicecomb.samples;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.servicecomb.demo.CategorizedTestCase;
+import org.apache.servicecomb.demo.TestMgr;
+import org.apache.servicecomb.samples.ThirdSvcConfiguration.WebsocketClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import io.vertx.core.http.WebSocket;
+
+@Component
+public class WebsocketIT implements CategorizedTestCase {
+  @Autowired
+  private WebsocketClient websocketClient;
+
+  @Override
+  public void testRestTransport() throws Exception {
+    StringBuffer sb = new StringBuffer();
+    AtomicBoolean closed = new AtomicBoolean(false);
+    CountDownLatch latch = new CountDownLatch(1);
+
+    WebSocket webSocket = websocketClient.websocket();
+    webSocket.textMessageHandler(s -> {
+      sb.append(s);
+      sb.append(" ");
+      webSocket.writeTextMessage(s);
+    });
+    webSocket.closeHandler(v -> {
+      closed.set(true);
+      latch.countDown();
+    });
+    latch.await(30, TimeUnit.SECONDS);
+    TestMgr.check(sb.toString(), "hello hello 0 hello 1 hello 2 hello 3 hello 
4 total 6 ");
+    TestMgr.check(closed.get(), true);
+  }
+}
diff --git a/demo/demo-etcd/test-client/src/main/resources/application.yml 
b/demo/demo-etcd/test-client/src/main/resources/application.yml
new file mode 100644
index 000000000..396bebfd8
--- /dev/null
+++ b/demo/demo-etcd/test-client/src/main/resources/application.yml
@@ -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.
+## ---------------------------------------------------------------------------
+servicecomb:
+  service:
+    application: demo-etcd
+    name: test-client
+    version: 0.0.1
+
+  rest:
+    address: 0.0.0.0:9097 # should be same with server.port to use web 
container
diff --git a/demo/demo-etcd/test-client/src/main/resources/log4j2.xml 
b/demo/demo-etcd/test-client/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..c51f7ad50
--- /dev/null
+++ b/demo/demo-etcd/test-client/src/main/resources/log4j2.xml
@@ -0,0 +1,41 @@
+<?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.
+  -->
+
+<!--this is sample configuration, please modify as your wish-->
+<configuration>
+  <Appenders>
+    <!-- can use MarkerFilter to separate logs with trace id
+    <Console name="Console" target="SYSTEM_OUT">
+      <MarkerFilter marker="SERVICECOMB_MARKER" onMatch="DENY" 
onMismatch="ACCEPT"/>
+      <PatternLayout pattern="[%d][%t][%p][%c:%L] %m%n"/>
+    </Console>
+    <Console name="Console-Tracing" target="SYSTEM_OUT">
+      <MarkerFilter marker="SERVICECOMB_MARKER" onMismatch="DENY" 
onMatch="ACCEPT"/>
+      <PatternLayout pattern="[%d][%t][%p][%c:%L][%X{SERVICECOMB_TRACE_ID}] 
%m%n"/>
+    </Console>
+    -->
+    <Console name="Console" target="SYSTEM_OUT">
+      <PatternLayout pattern="[%d][%t][%p][%c:%L][%X{SERVICECOMB_TRACE_ID}] 
%m%n"/>
+    </Console>
+  </Appenders>
+  <Loggers>
+    <Root level="info">
+      <AppenderRef ref="Console"/>
+    </Root>
+  </Loggers>
+</configuration>
diff --git 
a/demo/demo-etcd/test-client/src/test/java/org/apache/servicecomb/samples/EtcdIT.java
 
b/demo/demo-etcd/test-client/src/test/java/org/apache/servicecomb/samples/EtcdIT.java
new file mode 100644
index 000000000..833feff87
--- /dev/null
+++ 
b/demo/demo-etcd/test-client/src/test/java/org/apache/servicecomb/samples/EtcdIT.java
@@ -0,0 +1,53 @@
+/*
+ * 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.servicecomb.samples;
+
+import org.apache.servicecomb.demo.TestMgr;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(classes = TestClientApplication.class)
+public class EtcdIT {
+  private static final Logger LOGGER = LoggerFactory.getLogger(EtcdIT.class);
+
+  @BeforeEach
+  public void setUp() {
+    TestMgr.errors().clear();
+  }
+
+  @Test
+  public void clientGetsNoError() {
+    try {
+      TestClientApplication.run();
+    } catch (Exception e) {
+      TestMgr.failed("test case run failed", e);
+      LOGGER.error("-------------- test failed -------------");
+      LOGGER.error("", e);
+      LOGGER.error("-------------- test failed -------------");
+    }
+    TestMgr.summary();
+    Assertions.assertTrue(TestMgr.errors().isEmpty());
+  }
+}
diff --git a/demo/pom.xml b/demo/pom.xml
index bfdd595ef..fa88e1526 100644
--- a/demo/pom.xml
+++ b/demo/pom.xml
@@ -56,6 +56,7 @@
     <module>demo-cse-v1</module>
     <module>demo-cse-v2</module>
     <module>demo-nacos</module>
+    <module>demo-etcd</module>
     <module>demo-zookeeper</module>
   </modules>
 
diff --git a/dependencies/bom/pom.xml b/dependencies/bom/pom.xml
index e417ae4cf..f2218783f 100644
--- a/dependencies/bom/pom.xml
+++ b/dependencies/bom/pom.xml
@@ -278,6 +278,11 @@
         <artifactId>registry-zookeeper</artifactId>
         <version>${project.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.servicecomb</groupId>
+        <artifactId>registry-etcd</artifactId>
+        <version>${project.version}</version>
+      </dependency>
       <!-- ServiceComb: solutions -->
       <dependency>
         <groupId>org.apache.servicecomb</groupId>
diff --git a/dependencies/default/pom.xml b/dependencies/default/pom.xml
index 573afe7f7..d9cf1966f 100644
--- a/dependencies/default/pom.xml
+++ b/dependencies/default/pom.xml
@@ -91,6 +91,7 @@
     <vertx.version>4.5.10</vertx.version>
     <zipkin.version>3.4.1</zipkin.version>
     <zipkin-reporter.version>3.4.0</zipkin-reporter.version>
+    <jetcd-core.version>0.8.3</jetcd-core.version>
     <!-- Base dir of main -->
     <main.basedir>${basedir}/../..</main.basedir>
   </properties>
@@ -542,6 +543,12 @@
         <type>pom</type>
         <scope>import</scope>
       </dependency>
+
+      <dependency>
+        <groupId>io.etcd</groupId>
+        <artifactId>jetcd-core</artifactId>
+        <version>${jetcd-core.version}</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 </project>
diff --git a/service-registry/pom.xml b/service-registry/pom.xml
index 0fb0f3475..213f0b0f2 100644
--- a/service-registry/pom.xml
+++ b/service-registry/pom.xml
@@ -37,5 +37,6 @@
     <module>registry-zero-config</module>
     <module>registry-nacos</module>
     <module>registry-zookeeper</module>
+    <module>registry-etcd</module>
   </modules>
 </project>
diff --git a/service-registry/pom.xml b/service-registry/registry-etcd/pom.xml
similarity index 58%
copy from service-registry/pom.xml
copy to service-registry/registry-etcd/pom.xml
index 0fb0f3475..cf48811d3 100644
--- a/service-registry/pom.xml
+++ b/service-registry/registry-etcd/pom.xml
@@ -16,26 +16,37 @@
   ~ limitations under the License.
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+<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";>
   <parent>
     <groupId>org.apache.servicecomb</groupId>
-    <artifactId>java-chassis-parent</artifactId>
+    <artifactId>service-registry-parent</artifactId>
     <version>3.3.0-SNAPSHOT</version>
-    <relativePath>../parents/default</relativePath>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
-  <artifactId>service-registry-parent</artifactId>
-  <name>Java Chassis::Service Registry</name>
-  <packaging>pom</packaging>
+  <artifactId>registry-etcd</artifactId>
+  <name>Java Chassis::Service Registry::Etcd</name>
+
+  <dependencies>
+
+    <dependency>
+      <groupId>io.etcd</groupId>
+      <artifactId>jetcd-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>foundation-common</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>foundation-registry</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.servicecomb</groupId>
+      <artifactId>java-chassis-core</artifactId>
+    </dependency>
+  </dependencies>
 
-  <modules>
-    <module>registry-local</module>
-    <module>registry-service-center</module>
-    <module>registry-lightweight</module>
-    <module>registry-zero-config</module>
-    <module>registry-nacos</module>
-    <module>registry-zookeeper</module>
-  </modules>
 </project>
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConfiguration.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConfiguration.java
new file mode 100644
index 000000000..a926c7bbc
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConfiguration.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.servicecomb.registry.etcd;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class EtcdConfiguration {
+  @Bean
+  @ConfigurationProperties(prefix = EtcdConst.ETCD_REGISTRY_PREFIX)
+  public EtcdRegistryProperties etcdRegistryProperties() {
+    return new EtcdRegistryProperties();
+  }
+
+  @Bean
+  public EtcdDiscovery etcdDiscovery() {
+    return new EtcdDiscovery();
+  }
+
+  @Bean
+  public EtcdRegistration etcdRegistration() {
+    return new EtcdRegistration();
+  }
+}
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConst.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConst.java
new file mode 100644
index 000000000..03b28f0d2
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdConst.java
@@ -0,0 +1,30 @@
+/*
+ * 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.servicecomb.registry.etcd;
+
+public class EtcdConst {
+
+  public static final String ETCD_REGISTRY_NAME = "etcd-registry";
+
+  public static final String ETCD_DISCOVERY_ROOT = "/servicecomb/registry/%s";
+
+  public static final String ETCD_REGISTRY_PREFIX = 
"servicecomb.registry.etcd";
+
+  public static final String ETCD_DISCOVERY_ENABLED = ETCD_REGISTRY_PREFIX + 
".%s.%s.enabled";
+
+  public static final String ETCD_DEFAULT_ENVIRONMENT = "production";
+}
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscovery.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscovery.java
new file mode 100644
index 000000000..3f5888269
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscovery.java
@@ -0,0 +1,197 @@
+/*
+ * 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.servicecomb.registry.etcd;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.servicecomb.config.BootStrapProperties;
+import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
+import org.apache.servicecomb.foundation.common.utils.JsonUtils;
+import org.apache.servicecomb.registry.api.Discovery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+
+import com.google.common.collect.Lists;
+
+import io.etcd.jetcd.ByteSequence;
+import io.etcd.jetcd.Client;
+import io.etcd.jetcd.KeyValue;
+import io.etcd.jetcd.Watch;
+import io.etcd.jetcd.kv.GetResponse;
+import io.etcd.jetcd.options.GetOption;
+import io.etcd.jetcd.options.WatchOption;
+
+public class EtcdDiscovery implements Discovery<EtcdDiscoveryInstance> {
+
+  private Environment environment;
+
+  private String basePath;
+
+  private EtcdRegistryProperties etcdRegistryProperties;
+
+  private Client client;
+
+  private InstanceChangedListener<EtcdDiscoveryInstance> 
instanceChangedListener;
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(EtcdDiscovery.class);
+
+  private Map<String, Watch> watchMap = new ConcurrentHashMapEx<>();
+
+
+  @Autowired
+  @SuppressWarnings("unused")
+  public void setEnvironment(Environment environment) {
+    this.environment = environment;
+  }
+
+  @Autowired
+  @SuppressWarnings("unused")
+  public void setEtcdRegistryProperties(EtcdRegistryProperties 
etcdRegistryProperties) {
+    this.etcdRegistryProperties = etcdRegistryProperties;
+  }
+
+  @Override
+  public String name() {
+    return EtcdConst.ETCD_REGISTRY_NAME;
+  }
+
+  @Override
+  public boolean enabled(String application, String serviceName) {
+    return 
environment.getProperty(String.format(EtcdConst.ETCD_DISCOVERY_ENABLED, 
application, serviceName),
+        boolean.class, true);
+  }
+
+  @Override
+  public List<EtcdDiscoveryInstance> findServiceInstances(String application, 
String serviceName) {
+
+    String prefixPath = basePath + "/" + application + "/" + serviceName;
+    watchMap.computeIfAbsent(prefixPath, serName -> {
+      Watch watchClient = client.getWatchClient();
+      try {
+        ByteSequence prefixByteSeq = ByteSequence.from(prefixPath, 
Charset.defaultCharset());
+        watchClient.watch(prefixByteSeq, 
WatchOption.builder().withPrefix(prefixByteSeq).build(),
+            resp -> watchNode(application, serviceName, prefixPath));
+      } catch (Exception e) {
+        LOGGER.error("Failed to add watch", e);
+      }
+      return watchClient;
+    });
+
+    List<KeyValue> endpointKv = getValuesByPrefix(prefixPath);
+    return convertServiceInstanceList(endpointKv);
+  }
+
+  private void watchNode(String application, String serviceName, String 
prefixPath) {
+
+    CompletableFuture<GetResponse> getFuture = client.getKVClient()
+        .get(ByteSequence.from(prefixPath, StandardCharsets.UTF_8),
+            GetOption.builder().withPrefix(ByteSequence.from(prefixPath, 
StandardCharsets.UTF_8)).build());
+    getFuture.thenAcceptAsync(response -> {
+      List<EtcdDiscoveryInstance> discoveryInstanceList = 
convertServiceInstanceList(response.getKvs());
+      instanceChangedListener.onInstanceChanged(name(), application, 
serviceName, discoveryInstanceList);
+    }).exceptionally(e -> {
+      LOGGER.error("watchNode error", e);
+      return null;
+    });
+  }
+
+  private List<KeyValue> getValuesByPrefix(String prefix) {
+
+    CompletableFuture<GetResponse> getFuture = client.getKVClient()
+        .get(ByteSequence.from(prefix, StandardCharsets.UTF_8),
+            GetOption.builder().withPrefix(ByteSequence.from(prefix, 
StandardCharsets.UTF_8)).build());
+    GetResponse response = MuteExceptionUtil.builder().withLog("get kv by 
prefix error")
+        .executeCompletableFuture(getFuture);
+    return response.getKvs();
+  }
+
+  private List<EtcdDiscoveryInstance> 
convertServiceInstanceList(List<KeyValue> keyValueList) {
+
+    List<EtcdDiscoveryInstance> list = 
Lists.newArrayListWithExpectedSize(keyValueList.size());
+    for (KeyValue keyValue : keyValueList) {
+      EtcdDiscoveryInstance etcdDiscoveryInstance = 
getEtcdDiscoveryInstance(keyValue);
+      list.add(etcdDiscoveryInstance);
+    }
+    return list;
+  }
+
+  private static EtcdDiscoveryInstance getEtcdDiscoveryInstance(KeyValue 
keyValue) {
+    String valueJson = new String(keyValue.getValue().getBytes(), 
Charset.defaultCharset());
+
+    EtcdInstance etcdInstance = MuteExceptionUtil.builder()
+        .withLog("convert json value to obj from etcd failure, {}", valueJson)
+        .executeFunctionWithDoubleParam(JsonUtils::readValue, 
valueJson.getBytes(StandardCharsets.UTF_8),
+            EtcdInstance.class);
+    EtcdDiscoveryInstance etcdDiscoveryInstance = new 
EtcdDiscoveryInstance(etcdInstance);
+    return etcdDiscoveryInstance;
+  }
+
+  @Override
+  public List<String> findServices(String application) {
+
+    String prefixPath = basePath + "/" + application;
+    List<KeyValue> endpointKv = getValuesByPrefix(prefixPath);
+    return endpointKv.stream().map(kv -> 
kv.getKey().toString(StandardCharsets.UTF_8)).collect(Collectors.toList());
+  }
+
+  @Override
+  public void 
setInstanceChangedListener(InstanceChangedListener<EtcdDiscoveryInstance> 
instanceChangedListener) {
+    this.instanceChangedListener = instanceChangedListener;
+  }
+
+  @Override
+  public boolean enabled() {
+    return etcdRegistryProperties.isEnabled();
+  }
+
+  @Override
+  public void init() {
+    String env = BootStrapProperties.readServiceEnvironment(environment);
+    if (StringUtils.isEmpty(env)) {
+      env = EtcdConst.ETCD_DEFAULT_ENVIRONMENT;
+    }
+    basePath = String.format(EtcdConst.ETCD_DISCOVERY_ROOT, env);
+  }
+
+  @Override
+  public void run() {
+    if (StringUtils.isEmpty(etcdRegistryProperties.getAuthenticationInfo())) {
+      this.client = 
Client.builder().endpoints(etcdRegistryProperties.getConnectString()).build();
+    } else {
+      String[] authInfo = 
etcdRegistryProperties.getAuthenticationInfo().split(":");
+      this.client = 
Client.builder().endpoints(etcdRegistryProperties.getConnectString())
+          .user(ByteSequence.from(authInfo[0], Charset.defaultCharset()))
+          .password(ByteSequence.from(authInfo[1], 
Charset.defaultCharset())).build();
+    }
+  }
+
+  @Override
+  public void destroy() {
+    if (client != null) {
+      client.close();
+      watchMap = null;
+    }
+  }
+}
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscoveryInstance.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscoveryInstance.java
new file mode 100644
index 000000000..85f5ade01
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdDiscoveryInstance.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.servicecomb.registry.etcd;
+
+import org.apache.servicecomb.registry.api.DiscoveryInstance;
+import org.apache.servicecomb.registry.api.MicroserviceInstanceStatus;
+
+public class EtcdDiscoveryInstance extends EtcdInstance implements 
DiscoveryInstance {
+
+  public EtcdDiscoveryInstance(EtcdInstance other) {
+    super(other);
+  }
+
+  @Override
+  public MicroserviceInstanceStatus getStatus() {
+    return MicroserviceInstanceStatus.UP;
+  }
+
+  @Override
+  public String getRegistryName() {
+    return EtcdConst.ETCD_REGISTRY_NAME;
+  }
+}
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdInstance.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdInstance.java
new file mode 100644
index 000000000..19fbd8cad
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdInstance.java
@@ -0,0 +1,208 @@
+/*
+ * 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.servicecomb.registry.etcd;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.servicecomb.registry.api.DataCenterInfo;
+import org.apache.servicecomb.registry.api.MicroserviceInstance;
+
+public class EtcdInstance implements MicroserviceInstance {
+  private String serviceId;
+
+  private String instanceId;
+
+  private String environment;
+
+  private String application;
+
+  private String serviceName;
+
+  private String alias;
+
+  private String version;
+
+  private String description;
+
+  private DataCenterInfo dataCenterInfo;
+
+  private List<String> endpoints = new ArrayList<>();
+
+  private Map<String, String> schemas = new HashMap<>();
+
+  private Map<String, String> properties = new HashMap<>();
+
+  public EtcdInstance() {
+
+  }
+
+  public EtcdInstance(EtcdInstance other) {
+    this.serviceId = other.serviceId;
+    this.instanceId = other.instanceId;
+    this.environment = other.environment;
+    this.application = other.application;
+    this.serviceName = other.serviceName;
+    this.alias = other.alias;
+    this.version = other.version;
+    this.description = other.description;
+    this.dataCenterInfo = other.dataCenterInfo;
+    this.endpoints = other.endpoints;
+    this.schemas = other.schemas;
+    this.properties = other.properties;
+  }
+
+  public void setServiceId(String serviceId) {
+    this.serviceId = serviceId;
+  }
+
+  public void setInstanceId(String instanceId) {
+    this.instanceId = instanceId;
+  }
+
+  public void setEnvironment(String environment) {
+    this.environment = environment;
+  }
+
+  public void setApplication(String application) {
+    this.application = application;
+  }
+
+  public void setServiceName(String serviceName) {
+    this.serviceName = serviceName;
+  }
+
+  public void setAlias(String alias) {
+    this.alias = alias;
+  }
+
+  public void setVersion(String version) {
+    this.version = version;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public void setDataCenterInfo(DataCenterInfo dataCenterInfo) {
+    this.dataCenterInfo = dataCenterInfo;
+  }
+
+  public void setEndpoints(List<String> endpoints) {
+    this.endpoints = endpoints;
+  }
+
+  public void setSchemas(Map<String, String> schemas) {
+    this.schemas = schemas;
+  }
+
+  public void setProperties(Map<String, String> properties) {
+    this.properties = properties;
+  }
+
+  @Override
+  public String getEnvironment() {
+    return this.environment;
+  }
+
+  @Override
+  public String getApplication() {
+    return this.application;
+  }
+
+  @Override
+  public String getServiceName() {
+    return this.serviceName;
+  }
+
+  @Override
+  public String getAlias() {
+    return alias;
+  }
+
+  @Override
+  public String getVersion() {
+    return version;
+  }
+
+  @Override
+  public DataCenterInfo getDataCenterInfo() {
+    return dataCenterInfo;
+  }
+
+  @Override
+  public String getDescription() {
+    return description;
+  }
+
+  @Override
+  public Map<String, String> getProperties() {
+    return properties;
+  }
+
+  @Override
+  public Map<String, String> getSchemas() {
+    return schemas;
+  }
+
+  @Override
+  public List<String> getEndpoints() {
+    return endpoints;
+  }
+
+  public void addSchema(String schemaId, String content) {
+    this.schemas.put(schemaId, content);
+  }
+
+  public void addEndpoint(String endpoint) {
+    this.endpoints.add(endpoint);
+  }
+
+  public void addProperty(String key, String value) {
+    this.properties.put(key, value);
+  }
+
+  @Override
+  public String getInstanceId() {
+    return instanceId;
+  }
+
+  @Override
+  public String getServiceId() {
+    return serviceId;
+  }
+
+  @Override
+  public String toString() {
+    return "EtcdInstance{" +
+        "serviceId='" + serviceId + '\'' +
+        ", instanceId='" + instanceId + '\'' +
+        ", environment='" + environment + '\'' +
+        ", application='" + application + '\'' +
+        ", serviceName='" + serviceName + '\'' +
+        ", alias='" + alias + '\'' +
+        ", version='" + version + '\'' +
+        ", description='" + description + '\'' +
+        ", dataCenterInfo=" + dataCenterInfo +
+        ", endpoints=" + endpoints +
+        ", schemas=" + schemas +
+        ", properties=" + properties +
+        '}';
+  }
+}
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistration.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistration.java
new file mode 100644
index 000000000..0fae8b94d
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistration.java
@@ -0,0 +1,199 @@
+/*
+ * 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.servicecomb.registry.etcd;
+
+import java.nio.charset.Charset;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.servicecomb.config.BootStrapProperties;
+import org.apache.servicecomb.config.DataCenterProperties;
+import org.apache.servicecomb.foundation.common.utils.JsonUtils;
+import org.apache.servicecomb.registry.RegistrationId;
+import org.apache.servicecomb.registry.api.DataCenterInfo;
+import org.apache.servicecomb.registry.api.MicroserviceInstanceStatus;
+import org.apache.servicecomb.registry.api.Registration;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+
+import io.etcd.jetcd.ByteSequence;
+import io.etcd.jetcd.Client;
+import io.etcd.jetcd.KV;
+import io.etcd.jetcd.Lease;
+import io.etcd.jetcd.kv.PutResponse;
+import io.etcd.jetcd.options.PutOption;
+
+public class EtcdRegistration implements 
Registration<EtcdRegistrationInstance> {
+
+  private EtcdInstance etcdInstance;
+
+  private Environment environment;
+
+  private String basePath;
+
+  private RegistrationId registrationId;
+
+  private DataCenterProperties dataCenterProperties;
+
+  private EtcdRegistryProperties etcdRegistryProperties;
+
+  private Client client;
+
+  private ScheduledExecutorService executorService;
+
+  private String keyPath;
+
+  private Long leaseId;
+
+  @Autowired
+  @SuppressWarnings("unused")
+  public void setEnvironment(Environment environment) {
+    this.environment = environment;
+  }
+
+  @Autowired
+  @SuppressWarnings("unused")
+  public void setEtcdRegistryProperties(EtcdRegistryProperties 
etcdRegistryProperties) {
+    this.etcdRegistryProperties = etcdRegistryProperties;
+  }
+
+  @Autowired
+  public void setRegistrationId(RegistrationId registrationId) {
+    this.registrationId = registrationId;
+  }
+
+  @Autowired
+  @SuppressWarnings("unused")
+  public void setDataCenterProperties(DataCenterProperties 
dataCenterProperties) {
+    this.dataCenterProperties = dataCenterProperties;
+  }
+
+  @Override
+  public String name() {
+    return EtcdConst.ETCD_REGISTRY_NAME;
+  }
+
+  @Override
+  public EtcdRegistrationInstance getMicroserviceInstance() {
+    return new EtcdRegistrationInstance(etcdInstance);
+  }
+
+  @Override
+  public boolean updateMicroserviceInstanceStatus(MicroserviceInstanceStatus 
status) {
+    return true;
+  }
+
+  @Override
+  public void addSchema(String schemaId, String content) {
+    if (etcdRegistryProperties.isEnableSwaggerRegistration()) {
+      etcdInstance.addSchema(schemaId, content);
+    }
+  }
+
+  @Override
+  public void addEndpoint(String endpoint) {
+    etcdInstance.addEndpoint(endpoint);
+  }
+
+  @Override
+  public void addProperty(String key, String value) {
+    etcdInstance.addProperty(key, value);
+  }
+
+  @Override
+  public boolean enabled() {
+    return etcdRegistryProperties.isEnabled();
+  }
+
+  @Override
+  public void init() {
+    String env = BootStrapProperties.readServiceEnvironment(environment);
+    if (StringUtils.isEmpty(env)) {
+      env = EtcdConst.ETCD_DEFAULT_ENVIRONMENT;
+    }
+    basePath = String.format(EtcdConst.ETCD_DISCOVERY_ROOT, env);
+    etcdInstance = new EtcdInstance();
+    etcdInstance.setInstanceId(registrationId.getInstanceId());
+    etcdInstance.setEnvironment(env);
+    
etcdInstance.setApplication(BootStrapProperties.readApplication(environment));
+    
etcdInstance.setServiceName(BootStrapProperties.readServiceName(environment));
+    etcdInstance.setAlias(BootStrapProperties.readServiceAlias(environment));
+    
etcdInstance.setDescription(BootStrapProperties.readServiceDescription(environment));
+    if (StringUtils.isNotEmpty(dataCenterProperties.getName())) {
+      DataCenterInfo dataCenterInfo = new DataCenterInfo();
+      dataCenterInfo.setName(dataCenterProperties.getName());
+      dataCenterInfo.setRegion(dataCenterProperties.getRegion());
+      dataCenterInfo.setAvailableZone(dataCenterProperties.getAvailableZone());
+      etcdInstance.setDataCenterInfo(dataCenterInfo);
+    }
+    
etcdInstance.setProperties(BootStrapProperties.readServiceProperties(environment));
+    
etcdInstance.setVersion(BootStrapProperties.readServiceVersion(environment));
+  }
+
+  @Override
+  public void run() {
+    client = 
Client.builder().endpoints(etcdRegistryProperties.getConnectString())
+        .build();
+    keyPath = basePath + "/" +
+        BootStrapProperties.readApplication(environment) + "/" +
+        BootStrapProperties.readServiceName(environment) + "/" +
+        registrationId.getInstanceId();
+
+    String valueJson = MuteExceptionUtil.builder().withLog("to json, key:{}, 
value:{}", keyPath, etcdInstance)
+        .executeFunction(JsonUtils::writeValueAsString, etcdInstance);
+    register(ByteSequence.from(keyPath, Charset.defaultCharset()),
+        ByteSequence.from(valueJson, Charset.defaultCharset()));
+  }
+
+  public void register(ByteSequence key, ByteSequence value) {
+
+    Lease leaseClient = client.getLeaseClient();
+    leaseId = MuteExceptionUtil.builder().withLog("get lease id, key:{}, 
value:{}", keyPath, etcdInstance)
+        .executeCompletableFuture(leaseClient.grant(60)).getID();
+    KV kvClient = client.getKVClient();
+
+    PutOption putOption = PutOption.builder().withLeaseId(leaseId).build();
+    CompletableFuture<PutResponse> putResponse = kvClient.put(key, value, 
putOption);
+    putResponse.thenRun(() -> {
+      executorService = Executors.newSingleThreadScheduledExecutor();
+      executorService.scheduleAtFixedRate(
+          () -> MuteExceptionUtil.builder().withLog("reRegister, {}, {}", 
keyPath, etcdInstance)
+              .executeFunction(leaseClient::keepAliveOnce, leaseId), 0, 5, 
TimeUnit.SECONDS);
+    });
+  }
+
+  private void unregister() {
+    // close job
+    executorService.shutdownNow();
+
+    // close etcd node
+    Lease leaseClient = client.getLeaseClient();
+    leaseClient.revoke(leaseId);
+    client.getKVClient().delete(ByteSequence.from(keyPath, 
Charset.defaultCharset()));
+  }
+
+  @Override
+  public void destroy() {
+    if (client != null) {
+      unregister();
+      client.close();
+    }
+  }
+}
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistrationInstance.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistrationInstance.java
new file mode 100644
index 000000000..444fe8670
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistrationInstance.java
@@ -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.
+ */
+package org.apache.servicecomb.registry.etcd;
+
+import org.apache.servicecomb.registry.api.MicroserviceInstanceStatus;
+import org.apache.servicecomb.registry.api.RegistrationInstance;
+
+public class EtcdRegistrationInstance extends EtcdInstance implements 
RegistrationInstance {
+  public EtcdRegistrationInstance(EtcdInstance instance) {
+    super(instance);
+  }
+
+  @Override
+  public MicroserviceInstanceStatus getInitialStatus() {
+    return MicroserviceInstanceStatus.STARTING;
+  }
+
+  @Override
+  public MicroserviceInstanceStatus getReadyStatus() {
+    return MicroserviceInstanceStatus.UP;
+  }
+}
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistryProperties.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistryProperties.java
new file mode 100644
index 000000000..c4d61094b
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/EtcdRegistryProperties.java
@@ -0,0 +1,99 @@
+/*
+ * 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.servicecomb.registry.etcd;
+
+public class EtcdRegistryProperties {
+  private boolean enabled = true;
+
+  private boolean ephemeral = true;
+
+  private String connectString = "http://127.0.0.1:2379";;
+
+  private String authenticationSchema;
+
+  private String authenticationInfo;
+
+  private int connectionTimeoutMillis = 1000;
+
+  private int sessionTimeoutMillis = 60000;
+
+  private boolean enableSwaggerRegistration = false;
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public void setEnabled(boolean enabled) {
+    this.enabled = enabled;
+  }
+
+  public boolean isEphemeral() {
+    return ephemeral;
+  }
+
+  public void setEphemeral(boolean ephemeral) {
+    this.ephemeral = ephemeral;
+  }
+
+  public String getConnectString() {
+    return connectString;
+  }
+
+  public void setConnectString(String connectString) {
+    this.connectString = connectString;
+  }
+
+  public int getConnectionTimeoutMillis() {
+    return connectionTimeoutMillis;
+  }
+
+  public void setConnectionTimeoutMillis(int connectionTimeoutMillis) {
+    this.connectionTimeoutMillis = connectionTimeoutMillis;
+  }
+
+  public int getSessionTimeoutMillis() {
+    return sessionTimeoutMillis;
+  }
+
+  public void setSessionTimeoutMillis(int sessionTimeoutMillis) {
+    this.sessionTimeoutMillis = sessionTimeoutMillis;
+  }
+
+  public boolean isEnableSwaggerRegistration() {
+    return enableSwaggerRegistration;
+  }
+
+  public void setEnableSwaggerRegistration(boolean enableSwaggerRegistration) {
+    this.enableSwaggerRegistration = enableSwaggerRegistration;
+  }
+
+  public String getAuthenticationSchema() {
+    return authenticationSchema;
+  }
+
+  public void setAuthenticationSchema(String authenticationSchema) {
+    this.authenticationSchema = authenticationSchema;
+  }
+
+  public String getAuthenticationInfo() {
+    return authenticationInfo;
+  }
+
+  public void setAuthenticationInfo(String authenticationInfo) {
+    this.authenticationInfo = authenticationInfo;
+  }
+}
diff --git 
a/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/MuteExceptionUtil.java
 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/MuteExceptionUtil.java
new file mode 100644
index 000000000..83041cacf
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/java/org/apache/servicecomb/registry/etcd/MuteExceptionUtil.java
@@ -0,0 +1,94 @@
+/*
+ * 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.servicecomb.registry.etcd;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MuteExceptionUtil {
+
+  interface FunctionWithException<T, R> {
+    R apply(T t) throws Exception;
+  }
+
+  interface FunctionWithDoubleParam<T1, T2, R> {
+    R apply(T1 t1, T2 t2) throws Exception;
+  }
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(MuteExceptionUtil.class);
+
+  public static class MuteExceptionUtilBuilder {
+
+    private String logMessage;
+
+    private Object[] customMessageParams;
+
+    public MuteExceptionUtilBuilder withLog(String message, Object... params) {
+      this.logMessage = message;
+      this.customMessageParams = params;
+      return this;
+    }
+
+    private String getLogMessage(String defaultMessage) {
+      return logMessage != null ? logMessage : defaultMessage;
+    }
+
+    // 执行带异常处理的Function
+    public <T, R> R executeFunction(FunctionWithException<T, R> function, T t) 
{
+      try {
+        return function.apply(t);
+      } catch (Exception e) {
+        LOGGER.error(getLogMessage("execute Function failure..."), 
customMessageParams, e);
+        return null;
+      }
+    }
+
+    public <T> T executeSupplier(Supplier<T> supplier) {
+      try {
+        return supplier.get();
+      } catch (Exception e) {
+        LOGGER.error(getLogMessage("execute Supplier failure..."), 
customMessageParams, e);
+        return null;
+      }
+    }
+
+    public <T> T executeCompletableFuture(CompletableFuture<T> 
completableFuture) {
+      try {
+        return completableFuture.get();
+      } catch (Exception e) {
+        LOGGER.error(getLogMessage("execute CompletableFuture failure..."), 
customMessageParams, e);
+        return null;
+      }
+    }
+
+    public <T1, T2, R> R 
executeFunctionWithDoubleParam(FunctionWithDoubleParam<T1, T2, R> function, T1 
t1, T2 t2) {
+      try {
+        return function.apply(t1, t2);
+      } catch (Exception e) {
+        LOGGER.error(getLogMessage("execute FunctionWithDoubleParam 
failure..."), customMessageParams, e);
+        return null;
+      }
+    }
+  }
+
+  public static MuteExceptionUtilBuilder builder() {
+    return new MuteExceptionUtilBuilder();
+  }
+}
diff --git 
a/service-registry/registry-etcd/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
 
b/service-registry/registry-etcd/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..999ffb6b0
--- /dev/null
+++ 
b/service-registry/registry-etcd/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -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.servicecomb.registry.etcd.EtcdConfiguration

Reply via email to