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;
}
}