美文网首页
Spring优雅整合Redis缓存

Spring优雅整合Redis缓存

作者: 怪瘦Java | 来源:发表于2020-04-01 15:34 被阅读0次

    “小明,多系统的session共享,怎么处理?”“Redis缓存啊!” “小明,我想实现一个简单的消息队列?”“Redis缓存啊!”

    “小明,分布式锁这玩意有什么方案?”“Redis缓存啊!” “小明,公司系统响应如蜗牛,咋整?”“Redis缓存啊!”

    本着研究的精神,我们来分析下小明的第四个问题。

    准备:

    Idea2019.03/Gradle6.0.1/Maven3.6.3/JDK11.0.4/Lombok0.28/SpringBoot2.2.4RELEASE/mybatisPlus3.3.0/Soul2.1.2/

    Dubbo2.7.5/Druid1.2.21/Zookeeper3.5.5/Mysql8.0.11/Vue2.5/Redis3.2

    难度:新手-- 战士 --老兵--大师

    目标:

    Spring优雅整合Redis做数据库缓存

    步骤:

    为了遇见各种问题,同时保持时效性,我尽量使用最新的软件版本。源码地址: https://github.com/xiexiaobiao/vehicle-shop-admin

    1 先说结论

    Redis缓存不是金弹,若系统DB毫无压力,系统性能瓶颈不在DB上,不建议强加缓存层!

    增加业务复杂度:同一缓存必须被全部相关方法所覆盖,如订单缓存,只要涉及到订单数据更新的方法都要进行缓存逻辑处理。

    同时,KV存储时,因各方法返回的类型不同,这样就需要多个缓存池,但各方法后台的数据又存在关联,往往导致一个方法需

    要处理关联的多个缓存,从而形成网状处理逻辑。

    2. 存在并发问题:缓存没有锁机制,B线程进行DB更新,同时A线程请求数据,缓存中存在即返回,但B线程还未更新到缓存,导

    致缓存与DB不一致;或者A线程B线程都进行DB更新,但写入缓存的顺序发生颠倒,也会导致缓存与DB不一致,请看官君想想如何解决;

    3.内存消耗:小数据量可直接全部进内存,但海量数据不可能全部直接进入Redis,机器吃不消!可考虑只缓存DB数据索引,然后配合

    “布隆过滤器”拦截无效请求,有效请求再去DB查询;

    4. 缓存位置:缓存注解的方法,执行时序上应尽量靠近DB,远离前端,如放dao层,请看官君思考下为啥。

    适用场景 :1.确认DB为系统性能瓶颈,2.数据内容稳定,低频更新,高频查询,如历史订单数据;3.热点数据,如新上市商品;

    2 步骤

    2.1 原理

    这里我说的是注解模式,有四个注解,SpringCache缓存原理即注解+拦截器 org.springframework.cache.interceptor.CacheInterceptor 对方法进行拦截处理:

    @Cacheable:可标记在 类或方法 上。标记在类上则缓存该类所有方法的返回值。请求方法时,先在缓存进行key匹配,存在则直接取缓存数据并返回。主要参数表:

    @CacheEvict:从缓存中移除相应数据。主要参数表:

    @CachePut:方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,

    而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。主要参数表:

    @Caching: 多个Cache注解组合使用,比如新增用户时,同时要删除其他缓存,并更新用户信息缓存,即以上三个注解的集合。

    2.2 编码

    项目有五个微服务,我仅改造了customer服务模块:

    引入依赖,build.gradle文件:

    Redis配置项,resources/config/application-dev.yml文件:

    文件: com.biao.shop.customer.conf.RedisConf

    以上代码解析:1.声明缓存管理器CacheManager,会创建一个切面(aspect)并触发Spring缓存注解的切点,根据类或者方法所使用的注解以及缓存的状态,

    这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值 2. RedisTemplate即为Redis连接器,实际上即为jedis客户端。

    文件: com.biao.shop.customer.impl.ShopClientServiceImpl

    以上代码解析:

    1. 因方法返回类型不同,故建立了5个缓存  2. 使用SpEL表达式#root.args[0]取得方法第一个参数,使用#result取得返回对象,

    用于构造key  3. 对于@Cacheable不能使用#result返回对象做key值,如queryById(int id)方法,会导致NPE,,因为此注解将在方法执行前先

    进入缓存匹配,而#result则是在方法执行后计算  4. @Caching注解可一次集合多个注解,如deleteByUUid(String uuid)方法,删除一个用户记录,

    需同时进行更新shopClient,并清空其他几个缓存。

    2.3 测试

    运行起来整个项目,启动顺序:souladmin -> soulbootstrap -> zookeeper -> authority -> customer -> stock -> order -> business -> vue前端 ,

    进入后端管理页: 按页浏览客户信息,分别点击页签:

    可以看到缓存shopClientPage缓存了4项数据,key值即为方法的参数组合,再去点击页签,则系统后台无DB请求记录输出,说明直接使用了缓存:

    编辑客户信息,我随意打开了两个:

    可以看到缓存shopClientById增加了两个对象,再去点击编辑,则系统后台无DB查询记录输出,说明直接使用了缓存:

    按条件查询客户:

    可以看到缓存shopClientPage增加一项,因为key值不一样,故独立为一项缓存数据,多次点查询,则系统后台无DB查询SQL输出,说明直接使用了缓存:

    新增客户:

    可以看到shopClientPage缓存将会被清空,同时增加一个shopClient缓存的对象,即同时进行了多个缓存池操作:

    问题解答:

    前面说到的两个问题:

    1.多线程问题,可配合DB事务机制,进行缓存延时双删,每次DB更新前,先删除缓存中对象,更新后,再去删除一次缓存中对象,

    2.缓存方法位置问题,按照前端到后端的“倒金字塔模型”,越靠近前端,缓存数据对象被其他业务逻辑更新的可能性越大,靠近DB,能尽量保证每次DB的更新都能被缓存逻辑感知。

    原文链接:http://www.cnblogs.com/xxbiao/p/12593525.html

    相关文章

      网友评论

          本文标题:Spring优雅整合Redis缓存

          本文链接:https://www.haomeiwen.com/subject/qrlauhtx.html