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

目录

一、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;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/117111.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ArmSom---I2C开发指南

1. 简介 RK3588从入门到精通 本⽂介绍在rockchip平台下如何配置i2c接口的方法并且添加调试验证i2c外设的例子 开发板&#xff1a;ArmSoM-W3 Kernel&#xff1a;5.10.160 OS&#xff1a;Debian11 2. i2c接口概述 i2c 总线控制器通过串行数据&#xff08;SDA&#xff09;线…

【hcie-cloud】【1】华为云Stack解决方案介绍、华为文档获取方式 【上】

文章目录 华为文档获取方式前言云计算发展背景国家政策、社会发展驱动数字经济开启新时代深化数字化转型提升效率&#xff0c;国家数字主权云进入落地阶段从Cloud-Based到Cloud-Native&#xff0c;两种模式长期并存适合政企智能升级的云华为云Stack&#xff0c;政企智能升级首选…

ks ios快手极速版、概念版、创作版sig sig3

风控&#xff1a; 快手除了正版下&#xff0c;还有极速版&#xff0c;概念版&#xff0c;创作版。 然而看了下概念版还是风控比正版要低的多~ 抓包&#xff1a; 对于快手的ios版本抓包很简单&#xff0c;直接vpn转发抓包方式即可&#xff0c;不可用代理抓包&#xff1b;这个…

[SSD综述 1.4] SSD固态硬盘的架构和功能导论

依公知及经验整理,原创保护,禁止转载。 专栏 《SSD入门到精通系列》 <<<< 返回总目录 <<<< ​ 前言 机械硬盘的存储系统由于内部结构, 其IO访问性能无法进一步提高,CPU与存储器之间的性能差距逐渐扩大。以Nand Flash为存储介质的固态硬盘技术的发展,…

redis rdb aof

appendonly yes # appendfsync always appendfsync everysec # appendfsync no E:\Document_Redis_Windows\redis-2.4.5-win32-win64\64bit appendonly.aof

基于单片机的养殖场温度控制系统设计

博主主页&#xff1a;单片机辅导设计 博主简介&#xff1a;专注单片机技术领域和毕业设计项目。 主要内容&#xff1a;毕业设计、简历模板、学习资料、技术咨询。 文章目录 主要介绍一、控制系统设计二、系统方案设计2.1 系统运行方案设计2.1.1 羊舍环境温度的确定 三、 系统仿…

ElasticSearch高级功能详解与原理剖析

ES数据预处理 Ingest Node Elasticsearch 5.0后&#xff0c;引入的一种新的节点类型。默认配置下&#xff0c;每个节点都是Ingest Node&#xff1a; 具有预处理数据的能力&#xff0c;可拦截lndex或Bulk API的请求对数据进行转换&#xff0c;并重新返回给Index或Bulk APl 无…

项目实战:优化Servlet,把所有围绕Fruit操作的Servlet封装成一个Servlet

1、FruitServlet 这些Servlet都是围绕着Fruit进行的把所有对水果增删改查的Servlet放到一个Servlet里面&#xff0c;让tomcat实例化一个Servlet对象 package com.csdn.fruit.servlet; import com.csdn.fruit.dto.PageInfo; import com.csdn.fruit.dto.PageQueryParam; import c…

(景行锐创) 高性能计算平台 Pytorch 深度学习环境超详细教程

文章目录 前言1. 账号申请2. 登录高算平台网站3. 安装 Xshell&#xff0c;Xftp 软件4. 连接高算平台5. 安装 Anaconda6. 安装 CUDA7. 配置 cuDNN8. 安装 torch 和 torchvision9. 提交作业测试10. 解压与压缩文件11. 其他结语 前言 目前一些学校为了便于师生进行大规模的计算任…

vue工程化开发和脚手架

工程化开发和脚手架 1.开发Vue的两种方式 核心包传统开发模式&#xff1a;基于html / css / js 文件&#xff0c;直接引入核心包&#xff0c;开发 Vue。工程化开发模式&#xff1a;基于构建工具&#xff08;例如&#xff1a;webpack&#xff09;的环境中开发Vue。 工程化开…

