• 猿创征文 | 微服务 Spring Boot 整合Redis 实战开发解决高并发数据缓存


    一、什么是 缓存?

    缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码,例如:

    1:Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>(); 本地用于高并发
    
    例2:static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build(); 用于redis等缓存
    
    例3:Static final Map<K,V> map =  new HashMap(); 本地缓存
    
    • 1
    • 2
    • 3
    • 4
    • 5

    由于其被Static修饰,所以随着类的加载而被加载到内存之中,作为本地缓存,由于其又被final修饰,所以其引用(例3:map)和对象(例3:new HashMap())之间的关系是固定的,不能改变,因此不用担心赋值(=)导致缓存失效;

    ⛅为什么用缓存?

    一句话总结: 因为使用了缓存后,效率会大大的提升,减少了不必要的资源消耗,提升了用户体验。

    但是使用缓存会增加代码复杂度和运维的成本,例如:Redis 集群,多主多从,等等

    在这里插入图片描述

    ⚡如何使用缓存

    在实际开发中,我们会构建缓存来提升系统的稳定、高可用性,使其性能得到进一步的提升。最常用的是 我们 本地数据与Redis 数据库结合使用

    浏览器缓存:主要是存在于浏览器端的缓存

    应用层缓存: 可以分为tomcat本地缓存,比如map集合,或者是使用redis作为缓存

    数据库缓存: 在数据库中有一片空间是 buffer pool (缓冲池),增改查数据都会先加载到mysql的缓存中

    CPU缓存: 当代计算机最大的问题是 cpu性能提升了,但内存读写速度没有跟上,所以为了适应当下的情况,增加了cpu的L1,L2,L3级的缓存

    在这里插入图片描述

    二、实现一个商家缓存

    需求说明

    本 项目基于 Spring Boot 整合Redis 并引入 MyBatis-Plus 来完成开发

    • 要求达到第一次加载,查询redis缓存是否存在,若不存在,则查询数据库,查询完毕后,存入redis,再次访问时只获取redis缓存中的数据,不必再次加载数据库,减轻数据库压力。

    ⌛环境搭建

    本项目依赖于 3分钟搞懂阿里云服务器部署Reids并整合Spring Boot 微服务项目

    数据库 MySQL 8.0

    CREATE TABLE `tb_shop` (
      `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `name` varchar(128) NOT NULL COMMENT '商铺名称',
      `type_id` bigint(20) unsigned NOT NULL COMMENT '商铺类型的id',
      `images` varchar(1024) NOT NULL COMMENT '商铺图片,多个图片以'',''隔开',
      `area` varchar(128) DEFAULT NULL COMMENT '商圈,例如陆家嘴',
      `address` varchar(255) NOT NULL COMMENT '地址',
      `x` double unsigned NOT NULL COMMENT '经度',
      `y` double unsigned NOT NULL COMMENT '维度',
      `avg_price` bigint(10) unsigned DEFAULT NULL COMMENT '均价,取整数',
      `sold` int(10) unsigned zerofill NOT NULL COMMENT '销量',
      `comments` int(10) unsigned zerofill NOT NULL COMMENT '评论数量',
      `score` int(2) unsigned zerofill NOT NULL COMMENT '评分,1~5分,乘10保存,避免小数',
      `open_hours` varchar(32) DEFAULT NULL COMMENT '营业时间,例如 10:00-22:00',
      `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      PRIMARY KEY (`id`) USING BTREE,
      KEY `foreign_key_type` (`type_id`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    pom依赖

    // Mybatis-Plus 核心依赖
    <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.4.3version>
    dependency>
    
    // hutool 工具包,各种封装功能 一应俱全
    <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-allartifactId>
        <version>5.8.5version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    核心配置 application.yaml

    server:
      port: 8082
    spring:
      application:
        name: easydp
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/db_easy_dp?useSSL=false&serverTimezone=UTC
        username: root
        password: 111111
      redis:
        host: redis ip地址
        port: 6379
        password: redis密码,如没有不写即可
        lettuce:
          pool:
            max-active: 10
            max-idle: 10
            min-idle: 1
            time-between-eviction-runs: 10s
      jackson:
        default-property-inclusion: non_null # JSON处理时忽略非空字段
    mybatis-plus:
      type-aliases-package: com.chen.entity # 别名扫描包
    logging:
      level:
        com.chen: debug
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    ♨️核心源码

    Entity 实体类层

    package com.chen.entity;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.experimental.Accessors;
    
    import java.io.Serializable;
    import java.time.LocalDateTime;
    
    /**
     * @author whc
     * @date 2022/9/3 10:29
     */
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = true)
    @TableName("tb_shop")
    public class ShopEntity implements Serializable {
        private static final long serialVersionUID = 1L;
        /**
         * 主键
         */
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
    
        /**
         * 商铺名称
         */
        private String name;
    
        /**
         * 商铺类型的id
         */
        private Long typeId;
    
        /**
         * 商铺图片,多个图片以','隔开
         */
        private String images;
    
        /**
         * 商圈,例如陆家嘴
         */
        private String area;
    
        /**
         * 地址
         */
        private String address;
    
        /**
         * 经度
         */
        private Double x;
    
        /**
         * 维度
         */
        private Double y;
    
        /**
         * 均价,取整数
         */
        private Long avgPrice;
    
        /**
         * 销量
         */
        private Integer sold;
    
        /**
         * 评论数量
         */
        private Integer comments;
    
        /**
         * 评分,1~5分,乘10保存,避免小数
         */
        private Integer score;
    
        /**
         * 营业时间,例如 10:00-22:00
         */
        private String openHours;
    
        /**
         * 创建时间
         */
        private LocalDateTime createTime;
    
        /**
         * 更新时间
         */
        private LocalDateTime updateTime;
    
        @TableField(exist = false)
        private Double distance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    Mapper持久化层

    package com.chen.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.chen.entity.ShopEntity;
    
    /**
     * @author whc
     * @date 2022/9/3 10:33
     */
    public interface ShopMapper extends BaseMapper<ShopEntity> {
    	
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Service 接口

    package com.chen.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.chen.common.ResultBean;
    import com.chen.dto.ShopDTO;
    import com.chen.entity.ShopEntity;
    
    /**
     * @author whc
     * @date 2022/9/3 10:35
     */
    public interface ShopService extends IService<ShopEntity> {
    
        ResultBean<ShopDTO> queryById(Long id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ServiceImpl 实现层

    package com.chen.service.impl;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.json.JSONUtil;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.chen.common.ResultBean;
    import com.chen.dto.ShopDTO;
    import com.chen.entity.ShopEntity;
    import com.chen.mapper.ShopMapper;
    import com.chen.service.ShopService;
    import com.chen.utils.RedisConstants;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    
    /**
     * @author whc
     * @date 2022/9/3 10:36
     */
    @Slf4j
    @Service
    public class ShopServiceImpl extends ServiceImpl<ShopMapper, ShopEntity> implements ShopService{
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Override
        public ResultBean<ShopDTO> queryById(Long id) {
            try {
                // 拼接 redis key
                String key = RedisConstants.CACHE_SHOP_KEY + id;
    
                //从redis中获取是否已存在,若存在,则直接返回
                String json = stringRedisTemplate.opsForValue().get(key);
    
                //判断如果存在,就返回
                if (StrUtil.isNotBlank(json)) {
                    ShopDTO shopDTO = JSONUtil.toBean(json, ShopDTO.class);
                    return ResultBean.create(0, "success", shopDTO);
                }
    
                //从数据库查询数据	getById(id) 是 MyBatis-Plus 提供的查询方法,直接调用即可完成查询
                ShopEntity shopEntity = getById(id);
                //转换对象
                ShopDTO shopDTO = BeanUtil.toBean(shopEntity, ShopDTO.class);
    
                //将数据存入redis
                stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shopDTO));
                return ResultBean.create(0, "success", shopDTO);
            } catch (Exception e) {
                log.error("获取商品详情失败! e ==> {}", e);
                return ResultBean.create(-1, "获取商品详情失败! e ==> {}" + e);
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    Controller层

    package com.chen.controller;
    
    import com.chen.common.ResultBean;
    import com.chen.dto.ShopDTO;
    import com.chen.service.ShopService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    /**
     * @author whc
     * @date 2022/9/3 11:06
     */
    @RestController
    @CrossOrigin
    @RequestMapping("/shop")
    public class ShopController {
    
        @Autowired
        private ShopService shopService;
    
        @GetMapping("/{id}")
        public ResultBean<ShopDTO> queryShopById(@PathVariable("id") Long id) {
            return shopService.queryById(id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    工具类

    package com.chen.utils;
    
    /**
     * redis key 常量
     * @author whc
     * @date 2022/9/3 13:40
     */
    public class RedisConstants {
    
        public static final String CACHE_SHOP_KEY = "cache:shop:";
    
        public static final Long CACHE_SHOP_TTL = 30L;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ✅测试接口

    这里我使用了Redis可视化工具,RESP,地址:https://resp.app/zh/

    打开后可以直接连接你的redis数据库,可视化展示

    在这里插入图片描述

    利用 ApiFox测试接口,可参考 【云原生】前后端分离项目下 如何优雅的联调程序?
    在这里插入图片描述

    第一次调用耗时 1.61s ,是因为我们第一次redis中无数据,走了查询数据库的操作,然后存入redis,总耗时1.61s

    第二次调用

    在这里插入图片描述

    第二次调用直接走的缓存,可见效率提升了很多!

    三、采用 微服务 Spring Boot 注解开启缓存

    开启注解启动缓存

    Spring 默认支持缓存,但版本必须在3.1以上,在启动类加入 @EnableCaching 开启即可

    package com.chen;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cache.annotation.EnableCaching;
    
    /**
     * @author whc
     * @date 2022/9/3 10:27
     */
    //开启缓存支持
    @EnableCaching
    @MapperScan("com.chen.mapper")
    @SpringBootApplication
    public class MainApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(MainApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    ✂️@CacheEnable 注解详解

    @CacheEnable: 缓存存在,则使用缓存;不存在,则执行方法,并将结果塞入缓存

    ShopServiceImpl 实现类

     @Cacheable(cacheNames = "shop", key = "#root.methodName")
        public ShopDTO queryById(Long id) {
            try {
                String key = RedisConstants.CACHE_SHOP_KEY + id;
    
                String json = stringRedisTemplate.opsForValue().get(key);
    
                if (StrUtil.isNotBlank(json)) {
                    ShopDTO shopDTO = JSONUtil.toBean(json, ShopDTO.class);
                    return shopDTO;
                }
    
                ShopEntity shopEntity = getById(id);
                //转换对象
                ShopDTO shopDTO = BeanUtil.toBean(shopEntity, ShopDTO.class);
    
                stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shopDTO), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
                return shopDTO;
            } catch (Exception e) {
                log.error("获取商品详情失败! e ==> {}", e);
                return null;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    ➿调用接口测试

    第一次调用,耗时很长

    在这里插入图片描述

    再次调用,走缓存

    在这里插入图片描述

    查看Redis可视化key

    在这里插入图片描述

    大小 1.11k 字节

    再看json存入

    在这里插入图片描述

    大小 653 字节

    综上考虑,出于内存的原因,我们选择使用json存入redis,更省内存!

    ⛵小结

    以上就是【Bug 终结者】对 猿创征文 微服务 Spring Boot 整合Redis 实战开发解决高并发数据缓存 的简单介绍,缓存是我们比较常用的技术,在解决一些高并发场景下,我们巧妙的使用缓存可以极大的减轻服务器的压力,从而提高系统的高可用性,Redis基于内存并且是单线程的,所以说非常的快Redis缓存数据库很重要!

    如果这篇【文章】有帮助到你,希望可以给【Bug 终结者】点个赞👍,创作不易,如果有对【后端技术】、【前端领域】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!

  • 相关阅读:
    【无标题】Test
    Java -基础知识之类的初始化顺序
    【力扣刷题】重新格式化电话号码
    雷达编程实战之提高探测速度
    对DataFrame各行列累乘:prod()函数
    UVA11584划分成回文串 Partitioning by Palindromes
    【深入浅出系列】之代码可读性
    oracle标准版不支持tts
    用于图像处理的高斯滤波器 (LoG) 拉普拉斯
    外贸在谷歌搜索客户,为什么搜索出来的都是同行?
  • 原文地址:https://blog.csdn.net/weixin_45526437/article/details/126695872