缓存优化(SpringCache、XXL-JOB)
目录
首页优化
一、SpringCache:
1.@EnableCaching
2.@CachePut
3.@Cacheable
4.@CacheEvict
5.如何设置缓存时间
二、正式优化
准备工作:
开通区域列表:
查询区域时添加缓存:
启用、禁用区域时清理缓存:
首页服务列表:
查询首页服务列表添加缓存:
禁用区域时删除缓存:
三、XXL-JOB
1)编写代码端
2)配置控制端
定时更新缓存:
四、实战
首页优化
我们一般把小程序、网站、app的首页称为系统的门户,门户有下面特点:
-
门户的信息是动态的,但是更新频率不会非常高
-
门户作为系统的入口,访问频率会非常高
针对系统中访问频率很高的页面,我们常见的优化方案如下:
-
将页面做页面静态化处理
-
如果静态化之后依旧无法满足需求,可以将静态资源转移到CDN服务器
-
页面上变动的内容可以异步请求后端服务器获取数据,后端可以添加缓存来减轻数据库的压力
-
数据库方面可以通过合理的存储引擎选择、表字段设计、索引设计等进一步提高查询效率
一、SpringCache:
目前业界最主流的缓存技术就是Redis,而Java操作Redis目前有两种主流方案:
-
RedisTemplate:Spring提供的一个对象,内置了大量操作redis的方法,使用起来比较灵活
-
SpringCache:Spring提供的一套注解,可以基于方法级别对redis进行操作,使用起来非常简单
两种方式各有自己的优缺点,推荐大家以SpringCache为主,特殊情况下再采用RedisTemplate
SpringCache常用的注解有下面几个:
-
@EnableCaching:标注在启动类上,用来开启缓存注解功能
-
@Cacheable:主要标注在查询方法上,表示先从缓存中查询数据,如果有直接返回,如果没有再从数据库查询,返回的同时保存到缓存中一份
-
@CacheEvict:主要标注在新增、修改、删除方法上,用于将一条或多条数据从缓存中删除
-
@CachePut:用于更新缓存,它可以将方法的返回值放到缓存中
SpringCache支持多种缓存提供者,如Redis、EhCache、Caffeine等,我们可以自由切换他们
甚至我们可以不去提供任何的缓存服务器,SpringCache依旧可以使用,他会使用内存做为缓存提供者
1.@EnableCaching
@EnableCaching注解标注在启动类或者配置类上,用于开启基于注解的缓存功能

2.@CachePut
@CachePut:用于将方法的返回值放到缓存中,支持两个属性:
-
value: 缓存模块名称,每个模块下可以有很多key
-
key: 缓存的唯一标识,要注意的是它仅仅在一个value下保持唯一 也就是说value+key才能保证全局唯一



3.@Cacheable
@Cacheable:主要标注在查询方法上,表示先从缓存中查询数据,如果有直接返回,如果没有再从数据库查询,返回的同时保存到缓存中一份

4.@CacheEvict
@CacheEvict:用于删除缓存,将一条或多条数据从缓存中删除

5.如何设置缓存时间
默认情况下,使用@Cacheable或者@CachePut放入缓存中的数据都是永久生效的,如何设置缓存的过期时间呢?
在@Cacheable、@CachePut注解中有一个属性为cacheManager,表示缓存管理器,通过它可以设置缓存过期时间
-
创建一个配置类,然后向容器中放入一个RedisCacheManager
@Configuration public class SpringCacheConfig {@Beanpublic RedisCacheManager cacheManager30Minutes(RedisConnectionFactory connectionFactory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(30 * 60L));return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build();} } -
在@Cacheable注解中使用cacheManager引用刚刚放入容器中的缓存管理器
@Cacheable(value = "userCache",key = "#id",cacheManager = "cacheManager30Minutes")@GetMapping("/user/{id}")public User findById(@PathVariable Long id){return userMapper.getById(id);}

二、正式优化
接下来,我们为小程序首页添加缓存功能,需要添加的位置如下:
-
首页服务列表
-
热门服务列表(实战)
-
开通区域列表

