一. 为什么要用多级缓存?

  • 如果只使用redis来做缓存我们会有大量的请求到redis,但是每次请求的数据都是一样的,假如这一部分数据就放在应用服务器本地,那么就省去了请求redis的网络开销,请求速度就会快很多。但是使用redis横向扩展很方便。
  • 如果只使用Caffeine来做本地缓存,我们的应用服务器的内存是有限,并且单独为了缓存去扩展应用服务器是非常不划算。所以,只使用本地缓存也是有很大局限性的。

至此我们是不是有一个想法了,两个一起用。将热点数据放本地缓存(一级缓存),将非热点数据放redis缓存(二级缓存)。

缓存的选择

  • 一级缓存:Caffeine是一个一个高性能的 Java 缓存库;使用 Window TinyLfu 回收策略,提供了一个近乎最佳的命中率。
  • 二级缓存:redis是一高性能、高可用的key-value数据库,支持多种数据类型,支持集群,和应用服务器分开部署易于横向扩展。

数据流向

数据读取流程

数据删除流程

解决思路

Spring 本来就提供了Cache的支持,最核心的就是实现Cache和CacheManager接口。

二. 实战多级缓存的用法

项目说明

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

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

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

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

项目结构

配置文件说明

application.properties

#redis1
spring.redis1.host=127.0.0.1
spring.redis1.port=6379
spring.redis1.password=lhddemo
spring.redis1.database=0spring.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=8spring.redis1.enabled=1#profile
spring.profiles.active=cacheenable

说明:

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

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

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

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

所以要有相应的控制

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代码说明

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秒 , 最大容量1000homePage(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@Primarypublic 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;}@Beanpublic CacheManager getCacheManager() {return cacheManager;}
}

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

RedisConfig.java

@Configuration
public class RedisConfig {@Bean@Primarypublic LettuceConnectionFactory redis1LettuceConnectionFactory(RedisStandaloneConfiguration redis1RedisConfig,GenericObjectPoolConfig redis1PoolConfig) {LettuceClientConfiguration clientConfig =LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofMillis(100)).poolConfig(redis1PoolConfig).build();return new LettuceConnectionFactory(redis1RedisConfig, clientConfig);}@Beanpublic 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;}@Configurationpublic 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;@Beanpublic GenericObjectPoolConfig redis1PoolConfig() {GenericObjectPoolConfig config = new GenericObjectPoolConfig();config.setMaxTotal(maxActive);config.setMaxIdle(maxIdle);config.setMinIdle(minIdle);config.setMaxWaitMillis(maxWait);return config;}@Beanpublic RedisStandaloneConfiguration redis1RedisConfig() {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setHostName(host);config.setPassword(RedisPassword.of(password));config.setPort(port);config.setDatabase(database);return config;}}
}

作用:生成redis的连接

HomeController.java

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

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

GoodsServiceImpl.java

@Overridepublic 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

测试效果

访问地址:

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毫秒

具体的缓存时间可以根据自己业务数据的更新频率来确定 ,原则上:本地缓存的时长要比redis更短一些,因为redis中的数据我们通常会采用同步机制来更新, 而本地缓存因为在各台web服务内部,所以时间上不要太长!

总结

本文介绍了多级缓存的原理以及用法,通过这些知识的介绍相信你也收获了不少。希望这篇文章可以带你了解多级缓存,知道在什么场景下可以使用,Garnett还会不断的分享技术干货的,希望你们是我最好的观众!


原作者:Garnett
原文链接:Caffeine和Redis居然可以这么搭,想不到吧,爱了爱了
原出处:Garnett的Java之路
侵删

