SpringBoot+Vue博客前后端分离项目 七 统一缓存 后端(springboot + mybatisplus+redis+mysql+jwt+缓存)

目录

1.统一缓存

1.1 统一缓存的好处:

1.2 编码

2.缓存一致性问题

2.1.安装rocketMQ

2.1.1.下载镜像

2.1.2.创建namesrv

2.1.3.创建broker

2.1.4.创建RocketMQ-console

2.1.5.查询启动状态

2.1.6.浏览器预览

3.集成


1.统一缓存

1.1 统一缓存的好处:

  1. 响应速度提升:相比直接调用底层高流量的基础服务,调用缓存服务接口的系统响应时间大大减少
    (内存的访问速度远远大于磁盘的访问速度 ,1000倍起)
  2. 统一缓存,降低接入成本:一部分业务场景下可以直接标识统一缓存服务注释,便可以快速开启缓存功能,而不用再对接底层的多个子系统,极大地降低了各个业务线的接入成本。

1.2 编码

定义注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//高速缓冲存储器
public @interface Cache {
    //过期时间
    long expire() default 1 * 60 * 1000;
    //缓存标识
    String name() default "";
}

定义切面

package com.pjp.blog.common.cache;

import com.alibaba.fastjson.JSON;
import com.pjp.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.Duration;

//AOP 定义一个切面,切面定义了切点和通知的关系
@Component
@Aspect
@Slf4j
public class CacheAspect {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    //自己定义的注解标在哪儿 那儿的方法就是切点
    @Pointcut("@annotation(com.pjp.blog.common.cache.Cache)")
    public void pt(){}

    //环绕通知
    @Around("pt()") //"pt()" 关联了切点
    public Object around(ProceedingJoinPoint pjp){
        try {
            Signature signature = pjp.getSignature();
            //类名
            String className = pjp.getTarget().getClass().getSimpleName();
            //调用的方法名
            String methodName = signature.getName();


            Class[] parameterTypes = new Class[pjp.getArgs().length];
            Object[] args = pjp.getArgs();
            //参数
            String params = "";
            for(int i=0; i<args.length; i++) {
                if(args[i] != null) {
                    params += JSON.toJSONString(args[i]);
                    parameterTypes[i] = args[i].getClass();
                }else {
                    parameterTypes[i] = null;
                }
            }
            if (StringUtils.isNotEmpty(params)) {
                //加密 以防出现key过长以及字符转义获取不到的情况
                params = DigestUtils.md5Hex(params);
            }
            Method method = pjp.getSignature().getDeclaringType().getMethod(methodName, parameterTypes);
            //获取Cache注解
            Cache annotation = method.getAnnotation(Cache.class);
            //缓存过期时间
            long expire = annotation.expire();
            //缓存名称
            String name = annotation.name();
            
            //先从redis获取
            String redisKey = name + "::" + className+"::"+methodName+"::"+params;
            String redisValue = redisTemplate.opsForValue().get(redisKey);
            if (StringUtils.isNotEmpty(redisValue)){
                log.info("走了缓存~~~,{},{}",className,methodName);
                return JSON.parseObject(redisValue, Result.class);
            }
            //触发目标方法的执行(被代理的对象的方法)
            Object proceed = pjp.proceed();
            redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(proceed), Duration.ofMillis(expire));
            log.info("存入缓存~~~ {},{}",className,methodName);
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return Result.fail(-999,"系统错误");
    }

}

使用:

@PostMapping("hot")
    @Cache(expire = 5 * 60 * 1000,name = "hot_article")
    public Result hotArticle(){
        int limit = 5;
        return articleService.hotArticle(limit);
    }

2.缓存一致性问题

之前在文章列表读取,最新文章等接口的时候我们加了缓存,但是加了缓存会有一些问题,当我们修改或者用户浏览了文章,那么最新的修改和文章的浏览数量无法及时的更新,那么应该怎么做呢?

这里采用了RocketMQ来解决这个问题。

2.1.安装rocketMQ

2.1.1.下载镜像

docker search rocketmq

docker pull rocketmqinc/rocketmq
docker pull pangliang/rocketmq-console-ng

