Spring Data Redis + RabbitMQ - 基于 string 实现缓存、计数功能(同步数据)-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

一、Spring Data Redis

1.1、缓存功能

1.1.1、分析

1.1.2、案例实现

1.1.3、效果演示

1.2、计数功能Redis + RabbitMQ

1.2.1、分析

1.2.2、案例实现


一、Spring Data Redis


1.1、缓存功能

1.1.1、分析

使用 redis 作为缓存 MySQL 作为数据库组成的架构

整体思路

应用服务器访问数据的时候先查询 Redis如果 Redis 上存在该数据就从 Redis 中取数据直接交给应用服务器不用继续访问数据库了如果 Redis 上不存在该数据就会去 MySQL 中把读到的结构返回给应用服务器同时把这个数据也写入到 Redis 中.

由于 Redis 这样的缓存经常用来存储 “热点数据”也就是高频使用的数据那什么样的数据算高频呢这里暗含了一层假设某个数据一旦被用到了那么可能在最近这段时间就可能被反复用到.

随着时间推移越来越多的 key 在 redis 上访问不到那 redis 的数据不是越来越多么

  1. 把数据写给 redis 的同时会给这个 key 设置一个过期时间.
  2. Redis 也有内存不足的时候因此提供了 淘汰策略之前的文章展开讲过.

1.1.2、案例实现

例如论坛网站有些帖子的访问评论很高就需要设置成热点文章缓存起来比起去 MySQL 数据库中查询文章要快的多. 

实现思路

        根据上面理论暗含假设当前使用的文章就是热点文章也就是说如果在缓存中有该文章就直接返回如果没有就去数据库中查然后再缓存起来同时设置 30min不同场景合理分配 的过期时间.

帖子实体类.

@Data
public class Article {

    private String title;
    private String content;

}

文章 mapper.

@Mapper
public interface ArticleMapper {

    /**
     * 根据 id 查询文章
     * @param id
     * @return
     */
    Article selectArticleById(@Param("id") Integer id);

}
    <select id="selectArticleById" resultType="com.example.cyk.cache.Article">
        select * from article where id = #{id};
    </select>

帖子 controller

@RestController
@RequestMapping("/article")
public class ArticleController {


    @Autowired
    private IArticleService articleService;

    @GetMapping("/get")
    public HashMap<String, Object> get(@NonNull Integer id) {
        //1.获取文章服务
        Article article = articleService.getArticleInfo(id);
        //2.返回响应
        return HandlerResponse(1000, "操作成功", article);
    }

    /**
     * 处理返回格式
     * @param code
     * @param msg
     * @param data
     * @return
     */
    private HashMap<String, Object> HandlerResponse(Integer code, String msg, Object data) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", code);
        result.put("msg", msg);
        result.put("data", data);
        return result;
    }

}

帖子 service .

@Slf4j
@Service
public class ArticleService implements IArticleService {

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public Article getArticleInfo(Integer id) {
        //1.非空校验
        if(id == null) {
            log.warn("文章 id 为空");
            throw new RuntimeException("文章 id 为空");
        }
        //2.先去 redis 上看有没有文章对应的这个id
        //我这里约定 redis 上存储格式:
        //key: art:id
        //value: $title$content  ($ 是分隔符)
        //例如 key: art:1    value: $决定$今天要好好学习
        String articleInfo = redisTemplate.opsForValue().get("art:" + id);
        if(articleInfo != null) {
            //存在直接返回
            log.info("从 redis 中获取到文章数据");
            //1) 解析格式
            Article article = analysisArticle(articleInfo);
            //2) 返回数据
            return article;
        }
        //3.redis 上没有数据因此需要从 mysql 中取
        Article article = articleMapper.selectArticleById(id);
        if(article == null) {
            log.warn("文章不存在");
            throw new RuntimeException("文章不存在");
        }
        //4.将文章存到 redis 中
        //1) 合成 redis 所需格式的文章
        articleInfo = synthesisArticle(article);
        //2) 设置 5 分钟过期时间(为了演示效果)
        redisTemplate.opsForValue().set("art:" + id, articleInfo, 5, TimeUnit.SECONDS);
        log.info("从 mysql 中获取到文章数据");
        return article;
    }

    /**
     * 合成 redis 需要的格式(提前约定好的)
     * @param article
     * @return
     */
    private String synthesisArticle(Article article) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("$");
        stringBuilder.append(article.getTitle());
        stringBuilder.append("$");
        stringBuilder.append(article.getContent());
        return stringBuilder.toString();
    }

    /**
     * 解析文章格式
     * @param articleInfo
     * @return
     */
    private Article analysisArticle(String articleInfo) {
        Article article = new Article();
        String title = articleInfo.split("\\$")[1];
        String content = articleInfo.split("\\$")[2];
        article.setTitle(title);
        article.setContent(content);
        return article;
    }

}

