CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

这篇具有很好参考价值的文章主要介绍了CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

现在手上有个系统写操作比较少,很多接口都是读操作,也就是写多读少,性能上遇到瓶颈了,正所谓前人栽树、后人乘凉,原先系统每次都是查数据库的,性能比较低,如果先查 redis,redis 没数据再查数据库的话,但是还可以更快,那就是使用内存查询,依次按照内存、redis、db的顺序从快到慢查询,可使系统整体的性能提升一个档次,但是仅限于读多写少的场景,写多读少的场景没必要搞这么多缓存,搞多了缓存一致性也是个问题,就好比 mysql 数据库的读多写少,我们可以用 MYISM 存储引擎。

本文术语

  • CaffeineCache:一级缓存
  • Redis:二级缓存

本文项目地址

此项目已收录于 Gitee,感兴趣的小伙伴可以克隆下来去查看一下,也欢迎提出宝贵意见大家一起来优化这个项目。
MRCache:https://gitcode.net/qq_42875345/mrcache

设计思路

给系统加二层缓存,怎么加?每个接口都加个判断,先从内存查,内存没数据再查 redis 再查 db ?那工作量太大了,且代码耦合性太高,代码看着也难看一大坨同质化的代码。先说个结论。如果你们的项目架构比较好,所有本地接口或者是 Rpc 接口调用,采用了责任链来实现只需在责任链头部新增一个,查缓存的节点即可。责任链设计模式精讲入口,
如果没用到责任链,那利用 Aop 切面+自定义注解+ Spel 框架+ CaffeineCache 内存框架 来实现即可,工作量也不大,加个切面即可。接下来进入实战。

开发思路

下面贴一段 Spring 缓存中的 @CacheAble 注解使用代码,我们配个 RedisCacheManager 后,使用此注解即可将返回结果存入Redis。Redis 中有缓存则不会执行方法中的逻辑。思考那么是否我们可以写一个 @DoubleCacheAble 注解,将原先查 Redis 的逻辑替换成,先查本地缓存、再查 Redis、最后查 db 的逻辑呢?答案是可以的且有俩种实现方式。

@Cacheable(value = "doubleCache: ", key = "#student.sId", unless = "0")
public Object testCacheable(Student student) throws InterruptedException {
    Thread.sleep(1000 * 10);
    return map;
}
  1. 方式一:重写 Cache 、CacheManager 、CacheResolver 、KeyGenerator 接口,然后定制化里面的方法,改成自己的逻辑即可,加多少层缓存都没问题。二开比较繁琐,且容易出错

举个例子就拿 @CacheAble 的使用来说,为什么每次我们使用这些注解前都要加如下的配置,那是因为 spring.data.redis 包帮我们二次封装了 Cache、CacheManager 的逻辑,且提供了默认的 KeyGenerator、CacheResolver 等实现类,感兴趣的小伙子可以自行 debug 源码

@Data
@ConfigurationProperties(prefix = "spring.redis")
@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
    private int database;
    private String host;
    private int port;
    private String password;

    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ZERO)
                .disableCachingNullValues()
                .computePrefixWith(cacheName -> "caching_fm:" + cacheName);
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
                .cacheDefaults(configuration) // 默认配置(强烈建议配置上)。  比如动态创建出来的都会走此默认配置
                .build();
        return redisCacheManager;
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(host);
        configuration.setPassword(RedisPassword.of(password));
        configuration.setPort(port);
        configuration.setDatabase(database);
        LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
        return factory;
    }


}

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

还有一种方式也是最容易实现的一种方式就是前言提到的加一层切面,对所有查询操作切入,织入查二层缓存的逻辑。

@DoubleCacheAble 双缓存注解(如何设计?)

高效简洁的开发当然少不了我们的自定义注解辣,完全对标 @CacheAble ,支持动态 SPEL 解析,是否缓存空值等等。日后需要增加更复杂的功能完善该注解就行。一个注解代码不做过多解释。