准备工作:
-
在jzo2o-foundations工程的pom.xml中引入jzo2o-redis的依赖,它内置了大量Redis的配置;
该依赖包含了redis、SpringCache的依赖<dependency><groupId>com.jzo2o</groupId><artifactId>jzo2o-redis</artifactId> </dependency> -
在jzo2o-foundations工程的bootstrap.yml中引入redis的配置文件

-
在nacos配置shared-redis-cluster.yaml,开发环境使用redis单机环境

开通区域列表:
查询区域时添加缓存:
在查询开通区域列表的时候,先从缓存查询,如果有直接返回,没有再从数据库查询并存入缓存,这可以使用@Cacheable实现
找到查询开通区域列表的请求路径: /foundations/consumer/region/activeRegionList
定位到com.jzo2o.foundations.controller.consumer.RegionController.activeRegionList()
然后找到service方法添加如下注解(注意这里是在实现类):

启用、禁用区域时清理缓存:
当启用、禁用一个区域后,原来缓存的区域列表就不对了,需要删除它,可以使用@CacheEvict注解。找到启用、禁用区域的代码,修改如下:
//区域启用后,当前启用区域列表就发生变更,需要清空已启用的区域列表
@CacheEvict(value = RedisConstants.CacheName.JZ_CACHE, key = "'ACTIVE_REGIONS'")
@Override
public void active(Long id) {//.......
}
//禁用区域后, 当前启用区域列表就发生变更,需要清空已启用的区域列表
@CacheEvict(value = RedisConstants.CacheName.JZ_CACHE, key = "'ACTIVE_REGIONS'")
@Override
public void deactivate(Long id) {//...
}
首页服务列表:
当我们想设置多个@Cacheable、@CacheEvict注解时会发现不能重复注解,但是我们的业务又需要重复注解,这个时候就需要用到@Caching注解来一次性声明多个注解达成业务目标,比如接下来的两段代码:
查询首页服务列表添加缓存:
根据前端的请求路径定位到查询服务列表的方法,com.jzo2o.foundations.service.impl.ServeServiceImpl.firstPageServeList()
需要在此方法上添加一个@Cacheable注解,表示先从缓存查询,没有再从数据库查
但是我们这里做个优化,采用一个新的注解@Caching来实现,它可以将多个@Cacheable、@CacheEvict注解组合使用
@Caching(cacheable = {//返回数据为空,则缓存空值30分钟,这样可以避免缓存穿透@Cacheable(value = RedisConstants.CacheName.SERVE_ICON, key ="#regionId" ,unless ="#result.size() > 0",cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES),//返回值不为空,则永久缓存数据@Cacheable(value = RedisConstants.CacheName.SERVE_ICON, key ="#regionId" ,unless ="#result.size() == 0",cacheManager = RedisConstants.CacheManager.FOREVER)}
)
@Override
public List<ServeCategoryResDTO> firstPageServeList(Long regionId) {//...
}
在Cacheable注解中有两个属性可以指定条件进行缓存
condition:对方法参数进行判断,只缓存符合条件的
unless:对方法返回结果进行判断,只缓存不符合条件的
禁用区域时删除缓存:
当我们禁用一个区域的时候,除了删除开通区域列表,还需要删除首页服务列表、热门服务列表、服务分类列表、服务详情等
找到禁用区域代码(刚才上面那个),添加删除首页服务列表缓存的代码,如下:
@Caching(evict = {//删除开通区域列表@CacheEvict(value = RedisConstants.CacheName.JZ_CACHE, key = "'ACTIVE_REGIONS'"),//删除首页服务列表@CacheEvict(value = RedisConstants.CacheName.SERVE_ICON,key = "#id")}
)
@Override
public void deactivate(Long id) {//....
}
三、XXL-JOB
我们现在已经实现了在一些操作时添加、删除缓存,但是为了程序更具备健壮性,需要使用定时任务更新缓存
可以用来做定时任务的框架有很多,常见的有:
-
SpringTask:单节点定时任务框架,只能用在单体项目中
-
XXL-JOB:分布式定时任务框架,适用于单体项目和分布式项目
XXL-JOB是一个轻量级分布式任务调度平台,它的主要角色有两个:
-
调度中心:负责按照调度配置发出调度请求,主要职责为执行器管理、任务管理、监控运维、日志管理等
-
任务执行器:负责接收调度请求并执行任务逻辑;主要职责是执行任务、执行结果上报、日志服务等

XXL-JOB的使用主要就是分为两步:
-
在代码端编写定时任务要执行的代码
-
在控制端配置代码的执行时机和其他信息
1)编写代码端
-
导入准备好的演示工程
-
准备好要执行的任务代码

-
修改配置文件

2)配置控制端
-
配置执行器:在任务调度中心,点击进入"执行器管理"界面,添加执行器
AppName:执行器的唯一标识,不能重复
名称:执行的名称
注册方式:调度中心获取执行器地址的方式
机器地址:执行器的地址,当注册方式为手动时生效