使用免费 FlaskAPI 部署 YOLOv8

目标检测和实例分割是计算机视觉中关键的任务&#xff0c;使计算机能够在图像和视频中识别和定位物体。YOLOv8是一种先进的、实时的目标检测系统&#xff0c;因其速度和准确性而备受欢迎。 Flask是一个轻量级的Python Web框架&#xff0c;简化了Web应用程序的开发。通过结合Fla…

如何使用Selenium处理Cookie,今天彻底学会了

01 cookie介绍 HTTP协议是无状态的协议。一旦数据交换完毕&#xff0c;客户端与服务器端的连接就会关闭&#xff0c;再次交换数据需要建立新的连接&#xff0c;这就意味着服务器无法从连接上跟踪会话。也就是说即使第一次和服务器连接后并且登录成功后&#xff0c;第二次请求…

【vue2高德地图api】04-poi搜索

系列文章目录 文章目录 系列文章目录前言一、高德地图文档入口二、使用步骤1.创建文件以及路由2.编写页面代码3.样式4变量以及方法5.编写查询方法 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 本篇要实现的功能&#xff0c;看下图 提示&#x…

PivotNet:Vectorized Pivot Learning for End-to-end HD Map Construction

参考代码&#xff1a;BeMapNet。PS&#xff1a;代码暂未放出&#xff0c;关注该仓库动态 动机和主要贡献 在MapTR系列的算法中将单个车道线建模为固定数量的有序点集&#xff08;对应下图Evenly-based&#xff09;&#xff0c;这样的方式对于普通道路场景具备一定适应性。但是…

谁说 Linux 不能玩游戏?

在上个世纪最早推出视频游戏的例子是托马斯戈德史密斯&#xff08;Thomas T. Goldsmith Jr.&#xff09;于1947年开发的“「Cathode Ray Tube Amusement Device」”&#xff0c;它已经显着发展&#xff0c;并且已成为人类生活中必不可少的一部分。 通过美国游戏行业的统计数据&…

yum

什么是yum? Linux中我们也要进行工具/指令/程序&#xff0c;安装&#xff0c;检查卸载等&#xff0c;需要yum的软件 安装软件的方式&#xff1a; 1.源代码安装--交叉编译工作 2.rpm包直接安装 3.yum / apt-get yum:yum是我们linux预装的一个指令&#xff0c;搜索&#x…

【小白专用】PHP基本语法 23.11.04

PHP基本语法 PHP是超文本预处理器 由服务器解析执行 可以与 html 进行混编(嵌入) ,PHP是一种弱类型语言 1.1 PHP标记 PHP和其他Web语言一样&#xff0c;都是用一对标记将PHP代码包含起来&#xff0c;以便和HTML代码区分开来。PHP支持4种风格的标记&#xff0c;如表所示。 标…

webgoat-(A1)injection

SQL Injection (intro) SQL 命令主要分为三类&#xff1a; 数据操作语言 &#xff08;DML&#xff09;DML 语句可用于请求记录 &#xff08;SELECT&#xff09;、添加记录 &#xff08;INSERT&#xff09;、删除记录 &#xff08;DELETE&#xff09; 和修改现有记录 &#xff…

Scala中类的继承、抽象类和特质

1. 类的继承 1.1 Scala中的继承结构 Scala 中继承关系如下图&#xff1a; Any 是整个继承关系的根节点&#xff1b; AnyRef 包含 Scala Classes 和 Java Classes&#xff0c;等价于 Java 中的 java.lang.Object&#xff1b; AnyVal 是所有值类型的一个标记&#xff1b; Nul…

算法专栏 ---- trie树,并查集

trie树 #include <iostream> using namespace std; const int N 1000010; int son[N][26],cnt[N],idx; //明确前面两个数组以及idx的含义 //我们把son这个二维数组看成一个字典树 //本题要求26个字母&#xff0c;所以我们每个节点里面最多有26个儿子节点 //而我们本题要…