//作用于方法
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DoubleCacheAble {
    //缓存key:静态写死部分
    String value();

    //缓存key:动态spel部分
    String key();

    //操作类型
    String type();

    //是否缓存空值,默认不缓存空
    String unLess() default "0";
}

动态条件表达式?例如:#a.id=?(如何解析?)

一开始我还天真的以为,要不要写个算法来实现,想想都头大。后来想着 @CacheAble 这个注解不是已经实现了这个功能吗,把他里面的源码 copy 出来不就行了。但是 copy 了一会发现不对劲,各种缺包,于是乎开始 debug 源码,直到 debug 到如下这行代码,#student.id 被解析了,然后发现了 Spring 里面的存在一种名叫 SPEL 解析的技术包,拿来即用。

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)
然后摸索了一会便有了我如下的这个 demo ,就是对源码的封装解析了一下。本质都是利用 Spring 里面的工具类。可以看到动态表达式已经被我成功解析,不得感叹我可真是个小天才。

public static void main(String[] args) throws NoSuchMethodException {
    Method method = new DoubleCacheServiceImpl().getClass().getMethod("testCacheable", Student.class);
    Object[] cusArgs = new Object[1];
    cusArgs[0] = Student.builder()
            .sId(666)
            .sName("测试name")
            .build();
    Object value = PARSER.parseExpression("#student.sId+'-'+#student.sName")
            .getValue(new MethodBasedEvaluationContext(null, method, cusArgs, NAME_DISCOVERER));
    System.err.println("SPEL表达式解析出来的内容为:"+value);
}

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

具体的参数列个说明吧:

  • MethodBasedEvaluationContext:方法上下文,值是从中获取的
  • method:被解析的方法
  • cusArgs:被解析方法中的参数值
  • SpelExpressionParser:Spring 提供的 SPEL 解析包
  • DefaultParameterNameDiscoverer:用的默认,没深揪源码

缓存切面(如何设计?)

考虑到缓存一致性,以及注解的泛用性,其实这里面的代码要实现高可用还是有点难度的。首先我们要对增、删、改、查 操作都写对应的逻辑。例如查 db ,放缓存,此时 db 更新数据,为了保证 db 与缓存一致性,还需同步删除缓存,然后更新缓存。当然我这里不是专门开发消息中间件的,写本文的目的更多的是在于,让大家知道如何进行设计一个二级缓存框架。考虑到现在是简洁开发的天下,结合之前看 Spring 自动装配的源码,自己手撸一个 jar 包封装所有的逻辑,让大家只需导入 jar 包,就可以调用我定义的注解完成二级缓存查询。

/**
 * aop 环绕通知
 */
@Slf4j
public class DoubleCacheInterceptor implements MethodInterceptor {
    private AnalysisKeyCache analysisKeyCache;

    public DoubleCacheInterceptor(AnalysisKeyCache analysisKeyCache) {
        this.analysisKeyCache = analysisKeyCache;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        DoubleCacheAble doubleCache = getDoubleCache(invocation.getMethod());
        if(doubleCache==null) return invocation.proceed();
        String realKey = String.valueOf(getRealKey(invocation.getArguments(), invocation.getMethod(),
                doubleCache.key(), doubleCache.value()));
        Object cacheValue = analysisKeyCache.get(realKey);
        if (null != cacheValue) return cacheValue;
        Object proceed = invocation.proceed();
        analysisKeyCache.put(realKey, doubleCache.unLess(), proceed);
        return proceed;
    }

    public DoubleCacheAble getDoubleCache(Method method) {
        DoubleCacheAble targetDataSource = method.getAnnotation(DoubleCacheAble.class);
        if (targetDataSource == null) {
            Class<?> declaringClass = method.getDeclaringClass();
            targetDataSource = declaringClass.getAnnotation(DoubleCacheAble.class);
        }
        return targetDataSource;
    }