redis数据库价格_Caffeine和Redis居然可以这么搭,想不到吧,爱了爱了相关推荐

  1. Redis数据库(四)——Redis集群模式(主从复制、哨兵、Cluster)

    Redis数据库(四)--Redis集群模式(主从复制.哨兵.Cluster) 一.Redis主从复制 1.主从复制流程 二.哨兵模式 1.哨兵模式集群架构 2.哨兵模式主要功能 3.哨兵监控整个系统 ...

  2. Redis数据库(二)——Redis高可用、持久化及性能管理

    Redis数据库(二)--Redis高可用.持久化及性能管理 一.Redis 高可用 主要的高可用技术 二.Redis 持久化 1.持久化的功能 2.两种持久化方式 3.RDB 和 AOF 的区别 ① ...

  3. Redis数据库(三)——Redis数据类型

    Redis数据库(三)--Redis数据类型 一.String类型 1.set / get / append / strlen 2.incr / decr / incrby / decrby 3.ge ...

  4. Redis数据库系列(四)、Redis事务、乐观锁和分布式锁

    第四章.Redis事务.乐观锁和分布式锁 什么是事务机制? 4.1.关系型数据库中的事务机制遵循ACID规则 关系型数据库例如MySql.Oracle: 事务的英文是transaction,以现实中的 ...

  5. Redis数据库(一)——Redis简介、部署及常用命令

    文章目录 一.关系数据库与非关系型数据库概述 1.关系型数据库 2.非关系型数据库 3.关系数据库与非关系型数据库区别 ①.数据存储方式不同 ②.扩展方式不同 ③.对事务性的支持不同 4.非关系型数据 ...

  6. C/C++编程操作Redis数据库,hiredis包装redis数据库操作接口及测试(增删改查与连接)

    介绍一个实际应用场景,对于客户频繁需要查询的信息,可以将其放在redis内存数据库中,相当于一个缓存,每次查的时候先去redis内存数据库中去查询,如果查询不到再去oracle数据库中查询,这样提高了 ...

  7. redis数据库错误:MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persis

    转载自:解决redis连接错误:MISCONF Redis is configured to save RDB snapshots, but it is currently not able to.. ...

  8. php 安装redis数据库,Linux下安装Redis以及phpredis模块

    一:Linux下Redis的安装 1. 首先上官网下载Redis 压缩包,地址:http://redis.io/download下载 2. 通过远程管理工具,将压缩包拷贝到Linux服务器中,执行解压 ...

  9. redis数据库价格_阿里云数据库Redis购买流程

    下面介绍的阿里云数据库Redis购买流程已失效(因阿里云已改版),不必看了. 1.登录阿里云官网,进入控制台-阿里云数据库Redis . 2.在实例列表页, 点击[购买阿里云数据库Redis]按钮,进 ...

最新文章

  1. 京东Vue组件库NutUI 2.0发布:将支持跨平台!
  2. 饼图大小调整_别让这些细节毁了你的图表,饼图制作的三大准则和七大细节
  3. PHP程序员上相亲节目,结果遭女嘉宾瞬间全灭灯
  4. 【控制】《多智能体系统一致性协同演化控制理论与技术》纪良浩老师-第4章-具有随机扰动的多智能体系统脉冲一致性
  5. 启明云端分享| 2.4寸磁编码旋钮方案智能屏
  6. java 0 1背包_浅谈java实现背包算法(0-1背包问题)
  7. android os自动安裝软件,[图]Bliss OS 12进入开发阶段:可在桌面设备上安装Android 10系统...
  8. 光电编码器的原理及应用场合_旋转式光电编码器工作原理及在视觉检测中的使用...
  9. Python XML解析(转载)
  10. Golang 基础:接口使用、实现原理(eface iface)和设计模式
  11. Pug 介绍和在 Vue 中使用
  12. mapgis明码文件转为点线面文件_Geomap格式转化.doc
  13. 2021 Anomaly Detection (李宏毅
  14. canvas画地图运动轨迹【自己定位】
  15. 自定义控件之Canvas图形绘制基础练习-青春痘笑脸^_^
  16. 【LLYD】That 70s show: why the disco decade is back in fashion
  17. python中文转16进制_Python 16进制与中文相互转换的实现方法
  18. 计算机毕业设计垃圾分类回收微信小程序源码
  19. 说一下NFC,手机有NFC功能却不能模拟门禁卡?
  20. __name__属性的作用是什么

热门文章

  1. Delphi 中的 Var buffer 开类型参数
  2. 通过对代码进行调试讲解缓冲区溢出原理
  3. C盘突然爆满怎么办?
  4. cannot resolve symbol ‘R‘ 程序包R不存在
  5. 一文搞定C语言本地变量和全局变量
  6. UNIX再学习 -- 发送信号
  7. java怎么把大小写转换_JAVA 如何将String进行大小写转换
  8. 32位x86处理器架构
  9. How to Secure Your Smart Contracts: 6 Solidity Vulnerabilities and how to avoid them (Part 2)
  10. 谈谈AOP应用层切面设计