2.1.2.创建namesrv

创建需要挂载的目录

mkdir -p /docker/rocketmq/namesrv/logs   /docker/rocketmq/namesrv/store

下面的-v对应的就上面创建的地址

docker run -d \
--restart=always \
--name rmqnamesrv \
--privileged=true \
-p 9876:9876 \
-v /docker/rocketmq/namesrv/logs:/root/logs \
-v /docker/rocketmq/namesrv/store:/root/store \
-e "MAX_POSSIBLE_HEAP=100000000" \
rocketmqinc/rocketmq \
sh mqnamesrv

2.1.3.创建broker

创建3个文件目录

mkdir -p  /docker/rocketmq/broker/logs \
/docker/rocketmq/broker/store \
/docker/rocketmq/conf

创建broker.conf文件

touch /docker/rocketmq/conf/broker.conf

vim /docker/rocketmq/conf/broker.conf

brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = 47.113.184.34
docker run -d  \
--restart=always \
--name rmqbroker5 \
--link rmqnamesrv:namesrv \
--privileged=true \
-p 10911:10911 \
-p 10909:10909 \
-v  /docker/rocketmq/broker/logs:/root/logs \
-v  /docker/rocketmq/broker/store:/root/store \
-v /docker/rocketmq/conf/broker.conf:/opt/rocketmq-4.4.0/conf/broker.conf \
-e "NAMESRV_ADDR=namesrv:9876" \
-e "MAX_POSSIBLE_HEAP=200000000" \
rocketmqinc/rocketmq \
sh mqbroker -c /opt/rocketmq-4.4.0/conf/broker.conf

2.1.4.创建RocketMQ-console

docker run -d \
--restart=always \
--name rmqadmin \
-e "JAVA_OPTS=-Drocketmq.namesrv.addr=47.113.184.34:9876 \
-Dcom.rocketmq.sendMessageWithVIPChannel=false" \
-p 7777:8080 \
pangliang/rocketmq-console-ng

2.1.5.查询启动状态

docker ps -a

2.1.6.浏览器预览

3.集成

<dependency>
  <groupId>org.apache.rocketmq</groupId>
  <artifactId>rocketmq-spring-boot-starter</artifactId>
  <version>2.2.0</version>
</dependency>

添加对应的配置:

#rockermq配置
rocketmq.name-server=47.113.184.34:9876
rocketmq.producer.group=blog_group

代码:

//缓存一致性
if(isEdit){
    //发送一条消息给rocketmq 当前文章更新了 更新一下缓存
    ArticleMessage articleMessage = new ArticleMessage();
    articleMessage.setArticleId(article.getId());
    rocketMQTemplate.convertAndSend("blog-update-article",articleMessage);
}

消费者加上注释监听消息

package com.pjp.blog.service.mq;

import com.alibaba.fastjson.JSON;
import com.pjp.blog.service.ArticleService;
import com.pjp.blog.vo.ArticleMessage;
import com.pjp.blog.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Set;

@Slf4j
@Component
@RocketMQMessageListener(topic = "blog-update-article", consumerGroup = "blog-update-article-group")
public class ArticleListener implements RocketMQListener<ArticleMessage> {
    @Autowired
    private ArticleService articleService;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public void onMessage(ArticleMessage articleMessage) {
        log.info("收到的消息:{}", articleMessage);
        //做什么了,更新缓存
        //1. 更新查看文章详情的缓存
        Long articleId = articleMessage.getArticleId();
        String params = DigestUtils.md5Hex(articleId.toString());
        String redisKey = "view_article::ArticleController::findArticleById::" + params;
        Result articleResult = articleService.findArticleById(articleId);
        redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(articleResult), Duration.ofMillis(5 * 60 * 1000));
        log.info("更新了缓存:{}", redisKey);
        //2. 文章列表的缓存 不知道参数,解决办法 直接删除缓存
        Set<String> keys = redisTemplate.keys("listArticle*");
        keys.forEach(s -> {
            redisTemplate.delete(s);
            log.info("删除了文章列表的缓存:{}", s);
        });
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PJP__00

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值