缓存(cache)
为啥需要使用缓存
-
问题描述:企业级应用主要作用是信息处理,当需要读取数据时,由于受限于数据库的访问效率,导致整体系统性能偏低。(就是说:应用程序直接与数据库打交道,访问效率低。
- 如果长期对数据库进行数据访问,导致数据库压力太大。
-
解决方案:为了能够提高数据库的访问效率,降低数据库的压力:
- 开发者通常会在应用程序与数据库之间建立一种临时的数据存储机制,该区域中的数据在内存中保存,读写速度较快,可以有效解决数据库访问效率低下的问题。
- 理解:如果没有缓存的话,用户的每次查询都要向数据库查询,这样就会导致数据库的压力很大;
- 但是如果有缓存,应用程序先向缓存发送查询操作,如果缓存里面没有对应的数据,则缓存会向数据库发送查询操作,把查询到的数据存储到缓存里,然后再把缓存里的数据返回给应用程序,而且下一次想要获取同样的数据,就不需要再次访问数据库了;如果缓存里面有数据,则直接把数据返回给应用程序,就不需要向数据库访问了。
使用缓存后,应用程序与缓存打交道,缓存与数据库打交道,数据访问效率提高
- 但是如果有缓存,应用程序先向缓存发送查询操作,如果缓存里面没有对应的数据,则缓存会向数据库发送查询操作,把查询到的数据存储到缓存里,然后再把缓存里的数据返回给应用程序,而且下一次想要获取同样的数据,就不需要再次访问数据库了;如果缓存里面有数据,则直接把数据返回给应用程序,就不需要向数据库访问了。
缓存的作用
- 缓存是一种介于数据永久存储介质与应用程序之间的数据临时存储介质,使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能。
- 缓存里面的数据不一定只是数据库里面的数据,缓存也不一定就是为了减少数据库的查询。
- 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间
- 当数据不是从数据库里面提供的,而是从外面进来的(就好比只有应用程序和缓存):就是说缓存可以存储一些临时数据(或者说是整个系统运行过程中产生的数据),因为这些数据不需要进入到数据库,,所以就得存储到缓存里面了。
- 例子:我们登录的时候,使用手机号登录的时候,需要获取验证码,而这个验证码它不会存储到数据库里面的,因为它是临时生成的,然后通过短信发给你,然后再拿这个短信的验证码和你的手机号再去匹配,匹配成功才能登录。
- 当数据不是从数据库里面提供的,而是从外面进来的(就好比只有应用程序和缓存):就是说缓存可以存储一些临时数据(或者说是整个系统运行过程中产生的数据),因为这些数据不需要进入到数据库,,所以就得存储到缓存里面了。
- 可以使用多级缓存的设计结构:就是说应用程序与数据库之间,有多个缓存。
缓存的使用理解
- 如下面代码:定义一个HashMap集合,用来存放缓存的数据。
- 当我们发送一个id进行查询book类型的数据时,我们先从cache集合里面获取,如果无法获取,则从数据库中查询,然后再把从数据库中查询出来的数据放进集合cache里面。然后下一次查询这个id的数据时,就可以直接从集合cache中获取数据了,而不用再次向数据库查询对应的数据了。(从而就能够降低了数据库的访问压力了)
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
private HashMap<Integer,Book> cache = new HashMap<Integer,Book>();
@Override
public Book getById(Integer id) {
//如果当前缓存中没有本次要查询的数据,则进行查询,否则直接从缓存中获取数据返回
Book book = cache.get(id);
if(book == null){
book = bookDao.selectById(id);
cache.put(id,book);
}
return book;
}
}
SpringBoot启用内置缓存的操作
- springboot提供了缓存技术,方便缓存的使用。
- springboot缓存的使用操作:
- 启用缓存
- 设置进入缓存的数据
- 设置读取缓存的数据
- srpingboot提供的缓存技术除了提供默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理。
- Generic
- JCache
- Ehcache
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffenine
- Simple(默认)
- memcached
- jetcache(阿里)
如何使用springboot内置的缓存
- springboot技术提供有内置的缓存解决方案,可以帮助开发者快速开启缓存技术,并使用缓存技术进行数据的快速操作
- 例如:读取缓存数据 和 写入数据到缓存。
使用步骤(这里是springboot默认的缓存方案:Simple)
- 步骤①:导入springboot提供的缓存技术对应的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 步骤②:启用缓存,在引导类上方标注注解@EnableCaching配置springboot程序中可以使用缓存
@SpringBootApplication
//开启缓存功能
@EnableCaching
public class ApplicationCache {
public static void main(String[] args) {
SpringApplication.run(ApplicationCache.class, args);
}
}
- 步骤③:设置操作的数据是否使用缓存
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
// @Cacheable声明当前方法的返回值放入缓存中
// value属性描述缓存的存储位置,可以理解为是一个存储空间名,
// key属性描述了缓存中保存数据的名称,使用#id读取形参中的id值作为缓存名称
@Cacheable(value="cacheSpace",key="#id")
public Book getById(Integer id) {
return bookDao.selectById(id);
}
}
- 我们在配置文件中开启了Mybatis-plus的日志,然后当我们第一次访问的时候,会发现有sql语句的执行。如果再次访问这个请求,然后就不会再产生sql语句了,这就表明,它的第二次请求并没有操作数据库。
@Cacheable注解
- @Cacheable注解说明:
- @Cacheable声明 当前方法的返回值 放入缓存中,其中要指定缓存的存储位置,以及缓存中保存当前方法返回值对应的名称。
- 上例中value属性描述缓存的存储位置,cacheSpace可以理解为是一个存储空间名
- key属性描述了缓存中 保存数据的名称,使用#id读取形参中的id值作为缓存名称。
- 使用@Cacheable注解后,执行当前操作,如果发现对应名称在缓存中没有数据,就正常读取数据,然后放入缓存;如果对应名称在缓存中有数据,就终止当前业务方法执行,直接返回缓存中的数据。
springboot使用缓存缓存技术实现:手机验证码案例(存储临时数据的用法)
- 需求如下:
- 输入手机号获取验证码,组织文档以短信形式发送给用户(页面模拟)
- 输入手机号和验证码验证结果
- 需求分析:
- 提供controller,传入手机号,业务层通过计算出独有的6为验证码数据,存入缓存后返回此数据。
- 提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入的验证码进行比对,返回比对结果。
手机案例的操作步骤:
- 步骤①:导入springboot提供的缓存技术对应的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 步骤②:启用缓存,在引导类上方标注注解@EnableCaching配置springboot程序中可以使用缓存
@SpringBootApplication
//开启缓存功能
@EnableCaching
public class ApplicationCache {
public static void main(String[] args) {
SpringApplication.run(ApplicationCache.class,args);
}
}
- 步骤③:定义验证码对应的实体类,封装手机号与验证码两个属性
@Data
public class PhoneCode {
// 手机号码
private String tele;
// 验证码
private String code;
}
- 步骤④:定义验证码的生成策略与根据手机号读取验证码的功能
@Component
public class CodeUtils {
// code不够6位数时,用来补0的
private String [] patch = {"000000","00000","0000","000","00","0",""};
public String generator(String tele){
// 获取tele的 hash值
int hash = tele.hashCode();
// 设置加密码
int encryption = 20206666;
// 哈希异或加密码
long result = hash ^ encryption;
// 获取当前的时间
long nowTime = System.currentTimeMillis();
// 再次执行一下异或
result = result ^ nowTime;
// 取6位数
long code = result % 1000000;
code = code < 0 ? -code : code;
String codeStr = code + "";
int len = codeStr.length();
return patch[len] + codeStr;
}
// 把这个获取缓存中的code放在一个bean里:就会走spring容器管理的bean的操作
// 因为CodeUtils实体类使用了@Component注解,所以它是一个受spring管理的bean
// 又因为在业务层的实现类( CodeServiceImpl)里面,使用@Autowired自动配置CodeUtils,
// 所以在( CodeServiceImpl)中使用CodeUtiles调用它里面的方法get的过程中,
// CodeUtiles按照bean的形式加载,方法上面的注解生效,就能从缓存中得到数据了。
// 如果这个@Cacheable不运行,那么下面的get方法就是return null
//(作为ioc容器管理的对象,其中的注解会生效,但如果不使用spring管理的bean对象就直接调用,那注解不会生效
//
// 要想要这个注解运行的核心是:这个注解得被spring容器加载到,
// 这个@Cacheable注解的功能是在引导类添加的@EnableCaching(开启缓存功能)提供的
@Cacheable(value = "saveCode",key="#tele")
public String get(String tele){
return null;
}
}
对于校验验证码的功能应该放入(受spring管理的bean对象里面)中进行,比如这里的工具类,因为如果想获取code的值,spring容器加载注解@Cacheable,才能获取到缓存里面的验证码。(如果上面的get方法没有按照bean的形式加载方法,那get方法上面的@Cacheable(value = “saveCode”,key=“#tele”) 就不会生效)
为啥放在业务层实现类上不行呢,它有@Service代表它也受spring容器管理,因为get方法是在业务实现类上被调用的,是用this调用的,只是走普通方法的调用,没有经过spring容器,因此@Cacheable也没有启动,所以返回null(同一个bean内部调用方法不经过spring)
下面这个才是说的比较清晰的理解:
当一个方法调用同一个类中另一个有注解的方法时,注解是不会生效的。这是因为Spring在扫描bean时,会为带有@Async注解的方法生成一个代理类,代理类负责增加异步作用。但是,如果有注解的方法被同一个类中的其他方法调用,调用并没有通过代理类,而是直接通过原来的那个bean,所以注解就不会生效。
- 步骤⑤:定义验证码功能的业务层接口与实现类
public interface CodeService {
// 根据传进来的电话号码,用相应的算法,得出Code作为返回值
public String sendCode(String tele);
// 校验code
public boolean checkCode(PhoneCode smsCode);
}
@Service
public class CodeServiceImpl implements CodeService {
@Autowired
private CodeUtils codeUtils;
// @Cacheable(value = "saveCode", key = "#tele")
@CachePut(value = "saveCode", key = "#tele")
public String sendCode(String tele) {
// 生成验证码
String code = codeUtils.generator(tele);
return code;
}
public boolean checkCode(PhoneCode pCode) {
//取出内存中的验证码 与 传递过来的验证码比对,如果相同,返回true
// 传过来的值
String code = pCode.getCode();
// 获取内存中的值(基于bean进行调用)
String cacheCode = codeUtils.get(pCode.getTele());
return code.equals(cacheCode);
}
}
获取验证码后,当验证码失效时必须重新获取验证码,因此在获取验证码的功能上不能使用@Cacheable注解,@Cacheable注解是缓存中没有值则放入值,缓存中有值则取值。此处的功能仅仅是生成验证码并放入缓存,并不具有从缓存中取值的功能,因此不能使用@Cacheable注解,应该使用仅具有向缓存中保存数据的功能,使用@CachePut注解即可。
- 步骤⑥:定义验证码功能的web层接口,一个方法用于提供手机号获取验证码,一个方法用于提供(Code对象,即手机号和验证码)进行校验
@RestController
@RequestMapping("/code")
public class CodeController {
@Autowired
private CodeService codeService;
@GetMapping
public String getCode(String tele){
String code = codeService.sendCode(tele);
return code;
}
@PostMapping
public boolean checkCode(PhoneCode code){
return codeService.checkCode(code);
}
}
SpringBoot整合Ehcache缓存
-
Ehcache是一种缓存技术,使用springboot整合Ehcache其实就是变更一下缓存技术的实现方式,代码都和上面的一样。
-
步骤①:导入Ehcache的坐标
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
说明:这里为什么不是导入Ehcache的starter,而是导入技术坐标呢?
其实springboot整合缓存技术做的是通用格式,不管你整合哪种缓存技术,只是实现变化了,操作方式是一样的。这也体现出springboot技术的优点,统一同类技术的整合方式。
- 步骤②:配置缓存技术实现使用Ehcache
spring:
cache:
type: ehcache #配置缓存的类型type为ehcache
ehcache:
config: classpath:ehcache.xml # 指定ehcache的配置文件,文件名如果使用ehcache.xml则不配置也可以(默认),更改了文件名才需要指定
配置缓存的类型type为ehcache,此处需要说明一下,当前springboot可以整合的缓存技术中包含有ehcach,所以可以这样书写。type的值必须是springboot可以整合的缓存技术中包含有的
由于ehcache的配置有独立的配置文件格式,因此还需要指定ehcache的配置文件,以便于读取相应配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="D:\ehcache" /> <!--存储位置 -->
<!--默认缓存策略 -->
<!-- external:是否永久存在,设置为true则不会被清除,此时与timeout冲突,通常设置为false-->
<!-- diskPersistent:是否启用磁盘持久化-->
<!-- maxElementsInMemory:最大缓存数量-->
<!-- overflowToDisk:超过最大缓存数量是否持久化到磁盘-->
<!-- timeToIdleSeconds:最大不活动间隔,设置过长缓存容易溢出,设置过短无效果,可用于记录时效性数据,例如验证码-->
<!-- timeToLiveSeconds:最大存活时间-->
<!-- memoryStoreEvictionPolicy:缓存清除策略-->
<!-- 表示默认缓存 -->
<defaultCache
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="60"
timeToLiveSeconds="60"
memoryStoreEvictionPolicy="LRU" />
<!--@CachePut(value = "saveCode", key = "#tele"),这里配置了value值,就是指定使用哪个缓存,需要对该缓存进行配置
如果没有配置value属性,就使用上面的默认缓存
-->
<!-- name属性用来区分是哪一个缓存 -->
<cache
name="saveCode"
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="10"
timeToLiveSeconds="10"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
按照这上面的步骤做完后,直接就可以运行上面的手机验证码案例(就是改变了配置和缓存供应商,代码都不需要改变)
注意上面的验证码设置的缓存,设置了数据保存的位置是saveCode
@CachePut(value = "saveCode", key = "#tele")
public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
return code;
}
//这个设定需要保障ehcache中有一个缓存空间名称叫做smsCode的配置,前后要统一。
// 在企业开发过程中,通过设置不同名称的cache来设定不同的缓存策略,应用于不同的缓存数据。
总结:可以发现一点,原始代码没有任何修改,仅仅是加了一组配置就可以变更缓存供应商了。
数据淘汰策略
- LRU与LFU的区别:
-
- volatile-lru:挑选最近最少使用的数据淘汰(长时间不用的淘汰)
-
- volatile-lfu:挑选最近使用次数最少的数据淘汰(一段时间之内使用最少的淘汰)
-
- volatile-ttl:挑选将要过期的数据淘汰
-
- volatile-random:任意选择数据淘汰
-
Springboot整合Redis缓存
- springboot整合Redis和上面整合ehcache几乎一样:加依赖坐标,改缓存实现类型为redis,做redis的配置。差别之处只有一点,redis的配置可以在yml文件中直接进行配置,无需制作独立的配置文件。
步骤①:导入redis的坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
步骤②:配置缓存技术实现使用redis
spring:
redis:
host: localhost
port: 6379
cache:
type: redis
如果需要对redis作为缓存进行配置,注意不是对原始的redis进行配置,而是配置redis作为缓存使用相关的配置,隶属于spring.cache.redis节点下。(还有就是运行的时候记得打开Redis服务器。
spring:
redis:
host: localhost
port: 6379
cache:
type: redis
redis:
use-key-prefix: false
key-prefix: sms_
cache-null-values: false
time-to-live: 10s
SpringBoot整合Memcached缓存
- springboot并没有支持使用memcached作为其缓存解决方案,也就是说在type属性中没有memcached的配置选项,在整合之前先安装memcached。
Memcache的下载和安装
- windows版安装包下载地址:https://www.runoob.com/memcached/window-install-memcached.html。复制链接:根据页面的介绍进行下载
下载的安装包是解压缩就能使用的zip文件,解压缩完毕后会得到如下文件
memcached的使用
- 由于memcached未被springboot收录为缓存解决方案,因此使用memcached需要通过手工硬编码的方式来使用,需要自己写。
- memcached目前提供有三种客户端技术,分别是Memcached Client for Java、SpyMemcached和Xmemcached,其中性能指标各方面最好的客户端是Xmemcached.
步骤①:导入xmemcached的坐标
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.7</version>
</dependency>
步骤②:配置memcached,制作memcached的配置类
对于服务器的配置如果使用硬编码,就会写死到了代码。对此,应该将此数据提取出来,做成独立的配置属性。(在制作配置类之前在配置文件自定义对应的数据配置)
- 定义配置类,加载必要的配置属性,读取配置文件中memcached节点信息
@Component
@ConfigurationProperties(prefix = "memcached")
@Data
public class XMemcachedProperties {
private String servers;
private int poolSize;
private long opTimeout;
}
- 定义memcached节点信息
memcached:
servers: localhost:11211
poolSize: 10
opTimeout: 3000
- 在memcached配置类中加载信息
@Configuration
public class XMemcachedConfig {
@Autowired
private XMemcachedProperties props;
@Bean
public MemcachedClient getMemcachedClient() throws IOException {
MemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder(props.getServers());
memcachedClientBuilder.setConnectionPoolSize(props.getPoolSize());
memcachedClientBuilder.setOpTimeout(props.getOpTimeout());
MemcachedClient memcachedClient = memcachedClientBuilder.build();
return memcachedClient;
}
}
memcached默认对外服务端口11211。
步骤③:使用xmemcached客户端操作缓存,注入MemcachedClient对象
@Service
public class SMSCodeServiceImpl implements SMSCodeService {
@Autowired
private CodeUtils codeUtils;
@Autowired
private MemcachedClient memcachedClient;
public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
try {
memcachedClient.set(tele,10,code);
} catch (Exception e) {
e.printStackTrace();
}
return code;
}
public boolean checkCode(SMSCode smsCode) {
String code = null;
try {
code = memcachedClient.get(smsCode.getTele()).toString();
} catch (Exception e) {
e.printStackTrace();
}
return smsCode.getCode().equals(code);
}
}
SpringBoot整合jetcache缓存
- jetcache严格意义上来说,并不是一个缓存解决方案,只能说他算是一个缓存框架,然后把别的缓存放到jetcache中管理,这样就可以支持AB缓存(本地缓存和远程缓存)一起用了
- jetcache参考了springboot整合缓存的思想,整体技术使用方式和springboot的缓存解决方案思想非常类似。
- jetCache对SpringCache进行了封装,在原有的功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能。
- jetcache并不是随便拿两个缓存都能拼到一起去的。目前jetcache支持的缓存方案本地缓存支持两种,远程缓存支持两种,分别如下:
- 本地缓存(Local)
- LinkedHashMap
- Caffeine
- 远程缓存(Remote)
- Redis
- Tair
- 本地缓存(Local)
纯远程缓存的使用(默认是redis)
- 步骤①:导入springboot整合jetcache对应的坐标starter,当前坐标
默认使用的远程方案是redis
:
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.6.2</version>
</dependency>
- 步骤②:远程方案基本配置
# 定义端口
server:
port: 80
# 缓存的配置
jetcache:
remote: # 选择本地(local)的或者远程(remote)的
default: # default是一个名称,可以自己设置,但是在创建缓存时指定参数area,声明使用对应缓存即可
type: redis # 选定缓存方案,这里默认就是redis
host: localhost # 地址
port: 6379 # 端口配置
keyConvertor: fastjson # 指定key转换为字符串的工具(对象需要转换为json字符串)
valueEncode: java # 这两个value是设定存储的值做转换的时候,转换成什么样的数据
valueDecoder: java
poolConfig: # 这个配置必须配置
maxTotal: 50 # 最大连接数
ssm: # 名为ssm的缓存
type: redis
host: localhost
port: 6379
keyConvertor: fastjson
valueEncode: java
valueDecoder: java
poolConfig:
maxTotal: 50
其中poolConfig是必配项,否则会报错
- 步骤③:启用缓存,在引导类上方标注注解@EnableCreateCacheAnnotation配置springboot程序中可以使用注解的形式创建缓存
@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
public class jetcacheApplication {
public static void main(String[] args) {
SpringApplication.run(jetcacheApplication.class,args);
}
}
- 步骤④:创建缓存对象Cache,并使用注解@CreateCache标记当前缓存的信息,然后使用Cache对象的API操作缓存,put写缓存,get读缓存。
@Service
public class CodeServiceImpl implements CodeService {
@Autowired
private CodeUtils codeUtils;
// @CreateCache(area="ssm",name="jetCache_",expire = 10,timeUnit = TimeUnit.SECONDS)
@CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS)
// name属性表示缓存里面的key名的前缀
// expire属性是设置过期时间
// timeUnit属性设置过期时间的单位,默认就是秒
// area属性指定使用哪个缓存(这个缓存必须在配置文件里面有配置)
private Cache<String ,String> jetCache;
@Override
public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
// 放进缓存里面
jetCache.put(tele,code);
return code;
}
@Override
public boolean checkCode(Code smsCode) {
// 从缓存里面取出来
String code = jetCache.get(smsCode.getTele());
return smsCode.getCode().equals(code);
}
通过上述jetcache使用远程方案连接redis可以看出,jetcache操作缓存时的接口操作更符合开发者习惯,使用缓存就先获取缓存对象Cache,放数据进去就是put,取数据出来就是get,更加简单易懂。并且jetcache操作缓存时,可以为某个缓存对象设置过期时间,将同类型的数据放入缓存中,方便有效周期的管理。
纯本地缓存的使用
- 和上面的远程缓存的使用是一样的,就是配置不一样
步骤①:导入springboot整合jetcache对应的坐标starter
步骤②:本地缓存基本配置
jetcache:
local: # 把这里的local改成remote就是远程缓存了,但是下面的type属性需要更改,因为远程缓存和本地缓存使用的技术不一样
default:
type: linkedhashmap # 选用缓存方案
keyConvertor: fastjson
为了加速数据获取时key的匹配速度,jetcache要求指定key的类型转换器。简单说就是,如果你给了一个Object作为key的话,我先用key的类型转换器给转换成字符串,然后再保存。等到获取数据时,仍然是先使用给定的Object转换成字符串,然后根据字符串匹配。由于jetcache是阿里的技术,这里推荐key的类型转换器使用阿里的fastjson。
步骤③:启用缓存
步骤④:创建缓存对象Cache时,标注当前使用本地缓存
@Service
public class CodeServiceImpl implements CodeService {
@Autowired
private CodeUtils codeUtils;
// name属性表示缓存里面的key名的前缀
// expire属性是设置过期时间
// timeUnit属性设置过期时间的单位,默认就是秒
// area属性指定使用哪个缓存(这个缓存必须在配置文件里面有配置)
@CreateCache(area="ssm",name="jetCache_",expire = 100,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.LOCAL) //cacheType指定使用哪个缓存,默认是远程
private Cache<String ,String> jetCache;
@Override
public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
// 放进缓存里面
jetCache.put(tele,code);
return code;
}
@Override
public boolean checkCode(Code smsCode) {
// 从缓存里面取出来
String code = jetCache.get(smsCode.getTele());
return smsCode.getCode().equals(code);
}
}
本地和远程一起使用的配置
jetcache:
remote: # 选择本地(local)的或者远程(remote)的
default: # default是一个名称,可以自己设置,但是在创建缓存时指定参数area,声明使用对应缓存即可
type: redis # 选定缓存方案,这里默认就是redis
host: localhost # 地址
port: 6379 # 端口配置
keyConvertor: fastjson # 指定key转换为字符串的工具(对象需要转换为json字符串)
valueEncode: java # 这两个value是设定存储的值做转换的时候,转换成什么样的数据,就是说进入缓存,和从缓存出来的格式都保证一致(比如把对象序列化放进缓存里面,再从缓存里面把数据反序列化出来)
valueDecoder: java
poolConfig: # 这个配置必须配置
maxTotal: 50 # 最大连接数
local:
ssm: # 名为ssm的缓存
type: linkedhashmap
host: localhost
port: 6379
keyConvertor: fastjson
valueEncode: java
valueDecoder: java
poolConfig:
maxTotal: 50
在创建缓存的时候,配置cacheType为BOTH即则本地缓存与远程缓存同时使用。
@Service
public class CodeServiceImpl implements CodeService {
@CreateCache(name="jetCache_",expire = 1000,timeUnit = TimeUnit.SECONDS,cacheType = CacheType.BOTH)
private Cache<String ,String> jetCache;
}
cacheType如果不进行配置,默认值是REMOTE,即仅使用远程缓存方案。关于jetcache的配置,参考以下信息
属性 | 默认值 | 说明 |
---|---|---|
jetcache.statIntervalMinutes | 0 | 统计间隔,0表示不统计 |
jetcache.hiddenPackages | 无 | 自动生成name时,隐藏指定的包名前缀 |
jetcache.[local|remote].${area}.type | 无 | 缓存类型,本地支持linkedhashmap、caffeine,远程支持redis、tair |
jetcache.[local|remote].${area}.keyConvertor | 无 | key转换器,当前仅支持fastjson |
jetcache.[local|remote].${area}.valueEncoder | java | 仅remote类型的缓存需要指定,可选java和kryo |
jetcache.[local|remote].${area}.valueDecoder | java | 仅remote类型的缓存需要指定,可选java和kryo |
jetcache.[local|remote].${area}.limit | 100 | 仅local类型的缓存需要指定,缓存实例最大元素数 |
jetcache.[local|remote].${area}.expireAfterWriteInMillis | 无穷大 | 默认过期时间,毫秒单位 |
jetcache.local.${area}.expireAfterAccessInMillis | 0 | 仅local类型的缓存有效,毫秒单位,最大不活动间隔 |
以上方案仅支持手工控制缓存,但是springcache方案中的方法缓存特别好用,给一个方法添加一个注解,方法就会自动使用缓存。jetcache也提供了对应的功能,即方法缓存。
方法缓存
-
jetcache提供了方法缓存方案,只不过名称变更了而已。在对应的操作接口上方使用注解@Cached即可
-
步骤①:导入springboot整合jetcache对应的坐标starter
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.6.2</version>
</dependency>
- 步骤②:配置缓存
jetcache:
local: # 本地缓存的配置(可以配置多个,名称要不一样)
default: # 缓存的名称(自定义)
type: linkedhashmap
keyConvertor: fastjson
remote: # 远程缓存的配置(可以配置多个,名称要不一样)
default: # 缓存的名称(自定义)
type: redis
host: localhost
port: 6379
keyConvertor: fastjson
valueEncode: java
valueDecode: java
poolConfig:
maxTotal: 50
由于redis缓存中不支持保存对象,因此需要对redis设置当Object类型数据进入到redis中时如何进行类型转换。需要配置keyConvertor表示key的类型转换方式,同时标注value的转换类型方式,值进入redis时是java类型,标注valueEncode为java,值从redis中读取时转换成java,标注valueDecode为java。
注意,为了实现Object类型的值进出redis,需要保障进出redis的Object类型的数据必须实现序列化接口:Serializable。
@Data
public class Book implements Serializable {
private Integer id;
private String type;
private String name;
private String description;
}
- 步骤③:启用缓存时开启方法缓存功能,并配置basePackages,说明在哪些包中开启方法缓存
@SpringBootApplication
//jetcache启用缓存的主开关
@EnableCreateCacheAnnotation
//开启方法注解缓存
@EnableMethodCache(basePackages = "com.knife")
public class Springboot20JetCacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot20JetCacheApplication.class, args);
}
}
- 步骤④:使用注解@Cached标注当前方法使用缓存
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
@Override
@Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
public Book getById(Integer id) {
return bookDao.selectById(id);
}
}
远程方案的数据同步
- 由于远程方案中redis保存的数据可以被多个客户端共享,这就存在了数据同步问题。jetcache提供了3个注解解决此问题,分别在更新、删除操作时同步缓存数据,和读取缓存时定时刷新数据
更新缓存
@CacheUpdate(name="book_",key="#book.id",value="#book")
public boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
删除缓存
@CacheInvalidate(name="book_",key = "#id")
public boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
定时刷新缓存
@Cached(name="book_",key="#id",expire = 3600,cacheType = CacheType.REMOTE)
@CacheRefresh(refresh = 5) //这里表示5秒刷新一次
public Book getById(Integer id) {
return bookDao.selectById(id);
}
数据报表
jetcache还提供有简单的数据报表功能,帮助开发者快速查看缓存命中信息,只需要添加一个配置即可
jetcache:
statIntervalMinutes: 1 # 数据报表功能
local: # 本地缓存的配置(可以配置多个,名称要不一样)
default: # 缓存的名称(自定义)
type: linkedhashmap
keyConvertor: fastjson
remote: # 远程缓存的配置(可以配置多个,名称要不一样)
default: # 缓存的名称(自定义)
type: redis
host: localhost
port: 6379
keyConvertor: fastjson
valueEncode: java
valueDecode: java
poolConfig:
maxTotal: 50
设置后,每1分钟在控制台输出缓存数据命中信息
[DefaultExecutor] c.alicp.jetcache.support.StatInfoLogger : jetcache stat from 2022-02-28 09:32:15,892 to 2022-02-28 09:33:00,003
cache | qps| rate| get| hit| fail| expire| avgLoadTime| maxLoadTime
---------+-------+-------+------+-------+-------+---------+--------------+--------------
book_ | 0.66| 75.86%| 29| 22| 0| 0| 28.0| 188
---------+-------+-------+------+-------+-------+---------+--------------+--------------
- 总结
-
- jetcache是一个类似于springcache的缓存解决方案,自身不具有缓存功能,它提供有本地缓存与远程缓存多级共同使用的缓存解决方案
-
- jetcache提供的缓存解决方案受限于目前支持的方案,本地缓存支持两种,远程缓存支持两种
-
- 注意数据进入远程缓存时的类型转换问题
-
- jetcache提供方法缓存,并提供了对应的缓存更新与刷新功能
-
- jetcache提供有简单的缓存信息命中报表方便开发者即时监控缓存数据命中情况
-
j2cache缓存
- jscache是一个缓存整合框架,可以提供缓存的整合方案,使各种缓存搭配使用,自身不提供缓存功能。
springboot整合j2cache
- 步骤①:导入j2cache、redis、ehcache坐标
<!-- j2cache需要的依赖-->
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-core</artifactId>
<version>2.8.4-release</version>
</dependency>
<!-- j2cache的starter中默认包含了redis坐标,官方推荐使用redis作为二级缓存,因此此处无需导入redis坐标-->
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-spring-boot2-starter</artifactId>
<version>2.8.0-release</version>
</dependency>
<!-- ehcache的依赖-->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
- 步骤②:配置一级与二级缓存,并配置一二级缓存间数据传递方式,配置书写在名称为j2cache.properties的文件中。如果使用ehcache还需要单独添加ehcache的配置文件
application.yml文件
server:
port: 80
j2cache:
config-location: j2cache.properties
ehcache.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="D:\ehcache" />
<!--默认缓存策略 -->
<!-- external:是否永久存在,设置为true则不会被清除,此时与timeout冲突,通常设置为false-->
<!-- diskPersistent:是否启用磁盘持久化-->
<!-- maxElementsInMemory:最大缓存数量-->
<!-- overflowToDisk:超过最大缓存数量是否持久化到磁盘-->
<!-- timeToIdleSeconds:最大不活动间隔,设置过长缓存容易溢出,设置过短无效果,可用于记录时效性数据,例如验证码-->
<!-- timeToLiveSeconds:最大存活时间-->
<!-- memoryStoreEvictionPolicy:缓存清除策略-->
<defaultCache
eternal="false"
diskPersistent="false"
maxElementsInMemory="1000"
overflowToDisk="false"
timeToIdleSeconds="60"
timeToLiveSeconds="60"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
j2ehcache.properties文件
# 1级缓存的配置
# j2cache的一级缓存供应商是ehcache
j2cache.L1.provider_class=ehcache
# ehcache的配置文件是ehcache.xml
ehcache.config.Xml= ehcache.xml
# 配置是否开启二级缓存,默认为true
j2cache.l2-cache-open = true
# 2 级缓存的配置
j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
j2cache.L2.config_section = redis
redis.hosts = localhost:6379
# 选择redis的服务器,single表示使用单极模式的服务器
redis.mode = single
# key的命名前缀
redis.namespace = j2cache
# 1级缓存中的数据如何到达二级缓存
j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
此处配置不能乱配置,需要参照官方给出的配置说明进行。例如1级供应商选择ehcache,供应商名称仅仅是一个ehcache,但是2级供应商选择redis时要写专用的Spring整合Redis的供应商类名SpringRedisProvider,而且这个名称并不是所有的redis包中能提供的,也不是spring包中提供的。因此配置j2cache必须参照官方文档配置,而且还要去找专用的整合包,导入对应坐标才可以使用。
一级与二级缓存最重要的一个配置就是两者之间的数据沟通方式,此类配置也不是随意配置的,并且不同的缓存解决方案提供的数据沟通方式差异化很大,需要查询官方文档进行设置。
步骤③:使用缓存
@Service
public class CodeServiceImpl implements CodeService {
@Autowired
private CodeUtils codeUtils;
//定义一个缓存的对象
@Autowired
private CacheChannel cacheChannel;
@Override
public String sendCode(String tele) {
String code = codeUtils.generator(tele);
// 把数据放进缓存里面
cacheChannel.set("sms",tele,code);
return code;
}
@Override
public boolean checkCode(Code smsCode) {
// 从缓存中获取数据
String code = cacheChannel.get("sms",smsCode.getTele()).asString();
return smsCode.getCode().equals(code);
}
}
j2cache的使用和jetcache比较类似,但是无需开启使用的开关,直接定义缓存对象即可使用,缓存对象名CacheChannel。
j2cache-core核心包中的j2cache.properties文件
#J2Cache configuration
#########################################
# Cache Broadcast Method
# values:
# jgroups -> use jgroups's multicast
# redis -> use redis publish/subscribe mechanism (using jedis)
# lettuce -> use redis publish/subscribe mechanism (using lettuce, Recommend)
# rabbitmq -> use RabbitMQ publisher/consumer mechanism
# rocketmq -> use RocketMQ publisher/consumer mechanism
# none -> don't notify the other nodes in cluster
# xx.xxxx.xxxx.Xxxxx your own cache broadcast policy classname that implement net.oschina.j2cache.cluster.ClusterPolicy
#########################################
j2cache.broadcast = redis
# jgroups properties
jgroups.channel.name = j2cache
jgroups.configXml = /network.xml
# RabbitMQ properties
rabbitmq.exchange = j2cache
rabbitmq.host = localhost
rabbitmq.port = 5672
rabbitmq.username = guest
rabbitmq.password = guest
# RocketMQ properties
rocketmq.name = j2cache
rocketmq.topic = j2cache
# use ; to split multi hosts
rocketmq.hosts = 127.0.0.1:9876
#########################################
# Level 1&2 provider
# values:
# none -> disable this level cache
# ehcache -> use ehcache2 as level 1 cache
# ehcache3 -> use ehcache3 as level 1 cache
# caffeine -> use caffeine as level 1 cache(only in memory)
# redis -> use redis as level 2 cache (using jedis)
# lettuce -> use redis as level 2 cache (using lettuce)
# readonly-redis -> use redis as level 2 cache ,but never write data to it. if use this provider, you must uncomment `j2cache.L2.config_section` to make the redis configurations available.
# memcached -> use memcached as level 2 cache (xmemcached),
# [classname] -> use custom provider
#########################################
j2cache.L1.provider_class = caffeine
j2cache.L2.provider_class = redis
# When L2 provider isn't `redis`, using `L2.config_section = redis` to read redis configurations
# j2cache.L2.config_section = redis
# Enable/Disable ttl in redis cache data (if disabled, the object in redis will never expire, default:true)
# NOTICE: redis hash mode (redis.storage = hash) do not support this feature)
j2cache.sync_ttl_to_redis = true
# Whether to cache null objects by default (default false)
j2cache.default_cache_null_object = true
#########################################
# Cache Serialization Provider
# values:
# fst -> using fast-serialization (recommend)
# kryo -> using kryo serialization
# json -> using fst's json serialization (testing)
# fastjson -> using fastjson serialization (embed non-static class not support)
# java -> java standard
# fse -> using fse serialization
# [classname implements Serializer]
#########################################
j2cache.serialization = json
#json.map.person = net.oschina.j2cache.demo.Person
#########################################
# Ehcache configuration
#########################################
# ehcache.configXml = /ehcache.xml
# ehcache3.configXml = /ehcache3.xml
# ehcache3.defaultHeapSize = 1000
#########################################
# Caffeine configuration
# caffeine.region.[name] = size, xxxx[s|m|h|d]
#
#########################################
caffeine.properties = /caffeine.properties
#########################################
# Redis connection configuration
#########################################
#########################################
# Redis Cluster Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# cluster -> cluster servers (\u6570\u636e\u5e93\u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528 database = 0\uff09
# sharded -> sharded servers (\u5bc6\u7801\u3001\u6570\u636e\u5e93\u5fc5\u987b\u5728 hosts \u4e2d\u6307\u5b9a\uff0c\u4e14\u8fde\u63a5\u6c60\u914d\u7f6e\u65e0\u6548 ; redis://user:password@127.0.0.1:6379/0\uff09
#
#########################################
redis.mode = single
#redis storage mode (generic|hash)
redis.storage = generic
## redis pub/sub channel name
redis.channel = j2cache
## redis pub/sub server (using redis.hosts when empty)
redis.channel.host =
#cluster name just for sharded
redis.cluster_name = j2cache
## redis cache namespace optional, default[empty]
redis.namespace =
## redis command scan parameter count, default[1000]
#redis.scanCount = 1000
## connection
# Separate multiple redis nodes with commas, such as 192.168.0.10:6379,192.168.0.11:6379,192.168.0.12:6379
redis.hosts = 127.0.0.1:6379
redis.timeout = 2000
redis.password =
redis.database = 0
redis.ssl = false
## redis pool properties
redis.maxTotal = 100
redis.maxIdle = 10
redis.maxWaitMillis = 5000
redis.minEvictableIdleTimeMillis = 60000
redis.minIdle = 1
redis.numTestsPerEvictionRun = 10
redis.lifo = false
redis.softMinEvictableIdleTimeMillis = 10
redis.testOnBorrow = true
redis.testOnReturn = false
redis.testWhileIdle = true
redis.timeBetweenEvictionRunsMillis = 300000
redis.blockWhenExhausted = false
redis.jmxEnabled = false
#########################################
# Lettuce scheme
#
# redis -> single redis server
# rediss -> single redis server with ssl
# redis-sentinel -> redis sentinel
# redis-cluster -> cluster servers
#
#########################################
#########################################
# Lettuce Mode
#
# single -> single redis server
# sentinel -> master-slaves servers
# cluster -> cluster servers (\u6570\u636e\u5e93\u914d\u7f6e\u65e0\u6548\uff0c\u4f7f\u7528 database = 0\uff09
# sharded -> sharded servers (\u5bc6\u7801\u3001\u6570\u636e\u5e93\u5fc5\u987b\u5728 hosts \u4e2d\u6307\u5b9a\uff0c\u4e14\u8fde\u63a5\u6c60\u914d\u7f6e\u65e0\u6548 ; redis://user:password@127.0.0.1:6379/0\uff09
#
#########################################
## redis command scan parameter count, default[1000]
#lettuce.scanCount = 1000
lettuce.mode = single
lettuce.namespace =
lettuce.storage = hash
lettuce.channel = j2cache
lettuce.scheme = redis
lettuce.hosts = 127.0.0.1:6379
lettuce.password =
lettuce.database = 0
lettuce.sentinelMasterId =
lettuce.maxTotal = 100
lettuce.maxIdle = 10
lettuce.minIdle = 10
# timeout in milliseconds
lettuce.timeout = 10000
# redis cluster topology refresh interval in milliseconds
lettuce.clusterTopologyRefresh = 3000
#########################################
# memcached server configurations
# refer to https://gitee.com/mirrors/XMemcached
#########################################
memcached.servers = 127.0.0.1:11211
memcached.username =
memcached.password =
memcached.connectionPoolSize = 10
memcached.connectTimeout = 1000
memcached.failureMode = false
memcached.healSessionInterval = 1000
memcached.maxQueuedNoReplyOperations = 100
memcached.opTimeout = 100
memcached.sanitizeKeys = false