一、redis持久化
Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中 的数据库状态也会消失。所以 Redis 提供了持久化功能!
持久化过程保存什么
1.将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据 (RDB)
2.将数据的操作过程进行保存,日志形式,存储操作过程,关注点在数据的操作过程(AOF)
1.1 RDB方式
概念: 在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将 快照文件直接读到内存里
2.1 AOF方式
概念: AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令 达到恢复数据的目的;与RDB相比可以简单描述为改记录数据为记录数据产生的过程AOF的主要作用是 解决了数据持久化的实时性,目前已经是Redis持久化的主流方式
二、 Redis 删除策略
1.过期数据
Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态
- XX :具有时效性的数据
- -1 :永久有效的数据
- -2 :已经过期的数据或被删除的数据或未定义的数据
问:过期的数据真的删除了吗? 答:不是的
2.数据删除策略
数据删除策略的目标
在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕 机或内存泄露
1. 定时删除
2. 惰性删除
3. 定期删除
2.1定时删除
- 创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除 操作
- 优点:节约内存,到时就删除,快速释放掉不必要的内存占用
- 缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指 令吞吐量
- 总结:用处理器性能换取存储空间(拿时间换空间)
2.2 惰性删除
- 数据到达过期时间,不做处理。等下次访问该数据时。
-
- 发现已过期,删除,返回不存在
- 如果未过期,返回数据
- 优点:节约CPU性能,发现必须删除的时候才删除
- 缺点:内存压力很大,出现长期占用内存的数据
- 总结:用存储空间换取处理器性能(拿空间换时间)
2.3定期删除
两种方案都走极端,有没有折中方案?
- Redis启动服务器初始化时,读取配置server.hz的值,默认为10
- 每秒钟执行server.hz次serverCron()中的方法---databasesCron()
- activeExpireCycle()activeExpireCycle()对每个expires[*]逐一进行检测,每次执行250mslserver.hz
- 对某个expires[*]检测时,随机挑选W个key检测
-
- 如果key超时,删除key
- 如果一轮中删除的key的数量>W* 25%,循环该过程
- *如果一轮中删除的key的数量≤W* 25%,检查下一个expires[*,O-15循环
- W取值=ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP属性值
- 参数current_db用于记录activeExpireCycle()进入哪个expires[*]执行
- 如果activeExpireCycle()执行时间到期,下次从current_db继续向下执行
定期删除:周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删 除频度
优点1:CPU性能占用设置有峰值,检测频度可自定义设置
优点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
总结:周期性抽查存储空间 (随机抽查,重点抽查)
2.4 删除策略比对
1. 定时删除 节约内存,无占用 不分时段占用CPU资源,频度高 拿时间换空间
2. 惰性删除 内存占用严重 延时执行,CPU利用率高 拿空间换时间
3. 定期删除 内存定期随机清理 每秒花费固定的CPU资源维护内存 随机抽查,重点抽查
3.逐出算法
当新数据进入redis时,如果内存不足怎么办?
- Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充 足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储 空间。清理数据的策略称为逐出算法。
- 注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。 当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。 抛出异常:(error) OOM command not allowed when used memory >'maxmemory'
影响数据逐出的相关配置
- maxmemory最大可使用内存
占用物理内存的比例,默认值为0,表示不限制,生产环境中根据需求设定,通常设置在50%以上。
- maxmemory-samples每次选取待删除数据的个数
选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据
- maxmemory-policy删除策略
检测易失数据(可能会过期的数据集server.db[i].expires )
- volatile-lru:挑选最近最少使用的数据淘汰
- volatile-lfu:挑选最近使用次数最少的数据淘汰volatile-ttl:挑选将要过期的数据淘汰
- volatile-random:任意选择数据淘汰
检测全库数据(所有数据集server.db[i].dict )
- allkeys-lru:挑选最近最少使用的数据淘汰
- allkeys-lfu:挑选最近使用次数最少的数据淘汰
- alkeys-random:任意选择数据淘汰
放弃数据驱逐
no-enviction(驱逐)︰禁止驱逐数据(redis4.0中默认策略),会引发错误OOM (Out OfMemory)达到最大内存后的,对被挑选出来的数据进行删除的策略
三、企业级解决方案
1. 缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
- 给不同的Key的TTL添加随机值
- 利用Redis集群提高服务的可用性
- 给缓存业务添加降级限流策略给业务添加多级缓存
2. 缓存击穿
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
常见的解决方案有两种:
- 互斥锁
- 逻辑过期
逻辑分析:假设线程1在查询缓存之后,本来应该去查询数据库,然后把这个数据重新加载到缓存的,此时只要线程1走完这个逻辑,其他线程就都能从缓存中加载这些数据了,但是假设在线程1没有走完的时候,后续的线程2,线程3,线程4同时过来访问当前这个方法,那么这些线程都不能从缓存中查询到数据,那么他们就会同一时刻来访问查询缓存,都没查到,接着同一时间去访问数据库,同时的去执行数据库代码,对数据库访问压力过大
解决方案一、使用锁来解决: 因为锁能实现互斥性。假设线程过来,只能一个人一个人的来访问数据库,从而避免对于数据库访问压 力过大,但这也会影响查询的性能,因为此时会让查询的性能从并行变成了串行,我们可以采用 tryLock方法 + double check来解决这样的问题。 假设现在线程1过来访问,他查询缓存没有命中,但是此时他获得到了锁的资源,那么线程1就会一个人 去执行逻辑,假设现在线程2过来,线程2在执行过程中,并没有获得到锁,那么线程2就可以进行到休 眠,直到线程1把锁释放后,线程2获得到锁,然后再来执行逻辑,此时就能够从缓存中拿到数据了。
解决方案二、逻辑过期方案 方案分析:我们之所以会出现这个缓存击穿问题,主要原因是在于我们对key设置了过期时间,假设我 们不设置过期时间,其实就不会有缓存击穿的问题,但是不设置过期时间,这样数据不就一直占用我们 内存了吗,我们可以采用逻辑过期方案。 我们把过期时间设置在 redis的value中,注意:这个过期时间并不会直接作用于redis,而是我们后续 通过逻辑去处理。假设线程1去查询缓存,然后从value中判断出来当前的数据已经过期了,此时线程1 去获得互斥锁,那么其他线程会进行阻塞,获得了锁的线程他会开启一个 线程去进行 以前的重构数据 的逻辑,直到新开的线程完成这个逻辑后,才释放锁, 而线程1直接进行返回,假设现在线程3过来访 问,由于线程线程2持有着锁,所以线程3无法获得锁,线程3也直接返回数据,只有等到新开的线程2把 重建数据构建完后,其他线程才能走返回正确的数据。 这种方案巧妙在于,异步的构建缓存,缺点在于在构建完缓存之前,返回的都是脏数据。
进行对比
互斥锁方案:由于保证了互斥性,所以数据一致,且实现简单,因为仅仅只需要加一把锁而已,也没其 他的事情需要操心,所以没有额外的内存消耗,缺点在于有锁就有死锁问题的发生,且只能串行执行性 能肯定受到影响 逻辑过期方案: 线程读取过程中不需要等待,性能好,有一个额外的线程持有锁去进行重构数据,但是 在重构数据完成前,其他的线程只能返回之前的数据,且实现起来麻烦
3. 3.缓存穿透
缓存穿透 :缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这 些请求都会打到数据库。
常见的解决方案有两种:
- 缓存空对象
- 优点:实现简单,维护方便。
- 缺点:
- 额外的内存消耗
- 可能造成短期的不一致
- 布隆过滤
- 优点:内存占用较少,没有多余key。
- 缺点:实现复杂存在误判可能
缓存空对象思路分析:当我们客户端访问不存在的数据时,先请求redis,但是此时redis中没有数据, 此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,我们都知道数据 库能够承载的并发不如redis这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会 访问到数据库,简单的解决方案就是哪怕这个数据在数据库中也不存在,我们也把这个数据存入到redis 中去,这样,下次用户过来访问这个不存在的数据,那么在redis中也能找到这个数据就不会进入到缓存 了
布隆过滤:布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思 想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问 redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数 据后,再将其放入到redis中,
假设布隆过滤器判断这个数据不存在,则直接返回
这种方式优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思 想,就可能存在哈希冲