From 329f35c9b281014b1f6700675d5f85fc6d988052 Mon Sep 17 00:00:00 2001
From: "1437892690@qq.com" <1437892690@qq.com>
Date: Thu, 15 May 2025 15:29:14 +0800
Subject: [PATCH] =?UTF-8?q?[=E5=8A=9F=E8=83=BD]=20=E5=A2=9E=E5=8A=A0?=
=?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=8F=AF=E4=BB=A5=E5=9C=A8=E9=AB=98=E5=B9=B6?=
=?UTF-8?q?=E5=8F=91=E5=9C=BA=E6=99=AF=E4=B8=8B=E9=98=B2=E6=AD=A2=E8=A2=AB?=
=?UTF-8?q?=E5=87=BB=E7=A9=BF=E7=9A=84Mybaties=E4=BA=8C=E7=BA=A7=E7=BC=93?=
=?UTF-8?q?=E5=AD=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
关联 #[1421727635046400]增加一个可以在高并发场景下防止被击穿的Mybaties二级缓存 http://192.168.0.96:8090/demo/rdm.html#/story-detail/939050947543040/939050947543042/1421727635046400
---
.../cache/NeatLogicConcurrentSafeCache.java | 258 ++++++++++++++++++
.../restful/core/ApiComponentBase.java | 12 +-
.../core/BinaryStreamApiComponentBase.java | 12 +-
.../core/JsonStreamApiComponentBase.java | 15 +-
.../restful/core/RawApiComponentBase.java | 13 +-
.../dao/mapper/ApiLongCacheMapper.java | 23 --
.../restful/dao/mapper/ApiLongCacheMapper.xml | 43 ---
.../restful/dao/mapper/ApiMapper.xml | 4 +-
.../handler/AnonymousApiDispatcher.java | 17 +-
.../dispatch/handler/ApiDispatcher.java | 17 +-
.../dispatch/handler/PublicApiDispatcher.java | 6 +-
11 files changed, 287 insertions(+), 133 deletions(-)
create mode 100644 src/main/java/neatlogic/framework/dao/cache/NeatLogicConcurrentSafeCache.java
delete mode 100644 src/main/java/neatlogic/framework/restful/dao/mapper/ApiLongCacheMapper.java
delete mode 100644 src/main/java/neatlogic/framework/restful/dao/mapper/ApiLongCacheMapper.xml
diff --git a/src/main/java/neatlogic/framework/dao/cache/NeatLogicConcurrentSafeCache.java b/src/main/java/neatlogic/framework/dao/cache/NeatLogicConcurrentSafeCache.java
new file mode 100644
index 000000000..82a12ea5f
--- /dev/null
+++ b/src/main/java/neatlogic/framework/dao/cache/NeatLogicConcurrentSafeCache.java
@@ -0,0 +1,258 @@
+/*Copyright (C) 2024 深圳极向量科技有限公司 All Rights Reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .*/
+
+package neatlogic.framework.dao.cache;
+
+import neatlogic.framework.asynchronization.threadlocal.TenantContext;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.Element;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.cache.Cache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 高并发场景下,防止缓存击穿
+ */
+public class NeatLogicConcurrentSafeCache implements Cache {
+
+ private final static Logger logger = LoggerFactory.getLogger(NeatLogicConcurrentSafeCache.class);
+ /**
+ * The cache manager reference.
+ */
+ protected static CacheManager CACHE_MANAGER = CacheManager.create();
+ private final static ConcurrentHashMap LOCAL_LOCK_MAP = new ConcurrentHashMap<>();
+
+ private static String generateLockKey(String id, Object key) {
+ String tenant = null;
+ TenantContext tenantContext = TenantContext.get();
+ if (tenantContext != null) {
+ tenant = tenantContext.getTenantUuid();
+ }
+ if (StringUtils.isNotBlank(tenant)) {
+ return tenant + ":" + id + ":" + key;
+ } else {
+ return id + ":" + key;
+ }
+ }
+ /**
+ * The cache id (namespace)
+ */
+ protected final String id;
+
+
+ public NeatLogicConcurrentSafeCache(final String id) {
+ if (id == null) {
+ throw new IllegalArgumentException("Cache instances require an ID");
+ }
+ this.id = id;
+ }
+
+ private synchronized Ehcache getCache() {
+ TenantContext tenantContext = TenantContext.get();
+ String tenant = null;
+ if (tenantContext != null) {
+ tenant = tenantContext.getTenantUuid();
+ }
+ if (StringUtils.isNotBlank(tenant)) {
+ if (!CACHE_MANAGER.cacheExists(tenant + ":" + id)) {
+ CACHE_MANAGER.addCache(tenant + ":" + id);
+ }
+ return CACHE_MANAGER.getEhcache(tenant + ":" + id);
+ } else {
+ if (!CACHE_MANAGER.cacheExists(id)) {
+ CACHE_MANAGER.addCache(id);
+ }
+ return CACHE_MANAGER.getEhcache(id);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void clear() {
+ //System.out.println(System.currentTimeMillis() + ":clear cache:" + this.id);
+ getCache().removeAll();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object getObject(Object key) {
+ Element cachedElement = getCache().get(key);
+ if (cachedElement != null) {
+ return cachedElement.getObjectValue();
+ }
+ ReentrantLock lock = LOCAL_LOCK_MAP.computeIfAbsent(generateLockKey(getId(), key), k -> new ReentrantLock());
+ try {
+ boolean flag = lock.tryLock(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ cachedElement = getCache().get(key);
+ if (cachedElement != null) {
+ lock.unlock();
+ return cachedElement.getObjectValue();
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getSize() {
+ return getCache().getSize();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void putObject(Object key, Object value) {
+ getCache().put(new Element(key, value));
+ ReentrantLock lock = LOCAL_LOCK_MAP.remove(generateLockKey(getId(), key));
+ if (lock != null) {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Object removeObject(Object key) {
+ Object obj = getObject(key);
+ getCache().remove(key);
+ ReentrantLock lock = LOCAL_LOCK_MAP.remove(generateLockKey(getId(), key));
+ if (lock != null) {
+ lock.unlock();
+ }
+ return obj;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void unlock(Object key) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Cache)) {
+ return false;
+ }
+
+ Cache otherCache = (Cache) obj;
+ return id.equals(otherCache.getId());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public ReadWriteLock getReadWriteLock() {
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "EHCache {" + id + "}";
+ }
+
+ // DYNAMIC PROPERTIES
+
+ /**
+ * Sets the time to idle for an element before it expires. Is only used if
+ * the element is not eternal.
+ *
+ * @param timeToIdleSeconds the default amount of time to live for an element from its
+ * last accessed or modified date
+ */
+ public void setTimeToIdleSeconds(long timeToIdleSeconds) {
+ getCache().getCacheConfiguration().setTimeToIdleSeconds(timeToIdleSeconds);
+ }
+
+ /**
+ * Sets the time to idle for an element before it expires. Is only used if
+ * the element is not eternal.
+ *
+ * @param timeToLiveSeconds the default amount of time to live for an element from its
+ * creation date
+ */
+ public void setTimeToLiveSeconds(long timeToLiveSeconds) {
+ getCache().getCacheConfiguration().setTimeToLiveSeconds(timeToLiveSeconds);
+ }
+
+ /**
+ * Sets the maximum objects to be held in memory (0 = no limit).
+ * evicted (0 == no limit)
+ */
+ public void setMaxEntriesLocalHeap(long maxEntriesLocalHeap) {
+ getCache().getCacheConfiguration().setMaxEntriesLocalHeap(maxEntriesLocalHeap);
+ }
+
+ /**
+ * Sets the maximum number elements on Disk. 0 means unlimited.
+ * unlimited.
+ */
+ public void setMaxEntriesLocalDisk(long maxEntriesLocalDisk) {
+ getCache().getCacheConfiguration().setMaxEntriesLocalDisk(maxEntriesLocalDisk);
+ }
+
+ /**
+ * Sets the eviction policy. An invalid argument will set it to null.
+ *
+ * @param memoryStoreEvictionPolicy a String representation of the policy. One of "LRU", "LFU" or
+ * "FIFO".
+ */
+ public void setMemoryStoreEvictionPolicy(String memoryStoreEvictionPolicy) {
+ getCache().getCacheConfiguration().setMemoryStoreEvictionPolicy(memoryStoreEvictionPolicy);
+ }
+
+}
diff --git a/src/main/java/neatlogic/framework/restful/core/ApiComponentBase.java b/src/main/java/neatlogic/framework/restful/core/ApiComponentBase.java
index 52c1e331b..c64dd4435 100644
--- a/src/main/java/neatlogic/framework/restful/core/ApiComponentBase.java
+++ b/src/main/java/neatlogic/framework/restful/core/ApiComponentBase.java
@@ -23,7 +23,6 @@ import neatlogic.framework.dto.api.CacheControlVo;
import neatlogic.framework.exception.core.ApiFieldValidNotFoundException;
import neatlogic.framework.exception.core.ApiRuntimeException;
import neatlogic.framework.restful.core.privateapi.PrivateApiComponentFactory;
-import neatlogic.framework.restful.dao.mapper.ApiLongCacheMapper;
import neatlogic.framework.restful.dto.ApiVo;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -31,16 +30,12 @@ import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.AopContext;
import org.springframework.aop.support.AopUtils;
-import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public abstract class ApiComponentBase extends ApiValidateAndHelpBase implements MyApiComponent {
- @Resource
- private ApiLongCacheMapper apiLongCacheMapper;
-
public int needAudit() {
return 0;
}
@@ -174,12 +169,7 @@ public abstract class ApiComponentBase extends ApiValidateAndHelpBase implements
} finally {
long endTime = System.currentTimeMillis();
if (!apiVo.getModuleId().equals("master")) {
- ApiVo apiConfigVo = apiLongCacheMapper.getApiByToken(apiVo.getToken());
- // 如果没有配置,则使用默认配置
- if (apiConfigVo == null) {
- apiConfigVo = apiVo;
- }
- if (apiConfigVo.getNeedAudit() != null && apiConfigVo.getNeedAudit().equals(1)) {
+ if (apiVo.getNeedAudit() != null && apiVo.getNeedAudit().equals(1)) {
saveAudit(apiVo, paramObj, result, error, startTime, endTime);
}
}
diff --git a/src/main/java/neatlogic/framework/restful/core/BinaryStreamApiComponentBase.java b/src/main/java/neatlogic/framework/restful/core/BinaryStreamApiComponentBase.java
index 819010edb..b4964e6dd 100644
--- a/src/main/java/neatlogic/framework/restful/core/BinaryStreamApiComponentBase.java
+++ b/src/main/java/neatlogic/framework/restful/core/BinaryStreamApiComponentBase.java
@@ -4,22 +4,17 @@ import com.alibaba.fastjson.JSONObject;
import neatlogic.framework.common.constvalue.CacheControlType;
import neatlogic.framework.dto.api.CacheControlVo;
import neatlogic.framework.exception.core.ApiRuntimeException;
-import neatlogic.framework.restful.dao.mapper.ApiLongCacheMapper;
import neatlogic.framework.restful.dto.ApiVo;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.aop.support.AopUtils;
-import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public abstract class BinaryStreamApiComponentBase extends ApiValidateAndHelpBase implements MyBinaryStreamApiComponent {
- @Resource
- private ApiLongCacheMapper apiLongCacheMapper;
-
public int needAudit() {
return 0;
}
@@ -66,12 +61,7 @@ public abstract class BinaryStreamApiComponentBase extends ApiValidateAndHelpBas
throw e;
} finally {
long endTime = System.currentTimeMillis();
- ApiVo apiConfigVo = apiLongCacheMapper.getApiByToken(apiVo.getToken());
- // 如果没有配置,则使用默认配置
- if (apiConfigVo == null) {
- apiConfigVo = apiVo;
- }
- if (apiConfigVo.getNeedAudit() != null && apiConfigVo.getNeedAudit().equals(1)) {
+ if (apiVo.getNeedAudit() != null && apiVo.getNeedAudit().equals(1)) {
saveAudit(apiVo, paramObj, result, error, startTime, endTime);
}
}
diff --git a/src/main/java/neatlogic/framework/restful/core/JsonStreamApiComponentBase.java b/src/main/java/neatlogic/framework/restful/core/JsonStreamApiComponentBase.java
index b6c343a7e..78784c03c 100644
--- a/src/main/java/neatlogic/framework/restful/core/JsonStreamApiComponentBase.java
+++ b/src/main/java/neatlogic/framework/restful/core/JsonStreamApiComponentBase.java
@@ -3,25 +3,17 @@ package neatlogic.framework.restful.core;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONReader;
import neatlogic.framework.exception.core.ApiRuntimeException;
-import neatlogic.framework.restful.dao.mapper.ApiLongCacheMapper;
-import neatlogic.framework.restful.dao.mapper.ApiMapper;
import neatlogic.framework.restful.dto.ApiVo;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.aop.support.AopUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import javax.annotation.Resource;
import java.lang.reflect.Method;
public abstract class JsonStreamApiComponentBase extends ApiValidateAndHelpBase implements MyJsonStreamApiComponent {
// private static Logger logger =
// LoggerFactory.getLogger(JsonStreamApiComponentBase.class);
- @Resource
- private ApiLongCacheMapper apiLongCacheMapper;
-
-
public int needAudit() {
return 0;
}
@@ -56,12 +48,7 @@ public abstract class JsonStreamApiComponentBase extends ApiValidateAndHelpBase
throw e;
} finally {
long endTime = System.currentTimeMillis();
- ApiVo apiConfigVo = apiLongCacheMapper.getApiByToken(apiVo.getToken());
- // 如果没有配置,则使用默认配置
- if (apiConfigVo == null) {
- apiConfigVo = apiVo;
- }
- if (apiConfigVo.getNeedAudit() != null && apiConfigVo.getNeedAudit().equals(1)) {
+ if (apiVo.getNeedAudit() != null && apiVo.getNeedAudit().equals(1)) {
saveAudit(apiVo, paramObj, result, error, startTime, endTime);
}
}
diff --git a/src/main/java/neatlogic/framework/restful/core/RawApiComponentBase.java b/src/main/java/neatlogic/framework/restful/core/RawApiComponentBase.java
index e4bcafa7f..a2a5a2f94 100644
--- a/src/main/java/neatlogic/framework/restful/core/RawApiComponentBase.java
+++ b/src/main/java/neatlogic/framework/restful/core/RawApiComponentBase.java
@@ -21,23 +21,18 @@ import neatlogic.framework.common.config.Config;
import neatlogic.framework.dto.FieldValidResultVo;
import neatlogic.framework.dto.api.CacheControlVo;
import neatlogic.framework.exception.core.ApiRuntimeException;
-import neatlogic.framework.restful.dao.mapper.ApiLongCacheMapper;
import neatlogic.framework.restful.dto.ApiVo;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.aop.support.AopUtils;
-import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public abstract class RawApiComponentBase extends ApiValidateAndHelpBase implements MyRawApiComponent {
- @Resource
- private ApiLongCacheMapper apiLongCacheMapper;
-
public int needAudit() {
return 0;
}
@@ -110,16 +105,10 @@ public abstract class RawApiComponentBase extends ApiValidateAndHelpBase impleme
} finally {
long endTime = System.currentTimeMillis();
if (!apiVo.getModuleId().equals("master")) {
- ApiVo apiConfigVo = apiLongCacheMapper.getApiByToken(apiVo.getToken());
- // 如果没有配置,则使用默认配置
- if (apiConfigVo == null) {
- apiConfigVo = apiVo;
- }
- if (apiConfigVo.getNeedAudit() != null && apiConfigVo.getNeedAudit().equals(1)) {
+ if (apiVo.getNeedAudit() != null && apiVo.getNeedAudit().equals(1)) {
saveAudit(apiVo, param, result, error, startTime, endTime);
}
}
-
}
return result;
diff --git a/src/main/java/neatlogic/framework/restful/dao/mapper/ApiLongCacheMapper.java b/src/main/java/neatlogic/framework/restful/dao/mapper/ApiLongCacheMapper.java
deleted file mode 100644
index 113ea0bfd..000000000
--- a/src/main/java/neatlogic/framework/restful/dao/mapper/ApiLongCacheMapper.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*Copyright (C) 2024 深圳极向量科技有限公司 All Rights Reserved.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Affero General Public License for more details.
-
-You should have received a copy of the GNU Affero General Public License
-along with this program. If not, see .*/
-
-package neatlogic.framework.restful.dao.mapper;
-
-import neatlogic.framework.restful.dto.ApiVo;
-
-public interface ApiLongCacheMapper {
-
- ApiVo getApiByToken(String token);
-}
diff --git a/src/main/java/neatlogic/framework/restful/dao/mapper/ApiLongCacheMapper.xml b/src/main/java/neatlogic/framework/restful/dao/mapper/ApiLongCacheMapper.xml
deleted file mode 100644
index 82d45a4e1..000000000
--- a/src/main/java/neatlogic/framework/restful/dao/mapper/ApiLongCacheMapper.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/java/neatlogic/framework/restful/dao/mapper/ApiMapper.xml b/src/main/java/neatlogic/framework/restful/dao/mapper/ApiMapper.xml
index 949d6fd17..d6f442e6e 100644
--- a/src/main/java/neatlogic/framework/restful/dao/mapper/ApiMapper.xml
+++ b/src/main/java/neatlogic/framework/restful/dao/mapper/ApiMapper.xml
@@ -16,8 +16,8 @@ along with this program. If not, see .-->
-
-
+
+