数据一致性
查询:先查redis,有则返回查询结果,无则查数据库,并把返回结果set进redis
删改:先把redis中的数据删除。再操作数据库
新增不操作
缓存击穿
当一个热点key过期,但受到大量访问,此时请求同时到达数据库。
解决
访问缓存key失败,加分布式锁redis,访问数据库吧返回数据添加缓存。成功后释放锁。
如果有另一个线程同时访问此失效数据,也会上锁如果上锁失败(redis分布式锁),那么休眠20mm再查缓存
public User getUser(int id){
String s = stringRedisTemplate.opsForValue().get(id+"");
if(StringUtils.hasText(s)){
return JSONObject.parseObject(s, User.class);
}
//1. 未命中,加分布式锁
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(id + "_lock", "lock", 60, TimeUnit.SECONDS);
if(lock){
//2. 加锁成功,查询数据库,并写入缓存
List<User> users = jdbcTemplate.query("select * from user where id= ?",
new Object[]{id}, new BeanPropertyRowMapper<>(User.class));
stringRedisTemplate.opsForValue().set(id + "", JSONObject.toJSONString(users.get(0)), 30, TimeUnit.SECONDS);
//3. 释放锁,解锁的代码要写到finally中
stringRedisTemplate.delete(id + "_lock");
return users.get(0);
}else{
//4. 加锁失败,休眠20毫秒,继续查询缓存
try {
Thread.sleep(20);
} catch (InterruptedException e) {}
return getUser(id);
}
}
延时双删
springboot配置redis
不再用原生的数据库连接池。启动器提供了两个封装好的类。可以用来操纵redis中五种数据类型,分别是 redisTemplate StringRedistemplate
1.redisTemplate 支持对象的自动序列化(实现serializable接口),但可读性较差
2.StringRedisTemplate 所有参数要求字符串类型,有对象类型的话需要手动转换
重新指定序列化方式,key序列化和hash序列化都用string value和hashValue都用json
需要在自定义配置类中,重写redistemplate方法,并且重新指一下bean的名字。这样就可以使用自己的配置方法
方法1
@Bean(name = {"redisTemplate"})
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.json());
template.setHashKeySerializer(RedisSerializer.string());
template.setHashValueSerializer(RedisSerializer.json());
//把链接工厂设置到template中(为了获得连接)
template.setConnectionFactory(redisConnectionFactory);
return template;
}
方法2
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//创建 RedisTemplate 对象
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//设置 连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置key的序列化
redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
//设置 value 的序列化
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;
}
}
全量同步
slave-redis第一次连接主机时,向master请求数据同步。master通过判断请求中是否包含runid来确定是否是第一次连接。如果是第一次连接。那么把runid和offset(游标:确定同步数据到哪个位置)交给slave保存
master在运行中执行bgsave语句生成rdb文件(新线程),用来发送给slave同步数据。在master生成rdb的过程中,依然可以执行读写操作,这期间产生的数据变化无法写入rdb中,为了避免造成数据的丢失。这期间使用的指令被记录进replication-buffer中,与此同时,slave清空数据加载rdb文件,开始数据同步
最后master把replication-buffer中的命令发送给slave,slave执行命令。