Redis 缓存实现
NoSQL 数据库
key-value 存储系统(区别于 MYSQL 它存储的是键值对)
Redis 数据结构
基本数据结构:
- String 字符串类型:name:”mofeng”
- List 列表:names:[“mofeng”,”dogmofeng”,”lu”]
- Set 集合:names:[“mofeng”,”lu”] (值不能重复)
- Hash 哈希:nameAge:{“mofeng”:1,”dogLu”:2}
- Zset 集合:names[mofeng -9, doglu -12] (适合排行榜)
高级:
- bloomfilter(布隆过滤器,主要从大量的数据中快速过滤值,比如邮件黑名单拦截)
- geo(计算地理位置)
- hyperloglog(pv/uv)
- pub/sub(发布订阅,类以消息队列)
- BitMap(1001010101010101010101010101)
自定义序列化
为了防止写入 Redis 的数据乱码、浪费空间等,可以自定义序列化器,
新建 RedisTemplateConfig 类代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration public class RedisTemplateConfig {
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(RedisSerializer.string()); return redisTemplate; } }
|
controller 层代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
@GetMapping("/recommend") public BaseResponse<Page<User>> recommendUsers(long pageSize, long pageNum, HttpServletRequest request) { User loginUser = userService.getLoginUser(request); String redisKey = String.format("yupao:user:recommend:%s", loginUser.getId()); ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue(); 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 { valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS); } catch (Exception e) { log.error("redis set key error", e); } return ResultUtils.success(userPage); }
|
使用 Spring Data Redis 操作 Redis
Spring Data Redis (推荐)
地址:https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis
Spring Data:通用的数据访问框架,定义了一组 增删改查的接口
还可以操作:mysql、Redis、jpa
使用方式如下:
1)引入 Spring Data Redis 依赖
1 2 3 4 5 6
| <!-- https: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.6.4</version> </dependency>
|
2)配置 Redis 地址
1 2 3 4 5 6
| spring: redis: port: 6379 host: localhost database: 1
|
设计缓存 key
关键点:不同用户看到的数据不同
建议格式:
systemId:moduleld:fuc:options (防止其他系统的冲突)
比如:yupao:user:recommend:userId
注意:Redis 内存不能无限增加,一定要设置过期时间!!!
缓存预热
问题:即使使用了缓存,第一个用户访问还是很慢
缓存预热的优点:
- 解决第一个用户访问很慢,让用户始终快速访问
- 在一定程度上保护数据库
缺点:
- 增加开发成本(额外的开发、设计等)
- 预热的时机和时间错了,缓存的数据不对或者是未更新的数据
- 需要占用额外空间
怎么缓存预热
- 定时任务
- 手动触发
实现缓存预热
用定时任务,每天刷新所有用户的推荐列表
注意点:
- 缓存预热的意义(新增少、总用户多)
- 缓存的空间不能太大,要预留给其他缓存空间
- 缓存数据的周期(此处为每天)
在 main 主类添加注解 @EnableScheduling
cronTable 在线表达式生成,地址:https://cron.qqe2.com/
1 2
| @Scheduled(cron = "0 38 6 * * *")
|
新建 PreCacheJob 类实现缓存预热任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.yupi.yupao.mapper.UserMapper; import com.yupi.yupao.model.domain.User; import com.yupi.yupao.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit;
@Component @Slf4j public class PreCacheJob {
@Resource private UserService userService; @Resource private RedisTemplate<String, Object> redisTemplate;
private List<Long> mainUserList = Arrays.asList(1L);
@Scheduled(cron = "0 38 6 * * *") 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); } }
} }
|