• 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(不同场景合理分配) 的过期时间.

    帖子实体类.

    1. @Data
    2. public class Article {
    3. private String title;
    4. private String content;
    5. }

    文章 mapper.

    1. @Mapper
    2. public interface ArticleMapper {
    3. /**
    4. * 根据 id 查询文章
    5. * @param id
    6. * @return
    7. */
    8. Article selectArticleById(@Param("id") Integer id);
    9. }

    帖子 controller

    1. @RestController
    2. @RequestMapping("/article")
    3. public class ArticleController {
    4. @Autowired
    5. private IArticleService articleService;
    6. @GetMapping("/get")
    7. public HashMap get(@NonNull Integer id) {
    8. //1.获取文章服务
    9. Article article = articleService.getArticleInfo(id);
    10. //2.返回响应
    11. return HandlerResponse(1000, "操作成功", article);
    12. }
    13. /**
    14. * 处理返回格式
    15. * @param code
    16. * @param msg
    17. * @param data
    18. * @return
    19. */
    20. private HashMap HandlerResponse(Integer code, String msg, Object data) {
    21. HashMap result = new HashMap<>();
    22. result.put("code", code);
    23. result.put("msg", msg);
    24. result.put("data", data);
    25. return result;
    26. }
    27. }

    帖子 service .

    1. @Slf4j
    2. @Service
    3. public class ArticleService implements IArticleService {
    4. @Autowired
    5. private ArticleMapper articleMapper;
    6. @Autowired
    7. private StringRedisTemplate redisTemplate;
    8. @Override
    9. public Article getArticleInfo(Integer id) {
    10. //1.非空校验
    11. if(id == null) {
    12. log.warn("文章 id 为空");
    13. throw new RuntimeException("文章 id 为空");
    14. }
    15. //2.先去 redis 上看有没有文章对应的这个id
    16. //我这里约定 redis 上存储格式:
    17. //key: art:id
    18. //value: $title$content ($ 是分隔符)
    19. //例如 key: art:1 value: $决定$今天要好好学习
    20. String articleInfo = redisTemplate.opsForValue().get("art:" + id);
    21. if(articleInfo != null) {
    22. //存在直接返回
    23. log.info("从 redis 中获取到文章数据");
    24. //1) 解析格式
    25. Article article = analysisArticle(articleInfo);
    26. //2) 返回数据
    27. return article;
    28. }
    29. //3.redis 上没有数据,因此需要从 mysql 中取
    30. Article article = articleMapper.selectArticleById(id);
    31. if(article == null) {
    32. log.warn("文章不存在");
    33. throw new RuntimeException("文章不存在!");
    34. }
    35. //4.将文章存到 redis 中
    36. //1) 合成 redis 所需格式的文章
    37. articleInfo = synthesisArticle(article);
    38. //2) 设置 5 分钟过期时间(为了演示效果)
    39. redisTemplate.opsForValue().set("art:" + id, articleInfo, 5, TimeUnit.SECONDS);
    40. log.info("从 mysql 中获取到文章数据");
    41. return article;
    42. }
    43. /**
    44. * 合成 redis 需要的格式(提前约定好的)
    45. * @param article
    46. * @return
    47. */
    48. private String synthesisArticle(Article article) {
    49. StringBuilder stringBuilder = new StringBuilder();
    50. stringBuilder.append("$");
    51. stringBuilder.append(article.getTitle());
    52. stringBuilder.append("$");
    53. stringBuilder.append(article.getContent());
    54. return stringBuilder.toString();
    55. }
    56. /**
    57. * 解析文章格式
    58. * @param articleInfo
    59. * @return
    60. */
    61. private Article analysisArticle(String articleInfo) {
    62. Article article = new Article();
    63. String title = articleInfo.split("\\$")[1];
    64. String content = articleInfo.split("\\$")[2];
    65. article.setTitle(title);
    66. article.setContent(content);
    67. return article;
    68. }
    69. }

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

    文章实体类

    1. @Data
    2. public class Article implements Serializable {
    3. private Integer id;
    4. private String title;
    5. private String content;
    6. private Long visits; //访问量
    7. }

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

    1. public class MqFinal {
    2. //处理文章的直接交换机
    3. public static final String UPDATE_DIRECT = "article.update.direct";
    4. //用于修改文章数据的队列
    5. public static final String UPDATE_QUEUE = "article.update.queue";
    6. //bindingKey
    7. public static final String UPDATE_KEY = "article.update.key";
    8. }
    1. @Configuration
    2. public class MqConfig {
    3. /**
    4. * 消息转化器
    5. * @return
    6. */
    7. @Bean
    8. public MessageConverter jsonMessageConverter() {
    9. return new Jackson2JsonMessageConverter();
    10. }
    11. @Bean
    12. public DirectExchange ArticleDirectExchange() {
    13. return new DirectExchange(MqFinal.UPDATE_DIRECT, true, false);
    14. }
    15. @Bean
    16. public Queue ArticleUpdateQueue() {
    17. return new Queue(MqFinal.UPDATE_QUEUE, true);
    18. }
    19. @Bean
    20. public Binding ArticleUpdateBinding() {
    21. return BindingBuilder.bind(ArticleUpdateQueue()).to(ArticleDirectExchange()).with(MqFinal.UPDATE_KEY);
    22. }
    23. }

    mq 监听配置

    1. @Slf4j
    2. @Component
    3. public class MqListenerArticle {
    4. @Autowired
    5. private ArticleMapper articleMapper;
    6. /**
    7. * 同步数据库
    8. */
    9. @RabbitListener(queues = MqFinal.UPDATE_QUEUE)
    10. public void syncVisits(HashMap data) {
    11. Integer id = (Integer) data.get("id");
    12. // Rabbitmq 这里有一个问题,Map 中 Object 传入为 Long 类型,需要用 Integer 来接受,否则报错
    13. // 因此发送消息之前,体现将 Long 类型转化为 String,接收到消息之后只需要将 String 转化为 Long 即可
    14. String visits = (String) data.get("visits");
    15. articleMapper.updateArticleVisits(id, Long.valueOf(visits));
    16. log.info("访问量数据同步完成!");
    17. }
    18. }

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

    1. @Override
    2. public Article getArticleInfo(Integer id) {
    3. //1.非空校验
    4. if(id == null) {
    5. log.warn("文章 id 为空");
    6. throw new RuntimeException("文章 id 为空");
    7. }
    8. //2.访问量 +1
    9. //注意:incr 这个命令执行时,即使 key 不存在,也会自动生成 key,然后自增
    10. Long visits = redisTemplate.opsForValue().increment("v_art:" + id);
    11. //3.rabbitmq 实现异步数据同步(发送一个消息即可)
    12. HashMap visitsInfo = new HashMap<>();
    13. visitsInfo.put("id", id);
    14. visitsInfo.put("visits", visits.toString()); //转化原因前面解释过了
    15. rabbitTemplate.convertAndSend(MqFinal.UPDATE_DIRECT, MqFinal.UPDATE_KEY, visitsInfo);
    16. //4.获取文章数据
    17. //业务逻辑(这里为了可读性,就先不展示这里了)......
    18. //5.放入文章
    19. Article article = new Article();
    20. article.setVisits(visits);
    21. article.setId(id);
    22. return article;
    23. }

  • 相关阅读:
    Web开发之JavaScript知识点总结
    【Scheme】Scheme 编程学习 (五) —— 引述
    vue导入导出csv文件(插件papaparse + jschardet)
    在unity中如何利用预制体创造物体
    基于SSM+MySQL实现的酒店管理系统
    信息系统开发工程师面试笔记(技术一面)
    app查看 证书公钥和md5
    Java基于SSM开发的企业员工管理系统源码
    Vue简单介绍
    【2022最新算法】凌日搜索优化算法(Matlab代码实现)
  • 原文地址:https://blog.csdn.net/CYK_byte/article/details/134187605