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

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


The following commit(s) were added to refs/heads/master by this push:
     new e45eb0ed3d fix(apache-dubbo): cache parsed selector upstreams across 
requests (#7348)
e45eb0ed3d is described below

commit e45eb0ed3dd0186d2e4bd26186f922c84b6b9d70
Author: BobSong <[email protected]>
AuthorDate: Wed Sep 30 09:40:35 2026 +0800

    fix(apache-dubbo): cache parsed selector upstreams across requests (#7348)
    
    Co-authored-by: BobSong-dev <[email protected]>
    Co-authored-by: aias00 <[email protected]>
---
 .../apache/dubbo/cache/ApacheDubboConfigCache.java | 52 ++++++++++++++++++
 .../dubbo/proxy/ApacheDubboProxyService.java       |  5 +-
 .../dubbo/cache/ApacheDubboConfigCacheTest.java    | 64 ++++++++++++++++++++++
 3 files changed, 117 insertions(+), 4 deletions(-)

diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/cache/ApacheDubboConfigCache.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/cache/ApacheDubboConfigCache.java
index 6ffbd5dfd6..b058798985 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/cache/ApacheDubboConfigCache.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/cache/ApacheDubboConfigCache.java
@@ -17,6 +17,7 @@
 
 package org.apache.shenyu.plugin.apache.dubbo.cache;
 
+import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
@@ -24,7 +25,9 @@ import com.google.common.cache.RemovalListener;
 
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -46,11 +49,13 @@ import org.apache.dubbo.rpc.service.GenericService;
 import org.apache.shenyu.common.constant.Constants;
 import org.apache.shenyu.common.dto.MetaData;
 import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
 import org.apache.shenyu.common.dto.convert.plugin.DubboRegisterConfig;
 import org.apache.shenyu.common.dto.convert.rule.impl.DubboRuleHandle;
 import org.apache.shenyu.common.dto.convert.selector.DubboUpstream;
 import org.apache.shenyu.common.exception.ShenyuException;
 import org.apache.shenyu.common.utils.DigestUtils;
+import org.apache.shenyu.common.utils.GsonUtils;
 import org.apache.shenyu.plugin.dubbo.common.cache.DubboConfigCache;
 import org.apache.shenyu.plugin.dubbo.common.cache.DubboMethodParam;
 import org.apache.shenyu.plugin.dubbo.common.cache.DubboParam;
@@ -96,6 +101,10 @@ public final class ApacheDubboConfigCache extends 
DubboConfigCache {
                 }
             });
 