    public Object getRealKey(Object[] cusArgs, Method method, String key, String value) {
        Object realKey = value + new SpelExpressionParser().parseExpression(key)
                .getValue(new MethodBasedEvaluationContext(null, method, cusArgs, new DefaultParameterNameDiscoverer()));
        log.info("{} SPEL表达式解析得到的完整key: {}", method.getName(), realKey);
        return realKey;
    }
}

想到切面大家可能第一时间想到的是用 @Aspect+@Around 实现,但是对于开源项目来说,所有轮子都是自己造的,为什么还要用轮子拼轮子呢?况且由于切面过多,可能导致我们自己的切面无法第一时间执行这也是个问题, 因此我这里采用 MethodInterceptor (方法拦截器)方法实现 AOP 拦截。

缓存 CRUD 如何设计?(使用委派模式)

由于我们要用到 CaffeineCache+Redis 这俩种缓存,考虑到代码解耦,决定用委派模式实现。此处借鉴 Mybatis 二级缓存源码中的设计,利用委派模式将日志缓存、序列化缓存、LRU缓存、定时缓存、持久化缓存代码各自抽离出来,实现解耦的目的。CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)
为此我设计了如下四个缓存,当一个查询请求过来会先经过 AnalysisKeyCache 隐式的为 key 添加前缀,然后经过 SerializeCache 依次从 MRCaffeineCache 、RedisCache 获取值,最后将值反序列化给我们。看懂了我的这段代码,再去看 Mybatis 获取二级缓存的源码将十分简单。

  1. AnalysisKeyCache:为 key 加统一前缀
  2. SerializeCache:缓存 value 值转换成 byte 数组存储
  3. MRCaffeineCache:CaffeineCache 本地缓存(CRUD)
  4. RedisCache:Redis 缓存(CRUD)
    CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

整合自动装配

要想实现让大家开箱即用第三方 jar 包,自动装配少不了。在 resources 目录下创建一个 MATE_INF 文件夹,放入一个 spring.factories 文件,里面的内容 org.springframework.boot.autoconfigure.EnableAutoConfiguration 这段是固定的,Value 值代表要变成 Bean 的类。当 jar 引入各自项目中来时这些类就会变成项目中的 Bean。至于为什么推荐大家阅读自动装配的源码,本文不做过多阐述。CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)
然后编写 MRCacheAutoConfiguration 类,约定哪些类要变成 Bean 即可。
CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

Redis 过期 Key 如何处理?

存在这么一种情况就是二级缓存数据过期了,一级换存还有数据,为了保证缓存一致性,此时需监听过期 Key ,同步删除一级缓存。那么有人会说了,一级缓存过期不要删除二级缓存吗,一级缓存本就是为了缓解二级缓存压力而设计的,且为内存,一级缓存过期了无需做任何操作,毕竟二级缓存才是我们的兜底。

查缓存测试

新建一个项目引入我们的 MRCache 包,使用其提供的 @DoubleCacheAble 缓存,编写对应的测试 Service即可。做到 0 代码入侵。

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

调用接口查询数据,发现第一次查十分缓慢,第二次查很快走了缓存。

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

全局的 key 前缀也正常被设置。逻辑在 AnalysisKeyCache 里面,这里不做阐述了。

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

再次查询直接走的内存缓存了。

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

过期 Key 清除测试

手动修改 Redis 数据的 TTL 为 1,过一秒成功触发我们的监听方法执行里面的逻辑

CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

小咸鱼的技术窝

关注不迷路,日后分享更多技术干货,B站、CSDN、微信公众号同名,名称都是(小咸鱼的技术窝)更多详情在主页
CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)

推荐阅读

手把手debug自动装配源码、顺带弄懂了@Import等相关的源码(全文3w字、超详细)

深入mybatis源码解读~手把手带你debug分析源码文章来源地址https://www.toymoban.com/news/detail-492956.html

