文章目录
0.代码注意事项
(1)将Object转化为某一个实体类对象
- 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
- 转化代码
// 将Object转化为某一个实体类对象
String jsonString = JSONObject.toJSONString(o);
TbShop tbShop=JSONObject.parseObject(jsonString,TbShop.class);
(2)使用redisTemplate操作数据库时,注意存储对象
- 使用
opsForValue()(String类型)
操作存储对象为List
类型的存储对象会报错:redis报错WRONGTYPE Operation against a key holding the wrong kind of value
1.Redis 配置文件
- 直接使用 RedisTemplate 时,如果不进行序列化,当使用redis的图形化页面工具时,会出现键值不可见问题
- 加入该配置文件,可以解决使用RedisTemplate时,redis图形化界面正常显示
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
//该配置文件解决的是 redis的视图工具的乱码问题
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//使用fastjson序列化
Jackson2JsonRedisSerializer fastJsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
2.Redis三大场景问题
2.1.缓存穿透
(1)描述(查不到)
- 访问⼀个缓存和数据库都不存在的 key,此时会直接打到数据库上,并且查不到数据,没法写 缓存,所以下⼀次同样会打到数据库上。
- 此时,缓存起不到作⽤,请求每次都会⾛到数据库,流量⼤时数据库可能会被打挂。此时缓存就好像 被“穿透”了⼀样,起不到任何作⽤。
(2)解决方案
- 接⼝校验: 在正常业务流程中可能会存在少量访问不存在 key 的情况,但是⼀般不会出现⼤量的情况,所以这种场景最⼤的可能性是遭受了⾮法攻击。可以在最外层先做⼀层校验:⽤⼾鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对⾮正整数直接过滤等等。
- 缓存空值: 当访问缓存和DB都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设置。
- 布隆过滤器: 使⽤布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进⼀步查询缓存和数据库。
(3)code eg:
// 根据教师id查询课程详情信息
@RequestMapping("findOpenCourseById/{id}")
public BaseResp findTeacherById(@PathVariable("id") Integer id){
Boolean aBoolean = redisTemplate.hasKey(RedisKey.OPENCOURSE_LIST+id.toString());
EcoursesOpen course=null;
if (!aBoolean){
course = openCourseService.getById(id);
if (course!=null){
redisTemplate.opsForValue().set(RedisKey.OPENCOURSE_LIST+id.toString(),course);
}else {
// 防止缓存穿透
redisTemplate.opsForValue().set(RedisKey.OPENCOURSE_LIST+id.toString(),course);
redisTemplate.expire(RedisKey.OPENCOURSE_LIST+id.toString(),30, TimeUnit.SECONDS);
}
}
Object o = redisTemplate.opsForValue().get(RedisKey.OPENCOURSE_LIST+id.toString());
EcoursesOpen ecoursesOpen = JSONObject.parseObject(JSON.toJSONString(o), EcoursesOpen.class);
return new BaseResp().Ok("课程查询成功",ecoursesOpen,null);
}
2.2.缓存击穿
(1)描述(访问量大的同时,缓存过期)
- 某⼀个热点 key,在缓存过期的⼀瞬间,同时有⼤量的请求打进来,由于此时缓存过期了,所以请求最终都会⾛到数据库,造成瞬时数据库请求量⼤、压⼒骤增,甚⾄可能打垮数据库。
(2)解决方案
- 加互斥锁: 在并发的多个请求中,只有第⼀个请求线程能拿到锁并执⾏数据库查询操作,其他的 线程拿不到锁就阻塞等着,等到第⼀个线程将数据写⼊缓存后,其他线程直接⾛缓存拿数据。
-
热点数据不过期: 直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。
- 此种⽅式适⽤于⽐较极端的场景,例如流量特别特别⼤的场景。在使⽤时需要考虑业务能接受数据不⼀ 致的时间,还有就是异常情况的处理,不要到时候缓存刷新不上,⼀直是脏数据,那就凉了。
2.3.缓存雪崩
(1)描述
- ⼤量的热点 key 设置了相同的过期时间,导在缓存在同⼀时刻全部失效,造成瞬时数据库请求量⼤、压⼒骤增,引起雪崩,甚⾄导致数据库被打挂。
- 缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是⼀个热点 key,缓存雪崩是⼀组热点 key。
(2)解决方案
- 过期时间打散: 针对不同的数据设置不同的失效时长,使得每个 key 的过期时间分布开来,不会集中在同⼀时刻失效。
- 热点数据不过期: 该⽅式和缓存击穿⼀样,也是要着重考虑异步刷新的时间间隔、数据异常如何处理情况、以及业务所能接收数据不一致的时间间隔。
- 加互斥锁: 该⽅式和缓存击穿⼀样,按 key 维度加锁,对于同⼀个 key,只允许⼀个线程去计算,其他线程原地阻塞等待第⼀个线程的计算结果,然后直接⾛缓存即可。
3.数据双写一致性问题
(1)问题原因:
- 当我们对数据库进行操作后,数据已经放生了变化,但是从redis中获取时,因为该key已经存
在,则不会从数据库获取到新的数据,就会导致数据库的数据与redis中的数据不一致的产生。- 一般情况下:先操作数据库,再操作缓存(redis)
- 一般情况下:直接使缓存失效(删除redis),而不是更新缓存(更新redis)
(2)解决方案
-
先修改缓存(redis),再修改数据库
- 若redis修改成功,但是数据库修改失败,会导致数据不一致的产生,所以需要通过事务进行控制
-
先修改数据库,再修改缓存(redis)
- 若修改数据库成功,但是修改redis失败,也需要回滚进行数据一致性的保证,同时并发请求(查询)也会有一定时间的数据不一致情况
-
先失效缓存(删除redis),再修改数据库
- 删除redis后,如果此时有一个请求访问,会从数据库重新查询数据并建立缓存,导致数据不一致的情况产生
-
先修改数据库,再失效缓存(删除redis)
- 执行效率慢,但是可以保证数据的最终一致性
(3)保证双写一致性的策略
-
延迟双删策略
- 实现: 先进行缓存清除,再执行update数据库,最后(延迟N秒)再执行缓存清除。(执行清除两次缓存操作)
- 为什么要延迟N秒: 我们考虑这样一种情况,在我们两次删除缓存之间更新数据库之前,B事务读到了数据库中的脏数据,但是他的时间片耗尽了,结果更新数据库后,A事务进行了第二次清空缓存,时间片轮转回B时,B就会将旧数据缓存写进缓存当中去。此时我们使用延时双删策略,延后第二次删除缓存的时间,保证第二次删除缓存在所有的旧缓存之后,就可以确保不会有旧数据出现了
- 改进(不足): 但是我们思考延时双删策略,此策略只能保证最终一致性,保证了第二次删除缓存之后的数据均为新数据,那第二次删除缓存之前还是能够读到旧数据的,如果对于数据没有强一致性要求的话延时双删已经足够了,但是如果对于数据有强一致性要求延时双删显然就不满足条件了,这个时候我们进一步优化的话可以考虑加锁操作,在写更新时阻塞读操作,带来的影响就是可以保证强一致性,但是吞吐量会下降
4.Redis 数据持久化
- redis的持久化机制,是将内存中的数据写入磁盘中
(1)RDB—快照方式
-
概念: 在某个时间点,将 Redis 在内存中的数据库状态(数据库的键值对等信息)保存 到磁盘⾥⾯。
-
优点:
- RDB是全量存储,在数据量较小的情况下,执行速度较快
- RDB ⽂件是以数据块的形式进行保存的,可以通过拷贝RDB文件实现备份
-
缺点:
- 若redis出现故障,存在数据丢失的风险,丢失上一次redis持久化之后的数据
- RDB采用快照方式进行存储,不适合实时性存储
(2)AOF持续的日志添加
-
概念: 文件追加方式,当达到触发条件时,将redis执行的写操作指令存储在aof文件中
-
优点:
- aof是增量更新,适合实时性持久化
-
缺点:
- 对于相同的数据集,AOF ⽂件的⼤⼩⼀般会⽐ RDB ⽂件⼤。
(3)混合持久化
- 官方建议同时开启两种持久化机制,如果同时存在aof和rdb的情况下,aof优先
5.Redis 内存解决办法
(1)搭建主从集群
-
主从复制架构
- 搭建一主多从集群,一台主服务器用来写,多台从服务器用来读,主服务器负责将数据同步到从服务器中,保证主从服务器的数据一致性
- 特点: 主服务器内存有限,出现故障后无法自动切换
-
哨兵
- 一主多从,由哨兵监控服务器的状态,当主服务器挂掉后,会从从服务器中选举出一个节点作为从服务器替代挂掉的从服务器
- 特点: 可以实现高可用,自动切换挂掉的主机
-
集群
- 多主多从,复用了哨兵的逻辑,对其水平扩展,通常不少于6台服务器,每个主节点是对等的,只负责写一部分数据,当超过半数的服务器挂掉之后,集群也会瘫痪
- 通过集群模式和持久化机制,可以实现redis的高可靠
(2)配置内存淘汰策略
-
LRU: 最久最近未使用(存在时间最长,最近没有使用)
-
LFU: 最近最少未使用(最近最少使用的)
-
八种替换策略:
-
volatile-lfu: 从设置了过期时间的数据中,淘汰最近最少使用的key
-
allkeys-lfu: 从所有的key中淘汰最少使用的key
-
volatile-lru: 从设置了过期时间的数据中,淘汰最久未使用的key
-
allkeys-lfu: 从所有的key中淘汰最久未使用的key
-
volatile-random: 从设置了过期时间的数据中,随机淘汰一个key
-
allkeys-random: 从所有的key中, 随机淘汰一个key
-
volatile-ttl :淘汰过期时间最短的数据
-
noeviction:默认策略,不淘汰任何 key,直接返回错误
-