一,为什么要使用二级缓存?

我们通常会使用caffeine做本地缓存(或者叫做进程内缓存),

它的优点是速度快,操作方便,缺点是不方便管理,不方便扩展

而通常会使用redis作为分布式缓存,

它的优点是方便扩展,方便管理,但速度上肯定比本地缓存要慢一些,因为有网络io

所以在生产环境中,我们通常把两者都启用,

这样本地缓存做为一级缓存,虽然容量不够大,但也可以把热点数据缓存下来,

把高频访问拦截在redis的上游,

而redis做为二级缓存,把访问请求拦截在数据库的上游,

归根到底,这样可以更有效的减少到数据库的访问,

从而减轻数据库的压力,支持更高并发的访问

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,项目地址

https://github.com/liuhongdi/twocache

2,项目说明

我们在项目中使用了两级缓存:

本地缓存的时间为60秒,过期后则从redis中取数据,

如果redis中不存在,则从数据库获取数据,

从数据库得到数据后,要写入到redis

3,项目结构:如图

三,配置文件说明

1,application.properties

#redis1
spring.redis1.host=127.0.0.1
spring.redis1.port=6379
spring.redis1.password=lhddemo
spring.redis1.database=0 spring.redis1.lettuce.pool.max-active=32
spring.redis1.lettuce.pool.max-wait=300
spring.redis1.lettuce.pool.max-idle=16
spring.redis1.lettuce.pool.min-idle=8 spring.redis1.enabled=1 #profile
spring.profiles.active=cacheenable

说明: spring.redis1.enabled=1: 用来控制redis是否生效

spring.profiles.active=cacheenable: 用来控制caffeine是否生效,

在测试环境中我们有时需要关闭缓存来调试数据库,

在生产环境中如果缓存出现问题也有关闭缓存的需求,

所以要有相应的控制

2,mysql中的表结构:

CREATE TABLE `goods` (
`goodsId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`goodsName` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT 'name',
`subject` varchar(200) NOT NULL DEFAULT '' COMMENT '标题',
`price` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`stock` int(11) NOT NULL DEFAULT '0' COMMENT 'stock',
PRIMARY KEY (`goodsId`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表'

四, java代码说明

1,CacheConfig.java

@Profile("cacheenable")   //prod这个profile时缓存才生效
@Configuration
@EnableCaching //开启缓存
public class CacheConfig {
public static final int DEFAULT_MAXSIZE = 10000;
public static final int DEFAULT_TTL = 600; private SimpleCacheManager cacheManager = new SimpleCacheManager(); //定义cache名称、超时时长(秒)、最大容量
public enum CacheEnum{
goods(60,1000), //有效期60秒 , 最大容量1000
homePage(7200,1000), //有效期2个小时 , 最大容量1000
;
CacheEnum(int ttl, int maxSize) {
this.ttl = ttl;
this.maxSize = maxSize;
}
private int maxSize=DEFAULT_MAXSIZE; //最大數量
private int ttl=DEFAULT_TTL; //过期时间(秒)
public int getMaxSize() {
return maxSize;
}
public int getTtl() {
return ttl;
}
} //创建基于Caffeine的Cache Manager
@Bean
@Primary
public CacheManager caffeineCacheManager() {
ArrayList<CaffeineCache> caches = new ArrayList<CaffeineCache>();
for(CacheEnum c : CacheEnum.values()){
caches.add(new CaffeineCache(c.name(),
Caffeine.newBuilder().recordStats()
.expireAfterWrite(c.getTtl(), TimeUnit.SECONDS)
.maximumSize(c.getMaxSize()).build())
);
}
cacheManager.setCaches(caches);
return cacheManager;
} @Bean
public CacheManager getCacheManager() {
return cacheManager;
}
}

作用:把定义的缓存添加到Caffeine

2,RedisConfig.java

@Configuration
public class RedisConfig { @Bean
@Primary
public LettuceConnectionFactory redis1LettuceConnectionFactory(RedisStandaloneConfiguration redis1RedisConfig,
GenericObjectPoolConfig redis1PoolConfig) {
LettuceClientConfiguration clientConfig =
LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(100))
.poolConfig(redis1PoolConfig).build();
return new LettuceConnectionFactory(redis1RedisConfig, clientConfig);
} @Bean
public RedisTemplate<String, String> redis1Template(
@Qualifier("redis1LettuceConnectionFactory") LettuceConnectionFactory redis1LettuceConnectionFactory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//使用StringRedisSerializer来序列化和反序列化redis的key值
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
//开启事务
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.setConnectionFactory(redis1LettuceConnectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
} @Configuration
public static class Redis1Config {
@Value("${spring.redis1.host}")
private String host;
@Value("${spring.redis1.port}")
private Integer port;
@Value("${spring.redis1.password}")
private String password;
@Value("${spring.redis1.database}")
private Integer database; @Value("${spring.redis1.lettuce.pool.max-active}")
private Integer maxActive;
@Value("${spring.redis1.lettuce.pool.max-idle}")
private Integer maxIdle;
@Value("${spring.redis1.lettuce.pool.max-wait}")
private Long maxWait;
@Value("${spring.redis1.lettuce.pool.min-idle}")
private Integer minIdle; @Bean
public GenericObjectPoolConfig redis1PoolConfig() {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(maxActive);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setMaxWaitMillis(maxWait);
return config;
} @Bean
public RedisStandaloneConfiguration redis1RedisConfig() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(host);
config.setPassword(RedisPassword.of(password));
config.setPort(port);
config.setDatabase(database);
return config;
}
}
}

作用:生成redis的连接,

注意对value的处理使用了Jackson2JsonRedisSerializer,否则不能直接保存一个对象

3, HomeController.java

    //商品详情 参数:商品id
@Cacheable(value = "goods", key="#goodsId",sync = true)
@GetMapping("/goodsget")
@ResponseBody
public Goods goodsInfo(@RequestParam(value="goodsid",required = true,defaultValue = "0") Long goodsId) {
Goods goods = goodsService.getOneGoodsById(goodsId);
return goods;
}

注意使用Cacheable这个注解来使本地缓存生效

4,GoodsServiceImpl.java

    @Override
public Goods getOneGoodsById(Long goodsId) {
Goods goodsOne;
if (redis1enabled == 1) {
System.out.println("get data from redis");
Object goodsr = redis1Template.opsForValue().get("goods_"+String.valueOf(goodsId));
if (goodsr == null) {
System.out.println("get data from mysql");
goodsOne = goodsMapper.selectOneGoods(goodsId);
if (goodsOne == null) {
redis1Template.opsForValue().set("goods_"+String.valueOf(goodsId),"-1",600, TimeUnit.SECONDS);
} else {
redis1Template.opsForValue().set("goods_"+String.valueOf(goodsId),goodsOne,600, TimeUnit.SECONDS);
}
} else {
if (goodsr.equals("-1")) {
goodsOne = null;
} else {
goodsOne = (Goods)goodsr;
}
}
} else {
goodsOne = goodsMapper.selectOneGoods(goodsId);
}
return goodsOne;
}

作用:先从redis中得到数据,如果找不到则从数据库中访问,

注意做了redis1enabled是否==1的判断,即:redis全局生效时,

才使用redis,否则直接访问mysql

五,测试效果

1,访问地址:

http://127.0.0.1:8080/home/goodsget?goodsid=3

查看控制台的输出:

get data from redis
get data from mysql
costtime aop 方法doafterreturning:毫秒数:395

因为caffeine/redis中都没有数据,可以看到程序从mysql中查询数据

costtime aop 方法doafterreturning:毫秒数:0

再次刷新时,没有从redis/mysql中读数据,直接从caffeine返回,使用的时间不足1毫秒

get data from redis
costtime aop 方法doafterreturning:毫秒数:8

本地缓存过期后,可以看到数据在从redis中获取,用时8毫秒

2,具体的缓存时间可以根据自己业务数据的更新频率来确定 ,

原则上:本地缓存的时长要比redis更短一些,

因为redis中的数据我们通常会采用同步机制来更新,

而本地缓存因为在各台web服务内部,
     所以时间上不要太长

六,查看spring boot的版本:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)

spring boot:使用caffeine+redis做二级缓存(spring boot 2.3.1)的更多相关文章

  1. Mybatis的二级缓存、使用Redis做二级缓存

    目录 什么是二级缓存? 1. 开启二级缓存 如何使用二级缓存: userCache和flushCache 2. 使用Redis实现二级缓存 如何使用 3. Redis二级缓存源码分析 什么是二级缓存? ...

  2. SSM+redis整合(mybatis整合redis做二级缓存)

    SSM:是Spring+Struts+Mybatis ,另外还使用了PageHelper 前言: 这里主要是利用redis去做mybatis的二级缓存,mybaits映射文件中所有的select都会刷 ...

  3. mybatis 使用redis实现二级缓存(spring boot)

    mybatis 自定义redis做二级缓存 前言 如果关注功能实现,可以直接看功能实现部分 何时使用二级缓存 一个宗旨---不常变的稳定而常用的 一级是默认开启的sqlsession级别的. 只在单表 ...

  4. MySQL与Redis实现二级缓存

    redis简介 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库 Redis 与其他 key - value 缓存产品有以下三个特点: Redis支持数据的持久化, ...

  5. Spring Boot 2整合Redis做缓存

    既然是要用Redis做缓存,自然少不了安装了.但是本文主要讲Spring Boot与Redis整合.安装教程请另行百度! 1.首先是我们的Redis配置类 package com.tyc; impor ...

  6. SpringMVC +Spring + MyBatis + Mysql + Redis(作为二级缓存) 配置

    转载:http://blog.csdn.net/xiadi934/article/details/50786293 项目环境: 在SpringMVC +Spring + MyBatis + MySQL ...

  7. spring-boot-route(十二)整合redis做为缓存

    redis简介 redis作为一种非关系型数据库,读写非常快,应用十分广泛,它采用key-value的形式存储数据,value常用的五大数据类型有string(字符串),list(链表),set(集合 ...

  8. SpringMVC + MyBatis + Mysql + Redis(作为二级缓存) 配置

    2016年03月03日 10:37:47 标签: mysql / redis / mybatis / spring mvc / spring 33805 项目环境: 在SpringMVC + MyBa ...

  9. mybatis plus使用redis作为二级缓存

    建议缓存放到 service 层,你可以自定义自己的 BaseServiceImpl 重写注解父类方法,继承自己的实现.为了方便,这里我们将缓存放到mapper层.mybatis-plus整合redi ...

随机推荐

  1. 【Flutter 实战】菜单(Menu)功能

    老孟导读:今天介绍下Flutter中的菜单功能. PopupMenuButton 使用PopupMenuButton,点击时弹出菜单,用法如下: PopupMenuButton<String&g ...

  2. 简单地 Makefile 书写

    注意事项 每个标签分支前都不能用空格,必须用tab 标签外调用bash命令用 $(shell -),标签内可以正常使用 标签后可以指定其他标签,执行顺序是先执行其他标签,而后在执行自己 比如 all: ...

  3. Linux 【Shell脚本经典案例】

    Shell 简介 hell是linux的一外壳,它包在linux内核的外面,为用户和内核之间的交互提供了一个接口 当用户下达指令给操作系统的时候,实际上是把指令告诉shell,经过shell解释,处理 ...

  4. [LeetCode]121、122、309 买股票的最佳时机系列问题(DP)

    121.买卖股票的最佳时机 题目 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润. 注意 ...

  5. FreeSWITCH 处理Refer盲转时,UUI传递不对(没有将SIP 消息头Refer-To中的User-to-User传递给B-Leg)

    运行环境:     CentOS 7.6     FreeSWICH 1.6.18   一.问题场景:     FreeSWITCH收到REFER命令后,重新发起的INVITE消息中的 "U ...

  6. Flutter学习三之搭建一个简单的项目框架

    上一篇文章介绍了Dart的语法的基本使用,从这篇文章开始,开发一个基于玩Android网站的app.使用的他们开放的api来获取网站数据. 根据网站的结构,我们app最外层框架需要添加一个底部导航栏, ...

  7. mysql 事务、隔离级别

    一.事务的四大特性(ACID) 1.原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节.事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有 ...

  8. 手写:javascript中的关键字new

    简单介绍一下new new再熟悉不过了,new后面跟着构造函数,可以创建对象,这个对象的原型指向构造函数的原型对象,说起来可能有点绕,直接看代码吧 function Person(name, age) ...

  9. 2、JVM的内存

    1.JVM中的内存结构 从OS的角度来看,JVM运行时会把一部分内存虚拟机化,所以把内存分为直接内存(未被虚拟机化的内存)和运行时数据区(被虚拟机化的内存) JVM的运行时数据区若从线程的角度来看,可 ...

  10. Python-变量、变量作用域、垃圾回收机制原理-global nonlocal

    变量实现原理决定了Python使用的垃圾回收机制为变量引用计数,当这个对象引用计数为0时候,则会自动执行__del__函数回收资源, del方法只是把变量指向的对象引用计数减一而已并删除这个变量 表达 ...