分布式多级缓存(本地缓存,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(); }
@HybridCache

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();
}
@HybridChange

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

package com.*.server.live.core.hybridCache; /**
* 功能描述:多重缓存级别
* 作者:唐泽齐
*/
public interface HybridType {
String token();
}
HybridType

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());
} ; }
HybridCacheInterceptor
扩展代码包:

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

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

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

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();
}
}
ScopeToken
问题修复:
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;
}
分布式多级缓存(本地缓存,redis缓存)的更多相关文章
- 分布式改造剧集之Redis缓存采坑记
Redis缓存采坑记 前言 这个其实应该属于分布式改造剧集中的一集(第一集见前面博客:http://www.cnblogs.com/Kidezyq/p/8748961.html),本来按照顺序 ...
- 分布式中为什么要加入redis缓存的理解
面我们介绍了mybatis自带的二级缓存,但是这个缓存是单服务器工作,无法实现分布式缓存.那么什么是分布式缓存呢?假设现在有两个服务器1和2,用户访问的时候访问了1服务器,查询后的缓存就会放在1服务器 ...
- 缓存机制总结(JVM内置缓存机制,MyBatis和Hibernate缓存机制,Redis缓存)
一.JVM内置缓存(值存放在JVM缓存中) 我们可以先了解一下Cookie,Session,和Cache Cookie:当你在浏览网站的时候,WEB 服务器会先送一小小资料放在你的计算机上,Cooki ...
- 缓存工厂之Redis缓存
这几天没有按照计划分享技术博文,主要是去医院了,这里一想到在医院经历的种种,我真的有话要说:医院里的医务人员曾经被吹捧为美丽+和蔼+可亲的天使,在经受5天左右相互接触后不得不让感慨:遇见的有些人员在挂 ...
- Laravel之路——file缓存修改为redis缓存
1.Session: 修改.evn文件: SESSION_DRIVER:redis (如果还不行的话,修改config/session.php的driver) 2.缓存修改为redis 注意:使用 L ...
- 缓存策略:redis缓存之springCache
最近通过同学,突然知道服务器的缓存有很多猫腻,这里通过网上查询其他人的资料,进行记录: 缓存策略 比较简单的缓存策略: 1.失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放 ...
- 本地缓存,Redis缓存,数据库DB查询(结合代码分析)
问题背景 为什么要使用缓存?本地缓存/Redis缓存/数据库查询优先级? 一.为什么要使用缓存 原因:CPU的速度远远高于磁盘IO的速度问题:很多信息存在数据库当中的,每次查询数据库就是一次IO操作所 ...
- SpringBoot集成Redis分布式锁以及Redis缓存
https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...
- Redis 缓存失效机制
Redis缓存失效的故事要从EXPIRE这个命令说起,EXPIRE允许用户为某个key指定超时时间,当超过这个时间之后key对应的值会被清除,这篇文章主要在分析Redis源码的基础上站在Redis设计 ...
- TP5中用redis缓存
在config.php配置文件下找到缓存设置,将原来的文件缓存修改为redis缓存,也可以改为多种类型的缓存: // +---------------------------------------- ...
随机推荐
- 鲜花:bitset求解高维偏序
书接上回 一维偏序直接做.二维偏序套线段树或归并排序.三维偏序可以树套树或者 CDQ 套树,那四维偏序呢?可以 CDQ 套树套树.那五维偏序呢?可以发现,无论是 CDQ 分治还是树,都很难再继续嵌套, ...
- ARC143D Bridges
ARC143D Bridges 巧妙的图论题. 思路 分析题目,发现很像拆点. 由于拆点要设置出入点,这里我们也把 \(a_i\) 设成入点,把 \(a_i+n\) 设成出点,再次分析问题. 考虑我们 ...
- mysql8 安装后无法登录的问题
使用 apt 安装mysql 先search一下 sudo apt search mysql 得到结果 找到了这个 发现是8 那就装吧 sudo apt-get install mysql-serve ...
- GAN和CGAN——生成式对抗网络和条件生成式对抗网络
GAN的定义 GAN是一个评估和学习生成模型的框架.生成模型的目标是学习到输入样本的分布,用来生成样本.GAN和传统的生成模型不同,使用两个内置模型以"对抗"的方式来使学习分布不断 ...
- 2025年前端面试准备js篇
1.js的基本数据类型有哪些 undefined,null,bo0lean,number,string,object,Symbol,bigInt 分为原始类型和引用类型 原始类型:undefined, ...
- Python:pygame游戏编程之旅七(pygame基础知识讲解1)
与Python自带的random.math.time等模块一样,Pygame框架也带有许多模块来提供绘图.播放声音.处理鼠标输入等功能. 本章将讲述Pygame提供的基本模块及功能,并假设读者已经具有 ...
- Docker之基础(一)
接触Docker有很久一段时间, 但是没有好好总结一下, 借此公司项目全面容器化, 记录一下常用的Docker操作 概况: 本次容器化的项目包括PHP+Python项目,PHP是基于php-fpm的基 ...
- 如何正确使用 RMQ
序列分块.设块长为 \(B\).每块预处理出最大值.对于询问 \([l, r]\),答案就是整块最大值和散块最大值拼起来.答案显然是 \(O(n) \sim O(\dfrac{n}{B} + B)\) ...
- git 推送代码到多个 远端仓库
业务场景 在开发代码时,有时希望将代码推送到两个远端仓库. 实现方法 git remote add origin giturl1 git remote add backup giturl2 git p ...
- Yacc笔记
语义动是一个C语句的序列 $$ 表是和相应产生式头的非终结符号关联的属性值 $i 表示和相应产生式体中第 i 个文法符号(终结符或非终结符号)关联的属性值 按照产生式规约时会执行关联的语义动作 对于 ...