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

Posted 小王Java

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务 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: 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

♨️核心源码

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
@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);



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")
@SpringBootApplication
public 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 终结者】点个赞

以上是关于微服务 Spring Boot 整合Redis 实战开发解决高并发数据缓存的主要内容,如果未能解决你的问题,请参考以下文章

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

Spring Boot 整合 Prometheus

Spring Boot 整合 Prometheus

Spring Boot 整合 Prometheus

Spring Boot 整合 Prometheus

Spring Boot 整合 Prometheus