文章目录

  • 0.代码注意事项
  • 1.Redis 配置文件
  • 2.Redis三大场景问题
    • 2.1.缓存穿透
    • 2.2.缓存击穿
    • 2.3.缓存雪崩
  • 3.数据双写一致性问题
  • 4.Redis 数据持久化
  • 5.Redis 内存解决办法

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,直接返回错误