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 27 Redis
redis 查看所有的key方式
May 07 Redis
redis内存空间效率问题的深入探究
May 17 Redis
解析Redis Cluster原理
Jun 21 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
CentOS8.4安装Redis6.2.6的详细过程
Nov 20 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
sentinel支持的redis高可用集群配置详解
Apr 01 Redis
Redis 异步机制
May 15 Redis
使用Redis实现分布式锁的方法
Jun 16 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
php error_log 函数的使用
2009/04/13 PHP
php反弹shell实现代码
2009/04/22 PHP
php循环语句 for()与foreach()用法区别介绍
2012/09/05 PHP
浅析php与数据库代码开发规范
2013/08/08 PHP
PHP两种快速排序算法实例
2015/02/15 PHP
再谈IE中Flash控件的自动激活 ObjectWrap
2007/03/09 Javascript
扩展easyui.datagrid,添加数据loading遮罩效果代码
2010/11/02 Javascript
Jquery 一次处理多个ajax请求的代码
2011/09/02 Javascript
js加入收藏夹代码(兼容ie/ff/op)
2014/05/16 Javascript
js和jquery设置disabled属性为true使按钮失效
2014/08/07 Javascript
Bootstrap每天必学之简单入门
2015/11/19 Javascript
使用OpenLayers3 添加地图鼠标右键菜单
2015/12/29 Javascript
AngularJS整合Springmvc、Spring、Mybatis搭建开发环境
2016/02/25 Javascript
利用Vue.js框架实现火车票查询系统(附源码)
2017/02/27 Javascript
ES5 ES6中Array对象去除重复项的方法总结
2017/04/27 Javascript
vue.js动态数据绑定学习笔记
2017/05/19 Javascript
JavaScript实现购物车基本功能
2017/07/21 Javascript
js判断传入时间和当前时间大小实例(超简单)
2018/01/11 Javascript
JS加密插件CryptoJS实现的Base64加密示例
2020/08/16 Javascript
vue基于v-charts封装双向条形图的实现代码
2019/12/09 Javascript
js中火星坐标、百度坐标、WGS84坐标转换实现方法示例
2020/03/02 Javascript
react-router-dom 嵌套路由的实现
2020/05/02 Javascript
three.js显示中文字体与tween应用详析
2021/01/04 Javascript
Python使用win32com实现的模拟浏览器功能示例
2017/07/13 Python
python绘制漏斗图步骤详解
2019/03/04 Python
python烟花效果的代码实例
2020/02/25 Python
解决python和pycharm安装gmpy2 出现ERROR的问题
2020/08/28 Python
python产生模拟数据faker库的使用详解
2020/11/04 Python
CSS3制作酷炫的条纹背景
2017/11/09 HTML / CSS
Betsey Johnson官网:妖娆可爱的连衣裙及鞋子、手袋和配件
2016/12/30 全球购物
美国半成品食材配送服务商:Home Chef
2018/01/25 全球购物
小学综合实践活动总结
2014/07/07 职场文书
信用卡工资证明格式
2014/09/13 职场文书
浅谈由position属性引申的css进阶讨论
2021/05/25 HTML / CSS
阿里云服务器(windows)手动部署FTP站点详细教程
2022/08/05 Servers
MySQL使用IF语句及用case语句对条件并结果进行判断 
2022/09/23 MySQL