到了这里,关于CaffeineCache+Redis 接入系统做二层缓存思路实现(借鉴 mybatis 二级缓存、自动装配源码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 04.利用Redis国逻辑过期实现缓存功能---解决缓存击穿

    提示:学习如何利用Redis逻辑过期实现添加缓存功能解决缓存击穿 缓存击穿讲解图 : 解决方案: 采用互斥锁 采用逻辑过期 1. 准备pom环境 2. 配置ThreadLocal和过滤器 3. RedisData接收数据 3. Controller层:负责接收请求和向下分配 4. Service层:负责业务的处理逻辑

    2024年02月13日
    浏览(59)
  • 使用redis + 随机token 实现单点登入思路

    为什么要实现单点登入 一般对于业务方法都需要用户登入才能请求,如果每个方法都去判断用户是否登入太麻烦,可以使用拦截器对所有请求进行统一的登入状态判断。 实现方式 登入时使用随机生成算法例如UUID生成随机token 将redis的hash数据结构进行存储,并设置过期时间,

    2024年02月13日
    浏览(42)
  • 【Redis系列】Spring Boot 集成 Redis 实现缓存功能

    💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老 导航 檀越剑指大厂系列:全面总

    2024年04月10日
    浏览(60)
  • mall整合Redis实现缓存功能

    本文主要讲解mall整合Redis的过程,以短信验证码的存储验证为例。 Redis是用C语言开发的一个高性能键值对数据库,可用于数据缓存,主要用于处理大量数据的高访问负载。 下载Redis,下载地址:github.com/MicrosoftAr… 下载完后解压到指定目录 在当前地址栏输入cmd后,执行redis的启

    2024年01月19日
    浏览(44)
  • springboot redis实现缓存的例子

    好的,下面是带注释的代码: 1. 添加Redis依赖: 2. 配置Redis连接: 3. 创建一个缓存管理类: - `@Component`注解表示这是一个Spring Bean。 - `RedisCacheManager`实现了`CacheManager`接口,用于管理缓存。 - `RedisTemplate`是Spring提供的Redis客户端模板,用于操作Redis。 - `getCache`方法用于获取一

    2024年02月09日
    浏览(46)
  • SpringBoot整合Redis实现缓存信息监控

    SpringBoot整合Redis可以通过RedisTemplate和Redisson两种方式。 根据Connection获取 Redis缓存 信息 // 方式1:获取Redis缓存全部信息 Properties info = redisTemplate.getRequiredConnectionFactory().getConnection().info(); // 方式2:根据Connection获取Redis缓存指定信息: Properties info = redisTemplate.getRequiredConnection

    2024年01月20日
    浏览(47)
  • 使用Redis来实现点赞功能的基本思路

    使用Redis来实现点赞功能是一种高效的选择,因为Redis是一个内存数据库,适用于处理高并发的数据操作。以下是一个基本的点赞功能在Redis中的设计示例: 假设我们有一个文章或帖子,用户可以对其进行点赞,取消点赞,以及查看点赞总数。 记录点赞信息: 使用 Redis 的 S

    2024年02月13日
    浏览(39)
  • SpringBoot使用Redis实现分布式缓存

    ✅作者简介:2022年 博客新星 第八 。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:SpringBoot 框架从入门到精通 ✨特色专栏:国学周更-心性养成之路 🥭本文内容:SpringBoot使用

    2023年04月09日
    浏览(44)
  • Redis实战案例14-分布式锁的基本原理、不同实现方法对比以及基于Redis进行实现思路

    基于数据库的分布式锁:这种方式使用数据库的特性来实现分布式锁。具体流程如下: 获取锁:当一个节点需要获得锁时,它尝试在数据库中插入一个特定的唯一键值(如唯一约束的主键),如果插入成功,则表示获得了锁。 释放锁:当节点完成任务后,通过删除该唯一键

    2024年02月13日
    浏览(53)
  • 系统架构设计师之缓存技术:Redis与Memcache能力比较

    系统架构设计师之缓存技术:Redis与Memcache能力比较 

    2024年02月11日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包