共计 8041 个字符,预计需要花费 21 分钟才能阅读完成。
文章目录
一、什么是 缓存?
⛅为什么用缓存?
⚡如何应用缓存
二、实现一个商家缓存
⌛环境搭建
♨️外围源码
✅测试接口
三、采纳 微服务 Spring Boot 注解开启缓存
✂️@CacheEnable 注解详解
➿调用接口测试
⛵小结
一、什么是 缓存?
缓存(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 缓存中的数据,不用再次加载数据库,加重数据库压力。
⌛环境搭建
数据库 MySQL 8.0
CCREATE 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> { }
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,更省内存!
⛵小结
以上就是微服务 Spring Boot 整合 Redis 实战开发解决高并发数据缓存 的简略介绍,缓存是咱们比拟罕用的技术,在解决一些高并发场景下,咱们奇妙的应用缓存能够极大的加重服务器的压力,从而进步零碎的高可用性,Redis 基于内存并且是单线程的,所以说十分的快!Redis 缓存数据库很重要!