缓存(Cache)简介
- 位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为缓存
- 典型的如CPU与内存之间L1、L2、L3缓存,能让CPU更加聪明、更高效的执行任务
- 在软件项目中,相比于访问网络、磁盘、DB等介质或设备,内存具有更高的效率,所以很多的时候会利用内存作为缓存载体,以提高软件的性能
为什么要使用缓存
缓存在各种Web形态项目中有广泛应用
Java后端开发中有哪些缓存
本地缓存
- 成员变量或局部变量:像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来实现缓存
- 相比声明式缓存,更加灵活
SpringBoot中使用缓存
概述
- 在Spring Boot中,可以使用Spring缓存框架,集成丰富的第三方缓存技术,使用非常简单
- 一般会通过在application.properties中spring.cache.type配置相应的缓存类型,并附加配置一些相应配置完成缓存的使用
- 常用缓存类型:
- simple:内存缓存,使用ConcurrentMapCacheManager管理缓存,缓存数据存储于JVM中的ConcurrentHashMap;一般不可设置过期时间,需要手动删除或依赖于GC回收
- ehcache:EhCache缓存,使用EhCacheCacheManager管理缓存,缓存数据也存储于JVM,但可落地于磁盘;通过xml可配置过期时间等
- redis:Redis缓存,使用RedisCacheManager管理缓存,缓存数据存储于Redis缓存服务器
一般结合缓存名称(类似于分组)、缓存Key来确定一个缓存
SpringBoot中使用Redis
使用方式
- 在pom.xml中添加spring-boot-starter-cache、spring-boot-starter-data-redis依赖,以及commons-pool2依赖,这个是Lettuce依赖
- 在启动类添加@EnableCaching注解
- 在application.properties中添加配置:spring.cache.type=redis,并添加redis相关的配置,包括连接的服务器ip、端口、过期时间等,具体见示例
- 在程序中使用声明式缓存、编程式缓存方式进行操作
声明式使用
-
使用方式如下代码片断:
package com.lemon.demo.springboot5redis.service; import com.baomidou.mybatisplus.extension.service.IService; import com.lemon.demo.springboot5redis.dto.common.ResponseData; import com.lemon.demo.springboot5redis.dto.input.grade.GradeCreateInputDto; import com.lemon.demo.springboot5redis.dto.input.grade.GradeInputDto; import com.lemon.demo.springboot5redis.dto.output.grade.GradeOutputDto; import com.lemon.demo.springboot5redis.entity.GradeEntity; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import java.util.List; /** * 等级服务 * 【缓存】4、使用缓存,声明式使用 */ //【缓存】配置缓存名称 @CacheConfig(cacheNames = "grade") public interface GradeService extends IService<GradeEntity> { /** * 分页查询 * @param pageIndex * @param pageSize * @param name * @return */ ResponseData<List<GradeOutputDto>> query(Long pageIndex, Long pageSize, String name); /** * 查询所有 * @return */ ResponseData<List<GradeOutputDto>> queryAll(); /** * 根据Id获取 * @param id * @return */ //【缓存】添加缓存,使用第1个参数做为缓存的key,这里使用了简单的SpEL(Spring Expression Language)表达式 // unless表示除非...某个条件才不缓存,在这里,只有返回数据不为null才缓存 //#result表示取返回值 @Cacheable(key = "#p0" , unless = "#result.data==null") // @Cacheable(key = "#id") //相同效果 ResponseData<GradeOutputDto> getById(Integer id); /** * 创建 * @param inputDto * @return */ ResponseData create(GradeCreateInputDto inputDto); /** * 更新 * @param inputDto * @return */ //【缓存】删除缓存,使用第1个参数的id做为要删除缓存的key @CacheEvict(key = "#p0.id") ResponseData update(GradeInputDto inputDto); /** * 删除 * @param id */ //【缓存】删除缓存,使用第1个参数做为要删除缓存的key @CacheEvict(key = "#p0") ResponseData<Boolean> delete(Integer id); }
编程式使用
- 使用方式如下面代码片断:
public ResponseData<List<GradeOutputDto>> queryAll() {
ResponseData<List<GradeOutputDto>> responseData = new ResponseData<>();
try {
//返回DTO引用
List<GradeOutputDto> gradeOutputDtos = null;
//【缓存】根据缓存Key获取缓存,如果存在,从缓存中获取数据
if(redisTemplate.hasKey(CACHE_KEY_FOR_ALL_GRADE) == true){
//根据Key获取缓存信息
gradeOutputDtos = (List <GradeOutputDto>)redisTemplate.opsForList().range(CACHE_KEY_FOR_ALL_GRADE,0,redisTemplate.opsForList().size(CACHE_KEY_FOR_ALL_GRADE));
}
//如果不存在,则从数据库中获取
if(gradeOutputDtos == null) {
//查询
List<GradeEntity> gradeEntities = this.list();
//Entity转Dto
gradeOutputDtos = gradeEntities.stream().map(s->modelMapper.map(s,GradeOutputDto.class)).collect(Collectors.toList());
//【缓存】从数据库获取后,将数据缓存到Redis
redisTemplate.opsForList().leftPushAll(CACHE_KEY_FOR_ALL_GRADE,gradeOutputDtos);
//【缓存】设置过期时间,使用动态过期时间
redisTemplate.expire(CACHE_KEY_FOR_ALL_GRADE,redisTemplateUtil.getExpire(CACHE_NAME));
}
responseData = ResponseData.success(gradeOutputDtos);
} catch (Exception ex) {
responseData = ResponseData.failure(ex.getMessage());
ex.printStackTrace();
}
return responseData;
}
欢迎来到testingpai.com!
注册 关于