1、背景
线上你写代码的时候,想固然的认为写进 redis 的数据就必定会存在,后面致使系统各类 bug,谁来负责?
常见的有两个问题:
往 redis 写入的数据怎么没了?
可能有同窗会遇到,在生产环境的 redis 常常会丢掉一些数据,写进去了,过一下子可能就没了。个人天,同窗,你问这个问题就说明 redis 你就没用对啊。redis 是缓存,你给当存储了是吧?
啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵并且是有限的,磁盘是廉价并且是大量的。可能一台机器就几十个 G 的内存,可是能够有几个 T 的硬盘空间。redis 主要是基于内存来进行高性能、高并发的读写操做的。
那既然内存是有限的,好比 redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?固然会干掉 10G 的数据,而后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?固然是干掉不经常使用的数据,保留经常使用的数据了。
数据明明过时了,怎么还占用着内存?
这是由 redis 的过时策略来决定。
2、分析
redis 过时策略
redis是单线程,收割时间也会占用线程处理时间,若是收割过于频繁,会致使读写出现卡顿。
一、主库过时策略
1.一、定时扫描
首先将每一个设置了过时时间的key放到一个独立的hash中,默认每秒定时遍历这个hash而不是整个空间:
并不会遍历全部的key,采用一种简单的贪心策略
1.1.一、从过时key字典中,随机找20个key。
1.1.二、删除20gekey中过时的key
1.1.三、若是2中过时的key超过1/4,则重复第一步
1.1.四、每次处理的时间都不会25ms
若是有大量的key在同一时间段内过时,就会形成数据库的集中访问,就是缓存雪崩!
1.二、惰性策略
客户端访问的时候,会对这个key的过时时间进行检查,若是过时了就当即删除。惰性策略是对定时策略的补充,由于定时策略不会删除全部过时的key
二、从库过时策略
redis不会扫描从库,删除主库数据的时候,在aof文件里生成一条del指令,在主从同步的时候,从库会执行这条指令,删除过时key。
因此集群分布式锁算法的漏洞就是这样产生的。
3、常见的几种缓存失效策略
FIFO ,first in first out ,最早进入缓存的数据在缓存空间不够状况下(超出最大元素限制时)会被首先清理出去
LFU , Less Frequently Used ,一直以来最少被使用的元素会被被清理掉。这就要求缓存的元素有一个hit 属性,在缓存空间不够得状况下,hit 值最小的将会被清出缓存。
LRU ,Least Recently Used ,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又须要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
4、缓存更新策略
更新缓存的设计模式有四种:Cache aside, Read through, Write through, Write behind caching
一、Cache aside
读取:
失效:应用程序先从cache取数据,没有获得,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从cache中取数据,取到后返回。
更新:先把数据存到数据库中,成功后,再让缓存失效。
Read/Write Through Pattern
咱们能够看到,在上面的Cache Aside套路中,咱们的应用代码须要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。因此,应用程序比较啰嗦。而Read/Write Through套路是把更新数据库(Repository)的操做由缓存本身代理了,因此,对于应用层来讲,就简单不少了。能够理解为,应用认为后端就是一个单一的存储,而存储本身维护本身的Cache。
二、Read Through
Read Through 套路就是在查询操做中更新缓存,也就是说,当缓存失效的时候(过时或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务本身来加载,从而对应用方是透明的。
三、Write Through
Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,若是没有命中缓存,直接更新数据库,而后返回。若是命中了缓存,则更新缓存,而后再由Cache本身更新数据库(这是一个同步操做)
四、Write Behind Caching Pattern
Write Back套路,一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而咱们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操做飞快无比(由于直接操做内存嘛 ),由于异步,write backg还能够合并对同一个数据的屡次操做,因此性能的提升是至关可观的。
可是,其带来的问题是,数据不是强一致性的。
redis 过时策略是:按期删除+惰性删除。
所谓按期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过时时间的 key,检查其是否过时,若是过时就删除。
假设 redis 里放了 10w 个 key,都设置了过时时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过时 key 上了。注意,这里可不是每隔 100ms 就遍历全部的设置过时时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。
可是问题是,按期删除可能会致使不少过时 key 到了时间并无被删除掉,那咋整呢?因此就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 若是设置了过时时间那么是否过时了?若是过时了此时就会删除,不会给你返回任何东西。
获取 key 的时候,若是此时 key 已通过期,就删除,不会返回任何东西。
可是实际上这仍是有问题的,若是按期删除漏掉了不少过时 key,而后你也没及时去查,也就没走惰性删除,此时会怎么样?若是大量过时 key 堆积在内存里,致使 redis 内存块耗尽了,咋整?
答案是:走内存淘汰机制。
内存淘汰机制
redis 内存淘汰机制有如下几个:
- noeviction: 不删除策略, 达到最大内存限制时, 若是须要更多内存, 直接返回错误信息。 大多数写命令都会致使占用更多的内存(有极少数会例外, 如 DEL )。
- allkeys-lru:全部key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
- allkeys-random: 全部key通用; 随机删除一部分 key。
- volatile-lru:只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
- volatile-random:只限于设置了 expire 的部分; 随机删除一部分 key。
- volatile-ttl:只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。