-
配置任务:在调度中心新建任务,在任务管理->新建,填写以下内容
执行器:任务对应的执行器名称
调度类型:cron表达式,配置后面的表达式内容一起使用
JobHandler:任务执行器名称,需要对应到代码对任务代码上@XxlJob注解对应的名称

运行模式:BEAN模式:任务以JobHandler方式维护在执行器端;需要结合 "JobHandler" 属性使用
JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中JobHandler类“@XxlJob”注解的value值
执行参数:任务执行所需的参数

-
重启微服务,然后启动任务(或者选择执行一次),观察效果

定时更新缓存:
1.我们已经在虚拟机当中安装好了xxl-job的容器,只需要访问以下地址即可:http://192.168.101.68:8088/xxl-job-admin/;输入账号密码后登入:

2.接下来在foundations服务中引入依赖:
<dependency><groupId>com.jzo2o</groupId><artifactId>jzo2o-xxl-job</artifactId>
</dependency>
3.然后在bootstrap.yml文件下进行配置(具体配置被配置在了nacos当中):




4.编写任务代码:
编写代码,先删除开通区域、服务列表的缓存,再查询开通区域、服务列表进行缓存
package com.jzo2o.foundations.handler;import com.jzo2o.api.foundations.dto.response.RegionSimpleResDTO;
import com.jzo2o.foundations.constants.RedisConstants;
import com.jzo2o.foundations.service.IRegionService;
import com.jzo2o.foundations.service.IServeService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.List;//缓存同步类
@Component
@Slf4j
public class SpringCacheSyncHandler {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate IRegionService regionService;@Autowiredprivate IServeService serveService;@XxlJob("activeRegionCacheSync")public void activeRegionCacheSync() {log.info("=============开始更新开通区域列表缓存============");//1. 使用redisTemplate删除当前缓存中开通区域列表redisTemplate.delete("JZ_CACHE::ACTIVE_REGIONS");//2. 重新将开通区域列表添加到缓存List<RegionSimpleResDTO> regionSimpleResDTOS= regionService.queryActiveRegionListCache();log.info("=============开始更新首页服务列表缓存============");//3. 查询所有开通区域, 然后进行遍历for (RegionSimpleResDTO regionSimpleResDTO : regionSimpleResDTOS) {//获取到每个开通区域的idLong id = regionSimpleResDTO.getId();//根据区域id删除缓存数据redisTemplate.delete(RedisConstants.CacheName.SERVE_ICON + "::" + id);//重新查询,放入缓存serveService.firstPageServeList(id);}}
}
5.在调度中心配置任务:
进入任务管理,新增任务:


四、实战
在当前代码的基础上,按照下面要求,完成热门服务的缓存优化

1.找到ServeServiceImpl实体类下hotServeList方法并添加注解:

2.找到RegionServiceImpl下的deactivate(区域禁用)方法添加如下注解:

3.在定时任务代码中添加下图标识部分:

