Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题


Posted in Redis onFebruary 12, 2022

前言

最近在做阅读类的业务,需要记录用户的PV,UV;

项目状况:前期尝试业务阶段;

特点:

快速实现(不需要做太重,满足初期推广运营即可)快速投入市场去运营

收集用户的原始数据,三要素:

谁在什么时间阅读哪篇文章

提到PV,UV脑海中首先浮现特点:

需要考虑性能(每个客户每打开一篇文章进行记录)允许数据有较小误差(少部分数据丢失)

架构设计

架构图:

Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题

时序图

Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题

记录基础数据MySQL表结构

CREATE TABLE `zh_article_count` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `bu_no` varchar(32) DEFAULT NULL COMMENT '业务编码',
  `customer_id` varchar(32) DEFAULT NULL COMMENT '用户编码',
  `type` int(2) DEFAULT '0' COMMENT '统计类型:0APP内文章阅读',
  `article_no` varchar(32) DEFAULT NULL COMMENT '文章编码',
  `read_time` datetime DEFAULT NULL COMMENT '阅读时间',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `param1` int(2) DEFAULT NULL COMMENT '预留字段1',
  `param2` int(4) DEFAULT NULL COMMENT '预留字段2',
  `param3` int(11) DEFAULT NULL COMMENT '预留字段3',
  `param4` varchar(20) DEFAULT NULL COMMENT '预留字段4',
  `param5` varchar(32) DEFAULT NULL COMMENT '预留字段5',
  `param6` varchar(64) DEFAULT NULL COMMENT '预留字段6',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_zh_article_count_buno` (`bu_no`),
  KEY `key_zh_article_count_csign` (`customer_id`),
  KEY `key_zh_article_count_ano` (`article_no`),
  KEY `key_zh_article_count_rtime` (`read_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章阅读统计表';

技术实现方案

SpringBoot

Redis

MySQL

代码实现

完整代码(GitHub,欢迎大家Star,Fork,Watch)

https://github.com/dangnianchuntian/springboot

主要代码展示

Controller

/*
 * Copyright (c) 2020. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:Spring Boot实战解决高并发数据入库: Redis 缓存+MySQL 批量入库
 * 类名称:ArticleCountController.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */

package com.zhanghan.zhredistodb.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.zhanghan.zhredistodb.controller.request.PostArticleViewsRequest;
import com.zhanghan.zhredistodb.service.ArticleCountService;
@RestController
public class ArticleCountController {
    @Autowired
    private ArticleCountService articleCountService;
   /**
    * 记录用户访问记录
    */
    @RequestMapping(value = "/post/article/views", method = RequestMethod.POST)
    public Object postArticleViews(@RequestBody @Validated PostArticleViewsRequest postArticleViewsRequest) {
        return articleCountService.postArticleViews(postArticleViewsRequest);
    }
    /**
     *  批量将缓存中的数据同步到MySQL(模拟定时任务操作)
     */
    @RequestMapping(value = "/post/batch", method = RequestMethod.POST)
    public Object postBatch() {
        return articleCountService.postBatchRedisToDb();
}

Service

/*
 * Copyright (c) 2020. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:Spring Boot实战解决高并发数据入库: Redis 缓存+MySQL 批量入库
 * 类名称:ArticleCountServiceImpl.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */

package com.zhanghan.zhredistodb.service.impl;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.alibaba.fastjson.JSON;
import com.zhanghan.zhredistodb.controller.request.PostArticleViewsRequest;
import com.zhanghan.zhredistodb.dto.ArticleCountDto;
import com.zhanghan.zhredistodb.mybatis.mapper.XArticleCountMapper;
import com.zhanghan.zhredistodb.service.ArticleCountService;
import com.zhanghan.zhredistodb.util.wrapper.WrapMapper;
import cn.hutool.core.util.IdUtil;
@Service
public class ArticleCountServiceImpl implements ArticleCountService {
    private static Logger logger = LoggerFactory.getLogger(ArticleCountServiceImpl.class);
    @Autowired
    private RedisTemplate<String, String> strRedisTemplate;
    private XArticleCountMapper xArticleCountMapper;
    @Value("${zh.article.count.redis.key:zh}")
    private String zhArticleCountRedisKey;
    @Value("#{T(java.lang.Integer).parseInt('${zh..article.read.num:3}')}")
    private Integer articleReadNum;
    /**
     * 记录用户访问记录
     */
    @Override
    public Object postArticleViews(PostArticleViewsRequest postArticleViewsRequest) {
        ArticleCountDto articleCountDto = new ArticleCountDto();
        articleCountDto.setBuNo(IdUtil.simpleUUID());
        articleCountDto.setCustomerId(postArticleViewsRequest.getCustomerId());
        articleCountDto.setArticleNo(postArticleViewsRequest.getArticleNo());
        articleCountDto.setReadTime(new Date());
        String strArticleCountDto = JSON.toJSONString(articleCountDto);
        strRedisTemplate.opsForList().rightPush(zhArticleCountRedisKey, strArticleCountDto);
        return WrapMapper.ok();
    }
     * 批量将缓存中的数据同步到MySQL
    public Object postBatchRedisToDb() {
        Date now = new Date();
        while (true) {
            List<String> strArticleCountList =
                    strRedisTemplate.opsForList().range(zhArticleCountRedisKey, 0, articleReadNum);
            if (CollectionUtils.isEmpty(strArticleCountList)) {
                return WrapMapper.ok();
            }
            List<ArticleCountDto> articleCountDtoList = new ArrayList<>();
            strArticleCountList.stream().forEach(x -> {
                ArticleCountDto articleCountDto = JSON.parseObject(x, ArticleCountDto.class);
                articleCountDtoList.add(articleCountDto);
            });
            //过滤出本次定时任务之前的缓存中数据,防止死循环
            List<ArticleCountDto> beforeArticleCountDtoList = articleCountDtoList.stream().filter(x -> x.getReadTime()
                    .before(now)).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(beforeArticleCountDtoList)) {
            xArticleCountMapper.batchAdd(beforeArticleCountDtoList);
            Integer delSize = beforeArticleCountDtoList.size();
            strRedisTemplate.opsForList().trim(zhArticleCountRedisKey, delSize, -1L);
        }
}

测试

模拟用户请求访问后台(多次请求)

Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题

查看缓存中访问数据

Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题

模拟定时任务将缓存中数据同步到DB中

Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题

这时查看缓存中的数据已经没了

Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题

查看数据库表结构

Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题

总结

  • 项目中定时任务
  • 问演示方便用http代替定时任务调度;实际项目中用XXL-job
  • 定时任务项目中用redis锁防止并发(定时任务调度端多次调度等)
  • 后期运营数据可以从阅读记录表中拉数据进行相关分析
  • 访问量大:可以将MySQL中的阅读记录表定时迁移走(MySQL建历史表,MongoDB等)

到此这篇关于Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库的文章就介绍到这了,更多相关Spring Boot高并发数据入库内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
Redis安装启动及常见数据类型
Apr 14 Redis
redis 查看所有的key方式
May 07 Redis
5分钟教你docker安装启动redis全教程(全新方式)
May 29 Redis
详解Redis集群搭建的三种方式
May 31 Redis
详解Redis复制原理
Jun 04 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
解决Redis启动警告问题
Feb 24 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
解决 Redis 秒杀超卖场景的高并发
Apr 12 Redis
浅谈Redis的事件驱动模型
May 30 Redis
Redis全局ID生成器的实现
Jun 05 Redis
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 #Redis
聊聊redis-dump工具安装问题
Jan 18 #Redis
redis的list数据类型相关命令介绍及使用
Jan 18 #Redis
关于使用Redisson订阅数问题
Jan 18 #Redis
Redis中缓存穿透/击穿/雪崩问题和解决方法
linux下安装redis图文详细步骤
Springboot/Springcloud项目集成redis进行存取的过程解析
You might like
ThinkPHP跳转页success及error模板实例教程
2014/07/17 PHP
PHP 用session与gd库实现简单验证码生成与验证的类方法
2016/11/15 PHP
php常用正则函数实例小结
2016/12/29 PHP
完美解决Thinkphp3.2中插入相同数据的问题
2017/08/01 PHP
Javascript异步表单提交,图片上传,兼容异步模拟ajax技术
2010/05/10 Javascript
基于JS实现简单的样式切换效果代码
2015/09/04 Javascript
jquery原理以及学习技巧介绍
2015/11/11 Javascript
Bootstrap按钮下拉菜单组件详解
2016/05/10 Javascript
浅谈Javascript数据属性与访问器属性
2016/07/26 Javascript
AngularJS入门教程引导程序
2016/08/18 Javascript
Angular JS 生成动态二维码的方法
2017/02/23 Javascript
微信小程序商品详情页的底部弹出框效果
2020/11/16 Javascript
如何去除富文本中的html标签及vue、react、微信小程序中的过滤器
2018/11/21 Javascript
微信小程序组件传值图示过程详解
2019/07/31 Javascript
JS实现滑动拼图验证功能完整示例
2020/03/29 Javascript
bootstrap-table后端分页功能完整实例
2020/06/01 Javascript
[02:41]DOTA2英雄基础教程 冥魂大帝
2014/01/16 DOTA
python采用requests库模拟登录和抓取数据的简单示例
2014/07/05 Python
Python中用Decorator来简化元编程的教程
2015/04/13 Python
Django中使用celery完成异步任务的示例代码
2018/01/23 Python
基于Django用户认证系统详解
2018/02/21 Python
numpy matrix和array的乘和加实例
2018/06/28 Python
Python Tkinter 简单登录界面的实现
2019/06/14 Python
解决Django连接db遇到的问题
2019/08/29 Python
Python高阶函数、常用内置函数用法实例分析
2019/12/26 Python
3分钟看懂Python后端必须知道的Django的信号机制
2020/07/26 Python
Guess欧洲官网:美国服饰品牌
2019/08/06 全球购物
进步之星获奖感言
2014/02/22 职场文书
2014年两会学习心得范例
2014/03/17 职场文书
大学学生会竞选演讲稿
2014/04/25 职场文书
幼儿园八一建军节活动方案
2014/08/27 职场文书
大国崛起英国观后感
2015/06/02 职场文书
《钢铁是怎样炼成的》高中读后感
2019/08/07 职场文书
Python使用Kubernetes API访问集群
2021/05/30 Python
2021年国漫热度排行前十,完美世界上榜,第四是美国动画作品
2022/03/18 国漫
Win11 Beta 22621.601 和 22622.601今日发布 KB5017384修复内容汇总
2022/09/23 数码科技