分布式多级缓存(本地缓存,redis缓存)
结构包:
使用案例:
实现效果:
1、基本并发的本地缓存,基于分布式轻量级锁的redis缓存
2、热缓存(高频访问持续缓存)+快速过期(本地缓存2秒,redis缓存10秒)
3、方法级别缓存清理 (@HybridCache 与@HybridChange 绑定管理缓存 )
4、基于HybridType接口的可扩展式作用域,目前已实现:全局、token
5、基于HybridLevel接口的可扩展式缓存处理,目前已实现:本地缓存、redis缓存
核心代码包:
package com.*.server.live.core.hybridCache; import com.*.server.live.core.hybridCache.impl.DepthLocal; import com.*.server.live.core.hybridCache.impl.DepthRedis; import java.lang.annotation.*; /** * 功能描述:多重缓存 * 作者:唐泽齐 * @case @HybridCache(scope = ScopeGlobal.class) */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface HybridCache { /*缓存深度*/ Class<? extends HybridLevel> depth() default DepthRedis.class; /*缓存广度*/ Class<? extends HybridType> scope(); }
package com.*.server.live.core.hybridCache; import com.*.server.live.core.hybridCache.impl.DepthRedis; import java.lang.annotation.*; /** * 功能描述:多重缓存更新 * 作者:唐泽齐 * @case @HybridChange(clazz = LiveStudioController.class,method = "getStudio",args = {Long.class}) * @case @HybridChange(clazz = LiveStudioController.class,method = "getStudio",args = {}) */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface HybridChange { /*关联类*/ Class<? extends Object> clazz(); /*关联方法*/ String method(); /*关联方法入参类型*/ Class<?>[] args(); }
package com.*.server.live.core.hybridCache; import org.aspectj.lang.ProceedingJoinPoint; import javax.validation.constraints.NotNull; /** * 功能描述:多重缓存级别 * 作者:唐泽齐 */ public interface HybridLevel { /** * 缓存 * @param key 缓存key * @param id 混村ID * @param o 缓存值 * @param self 是否执行节点 * @param joinPoint 节点 * @return * @throws Throwable */ Object cache(@NotNull String key,@NotNull String id, Object o,@NotNull boolean self,@NotNull ProceedingJoinPoint joinPoint) throws Throwable; /** * 清缓存 * @param key 缓存key * @param self 是否执行节点 * @param joinPoint 节点 * @return * @throws Throwable */ Object del(@NotNull String key,@NotNull boolean self,@NotNull ProceedingJoinPoint joinPoint) throws Throwable; }
package com.*.server.live.core.hybridCache; /** * 功能描述:多重缓存级别 * 作者:唐泽齐 */ public interface HybridType { String token(); }
package com.*.server.live.core.hybridCache; import com.alibaba.fastjson.JSON; import com.alibaba.nacos.common.util.Md5Utils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; import java.lang.reflect.Method; /** * 功能描述:多重缓存接口 * 作者:唐泽齐 */ @Aspect @Configuration public class HybridCacheInterceptor { @Resource ApplicationContext applicationContext; final static String cache = "hybridCache:"; @Around(value = "@annotation(com.*.server.live.core.hybridCache.HybridCache)") public Object cache(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature ms = (MethodSignature) joinPoint.getSignature(); Method method = ms.getMethod(); HybridCache cacheAnnotation = method.getAnnotation(HybridCache.class); String key = getCacheKey(method); String id = getCacheId(method,joinPoint.getArgs()); HybridLevel hybridLevel = applicationContext.getBean(cacheAnnotation.depth()); return hybridLevel.cache(key, id, null, true, joinPoint); } @Around(value = "@annotation(com.*.server.live.core.hybridCache.HybridChange)") public Object del(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature ms = (MethodSignature) joinPoint.getSignature(); Method method = ms.getMethod(); HybridChange cacheAnnotation = method.getAnnotation(HybridChange.class); try { Method method1 = cacheAnnotation.clazz().getMethod(cacheAnnotation.method(),cacheAnnotation.args()); HybridCache cache = method1.getAnnotation(HybridCache.class); String key = getCacheKey(method1); HybridLevel hybridLevel = applicationContext.getBean(cache.depth()); return hybridLevel.del(key,true, joinPoint); } catch (Exception e) { return joinPoint.proceed(); } } /*获取缓存key*/ private String getCacheKey(Method method) { return cache + method.getDeclaringClass().getSimpleName() + ":" + method.getName(); } ; /*获取缓存id*/ private String getCacheId(Method method,Object[] args) { HybridCache cacheAnnotation = method.getAnnotation(HybridCache.class); HybridType hybridType = applicationContext.getBean(cacheAnnotation.scope()); return Md5Utils.getMD5((hybridType.token() + JSON.toJSONString(args)).getBytes()); } ; }
扩展代码包:
package com.*.server.live.core.hybridCache.impl; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.*.server.live.core.hybridCache.HybridLevel; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * 功能描述:本地缓存 * 作者:唐泽齐 */ @Component public class DepthLocal implements HybridLevel { /*自带读写锁*/ public volatile static Cache<String, Object> localCache = CacheBuilder.newBuilder().initialCapacity(144).expireAfterAccess(2, TimeUnit.SECONDS).build(); @Override public Object cache(String key, String id, Object o, boolean search, ProceedingJoinPoint joinPoint) throws Throwable { String keyId = key + ":" + id; MethodSignature ms = (MethodSignature) joinPoint.getSignature(); if (search && o == null && (o = localCache.getIfPresent(keyId)) == null) { synchronized (ms.getMethod()) { if ((o = localCache.getIfPresent(keyId)) == null) { o = joinPoint.proceed(); localCache.put(keyId, o); } } } if (o == null) { o = localCache.getIfPresent(keyId); } else { localCache.put(keyId, o); } return o; } @Override public Object del(String key, boolean self, ProceedingJoinPoint joinPoint) throws Throwable { Object o = null; if (self) { o = joinPoint.proceed(); } localCache.cleanUp(); return o; } }
package com.*.server.live.core.hybridCache.impl; import cn.hutool.core.util.RandomUtil; import com.*.common.redis.service.RedisService; import com.*.server.live.core.hybridCache.HybridLevel; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * 功能描述:redis缓存 * 作者:唐泽齐 */ @Component public class DepthRedis implements HybridLevel { @Resource private RedisService redisService; @Resource private HybridLevel depthLocal; @Override public synchronized Object cache(String key, String id, Object o,boolean search, ProceedingJoinPoint joinPoint) throws Throwable { o = depthLocal.cache(key, id, o,false,joinPoint); String lock = getLock(key, id); if(search && o== null && (o=redisService.hget(key , id)) == null ) { for (;;) { if(redisService.incr(lock,1l) <= 1l && (o=redisService.hget(key , id)) == null ) { try { o = joinPoint.proceed(); redisService.hset(key , id, o, 10l); } catch (Throwable e) { throw e; }finally { redisService.del(lock); } } else { o = redisService.hget(key , id); } if(o == null) { Thread.sleep(1); } else { break; } } } if (o == null) { o = redisService.hget(key , id); } else { depthLocal.cache(key, id, o,false,joinPoint); redisService.hset(key , id, o, 10l); } return o; } @Override public Object del(String key,boolean self, ProceedingJoinPoint joinPoint) throws Throwable { Object o = null; if (self) { o = joinPoint.proceed(); } redisService.del(key); depthLocal.del(key,false,joinPoint); return o; } private String getLock(String key, String id) { return this.getClass().getSimpleName()+":"+key+":"+id; } }
package com.*.server.live.core.hybridCache.impl; import com.*.server.live.core.hybridCache.HybridType; import org.springframework.stereotype.Component; /** * 功能描述:全局级别 * 作者:唐泽齐 */ @Component public class ScopeGlobal implements HybridType { @Override public String token() { return ""; } }
package com.*.server.live.core.hybridCache.impl; import com.*.common.core.utils.*Util; import com.*.server.live.core.hybridCache.HybridType; import org.springframework.stereotype.Component; /** * 功能描述:token级别 * 作者:唐泽齐 */ @Component public class ScopeToken implements HybridType { @Override public String token() { return *Util.getCurrentTokenValue(); } }
问题修复:
1.json问题: 类HybridCacheInterceptor调整使用JSON.toJSON(args).toString()
2.死循环问题:类DepthRedis调整使用
@Override
public synchronized Object cache(String key, String id, Object o,boolean search, ProceedingJoinPoint joinPoint) throws Throwable {
o = depthLocal.cache(key, id, o,false,joinPoint);
final String lock = getLock(key, id);
if(search && o== null && (o=redisService.hget(key , id)) == null ) {
if(redisService.incr(lock,1l) <= 1l && (o=redisService.hget(key , id)) == null ) {
try {
o = joinPoint.proceed();
redisService.hset(key , id, o, RandomUtil.randomLong(10l,20l));
} catch (Throwable e) {
throw e;
}finally {
redisService.del(lock);
}
} else {
Thread.sleep(10);
o = redisService.hget(key , id);
}
}
if (o == null) {
Thread.sleep(10);
o = redisService.hget(key , id);
} else {
depthLocal.cache(key, id, o,false,joinPoint);
redisService.hset(key , id, o, 10l);
}
return o;
}
问题修复:
1.取消清除缓存时的常规重试机制:
HybridCacheInterceptor -->
public Object del(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
Method method = ms.getMethod();
String updateKey = getCacheKey(method);
HybridChange cacheAnnotation = method.getAnnotation(HybridChange.class);
Method method1 = cacheAnnotation.clazz().getMethod(cacheAnnotation.method(),cacheAnnotation.args());
HybridCache cache = method1.getAnnotation(HybridCache.class);
String key = getCacheKey(method1);
try {
HybridLevel hybridLevel = applicationContext.getBean(cache.depth());
Object o = hybridLevel.del(key, true, joinPoint);
log.info("多级缓存 清除 {}-->{}",updateKey,key);
return o;
} catch (Exception e) {
log.info("多级缓存 清除失败 {}-->{}:{}",updateKey,key,e);
throw e;
}
}
问题修复:
1、分布式情况下缓存死锁问题:
@Override
public synchronized Object cache(String key, String id, Object o,boolean search, ProceedingJoinPoint joinPoint) throws Throwable {
o = depthLocal.cache(key, id, o,false,joinPoint);
final String lock = getLock(key, id);
if(search && o== null && (o=redisService.hget(key , id)) == null ) {
if(redisService.increx(lock,1l,1l) <= 1l && (o=redisService.hget(key , id)) == null ) {
try {
o = joinPoint.proceed();
redisService.hset(key , id, o, RandomUtil.randomLong(10l,20l));
} catch (Throwable e) {
throw e;
}finally {
redisService.del(lock);
}
} else {
Thread.sleep(10);
o = redisService.hget(key , id);
}
}
if (o == null) {
Thread.sleep(10);
o = redisService.hget(key , id);
} else {
depthLocal.cache(key, id, o,false,joinPoint);
redisService.hset(key , id, o, 10l);
}
return o;
}