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主要使用CacheManagerCache来统一不同的缓存技术

    • 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,简单、易使用

  • 缺点

    • 一般不可设置过期时间

    • 不可持久化

    • 不支持分布式应用场景

使用步骤

  1. 在pom.xml中添加spring-boot-starter-cache依赖

    <!--【缓存】1、Spring缓存依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
  2. 在启动类或配置类上添加@EnableCaching注解

    @Configuration
    //【缓存】2、添加启动注解,也可以配置在启动类上
    @EnableCaching
    public class CacheConfig {
    }
  3. 在application.properties中添加缓存类型配置

    #【缓存】3、缓存类型配置
    spring.cache.type=simple
  4. 在程序中使用声明式缓存、编程式缓存即可

    • 声明式缓存见附件项目中的GradeService接口

    • 编程式缓存见附件项目中的VersionServiceImpl类

注意

可通过IDEA控制台的是否有数据访问的SQL语句debugger日志,验证是否使用了缓存 可通过actuator查看当前使用的缓存类型

【演示】

  1. Spring Boot中使用默认内存缓存,见附件项目springboot-cache-simple中的类

    • GradeService接口,使用声明式缓存,分别缓存了所有等级、根据ID进行了缓存

    • VersionServiceImpl类,使用编程式缓存,分别缓存了所有等级、根据ID进行了缓存

EhCache

概述

  • 一个纯Java实现的简单、快速的缓存组件,能提升应用性能、减小数据库负载,提供FIFO、LRU(默认)、LFU多种淘汰算法

  • 纯Java的进程内缓存框架,还可做为Hibernate的缓存组件使用

  • 提供过期时间配置、缓存落地到硬盘等功能

  • 缺点

    • 不支持分布式应用场景

使用步骤

  1. 在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>
  2. 在启动类或配置类上添加@EnableCaching注解;与默认内存缓存一致

    @Configuration
    //【缓存】2、添加启动注解,也可以配置在启动类上
    @EnableCaching
    public class CacheConfig {
    }
  3. 在application.properties中添加缓存类型配置;与默认内存缓存一致,只是类型不同,且多了指向的ehcache.xml的配置

    #【缓存】3、缓存类型配置
    spring.cache.type=ehcache
    #【缓存】3、缓存配置文件位置
    spring.cache.ehcache.config=classpath:/ehcache.xml
  4. 在ehcache.xml中配置缓存特性,如缓存文件、缓存名称(分类,要与程序中的缓存名称相同)、过期时间等;ehcache缓存特有

  5. 在程序中使用声明式缓存、编程式缓存即可;与默认内存缓存一致

    • 声明式缓存见附件项目中的GradeService接口

    • 编程式缓存见附件项目中的VersionServiceImpl类

注意

可通过IDEA控制台的是否有数据访问的SQL语句debugger日志,验证是否使用了缓存 可通过actuator查看当前使用的缓存类型

【演示】

  1. Spring Boot中使用默认内存缓存,见附件项目springboot-cache-ehcache中的类

    • GradeService接口,使用声明式缓存,分别缓存了所有等级、根据ID进行了缓存;与默认内存缓存一致

    • VersionServiceImpl类,使用编程式缓存,分别缓存了所有等级、根据ID进行了缓存;与默认内存缓存一致

【练习】

  1. 练习实例内容,完成代码编写

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,可选

 四、安装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官方的

【演示】

  1. 使用redis-cli操作缓存数据

  2. 使用RDM操作缓存数据

数据类型

  • String:字符串类型,是Redis最基本的数据类型,标准的key-value存储形式,最大存储512M;应用广泛,比如多的需要缓存的数据都可系列化成字符串缓存进Redis

  • Hash:哈希表类型,其单个内容呈现为filed-value形式,天然适合存储对象

  • List:列表类型,按照插入顺序排序,使用LinkedList实现,可选择头部或尾部插入;应用场景如粉丝列表、最近访问列表等

  • Set:无序集合类型,与数学中的集合概念类似,并提供交集、并集、差集等操作;可应用于共同好友、共同喜好等应用

  • ZSet(Sorted Set):有序集合类型,不允许重复,并且有一个double类型的score权重值;可应用于排行榜之类的应用

Spring Boot中使用Redis

概述
  • 利用Spring缓存框架,使用方式与内存缓存、EhCache使用方式一致

  • 还能将缓存落地成类JSON的可阅读方式(非二进制)

使用步骤
  1. 在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>
  2. 在启动类或配置类上添加@EnableCaching注解;与默认内存缓存一致

    @Configuration
    //【缓存】2、添加启动注解,也可以配置在启动类上
    @EnableCaching
    public class CacheConfig {
    }
  3. 在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
  4. 在程序中使用声明式缓存、编程式缓存即可;与默认内存缓存一致

    • 声明式缓存见附件项目中的GradeService接口

    • 编程式缓存见附件项目中的VersionServiceImpl类

注意 可通过IDEA控制台的是否有数据访问的SQL语句debugger日志,验证是否使用了缓存 可通过actuator查看当前使用的缓存类型 可使用RDM实时查看Redis中缓存的数据、过期时间等信息

【演示】

  1. 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类

【演示】

  1. 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

实战和作业

  1. 编写程序,完成演示项目springboot-data-cache-redis

代码

网盘地址:链接:链接:https://pan.baidu.com/s/1T72uiiDBL4U9NXEtXSVTRQ?pwd=8888 

posted @ 2024-06-03 20:17  拐子  阅读(99)  评论(0编辑  收藏  举报