1.1.3、效果演示

1.2、计数功能Redis + RabbitMQ

1.2.1、分析

许多都会使应用用 Redis 作为计数的基础⼯具它可以实现快速计数、查询缓存的功能例如网站视频的播放量点赞数量......

Ps这些都是相比较 MySQL 数据库而言的Redis 可以通过简单的键值对操作完成计数任务并且实在内存中完成的而 MySQL 就需要先查询数据库然后 +1然后再存入数据库是在需要进行硬盘存储的

1.2.2、案例实现

实现思路

        假设用户点击某个帖子此时需要进行访问量 + 1 的操作这时候应用服务器就会直接去操作 Redis 执行 incr 命令然后将返回的数据反馈给用户最后 Redis 会以异步的方式RabbitMQ 实现异步将播放量同步到 MySQL 数据库中异步就表示这里并不是每一个播放请求都需要立即写入数据~ 至于什么时候写入需要根据实际的业务需求场景而定将数据持久化.

Ps实际中要开发⼀个成熟、稳定的真实计数系统要⾯临的挑战远不⽌如此简单防作弊、按 照不同维度计数、避免单点问题、数据持久化到底层数据源等。

文章实体类

@Data
public class Article implements Serializable {

    private Integer id;
    private String title;
    private String content;
    private Long visits; //访问量

}

rabbit 交换机、队列、绑定配置.

public class MqFinal {

    //处理文章的直接交换机
    public static final String UPDATE_DIRECT = "article.update.direct";
    //用于修改文章数据的队列
    public static final String UPDATE_QUEUE = "article.update.queue";
    //bindingKey
    public static final String UPDATE_KEY = "article.update.key";

}
@Configuration
public class MqConfig {

    /**
     * 消息转化器
     * @return
     */
    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public DirectExchange ArticleDirectExchange() {
        return new DirectExchange(MqFinal.UPDATE_DIRECT, true, false);
    }

    @Bean
    public Queue ArticleUpdateQueue() {
        return new Queue(MqFinal.UPDATE_QUEUE, true);
    }

    @Bean
    public Binding ArticleUpdateBinding() {
        return BindingBuilder.bind(ArticleUpdateQueue()).to(ArticleDirectExchange()).with(MqFinal.UPDATE_KEY);
    }

}

mq 监听配置

@Slf4j
@Component
public class MqListenerArticle {

    @Autowired
    private ArticleMapper articleMapper;

    /**
     * 同步数据库
     */
    @RabbitListener(queues = MqFinal.UPDATE_QUEUE)
    public void syncVisits(HashMap<String, Object> data) {
        Integer id = (Integer) data.get("id");
        // Rabbitmq 这里有一个问题Map<String, Object> 中 Object 传入为 Long 类型需要用 Integer 来接受否则报错
        // 因此发送消息之前体现将 Long 类型转化为 String接收到消息之后只需要将 String 转化为 Long 即可
        String visits = (String) data.get("visits");
        articleMapper.updateArticleVisits(id, Long.valueOf(visits));
        log.info("访问量数据同步完成");
    }

}

访问量增加服务这里为了可读性只展示了本业务的核心逻辑

    @Override
    public Article getArticleInfo(Integer id) {
        //1.非空校验
        if(id == null) {
            log.warn("文章 id 为空");
            throw new RuntimeException("文章 id 为空");
        }

        //2.访问量 +1
        //注意incr 这个命令执行时即使 key 不存在也会自动生成 key然后自增
        Long visits = redisTemplate.opsForValue().increment("v_art:" + id);
        //3.rabbitmq 实现异步数据同步(发送一个消息即可)
        HashMap<String, Object> visitsInfo = new HashMap<>();
        visitsInfo.put("id", id);
        visitsInfo.put("visits", visits.toString()); //转化原因前面解释过了
        rabbitTemplate.convertAndSend(MqFinal.UPDATE_DIRECT, MqFinal.UPDATE_KEY, visitsInfo);

        //4.获取文章数据
        //业务逻辑(这里为了可读性就先不展示这里了)......

        //5.放入文章
        Article article = new Article();
        article.setVisits(visits);
        article.setId(id);
        return article;
    }

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“Spring Data Redis + RabbitMQ - 基于 string 实现缓存、计数功能(同步数据)-CSDN博客” 的相关文章