java后端redis缓存缓存预热
java后端&redis缓存&缓存预热
缓存概述
- 缓存:从数据库(磁盘)中取数据到前端展示,速度很慢。为了提高速度可以使用缓存,即把数据预先查出来,放到一个更快读取的介质,比如内存,不用在从数据库很慢的查。
- 预加载缓存:定时更新缓存,为了不让第一次使用次此系统时数据加载很慢,可以在选一个用户访问较少的时间,定时加载缓存。
- 分布式锁:控制同一时间只用一台机器去执行定时任务,同一份代码不用在多个机器加载缓存。
- 缓存的实现
- Redis(分布式缓存)
- Memcached(分布式缓存)
- Etcd(云原生架构的分布式存储,公共的存储配置,扩容能力强)
- ehcache(单机)
- 本地缓存(java内存Map)
- Caffeine(java内存缓存性能高)
- Google Guava
单机缓存和分布式缓存
- 单机本地缓存:在同一个进程内的内存空间中缓存数据,数据读写都是在同一个进程内完成;
- 分布式缓存:一个独立部署的进程并且一般都是与应用进程部署在不同的机器,故需要通过网络来完成分布式缓存的数据读写操作的数据传输。
Redis
Redis定义:
NoSQL(非关系型数据库)
key-value键值对存储系统(区别于mysql的键值对数据库)
☆Redis的数据结构(5种基本+高级)
- 基本
- String字符串:name:“erha”
- List列表 : names:[“erha”,“erha02”](和数组的区别:列表的长度是不固定的,数组长度固定的)
- Set集合:names: [“erha”,“erha02”] (值不能重复)
- Hash哈希:nameAge:{“erha”:1,“haer02”:2}(键不能重复)
- Zset集合:names:{erha-99,erha02-100}(值从小到大排序)
- 高级
- bloomfilter(布隆过滤器,从大量的数据种快速过滤值,比如邮箱黑名单)
- geo(计算地理位置)
- hyperloglog(pv/uv)大数据统计
- pub/sub(发布订阅,类似消息队列)
- BitMap(把数据以10001110001的方式存储,存储大量可以压缩的值)
Redis在java中的实现方式(Spring Data Redis , Lettuce,Jedis 和Redisson)
不同的场景使用不同的实现方式,
1. 如果用spring框架,并且没有过多的定制化要求,可以使用spring Data Redis,最方便
2. 如果使用的不是Spring,并且追求简单,没有过高的性能要求,可以用jedis + jedis Pool
3. 如果不是spring,并且追求高性能高定制化可以使用Lettuce,支持异步,连接池
4. 如果项目是分布式的,需要用到一些分布式的特征(比如分布式锁,分布式集合),可以使用redisson
-
使用Spring Data Redis 实现
- spring Data:通用的数据访问框架,定义了一组增删改查的接口,操做mysql,redis,jpa等数据库,通过应入不同的数据库依赖实现对不同数据库的操作。
- 引入Redis依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.6.4</version> </dependency>
- 配置Redis地址
# Redis 配置 redis: port: 6379 host: localhost database: 0
- Redis增删改查示例【如何使用Redis依赖中的增删改查接口,使用redis提供的,操作redis的类和对象】
注:为了解决序列化问题,自编代码实现序列化配置//操作redis的类 @SpringBootTest public class RedisTest { //操作redis的对象 @Resource private RedisTemplate redisTemplate; @Test void test(){ ValueOperations valueOperations = redisTemplate.opsForValue(); //增 valueOperations.set("erhaString","fish"); valueOperations.set("erhaInt",1); valueOperations.set("erhaDouble",2.0); User user = new User(); user.setId(001); user.setUsername("testRedisString"); valueOperations.set("erhaUser",user); //查 Object erha = valueOperations.get("erhaString"); Assert.assertTrue("fish".equals((String) erha)); erha = valueOperations.get("erhaInt"); Assert.assertTrue(1== (int)erha); erha = valueOperations.get("erhaDouble"); Assert.assertTrue(2.0 == (Double) erha); erha = valueOperations.get("erhaUser"); System.out.println(valueOperations.get("erhaUser")); } }
@Configuration public class RedisTemplateConfig { @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){ RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); return redisTemplate; } }
- 在项目首页推荐出提取缓存
- 设计缓存key,不同用户看到的数据不同。systemId:moduleId:func:options(不和别人冲突)
lack:user:recommed:userId - 用缓存实现主页推荐
/** * 首页推荐接口(分页查询) * @param request * @return */ @GetMapping("/recommend") public BaseResponse<Page<User>> recommendUsers(long pageSize,long pageNum,HttpServletRequest request) { //先获取当前登录对象 User loginUser = userService.getLoginUser(request); ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue(); //判断有无缓存,有直接读缓存 String redisKey = String.format("lack:user:recommed:userId:%s",loginUser.getId()); Page<User> userPage =(Page<User>)redisTemplate.opsForValue().get(redisKey); if(userPage != null){ return ResultUtils.success(userPage); } //无缓存查数据库 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); userPage = userService.page(new Page<>(pageNum,pageSize),queryWrapper); //写缓存 try { //redis的内存不能无限增加,一定要设置过期时间 valueOperations.set(redisKey,userPage,30000, TimeUnit.MILLISECONDS); } catch (Exception e) { log.error("redis set key error",e); } return ResultUtils.success(userPage); }
- 设计缓存key,不同用户看到的数据不同。systemId:moduleId:func:options(不和别人冲突)
- 优化=>缓存预热
-
缓存预热解决的问题:
- 让第一个触发缓存的用户也能很快获取到推荐用户【提前把第一个用户需要的数据准备好,当用户一登录立即显示即可】
- 例如双十一这样可预期的场景,使用缓存预热降低数据库的压力,保护数据库。
-
缓存预热的优缺点:
- 优点:让用户始终访问很快
- 缺点:增加开发成本(额外开发);占用空间;如果预热时机和时间错了,有可能缓存的数据不正确。
-
缓存预热的意义:
- 提高用户的访问速度
- 保护数据库
-
缓存预热的注意点:
- 缓存空间不能太大,要预留给其他缓存空间
- 缓存数据的周期(每天一次)
-
实现缓存预热
- 手动模拟触发
- 定时触发(定时任务实现,每天刷新所有用户的推荐列表)
-
☆Spring Schedule(spring boot默认整合)
- 主类开启@EnableScheduling
- 给要定时执行的方法添加@Scheduled注解,编写方法
/** * 缓存预热任务 * */ @Slf4j @Component public class PreCacheJob { @Resource private UserService userService; @Resource private RedisTemplate<String, Object> redisTemplate; //重点用户 private List<Long> mainUserList = Arrays.asList(2l); //每天执行,预热推荐用户 @Scheduled(cron = "0 31 19 * * ? ") public void doCacheRecommendUser() { for (Long userId : mainUserList) { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); Page<User> userPage = userService.page(new Page<>(1, 20), queryWrapper); String redisKey = String.format("yupao:user:recommend:%s", userId); ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue(); //写缓存 try { valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS); } catch (Exception e) { log.error("redis set key error", e); } } } }
-
Quartz(独立于spring存在的定时任务框架)
-
XXL-job分布式任务调度平台(界面+sdk)
-
-
-
Jedis
独立于spring操作redis
redis的Java客户端 -
Lettuce
高阶的操作redis的java客户端 -
和Redisson
分布式操作redis的java客户端,像在本地使用集合一样操作redis