You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shenyu.apache.org by xi...@apache.org on 2022/06/27 08:53:34 UTC

[incubator-shenyu] branch master updated: [ISSUE #3367] optimize selector match (#3440)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 23b0ce652 [ISSUE #3367] optimize selector match (#3440)
23b0ce652 is described below

commit 23b0ce652f63f9a2ef532617bf9b8fa89d6009c9
Author: zouchangfu <50...@users.noreply.github.com>
AuthorDate: Mon Jun 27 16:53:25 2022 +0800

    [ISSUE #3367] optimize selector match (#3440)
    
    * [type: refactor] optimeze selector match
    
    * [type: refactor] optimeze rule match
    
    * fix code style
    
    * [type: refactor] use the thread security list
    
    * [type: refactor] obtain first selector and first rule
    
    * [type: refactor] add cache switch and memory size
    
    * [type: refactor] fix code style
    
    * add MatchDataCacheTest
    
    * [type: refactor] modify maxMemory to maxFreeMemory
    
    * [type: refactor] fix code style
    
    * [type: refactor] modify method name
    
    * [type: refactor] fix code style
    
    * [type: refactor] mock shenyuConfig
    
    * [type: refactor] fix test bug
    
    * [type: refactor] If selector match is empty or rule match is empty, cache the empty collection
    
    * [type: refactor] fix check style
    
    * [type: refactor] close cache switch
    
    * [type: bug] fix match bug
    
    * [type: bug] fix match bug
    
    * [type: refactor] fix code style
    
    * [type: refactor] optimize selector
    
    * [type: refactor] fix code style
    
    * [type: refactor] fix code style
    
    * [type: refactor] only uri condition cache
    
    * [type: refactor] Remove redundant code
    
    * [type: refactor] fix code style
    
    * [type: style] fix code
    
    * [type: feat] only cache one selector
    
    * [type: feat] fix cache bug
    
    * [type: fix] fix cache bug
---
 .../src/main/resources/application.yml             |   4 +
 .../apache/shenyu/common/config/ShenyuConfig.java  |  66 +++++++++++++
 .../shenyu/plugin/base/AbstractShenyuPlugin.java   | 103 +++++++++++++++++----
 .../base/cache/CommonPluginDataSubscriber.java     |   3 +
 .../shenyu/plugin/base/cache/MatchDataCache.java   |  91 ++++++++++++++++++
 .../plugin/base/AbstractShenyuPluginTest.java      |  12 ++-
 .../plugin/base/cache/MatchDataCacheTest.java      |  75 +++++++++++++++
 .../web/controller/LocalPluginController.java      |   2 +
 8 files changed, 336 insertions(+), 20 deletions(-)

diff --git a/shenyu-bootstrap/src/main/resources/application.yml b/shenyu-bootstrap/src/main/resources/application.yml
index f86cd8964..b5c2ea5a9 100644
--- a/shenyu-bootstrap/src/main/resources/application.yml
+++ b/shenyu-bootstrap/src/main/resources/application.yml
@@ -70,6 +70,10 @@ management:
       enabled: false
 
 shenyu:
+  matchCache:
+    enabled: true
+    # 256 * 1024 * 1024 = 256MB
+    maxFreeMemory: 268435456
   netty:
     http:
       # set to false, user can custom the netty tcp server config.
diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/config/ShenyuConfig.java b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ShenyuConfig.java
index 45001ef53..925e572be 100644
--- a/shenyu-common/src/main/java/org/apache/shenyu/common/config/ShenyuConfig.java
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ShenyuConfig.java
@@ -44,6 +44,8 @@ public class ShenyuConfig {
     private FallbackPath fallback = new FallbackPath();
     
     private ExtPlugin extPlugin = new ExtPlugin();
+
+    private MatchCache matchCache = new MatchCache();
     
     private Scheduler scheduler = new Scheduler();
     
@@ -224,6 +226,24 @@ public class ShenyuConfig {
     public void setExtPlugin(final ExtPlugin extPlugin) {
         this.extPlugin = extPlugin;
     }
+
+    /**
+     * Gets match cache.
+     *
+     * @return the match cache
+     */
+    public MatchCache getMatchCache() {
+        return matchCache;
+    }
+
+    /**
+     * Sets match cache.
+     *
+     * @param matchCache the match cache
+     */
+    public void setMatchCache(final MatchCache matchCache) {
+        this.matchCache = matchCache;
+    }
     
     /**
      * Gets file.
@@ -505,6 +525,52 @@ public class ShenyuConfig {
             this.scheduleDelay = scheduleDelay;
         }
     }
+
+    /**
+     * the match cache.
+     */
+    public static class MatchCache {
+
+        private boolean enabled;
+
+        private Integer maxFreeMemory = 256 * 1024 * 1024;
+
+        /**
+         * Gets enabled.
+         *
+         * @return the enabled
+         */
+        public boolean getEnabled() {
+            return enabled;
+        }
+
+        /**
+         * Sets enabled.
+         *
+         * @param enabled the enabled
+         */
+        public void setEnabled(final boolean enabled) {
+            this.enabled = enabled;
+        }
+
+        /**
+         * Gets maxFreeMemory.
+         *
+         * @return the maxFreeMemory
+         */
+        public Integer getMaxFreeMemory() {
+            return maxFreeMemory;
+        }
+
+        /**
+         * Sets maxFreeMemory.
+         *
+         * @param maxFreeMemory the maxFreeMemory
+         */
+        public void setMaxFreeMemory(final Integer maxFreeMemory) {
+            this.maxFreeMemory = maxFreeMemory;
+        }
+    }
     
     /**
      * The type Exclude path.
diff --git a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/AbstractShenyuPlugin.java b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/AbstractShenyuPlugin.java
index 20b9211b1..ff2e62e44 100644
--- a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/AbstractShenyuPlugin.java
+++ b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/AbstractShenyuPlugin.java
@@ -18,7 +18,10 @@
 package org.apache.shenyu.plugin.base;
 
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.shenyu.common.config.ShenyuConfig;
+import org.apache.shenyu.common.dto.ConditionData;
 import org.apache.shenyu.common.dto.PluginData;
 import org.apache.shenyu.common.dto.RuleData;
 import org.apache.shenyu.common.dto.SelectorData;
@@ -26,7 +29,9 @@ import org.apache.shenyu.common.enums.MatchModeEnum;
 import org.apache.shenyu.common.enums.SelectorTypeEnum;
 import org.apache.shenyu.plugin.api.ShenyuPlugin;
 import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
 import org.apache.shenyu.plugin.base.cache.BaseDataCache;
+import org.apache.shenyu.plugin.base.cache.MatchDataCache;
 import org.apache.shenyu.plugin.base.condition.strategy.MatchStrategyFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -45,9 +50,19 @@ import java.util.stream.Collectors;
  * abstract shenyu plugin please extends.
  */
 public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
-    
+
     private static final Logger LOG = LoggerFactory.getLogger(AbstractShenyuPlugin.class);
-    
+
+    private static final String URI_CONDITION_TYPE = "uri";
+
+    private ShenyuConfig.MatchCache matchCacheConfig;
+
+    private SelectorData defaultSelectorData = new SelectorData();
+
+    {
+        defaultSelectorData.setPluginName(named());
+    }
+
     /**
      * this is Template Method child has Implement your own logic.
      *
@@ -58,7 +73,7 @@ public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
      * @return {@code Mono<Void>} to indicate when request handling is complete
      */
     protected abstract Mono<Void> doExecute(ServerWebExchange exchange, ShenyuPluginChain chain, SelectorData selector, RuleData rule);
-    
+
     /**
      * Process the Web request and (optionally) delegate to the next
      * {@code ShenyuPlugin} through the given {@link ShenyuPluginChain}.
@@ -69,18 +84,29 @@ public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
      */
     @Override
     public Mono<Void> execute(final ServerWebExchange exchange, final ShenyuPluginChain chain) {
+        initMatchCacheConfig();
         String pluginName = named();
         PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
         if (pluginData != null && pluginData.getEnabled()) {
-            final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
-            if (CollectionUtils.isEmpty(selectors)) {
-                return handleSelectorIfNull(pluginName, exchange, chain);
+            final String path = exchange.getRequest().getURI().getPath();
+            Pair<Boolean, SelectorData> resultSelectorData = obtainSelectorDataCacheIfEnabled(exchange);
+            SelectorData selectorData = resultSelectorData.getRight();
+            if (Boolean.TRUE.equals(resultSelectorData.getLeft())) {
+                List<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
+                if (CollectionUtils.isEmpty(selectors)) {
+                    return handleSelectorIfNull(pluginName, exchange, chain);
+                }
+                Pair<Boolean, SelectorData> matchSelectorData = matchSelector(exchange, selectors);
+                selectorData = matchSelectorData.getRight();
+                if (matchSelectorData.getLeft()) {
+                    cacheSelectorDataIfEnabled(path, selectorData);
+                }
             }
-            SelectorData selectorData = matchSelector(exchange, selectors);
             if (Objects.isNull(selectorData)) {
                 return handleSelectorIfNull(pluginName, exchange, chain);
             }
             selectorLog(selectorData, pluginName);
+
             List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
             if (CollectionUtils.isEmpty(rules)) {
                 return handleRuleIfNull(pluginName, exchange, chain);
@@ -100,25 +126,64 @@ public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
         }
         return chain.execute(exchange);
     }
-    
+
+    private void initMatchCacheConfig() {
+        if (Objects.isNull(matchCacheConfig)) {
+            matchCacheConfig = SpringBeanUtils.getInstance().getBean(ShenyuConfig.class).getMatchCache();
+        }
+    }
+
+    private void cacheSelectorDataIfEnabled(final String path, final SelectorData selectorData) {
+        if (matchCacheConfig.getEnabled()) {
+            if (Objects.isNull(selectorData)) {
+                MatchDataCache.getInstance().cacheSelectorData(path, defaultSelectorData, matchCacheConfig.getMaxFreeMemory());
+            } else {
+                List<ConditionData> conditionList = selectorData.getConditionList();
+                if (CollectionUtils.isNotEmpty(conditionList)) {
+                    boolean isUriCondition = conditionList.stream().allMatch(v -> URI_CONDITION_TYPE.equals(v.getParamType()));
+                    if (isUriCondition) {
+                        MatchDataCache.getInstance().cacheSelectorData(path, selectorData, matchCacheConfig.getMaxFreeMemory());
+                    }
+                }
+            }
+        }
+    }
+
+    private Pair<Boolean, SelectorData> obtainSelectorDataCacheIfEnabled(final ServerWebExchange exchange) {
+        if (matchCacheConfig.getEnabled()) {
+            SelectorData selectorData = MatchDataCache.getInstance().obtainSelectorData(named(), exchange.getRequest().getURI().getPath());
+
+            if (Objects.isNull(selectorData)) {
+                return Pair.of(Boolean.TRUE, null);
+            }
+
+            if (StringUtils.isBlank(selectorData.getId())) {
+                return Pair.of(Boolean.FALSE, null);
+            }
+
+            return Pair.of(Boolean.FALSE, selectorData);
+        }
+        return Pair.of(Boolean.TRUE, null);
+    }
+
     protected Mono<Void> handleSelectorIfNull(final String pluginName, final ServerWebExchange exchange, final ShenyuPluginChain chain) {
         return chain.execute(exchange);
     }
-    
+
     protected Mono<Void> handleRuleIfNull(final String pluginName, final ServerWebExchange exchange, final ShenyuPluginChain chain) {
         return chain.execute(exchange);
     }
-    
-    private SelectorData matchSelector(final ServerWebExchange exchange, final Collection<SelectorData> selectors) {
+
+    private Pair<Boolean, SelectorData> matchSelector(final ServerWebExchange exchange, final Collection<SelectorData> selectors) {
         List<SelectorData> filterCollectors = selectors.stream()
                 .filter(selector -> selector.getEnabled() && filterSelector(selector, exchange)).collect(Collectors.toList());
         if (filterCollectors.size() > 1) {
-            return manyMatchSelector(filterCollectors);
+            return Pair.of(Boolean.FALSE, manyMatchSelector(filterCollectors));
         } else {
-            return filterCollectors.stream().findFirst().orElse(null);
+            return Pair.of(Boolean.TRUE, filterCollectors.stream().findFirst().orElse(null));
         }
     }
-    
+
     private SelectorData manyMatchSelector(final List<SelectorData> filterCollectors) {
         //What needs to be dealt with here is the and condition. If the number of and conditions is the same and is matched at the same time,
         // it will be sorted by the sort field.
@@ -135,7 +200,7 @@ public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
         List<Pair<Integer, SelectorData>> pairs = collect.get(max);
         return pairs.stream().map(Pair::getRight).min(Comparator.comparing(SelectorData::getSort)).orElse(null);
     }
-    
+
     private Boolean filterSelector(final SelectorData selector, final ServerWebExchange exchange) {
         if (selector.getType() == SelectorTypeEnum.CUSTOM_FLOW.getCode()) {
             if (CollectionUtils.isEmpty(selector.getConditionList())) {
@@ -145,21 +210,21 @@ public abstract class AbstractShenyuPlugin implements ShenyuPlugin {
         }
         return true;
     }
-    
+
     private RuleData matchRule(final ServerWebExchange exchange, final Collection<RuleData> rules) {
         return rules.stream().filter(rule -> filterRule(rule, exchange)).findFirst().orElse(null);
     }
-    
+
     private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {
         return ruleData.getEnabled() && MatchStrategyFactory.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);
     }
-    
+
     private void selectorLog(final SelectorData selectorData, final String pluginName) {
         if (selectorData.getLogged()) {
             LOG.info("{} selector success match , selector name :{}", pluginName, selectorData.getName());
         }
     }
-    
+
     private void ruleLog(final RuleData ruleData, final String pluginName) {
         if (ruleData.getLoged()) {
             LOG.info("{} rule success match , rule name :{}", pluginName, ruleData.getName());
diff --git a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/CommonPluginDataSubscriber.java b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/CommonPluginDataSubscriber.java
index 9a4c7831a..018d679f1 100644
--- a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/CommonPluginDataSubscriber.java
+++ b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/CommonPluginDataSubscriber.java
@@ -122,6 +122,7 @@ public class CommonPluginDataSubscriber implements PluginDataSubscriber {
     @Override
     public void refreshSelectorDataAll() {
         BaseDataCache.getInstance().cleanSelectorData();
+        MatchDataCache.getInstance().cleanSelectorData();
     }
     
     @Override
@@ -189,6 +190,7 @@ public class CommonPluginDataSubscriber implements PluginDataSubscriber {
         } else if (data instanceof SelectorData) {
             SelectorData selectorData = (SelectorData) data;
             BaseDataCache.getInstance().cacheSelectData(selectorData);
+            MatchDataCache.getInstance().removeSelectorData(selectorData.getPluginName());
             Optional.ofNullable(handlerMap.get(selectorData.getPluginName()))
                     .ifPresent(handler -> handler.handlerSelector(selectorData));
             
@@ -233,6 +235,7 @@ public class CommonPluginDataSubscriber implements PluginDataSubscriber {
         } else if (data instanceof SelectorData) {
             SelectorData selectorData = (SelectorData) data;
             BaseDataCache.getInstance().removeSelectData(selectorData);
+            MatchDataCache.getInstance().removeSelectorData(selectorData.getPluginName());
             Optional.ofNullable(handlerMap.get(selectorData.getPluginName()))
                     .ifPresent(handler -> handler.removeSelector(selectorData));
             
diff --git a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/MatchDataCache.java b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/MatchDataCache.java
new file mode 100644
index 000000000..aa1065c6d
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/cache/MatchDataCache.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.base.cache;
+
+import com.google.common.collect.Maps;
+import org.apache.shenyu.common.cache.MemorySafeLRUMap;
+import org.apache.shenyu.common.dto.SelectorData;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentMap;
+
+
+/**
+ * The match data cache.
+ */
+public final class MatchDataCache {
+
+    private static final MatchDataCache INSTANCE = new MatchDataCache();
+
+    /**
+     * pluginName -> LRUMap.
+     */
+    private static final ConcurrentMap<String, Map<String, SelectorData>> SELECTOR_DATA_MAP = Maps.newConcurrentMap();
+
+    private MatchDataCache() {
+    }
+
+    /**
+     * Gets instance.
+     *
+     * @return the instance
+     */
+    public static MatchDataCache getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Remove selector data.
+     *
+     * @param pluginName the pluginName
+     */
+    public void removeSelectorData(final String pluginName) {
+        SELECTOR_DATA_MAP.remove(pluginName);
+    }
+
+    /**
+     * Clean selector data.
+     */
+    public void cleanSelectorData() {
+        SELECTOR_DATA_MAP.clear();
+    }
+
+    /**
+     * Cache selector data.
+     *
+     * @param path         the path
+     * @param selectorData the selector data
+     * @param maxMemory    the max memory
+     */
+    public void cacheSelectorData(final String path, final SelectorData selectorData, final Integer maxMemory) {
+        SELECTOR_DATA_MAP.computeIfAbsent(selectorData.getPluginName(), map -> new MemorySafeLRUMap<>(maxMemory, 1 << 16)).put(path, selectorData);
+    }
+
+    /**
+     * Obtain selector data.
+     *
+     * @param pluginName the pluginName
+     * @param path       the path
+     * @return the selector data
+     */
+    public SelectorData obtainSelectorData(final String pluginName, final String path) {
+        final Map<String, SelectorData> lruMap = SELECTOR_DATA_MAP.get(pluginName);
+        return Optional.ofNullable(lruMap).orElse(Maps.newHashMap()).get(path);
+    }
+}
diff --git a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/AbstractShenyuPluginTest.java b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/AbstractShenyuPluginTest.java
index a2bb7afc2..2dfbbd64f 100644
--- a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/AbstractShenyuPluginTest.java
+++ b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/AbstractShenyuPluginTest.java
@@ -17,20 +17,23 @@
 
 package org.apache.shenyu.plugin.base;
 
+import org.apache.shenyu.common.config.ShenyuConfig;
 import org.apache.shenyu.common.dto.ConditionData;
 import org.apache.shenyu.common.dto.PluginData;
 import org.apache.shenyu.common.dto.RuleData;
 import org.apache.shenyu.common.dto.SelectorData;
 import org.apache.shenyu.common.enums.SelectorTypeEnum;
 import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
 import org.apache.shenyu.plugin.base.cache.BaseDataCache;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
 import org.springframework.mock.web.server.MockServerWebExchange;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
 import reactor.test.StepVerifier;
-import org.junit.jupiter.api.Test;
 
 import java.util.Collections;
 import java.util.List;
@@ -59,6 +62,7 @@ public final class AbstractShenyuPluginTest {
 
     @BeforeEach
     public void setUp() {
+        mockShenyuConfig();
         this.ruleData = RuleData.builder().id("1")
                 .selectorId("1").enabled(true)
                 .loged(true).sort(1).build();
@@ -153,6 +157,12 @@ public final class AbstractShenyuPluginTest {
         StepVerifier.create(testShenyuPlugin.execute(exchange, shenyuPluginChain)).expectSubscription().verifyComplete();
     }
 
+    private void mockShenyuConfig() {
+        ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
+        when(context.getBean(ShenyuConfig.class)).thenReturn(new ShenyuConfig());
+        SpringBeanUtils.getInstance().setApplicationContext(context);
+    }
+
     static class TestShenyuPlugin extends AbstractShenyuPlugin {
 
         @Override
diff --git a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/cache/MatchDataCacheTest.java b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/cache/MatchDataCacheTest.java
new file mode 100644
index 000000000..132b8717e
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/cache/MatchDataCacheTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.shenyu.plugin.base.cache;
+
+import org.apache.shenyu.common.cache.MemorySafeLRUMap;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@SuppressWarnings("unchecked")
+public final class MatchDataCacheTest {
+
+    private final String selectorMapStr = "SELECTOR_DATA_MAP";
+
+    private final String mockPluginName1 = "MOCK_PLUGIN_NAME_1";
+
+    private final String path1 = "/http/abc";
+
+    @Test
+    public void testCacheSelectorData() throws NoSuchFieldException, IllegalAccessException {
+        SelectorData firstCachedSelectorData = SelectorData.builder().id("1").pluginName(mockPluginName1).sort(1).build();
+        MatchDataCache.getInstance().cacheSelectorData(path1, firstCachedSelectorData, 5 * 1024);
+        ConcurrentHashMap<String, MemorySafeLRUMap<String, SelectorData>> selectorMap = getFieldByName(selectorMapStr);
+        assertEquals(firstCachedSelectorData, selectorMap.get(mockPluginName1).get(path1));
+        selectorMap.clear();
+    }
+
+    @Test
+    public void testObtainSelectorData() throws NoSuchFieldException, IllegalAccessException {
+        SelectorData firstSelectorData = SelectorData.builder().id("1").pluginName(mockPluginName1).sort(1).build();
+        ConcurrentHashMap<String, MemorySafeLRUMap<String, SelectorData>> selectorMap = getFieldByName(selectorMapStr);
+        selectorMap.put(mockPluginName1, new MemorySafeLRUMap<>(5 * 1024, 16));
+        selectorMap.get(mockPluginName1).put(path1, firstSelectorData);
+        SelectorData firstSelectorDataCache = MatchDataCache.getInstance().obtainSelectorData(mockPluginName1, path1);
+        assertEquals(firstSelectorData, firstSelectorDataCache);
+        selectorMap.clear();
+    }
+
+    @Test
+    public void testRemoveSelectorData() throws NoSuchFieldException, IllegalAccessException {
+        SelectorData firstCachedSelectorData = SelectorData.builder().id("1").pluginName(mockPluginName1).sort(1).build();
+        MatchDataCache.getInstance().cacheSelectorData(path1, firstCachedSelectorData, 5 * 1024);
+        MatchDataCache.getInstance().removeSelectorData(firstCachedSelectorData.getPluginName());
+        ConcurrentHashMap<String, MemorySafeLRUMap<String, SelectorData>> selectorMap = getFieldByName(selectorMapStr);
+        assertEquals(null, selectorMap.get(mockPluginName1));
+        selectorMap.clear();
+    }
+
+    @SuppressWarnings("rawtypes")
+    private ConcurrentHashMap getFieldByName(final String name) throws NoSuchFieldException, IllegalAccessException {
+        MatchDataCache matchDataCache = MatchDataCache.getInstance();
+        Field pluginMapField = matchDataCache.getClass().getDeclaredField(name);
+        pluginMapField.setAccessible(true);
+        return (ConcurrentHashMap) pluginMapField.get(matchDataCache);
+    }
+}
diff --git a/shenyu-web/src/main/java/org/apache/shenyu/web/controller/LocalPluginController.java b/shenyu-web/src/main/java/org/apache/shenyu/web/controller/LocalPluginController.java
index 2851116ae..61a153e96 100644
--- a/shenyu-web/src/main/java/org/apache/shenyu/web/controller/LocalPluginController.java
+++ b/shenyu-web/src/main/java/org/apache/shenyu/web/controller/LocalPluginController.java
@@ -30,6 +30,7 @@ import org.apache.shenyu.common.enums.SelectorTypeEnum;
 import org.apache.shenyu.common.utils.JsonUtils;
 import org.apache.shenyu.common.utils.UUIDUtils;
 import org.apache.shenyu.plugin.base.cache.BaseDataCache;
+import org.apache.shenyu.plugin.base.cache.MatchDataCache;
 import org.apache.shenyu.sync.data.api.PluginDataSubscriber;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -93,6 +94,7 @@ public class LocalPluginController {
         List<SelectorData> selectorData = BaseDataCache.getInstance().obtainSelectorData(name);
         List<String> selectorIds = selectorData.stream().map(SelectorData::getId).collect(Collectors.toList());
         BaseDataCache.getInstance().removeSelectDataByPluginName(name);
+        MatchDataCache.getInstance().removeSelectorData(name);
         for (String selectorId : selectorIds) {
             BaseDataCache.getInstance().removeRuleDataBySelectorId(selectorId);
         }