+    private final Cache<String, CachedUpstreams> upstreamCache = 
CacheBuilder.newBuilder()
+            .maximumSize(Constants.CACHE_MAX_COUNT)
+            .build();
+
     /**
      * Gets instance.
      *
@@ -105,6 +114,33 @@ public final class ApacheDubboConfigCache extends 
DubboConfigCache {
         return ApplicationConfigCacheInstance.INSTANCE;
     }
 
+    /**
+     * Return the usable upstream list carried by the selector handle, reusing 
the cached
+     * parse while the handle stays unchanged. Dubbo routing runs on every 
request, so the
+     * JSON deserialization must not be repeated per request; entries are 
keyed by selector
+     * id and validated against the exact handle string, which makes stale 
reads impossible
+     * without an invalidation hook.
+     *
+     * @param selectorData the selector carrying the serialized upstream list
+     * @return the filtered upstream list, empty when the handle holds no 
usable upstream
+     */
+    public List<DubboUpstream> getOrParseUpstreams(final SelectorData 
selectorData) {
+        if (Objects.isNull(selectorData) || 
StringUtils.isBlank(selectorData.getHandle())) {
+            return Collections.emptyList();
+        }
+        final CachedUpstreams cached = 
upstreamCache.getIfPresent(selectorData.getId());
+        if (Objects.nonNull(cached) && Objects.equals(cached.handle, 
selectorData.getHandle())) {
+            return cached.upstreams;
+        }
+        final List<DubboUpstream> parsed = 
GsonUtils.getInstance().fromList(selectorData.getHandle(), DubboUpstream.class);
+        final List<DubboUpstream> usable = CollectionUtils.isEmpty(parsed) ? 
Collections.emptyList()
+                : parsed.stream()
+                        .filter(u -> u.isStatus() && 
StringUtils.isNotBlank(u.getRegistry()))
+                        .collect(Collectors.toList());
+        upstreamCache.put(selectorData.getId(), new 
CachedUpstreams(selectorData.getHandle(), usable));
+        return usable;
+    }
+
     /**
      * Init.
      *
@@ -577,4 +613,20 @@ public final class ApacheDubboConfigCache extends 
DubboConfigCache {
 
         }
     }
+
+    /**
+     * Cached parse result of one selector handle. Keeps the exact handle 
string so a
+     * changed selector handle invalidates the entry without an explicit hook.
+     */
+    private static final class CachedUpstreams {
+
+        private final String handle;
+
+        private final List<DubboUpstream> upstreams;
+
+        private CachedUpstreams(final String handle, final List<DubboUpstream> 
upstreams) {
+            this.handle = handle;
+            this.upstreams = upstreams;
+        }
+    }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/proxy/ApacheDubboProxyService.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/proxy/ApacheDubboProxyService.java
index 0634ab88f8..3dd5401f48 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/proxy/ApacheDubboProxyService.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/main/java/org/apache/shenyu/plugin/apache/dubbo/proxy/ApacheDubboProxyService.java
@@ -34,7 +34,6 @@ import 
org.apache.shenyu.common.dto.convert.selector.DubboUpstream;
 import org.apache.shenyu.common.enums.LoadBalanceEnum;
 import org.apache.shenyu.common.enums.ResultEnum;
 import org.apache.shenyu.common.exception.ShenyuException;
-import org.apache.shenyu.common.utils.GsonUtils;
 import org.apache.shenyu.common.utils.JsonUtils;
 import org.apache.shenyu.common.utils.ParamCheckUtils;
 import org.apache.shenyu.loadbalancer.entity.Upstream;
@@ -126,9 +125,7 @@ public class ApacheDubboProxyService {
             namespace = 
exchange.getRequest().getHeaders().get(Constants.NAMESPACE).get(0);
         }
 
