Spring Boot入坑-6-缓存
-
位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为缓存(Cache)
-
典型的如CPU与内存之间L1、L2、L3缓存,能让CPU更有聪明、更高效的执行任务
-
在软件项目中,相比于访问网络、磁盘、DB等介质或设备,内存具有更高的效率,所以很多的时候会利用内存作为缓存载体,以提高软件的性能
为什么要使用缓存
-
在实际的软件应用过程中,缓存使用的原因多种,大体如下
为什么要使用缓存
缓存在Web中的应用场景
-
在实际的Web应用中,常用场景如下
后端应用中的缓存
-
在常见的软件项目后端应用中,缓存也有广泛的应用
-
常见的缓存形态有
-
本地缓存
-
成员变量或局部变量:像Map或List系统的,可以将数据库中的一些数据加载出来,在内存中做一些复杂的处理,不要每次都访问数据库,减少数据库的IO;但受JVM的堆大小和GC影响
-
全局变量或静态变量:能实现跨类或整个系统常用数据的缓存,比如一些基础的字典数据,如城市列表等,能快速访问;同样受JVM的堆大小和GC影响
-
ConcurrentHashMap:使用Spring缓存框架的默认缓存机制,简单、易用
-
EhCache:纯Java开源缓存框架,配置简单、结构清晰、功能强大,是一个非常轻量级的缓存实现
-
-
分布式缓存
-
Memcached:应用较广的开源分布式缓存产品之一,它本身其实不提供分布式解决方案,是通过客户端Hash算法取相应的服务器节点ID形式实现分布式;采用将内存分Page和Chunk形式分配缓存空间
-
Redis:是一个远程内存数据库(非关系型数据库),具有高性能、丰富数据类型、操作原子性、可持久化等特点
-
-
缓存相关概念
-
命中率,从缓存中读取的次数/总的读取次数,命中率越高,表示缓存设计的越好;所以缓存尽量针对更更新频率低读取频率高的数据,缓存数据颗粒度尽量小
-
缓存更新策略,由于缓存都是放在内存中,而内存相比如硬盘等介质,其容量有限,所以必须定期的清理未过期的数据;常用的缓存更新策略有
-
FIFO(First In First Out):先进先出,最简单的缓存更新策略
-
LFU(Least Frequently Used):最近最少使用算法,一定时间段内使用次数(频率)最少的那个被移除,借助计数器实现
-
LRU(Least Recently Used):最久未使用算法,使用时间距离现在最久的那个被移除,借助计数器和队列实现,Redis采用类似算法
-
TTL(Time To Live ):存活期,即从缓存中创建时间点开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)
-
TTI(Time To Idle):空闲期,即一个数据多久没被访问将从缓存中移除的时间
-
-
最大缓存容量,一般的情况下越大越好,但需要配合更好的算法来实现
Spring缓存框架
概述
-
Spring主要使用CacheManager和Cache来统一不同的缓存技术
-
CacheManager类,提供各种缓存技术的抽象
-
Cache接口,包含了对缓存的操作抽象
-
-
同时,针对不同的缓存技术,还提供了对CacheManager实现,如ConcurrentMapCacheManager、EhCacheCacheManager、ReidsCacheManager等
-
实际的Spring缓存使用过程中,主要通过声明式缓存和编程式缓存来进行
声明式缓存
-
使用注解完成,这些注解包含
-
@EnableCaching:声明式缓存启用标识,标识在启动类或配置类上,让声明式缓存生效
-
@CacheConfig:缓存配置,标识类,标识类中缓存的一些配置,如缓存名称
-
@Cacheable:新增缓存,标识方法;如果指定缓存key存在缓存,直接取缓存;如果不存在,执行方法,并将返回数据使用指定缓存key存入缓存
-
@CachePut:更新缓存,标识方法;执行方法,并将方法返回内容更新指定缓存key对应缓存
-
@CacheEvict:删除缓存,标识方法;执行方法,并将指定缓存名称或key的缓存删除
-
@Caching:组合使用缓存,标识方法;可组合@Cacheable、@CachePut、@CacheEvict实施在一个方法上
-
编程式缓存
-
主要通过CacheManager、Cache两上接口及相应实现类来实现对缓存的操作
-
与声明式缓存类似,需要定义缓存名称(类似于分组)、Key来实现缓存
-
相比声明式缓存,更加灵活
SpEL表达式【扩展】
-
Spring表达式语言(Spring Expression Language),缩写为SpEL
-
单独模块,只依赖于core模块
-
主要用于Spring容器内实时查询和操作数据
-
常见的使用方式:传统Spring XML配置中、@Value注解中、Spring声明式缓存中等
-
使用方式
-
#{…} 用于执行SpEL表达式,并将内容赋值给属性
-
${…} 主要用于加载外部属性文件中的值
-
声明式缓存中使用SpEL表达式
-
获取输入参数:#ai、pi、#参数名称,如#p0或#a0表示取第1个参数,#id表示取注解方法参数中名称为id的参数
-
获取输出结果:#result,CachePut 操作和处理 CacheEvict 操作都可使用
-
获取根对象:#root,CacheExpressionRootObject对象,缓存表达式的根对象,能取到上下文的方法名等参数
-
关于#root中的内容,主要有
-
#root.methodName, 当前被调用的方法名
-
#root.method.name,当前被调用的方法对象
-
#root.target,当前被调用的目标对象实例
-
#root.targetClass,当前被调用的目标对象的类
-
#root.args[0],当前方法参数组成的数组
-
#root.caches[0].name, 当前被调用的方法使用的Cache
-
Spring Boot中使用缓存
-
在Spring Boot中,集成了如下缓存技术:Generic、JCache (JSR-107)、EhCache、Hazelcast、Infinispan、Redis、Guava、Simple
-
一般会通过在application.properties中spring.cache.type配置相应的缓存类型,并附加配置一些相应配置完成缓存的配置
-
常用缓存类型
-
simple,默认缓存类型,使用ConcurrentMapCacheManager管理缓存,缓存数据存储于JVM中的ConcurrentHashMap类对象;一般不可设置过期时间,需要手动删除或依赖于GC回收
-
ehcache,EhCache缓存,使用EhCacheCacheManager管理缓存,缓存数据也存储于JVM,但可落地于磁盘;通过xml可配置过期时间等
-
redis,Redis缓存,使用RedisCacheManager管理缓存,缓存数据存储于Redis缓存服务器
-
默认内存缓存
概述
-
基于JVM,简单、易使用
-
缺点
-
一般不可设置过期时间
-
不可持久化
-
不支持分布式应用场景
-
使用步骤
-
在pom.xml中添加spring-boot-starter-cache依赖
<!--【缓存】1、Spring缓存依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
-
在启动类或配置类上添加@EnableCaching注解
@Configuration //【缓存】2、添加启动注解,也可以配置在启动类上 @EnableCaching public class CacheConfig { }
-
在application.properties中添加缓存类型配置
#【缓存】3、缓存类型配置 spring.cache.type=simple
-
在程序中使用声明式缓存、编程式缓存即可
-
声明式缓存见附件项目中的GradeService接口
-
编程式缓存见附件项目中的VersionServiceImpl类
-
注意
可通过IDEA控制台的是否有数据访问的SQL语句debugger日志,验证是否使用了缓存 可通过actuator查看当前使用的缓存类型
【演示】
-
Spring Boot中使用默认内存缓存,见附件项目springboot-cache-simple中的类
-
GradeService接口,使用声明式缓存,分别缓存了所有等级、根据ID进行了缓存
-
VersionServiceImpl类,使用编程式缓存,分别缓存了所有等级、根据ID进行了缓存
-
EhCache
概述
-
一个纯Java实现的简单、快速的缓存组件,能提升应用性能、减小数据库负载,提供FIFO、LRU(默认)、LFU多种淘汰算法
-
纯Java的进程内缓存框架,还可做为Hibernate的缓存组件使用
-
提供过期时间配置、缓存落地到硬盘等功能
-
缺点
-
不支持分布式应用场景
-
使用步骤
-
在pom.xml中添加spring-boot-starter-cache、ehcache依赖;与默认内存缓存一致,只多了ehcache依赖
<!--【缓存】1、Spring缓存依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!--【缓存】1、引入EhCache依赖--> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
-
在启动类或配置类上添加@EnableCaching注解;与默认内存缓存一致
@Configuration //【缓存】2、添加启动注解,也可以配置在启动类上 @EnableCaching public class CacheConfig { }
-
在application.properties中添加缓存类型配置;与默认内存缓存一致,只是类型不同,且多了指向的ehcache.xml的配置
#【缓存】3、缓存类型配置 spring.cache.type=ehcache #【缓存】3、缓存配置文件位置 spring.cache.ehcache.config=classpath:/ehcache.xml
-
在ehcache.xml中配置缓存特性,如缓存文件、缓存名称(分类,要与程序中的缓存名称相同)、过期时间等;ehcache缓存特有
-
在程序中使用声明式缓存、编程式缓存即可;与默认内存缓存一致
-
声明式缓存见附件项目中的GradeService接口
-
编程式缓存见附件项目中的VersionServiceImpl类
-
注意
可通过IDEA控制台的是否有数据访问的SQL语句debugger日志,验证是否使用了缓存 可通过actuator查看当前使用的缓存类型
【演示】
-
Spring Boot中使用默认内存缓存,见附件项目springboot-cache-ehcache中的类
-
GradeService接口,使用声明式缓存,分别缓存了所有等级、根据ID进行了缓存;与默认内存缓存一致
-
VersionServiceImpl类,使用编程式缓存,分别缓存了所有等级、根据ID进行了缓存;与默认内存缓存一致
-
【练习】
-
练习实例内容,完成代码编写
Redis
概述
-
一个高性能的key-value数据库
-
支持持久化
-
有丰富的数据类型
-
性能极高,读的速度是110000次/s,写的速度是81000次/s
-
所有操作都是原子操作,多个操作还支持事务
-
支持主从、集群模式
-
缓存过期策略使用LRU
-
分布式缓存
使用前准备
一、安装VMWare
虚拟化工具,用于本地虚拟化安装多台Linux操作系统,体验真实的企业部署场景
可选择VMWare Workstation或VMWare Workstation player,后者免费
安装文件见附件中的VMware-player-full-17.5.0-22583795.exe文件,或去官网下载
二、安装CentOS 7
在VMWare中,安装一台CentOS 7的虚拟主机
CentOS 7是Linux一个企业级的Linux发行版本
安装步骤见附件中的《VMWare中安装CentOS 7.docx》
三、安装XShell,可选
可以去官网下载免费版本,地址为:https://www.xshell.com/zh/free-for-home-school/
也可以使用其他连接工具,如SecureCRT
四、安装Redis
在CentOS 7虚拟主机中,使用yum命令安装Redis
安装步骤见附件中的《CentOS7安装Redis.docx》
五、安装RDM
在CentOS 7的宿主机(安装VMWare的Windows主机)上安装RDM(Redis Desktop Manager),主要用于图形化查看Reids中的缓存信息
安装文件见附件中的redis.desktop.manager.exe文件,或去官网下载
备选方案
直接在Windows主机上安装Redis和RDM
Windows版本的Redis安装文件见附件中的Redis-x64-3.0.504.msi文件,但并不是Redis官方的
【演示】
-
使用redis-cli操作缓存数据
-
使用RDM操作缓存数据
数据类型
-
String:字符串类型,是Redis最基本的数据类型,标准的key-value存储形式,最大存储512M;应用广泛,比如多的需要缓存的数据都可系列化成字符串缓存进Redis
-
Hash:哈希表类型,其单个内容呈现为filed-value形式,天然适合存储对象
-
List:列表类型,按照插入顺序排序,使用LinkedList实现,可选择头部或尾部插入;应用场景如粉丝列表、最近访问列表等
-
Set:无序集合类型,与数学中的集合概念类似,并提供交集、并集、差集等操作;可应用于共同好友、共同喜好等应用
-
ZSet(Sorted Set):有序集合类型,不允许重复,并且有一个double类型的score权重值;可应用于排行榜之类的应用
Spring Boot中使用Redis
概述
-
利用Spring缓存框架,使用方式与内存缓存、EhCache使用方式一致
-
还能将缓存落地成类JSON的可阅读方式(非二进制)
使用步骤
-
在pom.xml中添加spring-boot-starter-cache、spring-boot-starter-data-redis依赖,以及commons-pool2依赖(Lettuce的依赖);与默认内存缓存一致,只多了Redis相关依赖
<!--【缓存】1、Spring缓存依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- 【缓存】1、引入Redis依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--【缓存】1、lettuce依赖,使用连接池需要--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
-
在启动类或配置类上添加@EnableCaching注解;与默认内存缓存一致
@Configuration //【缓存】2、添加启动注解,也可以配置在启动类上 @EnableCaching public class CacheConfig { }
-
在application.properties中添加缓存类型配置、Redis相关配置;与默认内存缓存一致,只是类型不同,且多了Redis相关的配置
#【缓存】3、缓存类型配置 spring.cache.type=redis #【缓存】3、Redis配置 #主机 spring.redis.host=192.168.72.11 #端口 spring.redis.port=6379 #密码 spring.redis.password=redis #数据库序号 spring.redis.database=2 #连接超时时间(毫秒) spring.redis.timeout=3000 #默认过期时间(毫秒) spring.cache.redis.time-to-live=360000 #Redis哨兵模式配置 #spring.redis.sentinel.master=mymaster #spring.redis.sentinel.nodes=192.168.72.5:26379,192.168.72.6:26379 #【缓存】3、lettuce连接池配置 #连接池最大连接数 spring.redis.lettuce.pool.max-active=8 #最大阻塞等待时间(毫秒),-1表示无限等待 spring.redis.lettuce.pool.max-wait=-1 #最大空闲连接数 spring.redis.lettuce.pool.max-idle=8 #最小空闲连接数 spring.redis.lettuce.pool.min-idle=8
-
在程序中使用声明式缓存、编程式缓存即可;与默认内存缓存一致
-
声明式缓存见附件项目中的GradeService接口
-
编程式缓存见附件项目中的VersionServiceImpl类
-
注意 可通过IDEA控制台的是否有数据访问的SQL语句debugger日志,验证是否使用了缓存 可通过actuator查看当前使用的缓存类型 可使用RDM实时查看Redis中缓存的数据、过期时间等信息
【演示】
-
Spring Boot中使用默认内存缓存,见附件项目springboot-cache-redis中的类
-
GradeService接口,使用声明式缓存,分别缓存了所有等级、根据ID进行了缓存;与默认内存缓存一致
-
VersionServiceImpl类,使用编程式缓存,分别缓存了所有等级、根据ID进行了缓存;与默认内存缓存一致
-
RedisTemplate【扩展】
-
通过使用@Configuration结合@Bean可以将修改过的RedisTemplate注入到Spring容器,然后装配使用
-
RedisTemplate提供了对Redis的5种数据类型及对应指令的操作,简单、高效
-
针对指定指令的操作方法分别为opsForValue()、opsForHash()、opsForList()、opsForSet()、opsForZSet()
序列化方式【扩展】
-
默认使用JDK自身的序列化格式,不可读
-
可添加配置成类似JSON可读系列化格式,通过配置RedisTemplate实现
-
具体见附件项目中的config\RedisConfig类
缓存过期时间【扩展】
-
可在application.properties中配置全局缓存过期时间,如果不配置,将会永久有效
-
也可以针对单个缓存配置过期时间 ,通过配置RedisCacheManager实现
-
另外,也可以针对不同的缓存名称,定义不同的过期时间
-
具体见附件项目中的config\CacheKeyValueProperties类、config\RedisConfig类
【演示】
-
Spring Boot中使用RedisTemplate进行Redis缓存操作,见附件项目springboot-cache-redis中的类
-
UserServiceImpl类,使用编程式缓存,分别缓存了所有用户、根据ID进行了缓存;可通过RDM查看缓存内容
-
进阶【扩展】
常见的缓存问题
-
缓存穿透
-
指缓存和数据库中都没有数据,但用户不断的发起请求,导致数据库压力过大,缓存起不到应有的作用;如我们按业务id缓存数据,但用户请求一些无效id作持续请求或恶意请求
-
处理方式:a)增加id过滤,前提是业务id是有逻辑的;b)可短期缓存null值;c)使用布隆过滤器,把有效的key提前hash算法计算存储,无效的直接过滤掉
-
-
缓存击穿
-
指数据库中有数据,但缓存中没有数据,比较常见的是缓存时间到期时,这个时间点并发量如果很大,也会导致数据库压力过大
-
处理方式:a)热点数据永不过期,然后后台定时更新;b)使用SETNX(SET if Not eXists)做互斥锁,直接返回成功,但不返回数据;直到缓存从数据库获取到数据
-
-
缓存雪崩
-
与缓存击穿类似,只是在同一时刻很多的缓存key过期,导致数据库压力过大 处理方式: a)热点数据永不过期,然后后台定时更新;b)设置不同的key使用不同的过期时间,同时还可以附加随机过期时间
-
二级缓存:面对缓存击穿和缓存雪崩,也可以考虑使用二级缓存,处理相对复杂
-
缓存预热:对于一些有大量缓存的系统,比如活动类数据和热点数据,可以提前把相应的缓存在应用启动时就加载到进程中
-
持久化
-
RDB(Redis Database):定时将数据以二进制形式持久化到.rdb文件中,可用于灾难恢复,恢复效率比AOF高;但无法做到实时的持久化,如果存在设备故障,会有部分数据丢失;适用于全量备份或复制场景
RDB(图片来自网络) -
AOF(Append Only File):以日志的形式记录Redis服务器所处理的每次写入、删除操作,查询操作不会记录,且是文本形式记录;就算设备故障,也不会有数据丢失;但AOF文件相对较大,运行效率也稍低,恢复速度也较慢;可通过/etc/redis.conf修改appendonly为yes切换成AOF模式,两种文件默认都存放于/var/lib/redis下
AOF(图片来自网络)
高可用
-
主-备模式:主节点负责写,备用节点负责读取操作
-
哨兵模式(Sentinel):一主多从,每个节点有一个哨兵进程跟踪,当主节点宕机,从节点转为主节点
-
集群模式:是哨兵模式结合了主从模式实现,可提供更高的并发量;可结合哈希槽(Hash slot),使得数据均匀分布在集群中,参考:https://zhuanlan.zhihu.com/p/653258935
管理命令
-
redis-cli:用户进行redis服务器的数据操作
-
auth:对于有命令的服务器,使用进行授权,命令方式为auth 密码
-
redis-benchmark:Redis压力测试工具,如50个并发测试10000个请求命令redis-benchmark -c 50 -n 10000
实战和作业
代码