持久化
持久化策略
持久化策略包含
-
AOF:默认每秒对数据进行持久化
-
RDB:按条件触发持久化操作,满足任意一个条件 1) 900 1 900秒中修改1次 2) 300 10 300秒中修改10次 3) 60 10000 60秒中修改10000次
配置方法
修改redis.conf 配置文件
启动AOF的配置
appendonly yes 开启AOF
appendfsync everysec 每秒保存
选择持久化策略
-
允许少量数据丢失,性能要求高,选择RDB
-
只允许很少数据丢失,选择AOF
-
几乎不允许数据丢失,选择RDB + AOF
淘汰策略
Redis中的数据太多可能导致内存溢出,Redis会根据情况淘汰一些数据。 Redis的内存上限:64位系统,上限就是内存上限;32位系统,最大是4G
-
noevication(默认)不淘汰
-
volatile-ttl 在过期键中淘汰存活时间短的键
-
allkeys-lru (推荐)使用LRU算法淘汰比较少使用的键 LRU算法:Least Recently Used 最近最少使用算法,淘汰长期不用的缓存
LFU算法:Least Frequently Used 频率最少使用算法,淘汰使用频率少的缓存
-
volatile-lru 在过期的键中淘汰较少使用的
-
allkeys-random 在所有键中随机淘汰
-
volatile-random 在过期键中随机淘汰
-
allkeys-lfu
-
volatile-lfu
Redis并发
缓存雪崩
原因:
- Redis热点数据同时过期,大量请求全部打到MySQL,MySQL宕机
- 单个Redis服务出现问题或重启
解决:
- 将热点数据过期时间设置为随机值,避免同时过期
- 配置Redis集群,解决单点故障问题
缓存击穿
原因:
大量并发请求访问Redis同一个数据,还没有向Redis保存,有大量线程同时访问,导致MySQL压力过大
解决:
通过上锁(双检锁)实现线程同步执行
代码示例:
@Override
public Food getFoodById(Long id) {
// 获得字符串操作对象
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
// 查询Redis缓存
Food food = (Food) ops.get(PREFIX + id);
if (food == null) {
// 从数据库查询
food = foodMapper.selectById(id);
if (food != null) {
System.out.println("MySQL查到,返回" + food);
// 将查询结果保存到Redis
redisTemplate.opsForValue().set(PREFIX + id, food);
}
}
// 如果Redis缓存存在,就返回数据
if (food != null) {
System.out.println("Redis查到,返回" + food);
return food;
}
// MySQL和Redis都没有数据,则返回null
return null;
}
@GetMapping("/food/{id}")
public ResponseResult<Food> getFoodById(@PathVariable Long id){
Food food = foodService.getFoodById(id);
return ResponseResult.ok(food);
}
缓存穿透
原因:
大量请求访问MySQL没有的数据,Redis缓存无法命中,导致数据库压力过大
解决:
- 在Redis中保存空对象,给空对象设置过期时间
- 使用布隆过滤器筛选掉不存在的数据
代码示例1:给对象设置过期时间
@Override
public Student getStudentById(Long id) {
//获得字符串操作对象
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
//先查询Redis,如果存在数据就不执行同步代码块,直接返回
Student stu = (Student) ops.get(PREFIX + id);
if(stu == null) {
synchronized (this) {
System.out.println("进入了同步锁");
//先查询Redis
stu = (Student) ops.get(PREFIX + id);
//如果Redis缓存存在数据,就直接返回
if (stu != null) {
System.out.println("Redis查到,返回" + stu);
return stu;
}
//如果Redis没有查到数据,就查询MySQL
stu = studentMapper.selectById(id);
//MySQL查到数据,就保存到Redis
if (stu != null) {
System.out.println("MySQL查到,返回" + stu);
ops.set(PREFIX + id, stu);
return stu;
}else {
//MySQL没有数据,在Redis保存空对象,设置过期时间
System.out.println("MySQL没有数据");
Student student = new Student();
ops.set(PREFIX + id, student,5, TimeUnit.SECONDS);
}
}
}else {
System.out.println("没有执行同步锁");
}
return stu;
}
代码示例2:使用布隆过滤器
步骤一:导入依赖、定义配置类
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.6</version>
</dependency>
@Configuration
public class RedissonConfig {
@Bean
public RBloomFilter<String> bloomFilter(){
Config config = new Config();
config.setTransportMode(TransportMode.NIO);
SingleServerConfig singleServerConfig = config.useSingleServer();
singleServerConfig.setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create();
//创建布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("Food-filter");
bloomFilter.tryInit(10000000L,0.03);
return bloomFilter;
}
}
步骤二: 将数据的id保存到过滤器中
@Autowired
private RBloomFilter<String> rBloomFilter;
@GetMapping("/init-food-filter")
public ResponseResult<String> initFoodFilter(){
List<Food> list = foodService.list();
list.forEach(food -> {
rBloomFilter.add(String.valueOf(food.getId()));
});
return ResponseResult.ok("ok");
}
步骤三:使用过滤器排除不存在的数据
@Override
public Food getFoodById(Long id) {
// 先检查布隆过滤器,判断数据是否可能存在于Redis中
if (rBloomFilter.contains(String.valueOf(id))) {
// 获得字符串操作对象
ValueOperations<String, Object> ops = redisTemplate.opsForValue();
// 查询Redis缓存
Food food = (Food) ops.get(PREFIX + id);
if (food == null) {
synchronized (this) {
System.out.println("执行了同步锁");
food = (Food) ops.get(PREFIX + id);
if (food == null) {
// 从数据库查询
food = foodMapper.selectById(id);
if (food != null) {
System.out.println("MySQL查到,返回" + food);
// 将查询结果保存到Redis
redisTemplate.opsForValue().set(PREFIX + id, food);
}
}
}
}
// 如果Redis缓存存在,就返回数据
if (food != null) {
System.out.println("Redis查到,返回" + food);
return food;
}
} else {
System.out.println("布隆过滤器判断id不存在");
}
// MySQL和Redis都没有数据,则返回null
return null;
}