-        List<DubboUpstream> dubboUpstreams = 
GsonUtils.getInstance().fromList(selectorData.getHandle(), DubboUpstream.class);
-        dubboUpstreams = CollectionUtils.isEmpty(dubboUpstreams) ? null
-                : dubboUpstreams.stream().filter(u -> u.isStatus() && 
StringUtils.isNotBlank(u.getRegistry())).collect(Collectors.toList());
+        List<DubboUpstream> dubboUpstreams = 
ApacheDubboConfigCache.getInstance().getOrParseUpstreams(selectorData);
         // if dubboUpstreams is empty, use default plugin config
         if (CollectionUtils.isEmpty(dubboUpstreams)) {
             referenceKey = StringUtils.isNotBlank(namespace) ? namespace + 
Constants.COLONS + referenceKey : referenceKey;
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/test/java/org/apache/shenyu/plugin/apache/dubbo/cache/ApacheDubboConfigCacheTest.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/test/java/org/apache/shenyu/plugin/apache/dubbo/cache/ApacheDubboConfigCacheTest.java
index b11af07d5b..7280e31034 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/test/java/org/apache/shenyu/plugin/apache/dubbo/cache/ApacheDubboConfigCacheTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-apache-dubbo/src/test/java/org/apache/shenyu/plugin/apache/dubbo/cache/ApacheDubboConfigCacheTest.java
@@ -22,6 +22,7 @@ import org.apache.dubbo.config.ReferenceConfig;
 import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.rpc.service.GenericService;
 import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.common.dto.SelectorData;
 import org.apache.shenyu.common.dto.convert.plugin.DubboRegisterConfig;
 import org.apache.shenyu.common.dto.convert.selector.DubboUpstream;
 import org.apache.shenyu.common.utils.GsonUtils;
@@ -34,10 +35,13 @@ import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
 
 import java.lang.reflect.Field;
+import java.util.List;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.Mockito.mock;
@@ -179,4 +183,64 @@ public final class ApacheDubboConfigCacheTest {
         field.setAccessible(true);
         return (LoadingCache<String, ReferenceConfig<GenericService>>) 
field.get(apacheDubboConfigCache);
     }
+
+    @Test
+    public void testGetOrParseUpstreamsFiltersUnusableEntries() {
+        DubboUpstream active = 
DubboUpstream.builder().protocol("dubbo").status(true).build();
+        active.setRegistry("dubbo://127.0.0.1:20880");
+        DubboUpstream disabled = 
DubboUpstream.builder().protocol("dubbo").status(false).build();
+        disabled.setRegistry("dubbo://127.0.0.1:20881");
+        String handle = GsonUtils.getInstance().toJson(List.of(active, 
disabled));
+        SelectorData selectorData = new SelectorData();
+        selectorData.setId("selector-1");
+        selectorData.setHandle(handle);
+
+        List<DubboUpstream> result = 
apacheDubboConfigCache.getOrParseUpstreams(selectorData);
+
+        assertEquals(1, result.size());
+        assertTrue(result.get(0).isStatus());
+    }
+
+    @Test
+    public void testGetOrParseUpstreamsReusesParseWhileHandleUnchanged() {
+        DubboUpstream active = 
DubboUpstream.builder().protocol("dubbo").status(true).build();
+        active.setRegistry("dubbo://127.0.0.1:20880");
+        SelectorData selectorData = new SelectorData();
+        selectorData.setId("selector-2");
+        
selectorData.setHandle(GsonUtils.getInstance().toJson(List.of(active)));
+
+        List<DubboUpstream> first = 
apacheDubboConfigCache.getOrParseUpstreams(selectorData);
+        List<DubboUpstream> second = 
apacheDubboConfigCache.getOrParseUpstreams(selectorData);
+
+        assertSame(first, second);
+    }
+
+    @Test
+    public void testGetOrParseUpstreamsReparsesWhenHandleChanges() {
+        DubboUpstream first = 
DubboUpstream.builder().protocol("dubbo").status(true).build();
+        first.setRegistry("dubbo://127.0.0.1:20880");
+        SelectorData selectorData = new SelectorData();
+        selectorData.setId("selector-3");
+        selectorData.setHandle(GsonUtils.getInstance().toJson(List.of(first)));
+        assertEquals("dubbo://127.0.0.1:20880", 
apacheDubboConfigCache.getOrParseUpstreams(selectorData).get(0).getRegistry());
+
+        DubboUpstream second = 
DubboUpstream.builder().protocol("dubbo").status(true).build();
+        second.setRegistry("dubbo://127.0.0.2:20880");
+        
selectorData.setHandle(GsonUtils.getInstance().toJson(List.of(second)));
+        List<DubboUpstream> reparsed = 
apacheDubboConfigCache.getOrParseUpstreams(selectorData);
+
+        assertEquals(1, reparsed.size());
+        assertEquals("dubbo://127.0.0.2:20880", reparsed.get(0).getRegistry());
+    }
+
+    @Test
+    public void testGetOrParseUpstreamsBlankHandleReturnsEmpty() {
+        SelectorData selectorData = new SelectorData();
+        selectorData.setId("selector-4");
+        selectorData.setHandle(null);
+
+        
assertTrue(apacheDubboConfigCache.getOrParseUpstreams(selectorData).isEmpty());
+        assertTrue(apacheDubboConfigCache.getOrParseUpstreams(null).isEmpty());
+        
assertNotNull(apacheDubboConfigCache.getOrParseUpstreams(selectorData));
+    }
 }

Reply via email to