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 .--> - - + +