写点什么

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

作者:Bug终结者
  • 2023-04-20
    河北
  • 本文字数:5848 字

    阅读完需:约 19 分钟

微服务 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(); 本地缓存
复制代码


由于其被 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
复制代码


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: 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
复制代码

♨️核心源码

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;}
复制代码


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> { }
复制代码


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);}
复制代码


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@Servicepublic 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); } }
}
复制代码


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); }}
复制代码


工具类


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;}
复制代码

✅测试接口

这里我使用了 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")@SpringBootApplicationpublic class MainApplication {
public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); }}
复制代码

✂️@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; } }
复制代码

➿调用接口测试

第一次调用,耗时很长



再次调用,走缓存



查看 Redis 可视化 key



大小 1.11k 字节


再看 json 存入



大小 653 字节


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

⛵小结

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


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

发布于: 刚刚阅读数: 4
用户头像

Bug终结者

关注

励志成为一个优秀的开发者~ 2021-12-09 加入

星星之火,可以燎原

评论

发布
暂无评论
微服务 Spring Boot 整合Redis 实战开发解决高并发数据缓存_redis缓存_Bug终结者_InfoQ写作社区