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 beff9cf0f [ISSUE #3177] match once and only once (#3245)
beff9cf0f is described below

commit beff9cf0f752b67cb4bedee88a156f732746292f
Author: dragon-zhang <[email protected]>
AuthorDate: Fri Apr 15 10:29:42 2022 +0800

    [ISSUE #3177] match once and only once (#3245)
    
    * [ISSUE #3177] match once and only once
    
    * fix code style
    
    * use LRU cache
    
    * less compute
    
    * fix code style
    
    * fix bug and add test
    
    * fix test case
    
    * fix code style
    
    * change field name
    
    * fix testcase oom
    
    * fix comment
    
    * use Objects#isNull
    
    * fix javadoc
---
 .../shenyu/common/cache/MemorySafeLRUMap.java      | 72 ++++++++++++++++++++
 .../shenyu/common/cache/MemorySafeLRUMapTest.java  | 62 ++++++++++++++++++
 .../shenyu/plugin/global/cache/MetaDataCache.java  | 76 ++++++++++++++++++----
 3 files changed, 197 insertions(+), 13 deletions(-)

diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/cache/MemorySafeLRUMap.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/cache/MemorySafeLRUMap.java
new file mode 100644
index 000000000..3f3973d79
--- /dev/null
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/cache/MemorySafeLRUMap.java
@@ -0,0 +1,72 @@
+/*
+ * 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.common.cache;
+
+import org.apache.commons.collections4.map.LRUMap;
+import org.apache.shenyu.common.concurrent.MemoryLimitCalculator;
+
+import java.util.Map;
+
+/**
+ * The only difference between this class and {@link 
org.apache.commons.collections4.map.LRUMap}
+ * is that it handles memory issues via {@link 
org.apache.shenyu.common.concurrent.MemoryLimitCalculator}.
+ */
+public class MemorySafeLRUMap<K, V> extends LRUMap<K, V> {
+
+    private final int maxFreeMemory;
+
+    public MemorySafeLRUMap(final int maxFreeMemory,
+                            final int initialSize) {
+        super(MAXIMUM_CAPACITY, initialSize);
+        this.maxFreeMemory = maxFreeMemory;
+    }
+
+    public MemorySafeLRUMap(final int maxFreeMemory,
+                            final int initialSize,
+                            final float loadFactor) {
+        super(MAXIMUM_CAPACITY, initialSize, loadFactor);
+        this.maxFreeMemory = maxFreeMemory;
+    }
+
+    public MemorySafeLRUMap(final int maxFreeMemory,
+                            final int initialSize,
+                            final float loadFactor,
+                            final boolean scanUntilRemovable) {
+        super(MAXIMUM_CAPACITY, initialSize, loadFactor, scanUntilRemovable);
+        this.maxFreeMemory = maxFreeMemory;
+    }
+
+    public MemorySafeLRUMap(final int maxFreeMemory,
+                            final Map<? extends K, ? extends V> map) {
+        super(map);
+        this.maxFreeMemory = maxFreeMemory;
+    }
+
+    public MemorySafeLRUMap(final int maxFreeMemory,
+                            final Map<? extends K, ? extends V> map,
+                            final boolean scanUntilRemovable) {
+        super(map, scanUntilRemovable);
+        this.maxFreeMemory = maxFreeMemory;
+    }
+
+    @Override
+    public boolean isFull() {
+        // when free memory less than certain value, consider it's full
+        return size() > 0 && MemoryLimitCalculator.maxAvailable() < 
maxFreeMemory;
+    }
+}
diff --git 
a/shenyu-common/src/test/java/org/apache/shenyu/common/cache/MemorySafeLRUMapTest.java
 
b/shenyu-common/src/test/java/org/apache/shenyu/common/cache/MemorySafeLRUMapTest.java
new file mode 100644
index 000000000..37a6a6c5d
--- /dev/null
+++ 
b/shenyu-common/src/test/java/org/apache/shenyu/common/cache/MemorySafeLRUMapTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.common.cache;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Map;
+
+/**
+ * Test cases for MemorySafeLRUMapTest.
+ */
+public class MemorySafeLRUMapTest {
+    @Test
+    public void testPut() {
+        MemorySafeLRUMap<String, String> lru = new MemorySafeLRUMap<>(1 << 10, 
16);
+        lru.put("1", "1");
+        Assert.assertEquals(1, lru.size());
+        lru.put("2", "2");
+        lru.put("3", "3");
+        Assert.assertEquals(3, lru.size());
+    }
+
+    @Test
+    public void testLru() {
+        MemorySafeLRUMap<Integer, Integer> lru = new MemorySafeLRUMap<Integer, 
Integer>(1, 1024) {
+
+            private static final long serialVersionUID = 8897028073615563875L;
+
+            @Override
+            public boolean isFull() {
+                //just for test
+                return size() > 0;
+            }
+        };
+        lru.put(1, 1);
+        Assert.assertEquals(1, lru.size());
+        lru.put(2, 2);
+        lru.put(3, 3);
+        Assert.assertEquals(1, lru.size());
+        final Map.Entry<Integer, Integer> entry = 
lru.entrySet().iterator().next();
+        final Integer key = entry.getKey();
+        final Integer value = entry.getValue();
+        Assert.assertEquals(3, (int) key);
+        Assert.assertEquals(3, (int) value);
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-global/src/main/java/org/apache/shenyu/plugin/global/cache/MetaDataCache.java
 
b/shenyu-plugin/shenyu-plugin-global/src/main/java/org/apache/shenyu/plugin/global/cache/MetaDataCache.java
index 2d83c5f61..22039c796 100644
--- 
a/shenyu-plugin/shenyu-plugin-global/src/main/java/org/apache/shenyu/plugin/global/cache/MetaDataCache.java
+++ 
b/shenyu-plugin/shenyu-plugin-global/src/main/java/org/apache/shenyu/plugin/global/cache/MetaDataCache.java
@@ -18,27 +18,42 @@
 package org.apache.shenyu.plugin.global.cache;
 
 import com.google.common.collect.Maps;
+import org.apache.shenyu.common.cache.MemorySafeLRUMap;
 import org.apache.shenyu.common.dto.MetaData;
 import org.apache.shenyu.common.utils.PathMatchUtils;
 
+import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentSkipListSet;
 
 /**
  * The type Meta data cache.
  */
 public final class MetaDataCache {
-    
+
+    private static final int THE_256_MB = 256 * 1024 * 1024;
+
+    private static final MetaData NULL = new MetaData();
+
     private static final MetaDataCache INSTANCE = new MetaDataCache();
-    
+
     /**
      * path -> MetaData.
      */
     private static final ConcurrentMap<String, MetaData> META_DATA_MAP = 
Maps.newConcurrentMap();
-    
+
+    private static final MemorySafeLRUMap<String, MetaData> CACHE = new 
MemorySafeLRUMap<>(THE_256_MB, 1 << 16);
+
+    /**
+     * pathPattern -> path.
+     */
+    private static final ConcurrentMap<String, Set<String>> MAPPING = 
Maps.newConcurrentMap();
+
     private MetaDataCache() {
     }
-    
+
     /**
      * Gets instance.
      *
@@ -47,7 +62,7 @@ public final class MetaDataCache {
     public static MetaDataCache getInstance() {
         return INSTANCE;
     }
-    
+
     /**
      * Cache auth data.
      *
@@ -55,8 +70,12 @@ public final class MetaDataCache {
      */
     public void cache(final MetaData data) {
         META_DATA_MAP.put(data.getPath(), data);
+        // the update is also need to clean, but there is
+        // no way to distinguish between crate and update,
+        // so it is always clean
+        clean(data.getPath());
     }
-    
+
     /**
      * Remove auth data.
      *
@@ -64,8 +83,19 @@ public final class MetaDataCache {
      */
     public void remove(final MetaData data) {
         META_DATA_MAP.remove(data.getPath());
+        clean(data.getPath());
+    }
+
+    private void clean(final String key) {
+        //only clean springCloud
+        Optional.ofNullable(MAPPING.get(key))
+                .ifPresent(paths -> {
+                    for (String path : paths) {
+                        CACHE.remove(path);
+                    }
+                });
     }
-    
+
     /**
      * Obtain auth data meta data.
      *
@@ -73,11 +103,31 @@ public final class MetaDataCache {
      * @return the meta data
      */
     public MetaData obtain(final String path) {
-        return Optional.ofNullable(META_DATA_MAP.get(path))
-                .orElseGet(() -> META_DATA_MAP.get(META_DATA_MAP.keySet()
-                        .stream()
-                        .filter(k -> PathMatchUtils.match(k, path))
-                        .findFirst()
-                        .orElse("")));
+        final MetaData metaData = Optional.ofNullable(META_DATA_MAP.get(path))
+                .orElseGet(() -> {
+                    final MetaData exist = CACHE.get(path);
+                    if (Objects.nonNull(exist)) {
+                        return exist;
+                    }
+
+                    final String key = META_DATA_MAP.keySet()
+                            .stream()
+                            .filter(k -> PathMatchUtils.match(k, path))
+                            .findFirst()
+                            .orElse("");
+                    final MetaData value = META_DATA_MAP.get(key);
+                    // The extreme case will lead to OOM, that's why use LRU
+                    CACHE.put(path, Objects.isNull(value) ? NULL : value);
+
+                    Set<String> paths = MAPPING.get(key);
+                    if (Objects.isNull(paths)) {
+                        MAPPING.putIfAbsent(key, new 
ConcurrentSkipListSet<>());
+                        paths = MAPPING.get(key);
+                    }
+                    paths.add(path);
+
+                    return value;
+                });
+        return NULL.equals(metaData) ? null : metaData;
     }
 }

Reply via email to