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

2022-10-31 11:05:24

一、什么是 缓存?

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

1:StaticfinalConcurrentHashMap<K,V> map=newConcurrentHashMap<>(); 本地用于高并发

例2:staticfinalCache<K,V> USER_CACHE=CacheBuilder.newBuilder().build(); 用于redis等缓存

例3:StaticfinalMap<K,V> map=newHashMap(); 本地缓存

由于其被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

CREATETABLE`tb_shop`(`id`bigint(20)unsignedNOTNULLAUTO_INCREMENTCOMMENT'主键',`name`varchar(128)NOTNULLCOMMENT'商铺名称',`type_id`bigint(20)unsignedNOTNULLCOMMENT'商铺类型的id',`images`varchar(1024)NOTNULLCOMMENT'商铺图片,多个图片以'',''隔开',`area`varchar(128)DEFAULTNULLCOMMENT'商圈,例如陆家嘴',`address`varchar(255)NOTNULLCOMMENT'地址',`x`doubleunsignedNOTNULLCOMMENT'经度',`y`doubleunsignedNOTNULLCOMMENT'维度',`avg_price`bigint(10)unsignedDEFAULTNULLCOMMENT'均价,取整数',`sold`int(10)unsigned zerofillNOTNULLCOMMENT'销量',`comments`int(10)unsigned zerofillNOTNULLCOMMENT'评论数量',`score`int(2)unsigned zerofillNOTNULLCOMMENT'评分,1~5分,乘10保存,避免小数',`open_hours`varchar(32)DEFAULTNULLCOMMENT'营业时间,例如 10:00-22:00',`create_time`timestampNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`update_time`timestampNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',PRIMARYKEY(`id`)USINGBTREE,KEY`foreign_key_type`(`type_id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=15DEFAULTCHARSET=utf8mb4 ROW_FORMAT=COMPACT

pom依赖

// Mybatis-Plus 核心依赖<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency>

// hutool 工具包,各种封装功能 一应俱全<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.5</version></dependency>

核心配置 application.yaml

server:port:8082spring:application:name: easydpdatasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/db_easy_dp?useSSL=false&serverTimezone=UTCusername: rootpassword:111111redis:host: redis ip地址port:6379password: redis密码,如没有不写即可lettuce:pool:max-active:10max-idle:10min-idle:1time-between-eviction-runs: 10sjackson:default-property-inclusion: non_null# JSON处理时忽略非空字段mybatis-plus:type-aliases-package: com.chen.entity# 别名扫描包logging:level:com.chen: debug

♨️核心源码

Entity 实体类层

packagecom.chen.entity;importcom.baomidou.mybatisplus.annotation.IdType;importcom.baomidou.mybatisplus.annotation.TableField;importcom.baomidou.mybatisplus.annotation.TableId;importcom.baomidou.mybatisplus.annotation.TableName;importlombok.Data;importlombok.EqualsAndHashCode;importlombok.experimental.Accessors;importjava.io.Serializable;importjava.time.LocalDateTime;/**
 * @author whc
 * @date 2022/9/3 10:29
 */@Data@EqualsAndHashCode(callSuper=false)@Accessors(chain=true)@TableName("tb_shop")publicclassShopEntityimplementsSerializable{privatestaticfinallong serialVersionUID=1L;/**
     * 主键
     */@TableId(value="id", type=IdType.AUTO)privateLong id;/**
     * 商铺名称
     */privateString name;/**
     * 商铺类型的id
     */privateLong typeId;/**
     * 商铺图片,多个图片以','隔开
     */privateString images;/**
     * 商圈,例如陆家嘴
     */privateString area;/**
     * 地址
     */privateString address;/**
     * 经度
     */privateDouble x;/**
     * 维度
     */privateDouble y;/**
     * 均价,取整数
     */privateLong avgPrice;/**
     * 销量
     */privateInteger sold;/**
     * 评论数量
     */privateInteger comments;/**
     * 评分,1~5分,乘10保存,避免小数
     */privateInteger score;/**
     * 营业时间,例如 10:00-22:00
     */privateString openHours;/**
     * 创建时间
     */privateLocalDateTime createTime;/**
     * 更新时间
     */privateLocalDateTime updateTime;@TableField(exist=false)privateDouble distance;}

Mapper持久化层

packagecom.chen.mapper;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.chen.entity.ShopEntity;/**
 * @author whc
 * @date 2022/9/3 10:33
 */publicinterfaceShopMapperextendsBaseMapper<ShopEntity>{}

Service 接口

packagecom.chen.service;importcom.baomidou.mybatisplus.extension.service.IService;importcom.chen.common.ResultBean;importcom.chen.dto.ShopDTO;importcom.chen.entity.ShopEntity;/**
 * @author whc
 * @date 2022/9/3 10:35
 */publicinterfaceShopServiceextendsIService<ShopEntity>{ResultBean<ShopDTO>queryById(Long id);}

ServiceImpl 实现层

packagecom.chen.service.impl;importcn.hutool.core.bean.BeanUtil;importcn.hutool.core.util.StrUtil;importcn.hutool.json.JSONUtil;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importcom.chen.common.ResultBean;importcom.chen.dto.ShopDTO;importcom.chen.entity.ShopEntity;importcom.chen.mapper.ShopMapper;importcom.chen.service.ShopService;importcom.chen.utils.RedisConstants;importlombok.extern.slf4j.Slf4j;importorg.apache.commons.lang3.StringUtils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.cache.annotation.Cacheable;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;/**
 * @author whc
 * @date 2022/9/3 10:36
 */@Slf4j@ServicepublicclassShopServiceImplextendsServiceImpl<ShopMapper,ShopEntity>implementsShopService{@AutowiredprivateStringRedisTemplate stringRedisTemplate;@OverridepublicResultBean<ShopDTO>queryById(Long id){try{// 拼接 redis keyString key=RedisConstants.CACHE_SHOP_KEY+ id;//从redis中获取是否已存在,若存在,则直接返回String json= stringRedisTemplate.opsForValue().get(key);//判断如果存在,就返回if(StrUtil.isNotBlank(json)){ShopDTO shopDTO=JSONUtil.toBean(json,ShopDTO.class);returnResultBean.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));returnResultBean.create(0,"success", shopDTO);}catch(Exception e){
            log.error("获取商品详情失败! e ==> {}", e);returnResultBean.create(-1,"获取商品详情失败! e ==> {}"+ e);}}}

Controller层

packagecom.chen.controller;importcom.chen.common.ResultBean;importcom.chen.dto.ShopDTO;importcom.chen.service.ShopService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.*;/**
 * @author whc
 * @date 2022/9/3 11:06
 */@RestController@CrossOrigin@RequestMapping("/shop")publicclassShopController{@AutowiredprivateShopService shopService;@GetMapping("/{id}")publicResultBean<ShopDTO>queryShopById(@PathVariable("id")Long id){return shopService.queryById(id);}}

工具类

packagecom.chen.utils;/**
 * redis key 常量
 * @author whc
 * @date 2022/9/3 13:40
 */publicclassRedisConstants{publicstaticfinalString CACHE_SHOP_KEY="cache:shop:";publicstaticfinalLong CACHE_SHOP_TTL=30L;}

✅测试接口

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

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

在这里插入图片描述

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

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

第二次调用

在这里插入图片描述

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

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

开启注解启动缓存

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

packagecom.chen;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cache.annotation.EnableCaching;/**
 * @author whc
 * @date 2022/9/3 10:27
 *///开启缓存支持@EnableCaching@MapperScan("com.chen.mapper")@SpringBootApplicationpublicclassMainApplication{publicstaticvoidmain(String[] args){SpringApplication.run(MainApplication.class, args);}}

✂️@CacheEnable 注解详解

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

ShopServiceImpl 实现类

@Cacheable(cacheNames="shop", key="#root.methodName")publicShopDTOqueryById(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);returnnull;}}

➿调用接口测试

第一次调用,耗时很长

在这里插入图片描述

再次调用,走缓存

在这里插入图片描述

查看Redis可视化key

在这里插入图片描述

大小 1.11k 字节

再看json存入

在这里插入图片描述

大小 653 字节

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

⛵小结

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

如果这篇【文章】有帮助到你,希望可以给【Bug 终结者】点个赞👍,创作不易,如果有对【

  • 作者:Bug 终结者
  • 原文链接:https://wanghuichen.blog.csdn.net/article/details/126695872
    更新时间:2022-10-31 11:05:24