You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2024/11/22 09:32:10 UTC

(dubbo) 02/05: refactor xds resource subscription

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

albumenj pushed a commit to branch feature/xds
in repository https://gitbox.apache.org/repos/asf/dubbo.git

commit d97f79d9051d13c4fd08591ac7e2b1ca17e3cead
Author: Albumen Kevin <jh...@gmail.com>
AuthorDate: Fri Nov 22 17:23:14 2024 +0800

    refactor xds resource subscription
---
 .../dubbo-demo-xds/dubbo-demo-xds-consumer/pom.xml |   6 +
 .../xds/demo/consumer/XdsConsumerApplication.java  |   2 +-
 .../src/main/resources/application.yml             |   2 +-
 .../src/main/resources/bootstrap.json              |  92 ++++++
 .../src/main/resources/application.yml             |   2 +-
 .../src/main/resources/bootstrap.json              |  92 ++++++
 .../dubbo/remoting/http3/Http3SslContexts.java     |   2 +-
 .../java/org/apache/dubbo/xds/AdsObserver.java     | 113 +++++--
 .../java/org/apache/dubbo/xds/PilotExchanger.java  | 110 +------
 ...dsListener.java => XdsRawResourceListener.java} |   6 +-
 .../apache/dubbo/xds/XdsRawResourceProtocol.java   | 224 +++++++++++++
 .../xds/{protocol => }/XdsResourceListener.java    |   7 +-
 .../apache/dubbo/xds/directory/RoutingUtils.java   | 194 ++++++++++++
 .../apache/dubbo/xds/directory/XdsDirectory.java   | 352 ++++++++++++++++++---
 .../org/apache/dubbo/xds/listener/CdsListener.java |   7 +-
 .../org/apache/dubbo/xds/listener/LdsListener.java |   7 +-
 .../xds/listener/UpstreamTlsConfigListener.java    |   1 -
 .../dubbo/xds/protocol/AbstractProtocol.java       | 228 -------------
 .../org/apache/dubbo/xds/protocol/XdsProtocol.java |  39 ---
 .../dubbo/xds/protocol/impl/CdsProtocol.java       |  86 -----
 .../dubbo/xds/protocol/impl/EdsProtocol.java       |  83 -----
 .../dubbo/xds/protocol/impl/LdsProtocol.java       |  83 -----
 .../dubbo/xds/protocol/impl/RdsProtocol.java       |  83 -----
 .../dubbo/xds/resource/XdsClusterResource.java     |   2 +-
 .../dubbo/xds/resource/XdsEndpointResource.java    |   2 +-
 .../dubbo/xds/resource/XdsListenerResource.java    |   2 +-
 .../apache/dubbo/xds/resource/XdsResourceType.java |   2 +-
 .../dubbo/xds/resource/matcher/PathMatcher.java    |   2 +-
 .../dubbo/xds/resource/route/RouteMatch.java       |  14 +-
 .../dubbo/xds/resource/update/CdsUpdate.java       |   4 +-
 .../org/apache/dubbo/xds/router/XdsRouter.java     |   4 +-
 31 files changed, 1056 insertions(+), 797 deletions(-)

diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/pom.xml b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/pom.xml
index 73d3b92573..d296173533 100644
--- a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/pom.xml
+++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/pom.xml
@@ -37,6 +37,12 @@
       <version>${project.parent.version}</version>
     </dependency>
 
+    <dependency>
+      <groupId>org.apache.dubbo</groupId>
+      <artifactId>dubbo-remoting-http3</artifactId>
+      <version>${project.parent.version}</version>
+    </dependency>
+
     <dependency>
       <groupId>org.apache.dubbo</groupId>
       <artifactId>dubbo-triple-servlet</artifactId>
diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/java/org/apache/dubbo/xds/demo/consumer/XdsConsumerApplication.java b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/java/org/apache/dubbo/xds/demo/consumer/XdsConsumerApplication.java
index 4030a361ea..9b714b7a18 100644
--- a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/java/org/apache/dubbo/xds/demo/consumer/XdsConsumerApplication.java
+++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/java/org/apache/dubbo/xds/demo/consumer/XdsConsumerApplication.java
@@ -29,7 +29,7 @@ import org.springframework.stereotype.Service;
 @Service
 @EnableDubbo
 public class XdsConsumerApplication {
-    @DubboReference(providedBy = "dubbo-demo-xds-provider")
+    @DubboReference(providedBy = "echo:7070")
     private DemoService demoService;
 
     public static void main(String[] args) throws InterruptedException {
diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/application.yml
index 251a6afdac..4c2144dc3a 100644
--- a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/application.yml
+++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/application.yml
@@ -26,6 +26,6 @@ dubbo:
     name: tri
     port: 50050
   registry:
-    address: xds://istiod.istio-system.svc:15010?security=plaintext # istio://istiod.istio-system.svc:15012
+    address: xds://47.251.12.148:15010?security=plaintext # istio://istiod.istio-system.svc:15012
 
 
diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/bootstrap.json b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/bootstrap.json
new file mode 100644
index 0000000000..a9daf11483
--- /dev/null
+++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-consumer/src/main/resources/bootstrap.json
@@ -0,0 +1,92 @@
+{
+  "xds_servers": [
+    {
+      "server_uri": "47.251.12.148:15010",
+      "channel_creds": [
+        {
+          "type": "insecure"
+        }
+      ],
+      "server_features": [
+        "xds_v3"
+      ]
+    }
+  ],
+  "node": {
+    "id": "sidecar~192.168.19.141~echo-v1-5764868574-whqs9.echo-grpc~echo-grpc.svc.cluster.local",
+    "metadata": {
+      "ANNOTATIONS": {
+        "inject.istio.io/templates": "grpc-agent",
+        "istio.io/rev": "default",
+        "kubectl.kubernetes.io/default-container": "app",
+        "kubectl.kubernetes.io/default-logs-container": "app",
+        "kubernetes.io/config.seen": "2024-07-02T17:22:26.354582057+08:00",
+        "kubernetes.io/config.source": "api",
+        "prometheus.io/path": "/stats/prometheus",
+        "prometheus.io/port": "15020",
+        "prometheus.io/scrape": "true",
+        "proxy.istio.io/config": "{\"holdApplicationUntilProxyStarts\": true}",
+        "proxy.istio.io/overrides": "{\"containers\":[{\"name\":\"app\",\"image\":\"gcr.io/istio-testing/app:latest\",\"args\":[\"--metrics=15014\",\"--port\",\"18080\",\"--tcp\",\"19090\",\"--xds-grpc-server=17070\",\"--grpc\",\"17070\",\"--grpc\",\"17171\",\"--port\",\"3333\",\"--port\",\"8080\",\"--version\",\"v1\",\"--crt=/cert.crt\",\"--key=/cert.key\"],\"ports\":[{\"containerPort\":17070,\"protocol\":\"TCP\"},{\"containerPort\":17171,\"protocol\":\"TCP\"},{\"containerPort\":8080,\" [...]
+        "sidecar.istio.io/rewriteAppHTTPProbers": "false",
+        "sidecar.istio.io/status": "{\"initContainers\":null,\"containers\":[\"istio-proxy\",\"app\"],\"volumes\":[\"workload-socket\",\"workload-certs\",\"istio-xds\",\"istio-data\",\"istio-podinfo\",\"istiod-ca-cert\"],\"imagePullSecrets\":null,\"revision\":\"default\"}"
+      },
+      "APP_CONTAINERS": "app",
+      "CLUSTER_ID": "Kubernetes",
+      "ENVOY_PROMETHEUS_PORT": 15090,
+      "ENVOY_STATUS_PORT": 15021,
+      "GENERATOR": "grpc",
+      "INSTANCE_IPS": "192.168.19.141",
+      "ISTIO_PROXY_SHA": "7b292c7175692c822148b64005a731eb00365508",
+      "ISTIO_VERSION": "1.20.2",
+      "LABELS": {
+        "app": "echo",
+        "service.istio.io/canonical-name": "echo",
+        "service.istio.io/canonical-revision": "v1",
+        "version": "v1"
+      },
+      "MESH_ID": "cluster.local",
+      "NAME": "echo-v1-5859d7bc7d-wlb2d",
+      "NAMESPACE": "echo-grpc",
+      "NODE_NAME": "us-west-1.192.168.19.107",
+      "OWNER": "kubernetes://apis/apps/v1/namespaces/echo-grpc/deployments/echo-v1",
+      "PILOT_SAN": [
+        "istiod.istio-system.svc"
+      ],
+      "POD_PORTS": "[{\"containerPort\":17070,\"protocol\":\"TCP\"},{\"containerPort\":17171,\"protocol\":\"TCP\"},{\"containerPort\":8080,\"protocol\":\"TCP\"},{\"name\":\"tcp-health-port\",\"containerPort\":3333,\"protocol\":\"TCP\"}]",
+      "PROXY_CONFIG": {
+        "binaryPath": "/usr/local/bin/envoy",
+        "configPath": "./etc/istio/proxy",
+        "controlPlaneAuthPolicy": "MUTUAL_TLS",
+        "discoveryAddress": "istiod.istio-system.svc:15012",
+        "drainDuration": "45s",
+        "holdApplicationUntilProxyStarts": true,
+        "proxyAdminPort": 15000,
+        "serviceCluster": "istio-proxy",
+        "statNameLength": 189,
+        "statusPort": 15020,
+        "terminationDrainDuration": "5s",
+        "tracing": {
+          "zipkin": {
+            "address": "zipkin.istio-system:9411"
+          }
+        }
+      },
+      "SERVICE_ACCOUNT": "default",
+      "WORKLOAD_NAME": "echo-v1"
+    },
+    "locality": {},
+    "UserAgentVersionType": null
+  },
+  "certificate_providers": {
+    "default": {
+      "plugin_name": "file_watcher",
+      "config": {
+        "certificate_file": "/var/lib/istio/data/cert-chain.pem",
+        "private_key_file": "/var/lib/istio/data/key.pem",
+        "ca_certificate_file": "/var/lib/istio/data/root-cert.pem",
+        "refresh_interval": "900s"
+      }
+    }
+  },
+  "server_listener_resource_name_template": "xds.istio.io/grpc/lds/inbound/%s"
+}
diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/application.yml b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/application.yml
index 4d9b4bd788..78ef823a60 100644
--- a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/application.yml
+++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/application.yml
@@ -26,4 +26,4 @@ dubbo:
     name: tri
     port: 50051
   registry:
-    address: xds://istiod.istio-system.svc:15010?security=plaintext # istio://istiod.istio-system.svc:15012
+    address: xds://47.251.12.148:15010?security=plaintext # istio://istiod.istio-system.svc:15012
diff --git a/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/bootstrap.json b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/bootstrap.json
new file mode 100644
index 0000000000..a9daf11483
--- /dev/null
+++ b/dubbo-demo/dubbo-demo-xds/dubbo-demo-xds-provider/src/main/resources/bootstrap.json
@@ -0,0 +1,92 @@
+{
+  "xds_servers": [
+    {
+      "server_uri": "47.251.12.148:15010",
+      "channel_creds": [
+        {
+          "type": "insecure"
+        }
+      ],
+      "server_features": [
+        "xds_v3"
+      ]
+    }
+  ],
+  "node": {
+    "id": "sidecar~192.168.19.141~echo-v1-5764868574-whqs9.echo-grpc~echo-grpc.svc.cluster.local",
+    "metadata": {
+      "ANNOTATIONS": {
+        "inject.istio.io/templates": "grpc-agent",
+        "istio.io/rev": "default",
+        "kubectl.kubernetes.io/default-container": "app",
+        "kubectl.kubernetes.io/default-logs-container": "app",
+        "kubernetes.io/config.seen": "2024-07-02T17:22:26.354582057+08:00",
+        "kubernetes.io/config.source": "api",
+        "prometheus.io/path": "/stats/prometheus",
+        "prometheus.io/port": "15020",
+        "prometheus.io/scrape": "true",
+        "proxy.istio.io/config": "{\"holdApplicationUntilProxyStarts\": true}",
+        "proxy.istio.io/overrides": "{\"containers\":[{\"name\":\"app\",\"image\":\"gcr.io/istio-testing/app:latest\",\"args\":[\"--metrics=15014\",\"--port\",\"18080\",\"--tcp\",\"19090\",\"--xds-grpc-server=17070\",\"--grpc\",\"17070\",\"--grpc\",\"17171\",\"--port\",\"3333\",\"--port\",\"8080\",\"--version\",\"v1\",\"--crt=/cert.crt\",\"--key=/cert.key\"],\"ports\":[{\"containerPort\":17070,\"protocol\":\"TCP\"},{\"containerPort\":17171,\"protocol\":\"TCP\"},{\"containerPort\":8080,\" [...]
+        "sidecar.istio.io/rewriteAppHTTPProbers": "false",
+        "sidecar.istio.io/status": "{\"initContainers\":null,\"containers\":[\"istio-proxy\",\"app\"],\"volumes\":[\"workload-socket\",\"workload-certs\",\"istio-xds\",\"istio-data\",\"istio-podinfo\",\"istiod-ca-cert\"],\"imagePullSecrets\":null,\"revision\":\"default\"}"
+      },
+      "APP_CONTAINERS": "app",
+      "CLUSTER_ID": "Kubernetes",
+      "ENVOY_PROMETHEUS_PORT": 15090,
+      "ENVOY_STATUS_PORT": 15021,
+      "GENERATOR": "grpc",
+      "INSTANCE_IPS": "192.168.19.141",
+      "ISTIO_PROXY_SHA": "7b292c7175692c822148b64005a731eb00365508",
+      "ISTIO_VERSION": "1.20.2",
+      "LABELS": {
+        "app": "echo",
+        "service.istio.io/canonical-name": "echo",
+        "service.istio.io/canonical-revision": "v1",
+        "version": "v1"
+      },
+      "MESH_ID": "cluster.local",
+      "NAME": "echo-v1-5859d7bc7d-wlb2d",
+      "NAMESPACE": "echo-grpc",
+      "NODE_NAME": "us-west-1.192.168.19.107",
+      "OWNER": "kubernetes://apis/apps/v1/namespaces/echo-grpc/deployments/echo-v1",
+      "PILOT_SAN": [
+        "istiod.istio-system.svc"
+      ],
+      "POD_PORTS": "[{\"containerPort\":17070,\"protocol\":\"TCP\"},{\"containerPort\":17171,\"protocol\":\"TCP\"},{\"containerPort\":8080,\"protocol\":\"TCP\"},{\"name\":\"tcp-health-port\",\"containerPort\":3333,\"protocol\":\"TCP\"}]",
+      "PROXY_CONFIG": {
+        "binaryPath": "/usr/local/bin/envoy",
+        "configPath": "./etc/istio/proxy",
+        "controlPlaneAuthPolicy": "MUTUAL_TLS",
+        "discoveryAddress": "istiod.istio-system.svc:15012",
+        "drainDuration": "45s",
+        "holdApplicationUntilProxyStarts": true,
+        "proxyAdminPort": 15000,
+        "serviceCluster": "istio-proxy",
+        "statNameLength": 189,
+        "statusPort": 15020,
+        "terminationDrainDuration": "5s",
+        "tracing": {
+          "zipkin": {
+            "address": "zipkin.istio-system:9411"
+          }
+        }
+      },
+      "SERVICE_ACCOUNT": "default",
+      "WORKLOAD_NAME": "echo-v1"
+    },
+    "locality": {},
+    "UserAgentVersionType": null
+  },
+  "certificate_providers": {
+    "default": {
+      "plugin_name": "file_watcher",
+      "config": {
+        "certificate_file": "/var/lib/istio/data/cert-chain.pem",
+        "private_key_file": "/var/lib/istio/data/key.pem",
+        "ca_certificate_file": "/var/lib/istio/data/root-cert.pem",
+        "refresh_interval": "900s"
+      }
+    }
+  },
+  "server_listener_resource_name_template": "xds.istio.io/grpc/lds/inbound/%s"
+}
diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3SslContexts.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3SslContexts.java
index 950fbcad7d..f8ad728b88 100644
--- a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3SslContexts.java
+++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3SslContexts.java
@@ -65,7 +65,7 @@ public final class Http3SslContexts extends SslContext {
                         toX509Certificates(keyCertChainIn));
                 try (InputStream trustCertIn = cert.getTrustCertInputStream()) {
                     if (trustCertIn != null) {
-                        ClientAuth clientAuth = cert.getAuthPolicy() == AuthPolicy.CLIENT_AUTH
+                        ClientAuth clientAuth = cert.getAuthPolicy() == AuthPolicy.CLIENT_AUTH_STRICT
                                 ? ClientAuth.REQUIRE
                                 : ClientAuth.OPTIONAL;
                         builder.trustManager(toX509Certificates(trustCertIn)).clientAuth(clientAuth);
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/AdsObserver.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/AdsObserver.java
index ac20bb2ddc..f4758e78df 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/AdsObserver.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/AdsObserver.java
@@ -21,21 +21,30 @@ import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
 import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.xds.protocol.AbstractProtocol;
+import org.apache.dubbo.xds.resource.XdsResourceType;
+import org.apache.dubbo.xds.resource.update.ResourceUpdate;
+import org.apache.dubbo.xds.resource.update.ValidatedResourceUpdate;
 
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
 
 import io.envoyproxy.envoy.config.core.v3.Node;
 import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
 import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
 import io.grpc.stub.StreamObserver;
 
+import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_PARSING_XDS;
 import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_REQUEST_XDS;
 
 public class AdsObserver {
@@ -45,13 +54,14 @@ public class AdsObserver {
     private final Node node;
     private volatile XdsChannel xdsChannel;
 
-    private final Map<String, XdsListener> listeners = new ConcurrentHashMap<>();
+    private final Map<XdsResourceType<?>, ConcurrentMap<String, XdsRawResourceListener>> rawResourceListeners =
+            new ConcurrentHashMap<>();
 
     protected StreamObserver<DiscoveryRequest> requestObserver;
 
-    private CompletableFuture<String> future = new CompletableFuture<>();
+    private final CompletableFuture<String> future = new CompletableFuture<>();
 
-    private final Map<String, DiscoveryRequest> observedResources = new ConcurrentHashMap<>();
+    private final Map<String, XdsResourceType<?>> subscribedResourceTypeUrls = new HashMap<>();
 
     public AdsObserver(URL url, Node node) {
         this.url = url;
@@ -60,8 +70,68 @@ public class AdsObserver {
         this.applicationModel = url.getOrDefaultApplicationModel();
     }
 
-    public <T> void addListener(AbstractProtocol<T> protocol) {
-        listeners.put(protocol.getTypeUrl(), protocol);
+    public boolean hasSubscribed(XdsResourceType<?> type) {
+        return subscribedResourceTypeUrls.containsKey(type.typeUrl());
+    }
+
+    public void saveSubscribedType(XdsResourceType<?> type) {
+        subscribedResourceTypeUrls.put(type.typeUrl(), type);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends ResourceUpdate> XdsRawResourceProtocol<T> addListener(
+            String resourceName, XdsResourceType<T> clusterResourceType) {
+        ConcurrentMap<String, XdsRawResourceListener> resourceListeners =
+                rawResourceListeners.computeIfAbsent(clusterResourceType, k -> new ConcurrentHashMap<>());
+        return (XdsRawResourceProtocol<T>) resourceListeners.computeIfAbsent(
+                resourceName,
+                k -> new XdsRawResourceProtocol<>(this, NodeBuilder.build(), clusterResourceType, applicationModel));
+    }
+
+    public void adjustResourceSubscription(XdsResourceType<?> resourceType) {
+        this.request(buildDiscoveryRequest(resourceType, getResourcesToObserve(resourceType)));
+    }
+
+    public Set<String> getResourcesToObserve(XdsResourceType<?> resourceType) {
+        Map<String, XdsRawResourceListener> listenerMap =
+                rawResourceListeners.getOrDefault(resourceType, new ConcurrentHashMap<>());
+        Set<String> resourceNames = new HashSet<>();
+        for (Map.Entry<String, XdsRawResourceListener> entry : listenerMap.entrySet()) {
+            resourceNames.add(entry.getKey());
+        }
+        return resourceNames;
+    }
+
+    private <T extends ResourceUpdate> void process(
+            XdsResourceType<T> resourceTypeInstance, DiscoveryResponse response) {
+        ValidatedResourceUpdate<T> validatedResourceUpdate =
+                resourceTypeInstance.parse(XdsResourceType.xdsResourceTypeArgs, response.getResourcesList());
+        if (!validatedResourceUpdate.getErrors().isEmpty()) {
+            logger.error(
+                    REGISTRY_ERROR_PARSING_XDS,
+                    validatedResourceUpdate.getErrors().toArray());
+        }
+        ConcurrentMap<String, T> parsedResources = validatedResourceUpdate.getParsedResources().entrySet().stream()
+                .collect(Collectors.toConcurrentMap(
+                        Entry::getKey, e -> e.getValue().getResourceUpdate()));
+
+        Map<String, XdsRawResourceListener> resourceListenerMap =
+                rawResourceListeners.getOrDefault(resourceTypeInstance, new ConcurrentHashMap<>());
+        for (Map.Entry<String, XdsRawResourceListener> entry : resourceListenerMap.entrySet()) {
+            String resourceName = entry.getKey();
+            XdsRawResourceListener rawResourceListener = entry.getValue();
+            if (parsedResources.containsKey(resourceName)) {
+                rawResourceListener.onResourceUpdate(parsedResources.get(resourceName));
+            }
+        }
+    }
+
+    protected DiscoveryRequest buildDiscoveryRequest(XdsResourceType<?> resourceType, Set<String> resourceNames) {
+        return DiscoveryRequest.newBuilder()
+                .setNode(node)
+                .setTypeUrl(resourceType.typeUrl())
+                .addAllResourceNames(resourceNames)
+                .build();
     }
 
     public void request(DiscoveryRequest discoveryRequest) {
@@ -69,7 +139,6 @@ public class AdsObserver {
             requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(this, future));
         }
         requestObserver.onNext(discoveryRequest);
-        observedResources.put(discoveryRequest.getTypeUrl(), discoveryRequest);
         try {
             // TODO:This is to make the child thread receive the information.
             //  Maybe Using CountDownLatch would be better
@@ -87,11 +156,11 @@ public class AdsObserver {
     }
 
     private static class ResponseObserver implements StreamObserver<DiscoveryResponse> {
-        private AdsObserver adsObserver;
+        private final AdsObserver adsObserver;
 
-        private CompletableFuture future;
+        private final CompletableFuture<?> future;
 
-        public ResponseObserver(AdsObserver adsObserver, CompletableFuture future) {
+        public ResponseObserver(AdsObserver adsObserver, CompletableFuture<?> future) {
             this.adsObserver = adsObserver;
             this.future = future;
         }
@@ -102,22 +171,23 @@ public class AdsObserver {
             if (future != null) {
                 future.complete(null);
             }
-            XdsListener xdsListener = adsObserver.listeners.get(discoveryResponse.getTypeUrl());
-            xdsListener.process(discoveryResponse);
-            adsObserver.requestObserver.onNext(buildAck(discoveryResponse));
+
+            XdsResourceType<?> resourceType = fromTypeUrl(discoveryResponse.getTypeUrl());
+
+            adsObserver.process(resourceType, discoveryResponse);
+
+            adsObserver.requestObserver.onNext(buildAck(resourceType, discoveryResponse));
         }
 
-        protected DiscoveryRequest buildAck(DiscoveryResponse response) {
+        protected DiscoveryRequest buildAck(XdsResourceType<?> resourceType, DiscoveryResponse response) {
+
             // for ACK
             return DiscoveryRequest.newBuilder()
                     .setNode(adsObserver.node)
                     .setTypeUrl(response.getTypeUrl())
                     .setVersionInfo(response.getVersionInfo())
                     .setResponseNonce(response.getNonce())
-                    .addAllResourceNames(adsObserver
-                            .observedResources
-                            .get(response.getTypeUrl())
-                            .getResourceNamesList())
+                    .addAllResourceNames(adsObserver.getResourcesToObserve(resourceType))
                     .build();
         }
 
@@ -132,6 +202,10 @@ public class AdsObserver {
             logger.info("xDS Client completed");
             adsObserver.triggerReConnectTask();
         }
+
+        XdsResourceType<?> fromTypeUrl(String typeUrl) {
+            return adsObserver.subscribedResourceTypeUrls.get(typeUrl);
+        }
     }
 
     private void triggerReConnectTask() {
@@ -149,7 +223,8 @@ public class AdsObserver {
             if (xdsChannel.getChannel() != null) {
                 // Child thread not need to wait other child thread.
                 requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(this, null));
-                observedResources.values().forEach(requestObserver::onNext);
+                // FIXME, make sure recover all resource subscriptions.
+                //                observedResources.values().forEach(requestObserver::onNext);
                 return;
             } else {
                 logger.error(
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/PilotExchanger.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/PilotExchanger.java
index 2c55cce832..ad341f0d25 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/PilotExchanger.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/PilotExchanger.java
@@ -18,122 +18,42 @@ package org.apache.dubbo.xds;
 
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.utils.ConcurrentHashSet;
+import org.apache.dubbo.rpc.model.ApplicationModel;
 import org.apache.dubbo.xds.directory.XdsDirectory;
-import org.apache.dubbo.xds.protocol.XdsResourceListener;
-import org.apache.dubbo.xds.protocol.impl.CdsProtocol;
-import org.apache.dubbo.xds.protocol.impl.EdsProtocol;
-import org.apache.dubbo.xds.protocol.impl.LdsProtocol;
-import org.apache.dubbo.xds.protocol.impl.RdsProtocol;
-import org.apache.dubbo.xds.resource.route.VirtualHost;
-import org.apache.dubbo.xds.resource.update.EdsUpdate;
-import org.apache.dubbo.xds.resource.update.RdsUpdate;
+import org.apache.dubbo.xds.resource.XdsResourceType;
+import org.apache.dubbo.xds.resource.update.ResourceUpdate;
 
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 
 public class PilotExchanger {
 
+    private int pollingTimeout;
+    private ApplicationModel applicationModel;
     protected final AdsObserver adsObserver;
 
-    protected final LdsProtocol ldsProtocol;
-
-    protected final RdsProtocol rdsProtocol;
-
-    protected final EdsProtocol edsProtocol;
-
-    protected final CdsProtocol cdsProtocol;
-
     private final Set<String> domainObserveRequest = new ConcurrentHashSet<String>();
 
     private static PilotExchanger GLOBAL_PILOT_EXCHANGER = null;
 
-    private static final Map<String, VirtualHost> xdsVirtualHostMap = new ConcurrentHashMap<>();
-
-    private static final Map<String, EdsUpdate> xdsEndpointMap = new ConcurrentHashMap<>();
-
-    private final Map<String, Set<XdsDirectory>> rdsListeners = new ConcurrentHashMap<>();
-
-    private final Map<String, Set<XdsDirectory>> cdsListeners = new ConcurrentHashMap<>();
-
     protected PilotExchanger(URL url) {
-        int pollingTimeout = url.getParameter("pollingTimeout", 10);
+        this.pollingTimeout = url.getParameter("pollingTimeout", 10);
         adsObserver = new AdsObserver(url, NodeBuilder.build());
-
-        this.rdsProtocol =
-                new RdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, url.getOrDefaultApplicationModel());
-        this.edsProtocol =
-                new EdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, url.getOrDefaultApplicationModel());
-        this.ldsProtocol =
-                new LdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, url.getOrDefaultApplicationModel());
-        this.cdsProtocol =
-                new CdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout, url.getOrDefaultApplicationModel());
-
-        XdsResourceListener<RdsUpdate> pilotRdsListener = xdsRouteConfigurations -> xdsRouteConfigurations.forEach(
-                xdsRouteConfiguration -> xdsRouteConfiguration.getVirtualHosts().forEach(virtualHost -> {
-                    String serviceName = virtualHost.getDomains().get(0).split("\\.")[0];
-                    this.xdsVirtualHostMap.put(serviceName, virtualHost);
-                    // when resource update, notify subscribers
-                    if (rdsListeners.containsKey(serviceName)) {
-                        for (XdsDirectory listener : rdsListeners.get(serviceName)) {
-                            listener.onRdsChange(serviceName, virtualHost);
-                        }
-                    }
-                }));
-
-        XdsResourceListener<EdsUpdate> pilotEdsListener = edsUpdates -> {
-            edsUpdates.forEach(edsUpdate -> {
-                this.xdsEndpointMap.put(edsUpdate.getClusterName(), edsUpdate);
-                if (cdsListeners.containsKey(edsUpdate.getClusterName())) {
-                    for (XdsDirectory listener : cdsListeners.get(edsUpdate.getClusterName())) {
-                        listener.onEdsChange(edsUpdate.getClusterName(), edsUpdate);
-                    }
-                }
-            });
-        };
-
-        this.rdsProtocol.registerListen(pilotRdsListener);
-        this.edsProtocol.registerListen(pilotEdsListener);
-        // lds resources callback,listen to all rds resources in the callback function
-        this.ldsProtocol.registerListen(rdsProtocol.getLdsListener());
-        this.cdsProtocol.registerListen(edsProtocol.getCdsListener());
-
-        // cds resources callback,listen to all cds resources in the callback function
-        this.cdsProtocol.subscribeClusters();
-        this.ldsProtocol.subscribeListeners();
-    }
-
-    public static Map<String, VirtualHost> getXdsVirtualHostMap() {
-        return xdsVirtualHostMap;
-    }
-
-    public static Map<String, EdsUpdate> getXdsEndpointMap() {
-        return xdsEndpointMap;
+        this.applicationModel = url.getOrDefaultApplicationModel();
     }
 
-    public void subscribeRds(String applicationName, XdsDirectory listener) {
-        rdsListeners.computeIfAbsent(applicationName, key -> new ConcurrentHashSet<>());
-        rdsListeners.get(applicationName).add(listener);
-        if (xdsVirtualHostMap.containsKey(applicationName)) {
-            listener.onRdsChange(applicationName, this.xdsVirtualHostMap.get(applicationName));
+    public <T extends ResourceUpdate> void subscribeXdsResource(
+            String resourceName, XdsResourceType<T> resourceType, XdsResourceListener<T> resourceListener) {
+        if (!adsObserver.hasSubscribed(resourceType)) {
+            adsObserver.saveSubscribedType(resourceType);
         }
-    }
 
-    public void unSubscribeRds(String applicationName, XdsDirectory listener) {
-        rdsListeners.get(applicationName).remove(listener);
-    }
-
-    public void subscribeCds(String clusterName, XdsDirectory listener) {
-        cdsListeners.computeIfAbsent(clusterName, key -> new ConcurrentHashSet<>());
-        cdsListeners.get(clusterName).add(listener);
-        if (xdsEndpointMap.containsKey(clusterName)) {
-            listener.onEdsChange(clusterName, xdsEndpointMap.get(clusterName));
+        XdsRawResourceProtocol<T> xdsProtocol = adsObserver.addListener(resourceName, resourceType);
+        if (xdsProtocol != null) {
+            xdsProtocol.subscribeResource(resourceName, resourceType, resourceListener);
         }
     }
 
-    public void unSubscribeCds(String clusterName, XdsDirectory listener) {
-        cdsListeners.get(clusterName).remove(listener);
-    }
+    public void unSubscribeXdsResource(String clusterName, XdsDirectory listener) {}
 
     public static PilotExchanger initialize(URL url) {
         synchronized (PilotExchanger.class) {
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsRawResourceListener.java
similarity index 82%
rename from dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsListener.java
rename to dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsRawResourceListener.java
index fcd8d65b84..95549f0efa 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsListener.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsRawResourceListener.java
@@ -16,8 +16,8 @@
  */
 package org.apache.dubbo.xds;
 
-import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
+import org.apache.dubbo.xds.resource.update.ResourceUpdate;
 
-public interface XdsListener {
-    void process(DiscoveryResponse discoveryResponse);
+public interface XdsRawResourceListener<T extends ResourceUpdate> {
+    void onResourceUpdate(T resourceUpdate);
 }
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsRawResourceProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsRawResourceProtocol.java
new file mode 100644
index 0000000000..7cd7bbb47f
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsRawResourceProtocol.java
@@ -0,0 +1,224 @@
+/*
+ * 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.dubbo.xds;
+
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.xds.resource.XdsResourceType;
+import org.apache.dubbo.xds.resource.update.ResourceUpdate;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import io.envoyproxy.envoy.config.core.v3.Node;
+
+public class XdsRawResourceProtocol<T extends ResourceUpdate> implements XdsRawResourceListener<T> {
+
+    private static final ErrorTypeAwareLogger logger =
+            LoggerFactory.getErrorTypeAwareLogger(XdsRawResourceProtocol.class);
+
+    protected AdsObserver adsObserver;
+
+    protected final Node node;
+
+    protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+    protected final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
+
+    protected final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
+
+    protected Set<String> observeResourcesName;
+
+    public static final String emptyResourceName = "emptyResourcesName";
+    private final ReentrantLock resourceLock = new ReentrantLock();
+
+    protected Map<Set<String>, List<Consumer<Map<String, T>>>> consumerObserveMap = new ConcurrentHashMap<>();
+
+    public Map<Set<String>, List<Consumer<Map<String, T>>>> getConsumerObserveMap() {
+        return consumerObserveMap;
+    }
+
+    private XdsResourceType<T> resourceTypeInstance;
+
+    protected volatile T resourceUpdate;
+    // serviceKey to watcher
+    protected volatile Map<String, XdsResourceListener<T>> resourceListeners = new ConcurrentHashMap<>();
+
+    protected ApplicationModel applicationModel;
+
+    public XdsRawResourceProtocol(
+            AdsObserver adsObserver, Node node, XdsResourceType<T> resourceType, ApplicationModel applicationModel) {
+        this.adsObserver = adsObserver;
+        this.node = node;
+        this.applicationModel = applicationModel;
+        this.resourceTypeInstance = resourceType;
+    }
+
+    public String getTypeUrl() {
+        return resourceTypeInstance.typeUrl();
+    }
+
+    private void discoveryResponseListener(Map<String, T> oldResult, Map<String, T> newResult) {
+        Set<String> changedResourceNames = new HashSet<>();
+        oldResult.forEach((key, origin) -> {
+            if (!Objects.equals(origin, newResult.get(key))) {
+                changedResourceNames.add(key);
+            }
+        });
+        newResult.forEach((key, origin) -> {
+            if (!Objects.equals(origin, oldResult.get(key))) {
+                changedResourceNames.add(key);
+            }
+        });
+        if (changedResourceNames.isEmpty()) {
+            return;
+        }
+
+        logger.info("Receive resource update notification from xds server. Change resource count: "
+                + changedResourceNames.stream() + ". Type: " + getTypeUrl());
+
+        // call once for full data
+        try {
+            readLock.lock();
+            for (Map.Entry<Set<String>, List<Consumer<Map<String, T>>>> entry : consumerObserveMap.entrySet()) {
+                if (entry.getKey().stream().noneMatch(changedResourceNames::contains)) {
+                    // none update
+                    continue;
+                }
+
+                Map<String, T> dsResultMap =
+                        entry.getKey().stream().collect(Collectors.toMap(k -> k, v -> newResult.get(v)));
+                entry.getValue().forEach(o -> o.accept(dsResultMap));
+            }
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    @Override
+    public void onResourceUpdate(T resourceUpdate) {
+        if (resourceUpdate == null) {
+            return;
+        }
+
+        T oldData = this.resourceUpdate;
+        this.resourceUpdate = resourceUpdate;
+
+        if (!Objects.equals(oldData, resourceUpdate)) {
+            resourceListeners.forEach((resourceName, listener) -> {
+                listener.onResourceUpdate(resourceUpdate);
+            });
+        }
+    }
+
+    public void subscribeResource(
+            String resourceName, XdsResourceType<T> resourceType, XdsResourceListener<T> listener) {
+        if (resourceName == null) {
+            return;
+        }
+
+        XdsResourceListener<T> existingListener = resourceListeners.putIfAbsent(resourceName, listener);
+        if (existingListener == null) {
+            // update resource subscription
+            adsObserver.adjustResourceSubscription(resourceType);
+        } else {
+            listener.onResourceUpdate(resourceUpdate);
+        }
+    }
+
+    //
+    //    public void subscribeResource(Set<String> resourceNames) {
+    //        resourceNames = resourceNames == null ? Collections.emptySet() : resourceNames;
+    //
+    //        if (!resourceNames.isEmpty() && isCacheExistResource(resourceNames)) {
+    //            getResourceFromCache(resourceNames);
+    //        } else {
+    //            getResourceFromRemote(resourceNames);
+    //        }
+    //    }
+    //
+    //    private Map<String, T> getResourceFromCache(Set<String> resourceNames) {
+    //        return resourceNames.stream()
+    //                .filter(o -> !StringUtils.isEmpty(o))
+    //                .collect(Collectors.toMap(k -> k, this::getCacheResource));
+    //    }
+    //
+    //    public Map<String, T> getResourceFromRemote(Set<String> resourceNames) {
+    //        try {
+    //            resourceLock.lock();
+    //            CompletableFuture<Map<String, T>> future = new CompletableFuture<>();
+    //            observeResourcesName = resourceNames;
+    //            Set<String> consumerObserveResourceNames = new HashSet<>();
+    //            if (resourceNames.isEmpty()) {
+    //                consumerObserveResourceNames.add(emptyResourceName);
+    //            } else {
+    //                consumerObserveResourceNames = resourceNames;
+    //            }
+    //
+    //            Consumer<Map<String, T>> futureConsumer = future::complete;
+    //            try {
+    //                writeLock.lock();
+    //                ConcurrentHashMapUtils.computeIfAbsent(
+    //                                (ConcurrentHashMap<Set<String>, List<Consumer<Map<String, T>>>>)
+    // consumerObserveMap,
+    //                                consumerObserveResourceNames,
+    //                                key -> new ArrayList<>())
+    //                        .add(futureConsumer);
+    //            } finally {
+    //                writeLock.unlock();
+    //            }
+    //
+    //            Set<String> resourceNamesToObserve = new HashSet<>(resourceNames);
+    //            resourceNamesToObserve.addAll(resourcesMap.keySet());
+    //            adsObserver.request(buildDiscoveryRequest(resourceNamesToObserve));
+    //            logger.info("Send xDS Observe request to remote. Resource count: " + resourceNamesToObserve.size()
+    //                    + ". Resource Type: " + getTypeUrl());
+    //        } finally {
+    //            resourceLock.unlock();
+    //        }
+    //        return Collections.emptyMap();
+    //    }
+
+    //    public boolean isCacheExistResource(Set<String> resourceNames) {
+    //        for (String resourceName : resourceNames) {
+    //            if ("".equals(resourceName)) {
+    //                continue;
+    //            }
+    //            if (!resourcesMap.containsKey(resourceName)) {
+    //                return false;
+    //            }
+    //        }
+    //        return true;
+    //    }
+    //
+    //    public T getCacheResource(String resourceName) {
+    //        if (resourceName == null || resourceName.length() == 0) {
+    //            return null;
+    //        }
+    //        return resourcesMap.get(resourceName);
+    //    }
+
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsResourceListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsResourceListener.java
similarity index 88%
rename from dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsResourceListener.java
rename to dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsResourceListener.java
index fa9a4998f2..5dd7aa0123 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsResourceListener.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/XdsResourceListener.java
@@ -14,11 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.dubbo.xds.protocol;
-
-import java.util.List;
+package org.apache.dubbo.xds;
 
 public interface XdsResourceListener<T> {
-
-    void onResourceUpdate(List<T> resource);
+    void onResourceUpdate(T resource);
 }
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/RoutingUtils.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/RoutingUtils.java
new file mode 100644
index 0000000000..9bdcc9c47c
--- /dev/null
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/RoutingUtils.java
@@ -0,0 +1,194 @@
+/*
+ * 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.dubbo.xds.directory;
+
+import org.apache.dubbo.xds.resource.common.ThreadSafeRandom;
+import org.apache.dubbo.xds.resource.matcher.FractionMatcher;
+import org.apache.dubbo.xds.resource.matcher.HeaderMatcher;
+import org.apache.dubbo.xds.resource.matcher.PathMatcher;
+import org.apache.dubbo.xds.resource.route.RouteMatch;
+import org.apache.dubbo.xds.resource.route.VirtualHost;
+
+import javax.annotation.Nullable;
+
+import java.util.List;
+import java.util.Locale;
+
+import com.google.common.base.Joiner;
+import io.grpc.Metadata;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Utilities for performing virtual host domain name matching and route matching.
+ */
+public final class RoutingUtils {
+    // Prevent instantiation.
+    private RoutingUtils() {}
+
+    /**
+     * Returns the {@link VirtualHost} with the best match domain for the given hostname.
+     */
+    @Nullable
+    static VirtualHost findVirtualHostForHostName(List<VirtualHost> virtualHosts, String hostName) {
+        // Domain search order:
+        //  1. Exact domain names: ``www.foo.com``.
+        //  2. Suffix domain wildcards: ``*.foo.com`` or ``*-bar.foo.com``.
+        //  3. Prefix domain wildcards: ``foo.*`` or ``foo-*``.
+        //  4. Special wildcard ``*`` matching any domain.
+        //
+        //  The longest wildcards match first.
+        //  Assuming only a single virtual host in the entire route configuration can match
+        //  on ``*`` and a domain must be unique across all virtual hosts.
+        int matchingLen = -1; // longest length of wildcard pattern that matches host name
+        boolean exactMatchFound = false; // true if a virtual host with exactly matched domain found
+        VirtualHost targetVirtualHost = null; // target VirtualHost with longest matched domain
+        for (VirtualHost vHost : virtualHosts) {
+            for (String domain : vHost.getDomains()) {
+                boolean selected = false;
+                if (matchHostName(hostName, domain)) { // matching
+                    if (!domain.contains("*")) { // exact matching
+                        exactMatchFound = true;
+                        targetVirtualHost = vHost;
+                        break;
+                    } else if (domain.length() > matchingLen) { // longer matching pattern
+                        selected = true;
+                    } else if (domain.length() == matchingLen && domain.startsWith("*")) { // suffix matching
+                        selected = true;
+                    }
+                }
+                if (selected) {
+                    matchingLen = domain.length();
+                    targetVirtualHost = vHost;
+                }
+            }
+            if (exactMatchFound) {
+                break;
+            }
+        }
+        return targetVirtualHost;
+    }
+
+    /**
+     * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern} with
+     * case-insensitive.
+     *
+     * <p>Wildcard pattern rules:
+     * <ol>
+     * <li>A single asterisk (*) matches any domain.</li>
+     * <li>Asterisk (*) is only permitted in the left-most or the right-most part of the pattern,
+     *     but not both.</li>
+     * </ol>
+     */
+    private static boolean matchHostName(String hostName, String pattern) {
+        checkArgument(
+                hostName.length() != 0 && !hostName.startsWith(".") && !hostName.endsWith("."), "Invalid host name");
+        checkArgument(
+                pattern.length() != 0 && !pattern.startsWith(".") && !pattern.endsWith("."),
+                "Invalid pattern/domain name");
+
+        hostName = hostName.toLowerCase(Locale.US);
+        pattern = pattern.toLowerCase(Locale.US);
+        // hostName and pattern are now in lower case -- domain names are case-insensitive.
+
+        if (!pattern.contains("*")) {
+            // Not a wildcard pattern -- hostName and pattern must match exactly.
+            return hostName.equals(pattern);
+        }
+        // Wildcard pattern
+
+        if (pattern.length() == 1) {
+            return true;
+        }
+
+        int index = pattern.indexOf('*');
+
+        // At most one asterisk (*) is allowed.
+        if (pattern.indexOf('*', index + 1) != -1) {
+            return false;
+        }
+
+        // Asterisk can only match prefix or suffix.
+        if (index != 0 && index != pattern.length() - 1) {
+            return false;
+        }
+
+        // HostName must be at least as long as the pattern because asterisk has to
+        // match one or more characters.
+        if (hostName.length() < pattern.length()) {
+            return false;
+        }
+
+        if (index == 0 && hostName.endsWith(pattern.substring(1))) {
+            // Prefix matching fails.
+            return true;
+        }
+
+        // Pattern matches hostname if suffix matching succeeds.
+        return index == pattern.length() - 1 && hostName.startsWith(pattern.substring(0, pattern.length() - 1));
+    }
+
+    /**
+     * Returns {@code true} iff the given {@link RouteMatch} matches the RPC's full method name and
+     * headers.
+     */
+    static boolean matchRoute(RouteMatch routeMatch, String fullMethodName, Metadata headers, ThreadSafeRandom random) {
+        if (!matchPath(routeMatch.getPathMatcher(), fullMethodName)) {
+            return false;
+        }
+        for (HeaderMatcher headerMatcher : routeMatch.getHeaderMatchers()) {
+            if (!headerMatcher.matches(getHeaderValue(headers, headerMatcher.name()))) {
+                return false;
+            }
+        }
+        FractionMatcher fraction = routeMatch.getFractionMatcher();
+        return fraction == null || random.nextInt(fraction.getDenominator()) < fraction.getNumerator();
+    }
+
+    private static boolean matchPath(PathMatcher pathMatcher, String fullMethodName) {
+        if (pathMatcher.getPath() != null) {
+            return pathMatcher.isCaseSensitive()
+                    ? pathMatcher.getPath().equals(fullMethodName)
+                    : pathMatcher.getPath().equalsIgnoreCase(fullMethodName);
+        } else if (pathMatcher.getPrefix() != null) {
+            return pathMatcher.isCaseSensitive()
+                    ? fullMethodName.startsWith(pathMatcher.getPrefix())
+                    : fullMethodName
+                            .toLowerCase(Locale.US)
+                            .startsWith(pathMatcher.getPrefix().toLowerCase(Locale.US));
+        }
+        return pathMatcher.getRegEx().matches(fullMethodName);
+    }
+
+    @Nullable
+    private static String getHeaderValue(Metadata headers, String headerName) {
+        if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
+            return null;
+        }
+        if (headerName.equals("content-type")) {
+            return "application/grpc";
+        }
+        Metadata.Key<String> key;
+        try {
+            key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+        Iterable<String> values = headers.getAll(key);
+        return values == null ? null : Joiner.on(",").join(values);
+    }
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/XdsDirectory.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/XdsDirectory.java
index e4c121e479..441b8080db 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/XdsDirectory.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/directory/XdsDirectory.java
@@ -19,6 +19,7 @@ package org.apache.dubbo.xds.directory;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.url.component.URLAddress;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
@@ -28,20 +29,43 @@ import org.apache.dubbo.rpc.cluster.SingleRouterChain;
 import org.apache.dubbo.rpc.cluster.directory.AbstractDirectory;
 import org.apache.dubbo.rpc.cluster.router.state.BitList;
 import org.apache.dubbo.xds.PilotExchanger;
+import org.apache.dubbo.xds.XdsResourceListener;
+import org.apache.dubbo.xds.directory.XdsDirectory.LdsUpdateWatcher.RdsUpdateWatcher;
+import org.apache.dubbo.xds.resource.XdsClusterResource;
+import org.apache.dubbo.xds.resource.XdsListenerResource;
+import org.apache.dubbo.xds.resource.XdsRouteConfigureResource;
+import org.apache.dubbo.xds.resource.cluster.OutlierDetection;
+import org.apache.dubbo.xds.resource.common.Locality;
+import org.apache.dubbo.xds.resource.endpoint.DropOverload;
 import org.apache.dubbo.xds.resource.endpoint.LbEndpoint;
+import org.apache.dubbo.xds.resource.endpoint.LocalityLbEndpoints;
+import org.apache.dubbo.xds.resource.filter.NamedFilterConfig;
+import org.apache.dubbo.xds.resource.listener.HttpConnectionManager;
+import org.apache.dubbo.xds.resource.listener.security.UpstreamTlsContext;
 import org.apache.dubbo.xds.resource.route.ClusterWeight;
 import org.apache.dubbo.xds.resource.route.Route;
 import org.apache.dubbo.xds.resource.route.RouteAction;
 import org.apache.dubbo.xds.resource.route.VirtualHost;
+import org.apache.dubbo.xds.resource.update.CdsUpdate;
+import org.apache.dubbo.xds.resource.update.CdsUpdate.ClusterType;
 import org.apache.dubbo.xds.resource.update.EdsUpdate;
+import org.apache.dubbo.xds.resource.update.LdsUpdate;
+import org.apache.dubbo.xds.resource.update.RdsUpdate;
 
+import javax.annotation.Nullable;
+
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
+
+import com.google.common.collect.Sets;
 
 public class XdsDirectory<T> extends AbstractDirectory<T> {
 
@@ -63,6 +87,11 @@ public class XdsDirectory<T> extends AbstractDirectory<T> {
 
     private static ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(XdsDirectory.class);
 
+    private Map<String, LdsUpdateWatcher> ldsWatchers = new HashMap<>();
+    private Map<String, RdsUpdateWatcher> rdsWatchers = new HashMap<>();
+    private Map<String, CdsUpdateNodeDirectory> cdsWatchers = new HashMap<>();
+    private Map<String, EdsUpdateLeafDirectory> edsWatchers = new HashMap<>();
+
     public XdsDirectory(Directory<T> directory) {
         super(directory.getUrl(), null, true, directory.getConsumerUrl());
         this.serviceType = directory.getInterface();
@@ -76,7 +105,9 @@ public class XdsDirectory<T> extends AbstractDirectory<T> {
 
         // subscribe resource
         for (String applicationName : applicationNames) {
-            pilotExchanger.subscribeRds(applicationName, this);
+            LdsUpdateWatcher ldsUpdateWatcher = new LdsUpdateWatcher(applicationName);
+            ldsWatchers.putIfAbsent(applicationName, ldsUpdateWatcher);
+            pilotExchanger.subscribeXdsResource(applicationName, XdsListenerResource.getInstance(), ldsUpdateWatcher);
         }
     }
 
@@ -112,13 +143,6 @@ public class XdsDirectory<T> extends AbstractDirectory<T> {
         return super.getInvokers();
     }
 
-    public void onRdsChange(String applicationName, VirtualHost xdsVirtualHost) {
-        Set<String> oldCluster = getAllCluster();
-        xdsVirtualHostMap.put(applicationName, xdsVirtualHost);
-        Set<String> newCluster = getAllCluster();
-        changeClusterSubscribe(oldCluster, newCluster);
-    }
-
     private Set<String> getAllCluster() {
         if (CollectionUtils.isEmptyMap(xdsVirtualHostMap)) {
             return new HashSet<>();
@@ -139,53 +163,283 @@ public class XdsDirectory<T> extends AbstractDirectory<T> {
         return clusters;
     }
 
-    private void changeClusterSubscribe(Set<String> oldCluster, Set<String> newCluster) {
-        Set<String> removeSubscribe = new HashSet<>(oldCluster);
-        Set<String> addSubscribe = new HashSet<>(newCluster);
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        //
+        //        pilotExchanger.unSubscribeXdsResource(resourceName, this);
+    }
+
+    public class LdsUpdateWatcher implements XdsResourceListener<LdsUpdate> {
+        private final String ldsResourceName;
+
+        @Nullable
+        private Set<String> existingClusters; // clusters to which new requests can be routed
 
-        removeSubscribe.removeAll(newCluster);
-        addSubscribe.removeAll(oldCluster);
+        @Nullable
+        private RdsUpdateWatcher rdsUpdateWatcher;
 
-        // remove subscribe cluster
-        for (String cluster : removeSubscribe) {
-            pilotExchanger.unSubscribeCds(cluster, this);
-            xdsEndpointMap.remove(cluster);
-            // TODO: delete invokers which belong unsubscribed cluster
+        public LdsUpdateWatcher(String ldsResourceName) {
+            this.ldsResourceName = ldsResourceName;
         }
-        // add subscribe cluster
-        for (String cluster : addSubscribe) {
-            pilotExchanger.subscribeCds(cluster, this);
+
+        @Override
+        public void onResourceUpdate(LdsUpdate update) {
+            HttpConnectionManager httpConnectionManager = update.getHttpConnectionManager();
+            List<VirtualHost> virtualHosts = httpConnectionManager.getVirtualHosts();
+            String rdsName = httpConnectionManager.getRdsName();
+
+            if (virtualHosts != null) {
+                updateRoutes(
+                        virtualHosts,
+                        httpConnectionManager.getHttpMaxStreamDurationNano(),
+                        httpConnectionManager.getHttpFilterConfigs());
+            } else {
+                rdsUpdateWatcher = new RdsUpdateWatcher(
+                        rdsName,
+                        httpConnectionManager.getHttpMaxStreamDurationNano(),
+                        httpConnectionManager.getHttpFilterConfigs());
+                rdsWatchers.putIfAbsent(rdsName, rdsUpdateWatcher);
+                pilotExchanger.subscribeXdsResource(rdsName, XdsRouteConfigureResource.getInstance(), rdsUpdateWatcher);
+            }
+        }
+
+        private void updateRoutes(
+                List<VirtualHost> virtualHosts,
+                long httpMaxStreamDurationNano,
+                @Nullable List<NamedFilterConfig> filterConfigs) {
+            //            String authority = overrideAuthority != null ? overrideAuthority : ldsResourceName;
+            String authority = ldsResourceName;
+            VirtualHost virtualHost = RoutingUtils.findVirtualHostForHostName(virtualHosts, authority);
+            if (virtualHost == null) {
+                return;
+            }
+
+            List<Route> routes = virtualHost.getRoutes();
+
+            // Populate all clusters to which requests can be routed to through the virtual host.
+            Set<String> clusters = new HashSet<>();
+            // uniqueName -> clusterName
+            Map<String, String> clusterNameMap = new HashMap<>();
+            for (Route route : routes) {
+                RouteAction action = route.getRouteAction();
+                String clusterName;
+                if (action != null) {
+                    if (action.getCluster() != null) {
+                        clusterName = action.getCluster();
+                        clusters.add(clusterName);
+                        clusterNameMap.put(clusterName, action.getCluster());
+                    } else if (action.getWeightedClusters() != null) {
+                        for (ClusterWeight weighedCluster : action.getWeightedClusters()) {
+                            clusterName = weighedCluster.getName();
+                            clusters.add(clusterName);
+                            clusterNameMap.put(clusterName, weighedCluster.getName());
+                        }
+                    }
+                }
+            }
+
+            boolean shouldUpdateResult = existingClusters == null;
+            Set<String> addedClusters =
+                    existingClusters == null ? clusters : Sets.difference(clusters, existingClusters);
+            Set<String> deletedClusters =
+                    existingClusters == null ? Collections.emptySet() : Sets.difference(existingClusters, clusters);
+            existingClusters = clusters;
+            for (String cluster : addedClusters) {
+                CdsUpdateNodeDirectory cdsUpdateWatcher = new CdsUpdateNodeDirectory();
+                cdsWatchers.putIfAbsent(cluster, cdsUpdateWatcher);
+                pilotExchanger.subscribeXdsResource(cluster, XdsClusterResource.getInstance(), cdsUpdateWatcher);
+            }
+        }
+
+        public class RdsUpdateWatcher implements XdsResourceListener<RdsUpdate> {
+            private String rdsName;
+
+            private final long httpMaxStreamDurationNano;
+
+            @Nullable
+            private final List<NamedFilterConfig> filterConfigs;
+
+            public RdsUpdateWatcher(
+                    String rdsName, long httpMaxStreamDurationNano, @Nullable List<NamedFilterConfig> filterConfigs) {
+                this.rdsName = rdsName;
+                this.httpMaxStreamDurationNano = httpMaxStreamDurationNano;
+                this.filterConfigs = filterConfigs;
+            }
+
+            @Override
+            public void onResourceUpdate(RdsUpdate update) {
+                if (RdsUpdateWatcher.this != rdsUpdateWatcher) {
+                    return;
+                }
+                updateRoutes(update.getVirtualHosts(), httpMaxStreamDurationNano, filterConfigs);
+            }
         }
     }
 
-    public void onEdsChange(String clusterName, EdsUpdate edsUpdate) {
-        xdsEndpointMap.put(clusterName, edsUpdate);
-        //        String lbPolicy = xdsCluster.getLbPolicy();
-        List<LbEndpoint> xdsEndpoints = edsUpdate.getLocalityLbEndpointsMap().values().stream()
-                .flatMap(e -> e.getEndpoints().stream())
-                .collect(Collectors.toList());
-        BitList<Invoker<T>> invokers = new BitList<>(Collections.emptyList());
-        xdsEndpoints.forEach(e -> {
-            String ip = e.getAddresses().getFirst().getAddress();
-            int port = e.getAddresses().getFirst().getPort();
-            URL url = new URL(this.protocolName, ip, port, this.serviceType.getName(), this.url.getParameters());
-            // set cluster name
-            url = url.addParameter("clusterID", clusterName);
-            // set load balance policy
-            //            url = url.addParameter("loadbalance", lbPolicy);
-            //  cluster to invoker
-            Invoker<T> invoker = this.protocol.refer(this.serviceType, url);
-            invokers.add(invoker);
-        });
-        // TODO: Consider cases where some clients are not available
-        // super.getInvokers().addAll(invokers);
-        // TODO: Need add new api which can add invokers, because a XdsDirectory need monitor multi clusters.
-        super.setInvokers(invokers);
-        //        xdsCluster.setInvokers(invokers);
+    /**
+     * This is the internal node of the Directory tree, which is responsible for creating invokers from clusters.
+     *
+     * Each invoker instance created in this should be representing a cluster pointing to another Directory instead of a specific instance invoker.
+     */
+    public class CdsUpdateNodeDirectory implements XdsResourceListener<CdsUpdate> {
+        @Override
+        public void onResourceUpdate(CdsUpdate update) {
+            // 啥都不干,就是把 aggregate logicalDns eds 三种做个分类处理,其中eds的不用做什么事情
+            if (update.getClusterType() == ClusterType.AGGREGATE) {
+                String clusterName = update.getClusterName();
+                for (String cluster : update.getPrioritizedClusterNames()) {
+                    // create internal node directory.
+                }
+            } else if (update.getClusterType() == ClusterType.EDS) {
+                // create leaf directory.
+            } else {
+
+            }
+        }
     }
 
-    @Override
-    public boolean isAvailable() {
-        return true;
+    /**
+     * This is the leaf node of the Directory tree, which is responsible for creating invokers from endpoints.
+     *
+     * Each invoker instance created in this  should be representing a specific dubbo provider instance.
+     */
+    public class EdsUpdateLeafDirectory implements XdsResourceListener<EdsUpdate> {
+        private final String clusterName;
+        private final String edsResourceName;
+
+        @Nullable
+        protected final Long maxConcurrentRequests;
+
+        @Nullable
+        protected final UpstreamTlsContext tlsContext;
+
+        @Nullable
+        protected final OutlierDetection outlierDetection;
+
+        private Map<Locality, String> localityPriorityNames = Collections.emptyMap();
+
+        int priorityNameGenId = 1;
+
+        public EdsUpdateLeafDirectory(
+                String clusterName,
+                String edsResourceName,
+                @Nullable Long maxConcurrentRequests,
+                @Nullable UpstreamTlsContext tlsContext,
+                @Nullable OutlierDetection outlierDetection) {
+            this.clusterName = clusterName;
+            this.edsResourceName = edsResourceName;
+            this.maxConcurrentRequests = maxConcurrentRequests;
+            this.tlsContext = tlsContext;
+            this.outlierDetection = outlierDetection;
+        }
+
+        @Override
+        public void onResourceUpdate(EdsUpdate update) {
+            Map<Locality, LocalityLbEndpoints> localityLbEndpoints = update.getLocalityLbEndpointsMap();
+            List<DropOverload> dropOverloads = update.getDropPolicies();
+            List<URLAddress> addresses = new ArrayList<>();
+            Map<String, Map<Locality, Integer>> prioritizedLocalityWeights = new HashMap<>();
+            List<String> sortedPriorityNames = generatePriorityNames(clusterName, localityLbEndpoints);
+            for (Locality locality : localityLbEndpoints.keySet()) {
+                LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality);
+                String priorityName = localityPriorityNames.get(locality);
+                boolean discard = true;
+                for (LbEndpoint endpoint : localityLbInfo.getEndpoints()) {
+                    if (endpoint.isHealthy()) {
+                        discard = false;
+                        long weight = localityLbInfo.getLocalityWeight();
+                        if (endpoint.getLoadBalancingWeight() != 0) {
+                            weight *= endpoint.getLoadBalancingWeight();
+                        }
+                        addresses.add(endpoint.getAddresses().get(0));
+                    }
+                }
+                if (discard) {
+                    logger.info("Discard locality {0} with 0 healthy endpoints", locality);
+                    continue;
+                }
+                if (!prioritizedLocalityWeights.containsKey(priorityName)) {
+                    prioritizedLocalityWeights.put(priorityName, new HashMap<Locality, Integer>());
+                }
+                prioritizedLocalityWeights.get(priorityName).put(locality, localityLbInfo.getLocalityWeight());
+            }
+
+            sortedPriorityNames.retainAll(prioritizedLocalityWeights.keySet());
+        }
+
+        private List<String> generatePriorityNames(
+                String name, Map<Locality, LocalityLbEndpoints> localityLbEndpoints) {
+            TreeMap<Integer, List<Locality>> todo = new TreeMap<>();
+            for (Locality locality : localityLbEndpoints.keySet()) {
+                int priority = localityLbEndpoints.get(locality).getPriority();
+                if (!todo.containsKey(priority)) {
+                    todo.put(priority, new ArrayList<>());
+                }
+                todo.get(priority).add(locality);
+            }
+            Map<Locality, String> newNames = new HashMap<>();
+            Set<String> usedNames = new HashSet<>();
+            List<String> ret = new ArrayList<>();
+            for (Integer priority : todo.keySet()) {
+                String foundName = "";
+                for (Locality locality : todo.get(priority)) {
+                    if (localityPriorityNames.containsKey(locality)
+                            && usedNames.add(localityPriorityNames.get(locality))) {
+                        foundName = localityPriorityNames.get(locality);
+                        break;
+                    }
+                }
+                if ("".equals(foundName)) {
+                    foundName = String.format(Locale.US, "%s[child%d]", name, priorityNameGenId++);
+                }
+                for (Locality locality : todo.get(priority)) {
+                    newNames.put(locality, foundName);
+                }
+                ret.add(foundName);
+            }
+            localityPriorityNames = newNames;
+            return ret;
+        }
     }
+
+    //
+    //    public void onResourceUpdate(CdsUpdate cdsUpdate) {
+    //        // for eds cluster, do nothing
+    //
+    //        // for aggregate clusters, do subscription
+    //        String clusterName = cdsUpdate.getClusterName();
+    //        this.pilotExchanger.subscribeCds(clusterName, this);
+    //    }
+    //
+    //    public void onResourceUpdate(String clusterName, EdsUpdate edsUpdate) {
+    //        xdsEndpointMap.put(clusterName, edsUpdate);
+    //        //        String lbPolicy = xdsCluster.getLbPolicy();
+    //        List<LbEndpoint> xdsEndpoints = edsUpdate.getLocalityLbEndpointsMap().values().stream()
+    //                .flatMap(e -> e.getEndpoints().stream())
+    //                .collect(Collectors.toList());
+    //        BitList<Invoker<T>> invokers = new BitList<>(Collections.emptyList());
+    //        xdsEndpoints.forEach(e -> {
+    //            String ip = e.getAddresses().get(0).getAddress();
+    //            int port = e.getAddresses().get(0).getPort();
+    //            URL url = new URL(this.protocolName, ip, port, this.serviceType.getName(), this.url.getParameters());
+    //            // set cluster name
+    //            url = url.addParameter("clusterID", clusterName);
+    //            // set load balance policy
+    //            //            url = url.addParameter("loadbalance", lbPolicy);
+    //            //  cluster to invoker
+    //            Invoker<T> invoker = this.protocol.refer(this.serviceType, url);
+    //            invokers.add(invoker);
+    //        });
+    //        // TODO: Consider cases where some clients are not available
+    //        // super.getInvokers().addAll(invokers);
+    //        // TODO: Need add new api which can add invokers, because a XdsDirectory need monitor multi clusters.
+    //        super.setInvokers(invokers);
+    //        //        xdsCluster.setInvokers(invokers);
+    //    }
 }
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/CdsListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/CdsListener.java
index 2e875d6fc6..bc8498cbbb 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/CdsListener.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/CdsListener.java
@@ -18,8 +18,11 @@ package org.apache.dubbo.xds.listener;
 
 import org.apache.dubbo.common.extension.ExtensionScope;
 import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.xds.protocol.XdsResourceListener;
 import org.apache.dubbo.xds.resource.update.CdsUpdate;
 
+import java.util.List;
+
 @SPI(scope = ExtensionScope.APPLICATION)
-public interface CdsListener extends XdsResourceListener<CdsUpdate> {}
+public interface CdsListener {
+    void onResourceUpdate(List<CdsUpdate> resource);
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/LdsListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/LdsListener.java
index e73e652c64..f874e2e086 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/LdsListener.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/LdsListener.java
@@ -18,8 +18,11 @@ package org.apache.dubbo.xds.listener;
 
 import org.apache.dubbo.common.extension.ExtensionScope;
 import org.apache.dubbo.common.extension.SPI;
-import org.apache.dubbo.xds.protocol.XdsResourceListener;
 import org.apache.dubbo.xds.resource.update.LdsUpdate;
 
+import java.util.List;
+
 @SPI(scope = ExtensionScope.APPLICATION)
-public interface LdsListener extends XdsResourceListener<LdsUpdate> {}
+public interface LdsListener {
+    void onResourceUpdate(List<LdsUpdate> resource);
+}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/UpstreamTlsConfigListener.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/UpstreamTlsConfigListener.java
index 6380f36570..d5a6e0f554 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/UpstreamTlsConfigListener.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/listener/UpstreamTlsConfigListener.java
@@ -52,7 +52,6 @@ public class UpstreamTlsConfigListener implements CdsListener {
         this.tlsConfigRepository = application.getBeanFactory().getOrRegisterBean(XdsTlsConfigRepository.class);
     }
 
-    @Override
     public void onResourceUpdate(List<CdsUpdate> resources) {
         Map<String, UpstreamTlsConfig> configs = new ConcurrentHashMap<>(16);
         List<Cluster> clusters =
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/AbstractProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/AbstractProtocol.java
deleted file mode 100644
index 7f04e1d8a5..0000000000
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/AbstractProtocol.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * 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.dubbo.xds.protocol;
-
-import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.ConcurrentHashMapUtils;
-import org.apache.dubbo.common.utils.StringUtils;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.xds.AdsObserver;
-import org.apache.dubbo.xds.XdsListener;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
-import io.envoyproxy.envoy.config.core.v3.Node;
-import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
-import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
-
-public abstract class AbstractProtocol<T> implements XdsProtocol, XdsListener {
-
-    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractProtocol.class);
-
-    protected AdsObserver adsObserver;
-
-    protected final Node node;
-
-    private final int checkInterval;
-
-    protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
-
-    protected final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
-
-    protected final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
-
-    protected Set<String> observeResourcesName;
-
-    public static final String emptyResourceName = "emptyResourcesName";
-    private final ReentrantLock resourceLock = new ReentrantLock();
-
-    protected Map<Set<String>, List<Consumer<Map<String, T>>>> consumerObserveMap = new ConcurrentHashMap<>();
-
-    public Map<Set<String>, List<Consumer<Map<String, T>>>> getConsumerObserveMap() {
-        return consumerObserveMap;
-    }
-
-    protected Map<String, T> resourcesMap = new ConcurrentHashMap<>();
-
-    protected List<XdsResourceListener<T>> resourceListeners = new CopyOnWriteArrayList<>();
-
-    protected ApplicationModel applicationModel;
-
-    public AbstractProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) {
-        this.adsObserver = adsObserver;
-        this.node = node;
-        this.checkInterval = checkInterval;
-        this.applicationModel = applicationModel;
-        adsObserver.addListener(this);
-    }
-
-    public void registerListen(XdsResourceListener<T> listener) {
-        this.resourceListeners.add(listener);
-    }
-
-    /**
-     * Abstract method to obtain Type-URL from sub-class
-     *
-     * @return Type-URL of xDS
-     */
-    public abstract String getTypeUrl();
-
-    public boolean isCacheExistResource(Set<String> resourceNames) {
-        for (String resourceName : resourceNames) {
-            if ("".equals(resourceName)) {
-                continue;
-            }
-            if (!resourcesMap.containsKey(resourceName)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public T getCacheResource(String resourceName) {
-        if (resourceName == null || resourceName.length() == 0) {
-            return null;
-        }
-        return resourcesMap.get(resourceName);
-    }
-
-    @Override
-    public void subscribeResource(Set<String> resourceNames) {
-        resourceNames = resourceNames == null ? Collections.emptySet() : resourceNames;
-
-        if (!resourceNames.isEmpty() && isCacheExistResource(resourceNames)) {
-            getResourceFromCache(resourceNames);
-        } else {
-            getResourceFromRemote(resourceNames);
-        }
-    }
-
-    private Map<String, T> getResourceFromCache(Set<String> resourceNames) {
-        return resourceNames.stream()
-                .filter(o -> !StringUtils.isEmpty(o))
-                .collect(Collectors.toMap(k -> k, this::getCacheResource));
-    }
-
-    public Map<String, T> getResourceFromRemote(Set<String> resourceNames) {
-        try {
-            resourceLock.lock();
-            CompletableFuture<Map<String, T>> future = new CompletableFuture<>();
-            observeResourcesName = resourceNames;
-            Set<String> consumerObserveResourceNames = new HashSet<>();
-            if (resourceNames.isEmpty()) {
-                consumerObserveResourceNames.add(emptyResourceName);
-            } else {
-                consumerObserveResourceNames = resourceNames;
-            }
-
-            Consumer<Map<String, T>> futureConsumer = future::complete;
-            try {
-                writeLock.lock();
-                ConcurrentHashMapUtils.computeIfAbsent(
-                                (ConcurrentHashMap<Set<String>, List<Consumer<Map<String, T>>>>) consumerObserveMap,
-                                consumerObserveResourceNames,
-                                key -> new ArrayList<>())
-                        .add(futureConsumer);
-            } finally {
-                writeLock.unlock();
-            }
-
-            Set<String> resourceNamesToObserve = new HashSet<>(resourceNames);
-            resourceNamesToObserve.addAll(resourcesMap.keySet());
-            adsObserver.request(buildDiscoveryRequest(resourceNamesToObserve));
-            logger.info("Send xDS Observe request to remote. Resource count: " + resourceNamesToObserve.size()
-                    + ". Resource Type: " + getTypeUrl());
-        } finally {
-            resourceLock.unlock();
-        }
-        return Collections.emptyMap();
-    }
-
-    protected DiscoveryRequest buildDiscoveryRequest(Set<String> resourceNames) {
-        return DiscoveryRequest.newBuilder()
-                .setNode(node)
-                .setTypeUrl(getTypeUrl())
-                .addAllResourceNames(resourceNames)
-                .build();
-    }
-
-    //    protected abstract Map<String, T> decodeDiscoveryResponse(DiscoveryResponse response);
-
-    protected abstract Map<String, T> decodeDiscoveryResponse(DiscoveryResponse response);
-
-    @Override
-    public final void process(DiscoveryResponse discoveryResponse) {
-        //        Map<String, T> newResult = decodeDiscoveryResponse(discoveryResponse);
-        Map<String, T> oldResource = resourcesMap;
-        // discoveryResponseListener(oldResource, newResult);
-
-        Map<String, T> newResult = decodeDiscoveryResponse(discoveryResponse);
-        resourceListeners.forEach(l -> l.onResourceUpdate(new ArrayList<>(newResult.values())));
-        resourcesMap = newResult;
-    }
-
-    private void discoveryResponseListener(Map<String, T> oldResult, Map<String, T> newResult) {
-        Set<String> changedResourceNames = new HashSet<>();
-        oldResult.forEach((key, origin) -> {
-            if (!Objects.equals(origin, newResult.get(key))) {
-                changedResourceNames.add(key);
-            }
-        });
-        newResult.forEach((key, origin) -> {
-            if (!Objects.equals(origin, oldResult.get(key))) {
-                changedResourceNames.add(key);
-            }
-        });
-        if (changedResourceNames.isEmpty()) {
-            return;
-        }
-
-        logger.info("Receive resource update notification from xds server. Change resource count: "
-                + changedResourceNames.stream() + ". Type: " + getTypeUrl());
-
-        // call once for full data
-        try {
-            readLock.lock();
-            for (Map.Entry<Set<String>, List<Consumer<Map<String, T>>>> entry : consumerObserveMap.entrySet()) {
-                if (entry.getKey().stream().noneMatch(changedResourceNames::contains)) {
-                    // none update
-                    continue;
-                }
-
-                Map<String, T> dsResultMap =
-                        entry.getKey().stream().collect(Collectors.toMap(k -> k, v -> newResult.get(v)));
-                entry.getValue().forEach(o -> o.accept(dsResultMap));
-            }
-        } finally {
-            readLock.unlock();
-        }
-    }
-}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsProtocol.java
deleted file mode 100644
index 838988ad51..0000000000
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/XdsProtocol.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.dubbo.xds.protocol;
-
-import java.util.Set;
-
-public interface XdsProtocol {
-    /**
-     * Gets all resources by the specified resource name.
-     * For LDS, the {@param resourceNames} is ignored
-     *
-     * @param resourceNames specified resource name
-     * @return resources, null if request failed
-     */
-    void subscribeResource(Set<String> resourceNames);
-
-    /**
-     * Add a observer resource with {@link Consumer}
-     *
-     * @param resourceNames specified resource name
-     * @param consumer      resource notifier, will be called when resource updated
-     * @return requestId, used when resourceNames update with {@link XdsProtocol#updateObserve(long, Set)}
-     */
-    // void observeResource(Set<String> resourceNames, Consumer<Map<String, T>> consumer, boolean isReConnect);
-}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/CdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/CdsProtocol.java
deleted file mode 100644
index 33525699a4..0000000000
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/CdsProtocol.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.dubbo.xds.protocol.impl;
-
-import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.xds.AdsObserver;
-import org.apache.dubbo.xds.listener.CdsListener;
-import org.apache.dubbo.xds.protocol.AbstractProtocol;
-import org.apache.dubbo.xds.resource.XdsClusterResource;
-import org.apache.dubbo.xds.resource.XdsResourceType;
-import org.apache.dubbo.xds.resource.update.CdsUpdate;
-import org.apache.dubbo.xds.resource.update.ValidatedResourceUpdate;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
-import io.envoyproxy.envoy.config.core.v3.Node;
-import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
-
-import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_PARSING_XDS;
-
-public class CdsProtocol extends AbstractProtocol<CdsUpdate> {
-    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(CdsProtocol.class);
-
-    public void setUpdateCallback(Consumer<Set<String>> updateCallback) {
-        this.updateCallback = updateCallback;
-    }
-
-    private static final XdsClusterResource xdsClusterResource = XdsClusterResource.getInstance();
-
-    private Consumer<Set<String>> updateCallback;
-
-    public CdsProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) {
-        super(adsObserver, node, checkInterval, applicationModel);
-        List<CdsListener> ldsListeners =
-                applicationModel.getExtensionLoader(CdsListener.class).getActivateExtensions();
-        ldsListeners.forEach(this::registerListen);
-    }
-
-    @Override
-    public String getTypeUrl() {
-        return "type.googleapis.com/envoy.config.cluster.v3.Cluster";
-    }
-
-    public void subscribeClusters() {
-        subscribeResource(null);
-    }
-
-    @Override
-    protected Map<String, CdsUpdate> decodeDiscoveryResponse(DiscoveryResponse response) {
-        if (!getTypeUrl().equals(response.getTypeUrl())) {
-            return Collections.emptyMap();
-        }
-        ValidatedResourceUpdate<CdsUpdate> validatedResourceUpdate =
-                xdsClusterResource.parse(XdsResourceType.xdsResourceTypeArgs, response.getResourcesList());
-        if (!validatedResourceUpdate.getErrors().isEmpty()) {
-            logger.error(
-                    REGISTRY_ERROR_PARSING_XDS,
-                    validatedResourceUpdate.getErrors().toArray());
-        }
-        return validatedResourceUpdate.getParsedResources().entrySet().stream()
-                .collect(Collectors.toConcurrentMap(
-                        Entry::getKey, e -> e.getValue().getResourceUpdate()));
-    }
-}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/EdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/EdsProtocol.java
deleted file mode 100644
index 11314fc013..0000000000
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/EdsProtocol.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.dubbo.xds.protocol.impl;
-
-import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.xds.AdsObserver;
-import org.apache.dubbo.xds.protocol.AbstractProtocol;
-import org.apache.dubbo.xds.protocol.XdsResourceListener;
-import org.apache.dubbo.xds.resource.XdsEndpointResource;
-import org.apache.dubbo.xds.resource.XdsResourceType;
-import org.apache.dubbo.xds.resource.update.CdsUpdate;
-import org.apache.dubbo.xds.resource.update.EdsUpdate;
-import org.apache.dubbo.xds.resource.update.ValidatedResourceUpdate;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import io.envoyproxy.envoy.config.core.v3.Node;
-import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
-
-import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_PARSING_XDS;
-
-public class EdsProtocol extends AbstractProtocol<EdsUpdate> {
-
-    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(EdsProtocol.class);
-
-    private static final XdsEndpointResource xdsEndpointResource = XdsEndpointResource.getInstance();
-
-    private XdsResourceListener<CdsUpdate> clusterListener = clusters -> {
-        Set<String> clusterNames =
-                clusters.stream().map(CdsUpdate::getClusterName).collect(Collectors.toSet());
-        this.subscribeResource(clusterNames);
-    };
-
-    public EdsProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) {
-        super(adsObserver, node, checkInterval, applicationModel);
-    }
-
-    @Override
-    public String getTypeUrl() {
-        return "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment";
-    }
-
-    public XdsResourceListener<CdsUpdate> getCdsListener() {
-        return clusterListener;
-    }
-
-    @Override
-    protected Map<String, EdsUpdate> decodeDiscoveryResponse(DiscoveryResponse response) {
-        if (!getTypeUrl().equals(response.getTypeUrl())) {
-            return Collections.emptyMap();
-        }
-        ValidatedResourceUpdate<EdsUpdate> validatedResourceUpdate =
-                xdsEndpointResource.parse(XdsResourceType.xdsResourceTypeArgs, response.getResourcesList());
-        if (!validatedResourceUpdate.getErrors().isEmpty()) {
-            logger.error(
-                    REGISTRY_ERROR_PARSING_XDS,
-                    validatedResourceUpdate.getErrors().toArray());
-        }
-        return validatedResourceUpdate.getParsedResources().entrySet().stream()
-                .collect(Collectors.toConcurrentMap(
-                        Entry::getKey, e -> e.getValue().getResourceUpdate()));
-    }
-}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/LdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/LdsProtocol.java
deleted file mode 100644
index bc8cd0c548..0000000000
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/LdsProtocol.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.dubbo.xds.protocol.impl;
-
-import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.xds.AdsObserver;
-import org.apache.dubbo.xds.listener.LdsListener;
-import org.apache.dubbo.xds.protocol.AbstractProtocol;
-import org.apache.dubbo.xds.resource.XdsListenerResource;
-import org.apache.dubbo.xds.resource.XdsResourceType;
-import org.apache.dubbo.xds.resource.update.LdsUpdate;
-import org.apache.dubbo.xds.resource.update.ValidatedResourceUpdate;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.stream.Collectors;
-
-import io.envoyproxy.envoy.config.core.v3.Node;
-import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
-
-import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_PARSING_XDS;
-
-public class LdsProtocol extends AbstractProtocol<LdsUpdate> {
-
-    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(LdsProtocol.class);
-
-    private static final XdsListenerResource xdsListenerResource = XdsListenerResource.getInstance();
-
-    public LdsProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) {
-        super(adsObserver, node, checkInterval, applicationModel);
-        List<LdsListener> ldsListeners =
-                applicationModel.getExtensionLoader(LdsListener.class).getActivateExtensions();
-        ldsListeners.forEach(this::registerListen);
-    }
-
-    @Override
-    public String getTypeUrl() {
-        return "type.googleapis.com/envoy.config.listener.v3.Listener";
-    }
-
-    public void subscribeListeners() {
-        subscribeResource(null);
-    }
-
-    @Override
-    protected Map<String, LdsUpdate> decodeDiscoveryResponse(DiscoveryResponse response) {
-        if (!getTypeUrl().equals(response.getTypeUrl())) {
-            return Collections.emptyMap();
-        }
-
-        if (!getTypeUrl().equals(response.getTypeUrl())) {
-            return Collections.emptyMap();
-        }
-        ValidatedResourceUpdate<LdsUpdate> validatedResourceUpdate =
-                xdsListenerResource.parse(XdsResourceType.xdsResourceTypeArgs, response.getResourcesList());
-        if (!validatedResourceUpdate.getErrors().isEmpty()) {
-            logger.error(
-                    REGISTRY_ERROR_PARSING_XDS,
-                    validatedResourceUpdate.getErrors().toArray());
-        }
-        return validatedResourceUpdate.getParsedResources().entrySet().stream()
-                .collect(Collectors.toConcurrentMap(
-                        Entry::getKey, e -> e.getValue().getResourceUpdate()));
-    }
-}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/RdsProtocol.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/RdsProtocol.java
deleted file mode 100644
index 5b84f2cb17..0000000000
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/protocol/impl/RdsProtocol.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.dubbo.xds.protocol.impl;
-
-import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.apache.dubbo.xds.AdsObserver;
-import org.apache.dubbo.xds.protocol.AbstractProtocol;
-import org.apache.dubbo.xds.protocol.XdsResourceListener;
-import org.apache.dubbo.xds.resource.XdsResourceType;
-import org.apache.dubbo.xds.resource.XdsRouteConfigureResource;
-import org.apache.dubbo.xds.resource.update.LdsUpdate;
-import org.apache.dubbo.xds.resource.update.RdsUpdate;
-import org.apache.dubbo.xds.resource.update.ValidatedResourceUpdate;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import io.envoyproxy.envoy.config.core.v3.Node;
-import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
-
-import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_PARSING_XDS;
-
-public class RdsProtocol extends AbstractProtocol<RdsUpdate> {
-
-    private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RdsProtocol.class);
-
-    private static final XdsRouteConfigureResource xdsRouteConfigureResource = XdsRouteConfigureResource.getInstance();
-
-    public RdsProtocol(AdsObserver adsObserver, Node node, int checkInterval, ApplicationModel applicationModel) {
-        super(adsObserver, node, checkInterval, applicationModel);
-    }
-
-    @Override
-    public String getTypeUrl() {
-        return "type.googleapis.com/envoy.config.route.v3.RouteConfiguration";
-    }
-
-    @Override
-    protected Map<String, RdsUpdate> decodeDiscoveryResponse(DiscoveryResponse response) {
-        if (!getTypeUrl().equals(response.getTypeUrl())) {
-            return Collections.emptyMap();
-        }
-        ValidatedResourceUpdate<RdsUpdate> updates =
-                xdsRouteConfigureResource.parse(XdsResourceType.xdsResourceTypeArgs, response.getResourcesList());
-        if (!updates.getInvalidResources().isEmpty()) {
-            logger.error(REGISTRY_ERROR_PARSING_XDS, updates.getErrors().toArray());
-        }
-        return updates.getParsedResources().entrySet().stream()
-                .collect(Collectors.toConcurrentMap(
-                        Entry::getKey, v -> v.getValue().getResourceUpdate()));
-    }
-
-    public XdsResourceListener<LdsUpdate> getLdsListener() {
-        return ldsListener;
-    }
-
-    private final XdsResourceListener<LdsUpdate> ldsListener = resource -> {
-        Set<String> set = resource.stream()
-                .flatMap(l -> l.getListener().getFilterChains().stream())
-                .map(c -> c.getHttpConnectionManager().getRdsName())
-                .collect(Collectors.toSet());
-        this.subscribeResource(set);
-    };
-}
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsClusterResource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsClusterResource.java
index 507244f339..74155f67d5 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsClusterResource.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsClusterResource.java
@@ -69,7 +69,7 @@ public class XdsClusterResource extends XdsResourceType<CdsUpdate> {
     }
 
     @Override
-    String typeUrl() {
+    public String typeUrl() {
         return ADS_TYPE_URL_CDS;
     }
 
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsEndpointResource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsEndpointResource.java
index 8991502331..b628355371 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsEndpointResource.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsEndpointResource.java
@@ -62,7 +62,7 @@ public class XdsEndpointResource extends XdsResourceType<EdsUpdate> {
     }
 
     @Override
-    String typeUrl() {
+    public String typeUrl() {
         return ADS_TYPE_URL_EDS;
     }
 
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsListenerResource.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsListenerResource.java
index 74c094a7db..6a233c436a 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsListenerResource.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsListenerResource.java
@@ -90,7 +90,7 @@ public class XdsListenerResource extends XdsResourceType<LdsUpdate> {
     }
 
     @Override
-    String typeUrl() {
+    public String typeUrl() {
         return ADS_TYPE_URL_LDS;
     }
 
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsResourceType.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsResourceType.java
index 3690b0f73e..2f014f636b 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsResourceType.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/XdsResourceType.java
@@ -72,7 +72,7 @@ public abstract class XdsResourceType<T extends ResourceUpdate> {
 
     abstract String typeName();
 
-    abstract String typeUrl();
+    public abstract String typeUrl();
 
     // Do not confuse with the SotW approach: it is the mechanism in which the client must specify all
     // resource names it is interested in with each request. Different resource types may behave
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/matcher/PathMatcher.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/matcher/PathMatcher.java
index 30fa722834..56d9cf64a1 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/matcher/PathMatcher.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/matcher/PathMatcher.java
@@ -76,7 +76,7 @@ public class PathMatcher {
         return regEx;
     }
 
-    boolean isCaseSensitive() {
+    public boolean isCaseSensitive() {
         return caseSensitive;
     }
 
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/route/RouteMatch.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/route/RouteMatch.java
index dcd11d3672..1b4512b0ba 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/route/RouteMatch.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/route/RouteMatch.java
@@ -47,16 +47,16 @@ public final class RouteMatch {
         this.fractionMatcher = fractionMatcher;
     }
 
-    PathMatcher pathMatcher() {
+    public PathMatcher getPathMatcher() {
         return pathMatcher;
     }
 
-    List<HeaderMatcher> headerMatchers() {
+    public List<HeaderMatcher> getHeaderMatchers() {
         return headerMatchers;
     }
 
     @Nullable
-    FractionMatcher fractionMatcher() {
+    public FractionMatcher getFractionMatcher() {
         return fractionMatcher;
     }
 
@@ -71,11 +71,11 @@ public final class RouteMatch {
         }
         if (o instanceof RouteMatch) {
             RouteMatch that = (RouteMatch) o;
-            return this.pathMatcher.equals(that.pathMatcher())
-                    && this.headerMatchers.equals(that.headerMatchers())
+            return this.pathMatcher.equals(that.getPathMatcher())
+                    && this.headerMatchers.equals(that.getHeaderMatchers())
                     && (this.fractionMatcher == null
-                            ? that.fractionMatcher() == null
-                            : this.fractionMatcher.equals(that.fractionMatcher()));
+                            ? that.getFractionMatcher() == null
+                            : this.fractionMatcher.equals(that.getFractionMatcher()));
         }
         return false;
     }
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/update/CdsUpdate.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/update/CdsUpdate.java
index 92f2b4bcee..4f24b772d9 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/update/CdsUpdate.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/resource/update/CdsUpdate.java
@@ -31,13 +31,13 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster;
 
 public class CdsUpdate implements ResourceUpdate {
 
-    enum ClusterType {
+    public enum ClusterType {
         EDS,
         LOGICAL_DNS,
         AGGREGATE
     }
 
-    enum LbPolicy {
+    public enum LbPolicy {
         ROUND_ROBIN,
         RING_HASH,
         LEAST_REQUEST
diff --git a/dubbo-xds/src/main/java/org/apache/dubbo/xds/router/XdsRouter.java b/dubbo-xds/src/main/java/org/apache/dubbo/xds/router/XdsRouter.java
index 04917e6e0b..64d6055bad 100644
--- a/dubbo-xds/src/main/java/org/apache/dubbo/xds/router/XdsRouter.java
+++ b/dubbo-xds/src/main/java/org/apache/dubbo/xds/router/XdsRouter.java
@@ -81,7 +81,9 @@ public class XdsRouter<T> extends AbstractStateRouter<T> {
     private String matchCluster(Invocation invocation) {
         String cluster = null;
         String serviceName = invocation.getInvoker().getUrl().getParameter("provided-by");
-        VirtualHost xdsVirtualHost = pilotExchanger.getXdsVirtualHostMap().get(serviceName);
+        //        VirtualHost xdsVirtualHost = pilotExchanger.getXdsVirtualHostMap().get(serviceName);
+        // FIXME
+        VirtualHost xdsVirtualHost = xdsVirtualHostMap.get(serviceName);
 
         // match route
         for (Route xdsRoute : xdsVirtualHost.getRoutes()) {