Redis缓存概念的基本了解篇

一、更新缓存


此处的更新缓存是指,Redis中原有的缓存被删除,通过了解这个,就可以知道,Redis中缓存的变化情况,有了这个概念,就可以比较好地使用Redis的缓存技术。

更新方式

  • 内存淘汰:到达设置的max-memery,触发淘汰机制,可以自己设置淘汰策略
  • 超时剔除:设置过期时间TTL
  • 主动更新:手动删除,通常用于解决缓存与数据库不一致的问题

1、有哪些内存淘汰策略?


独立的两种是noevictionttl,其余可以只记住三种:lrulfurandom,可以分为六种,是因为剔除的时候还考虑是否配置了TTL。

  • noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键(默认)。
  • allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键。
  • volatile-lru:加入键的时候,如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键。
  • allkeys-random:加入键的时候如果过限,从所有key随机删除。
  • volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐。
  • volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键。
  • volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键。
  • allkeys-lfu:从所有键中驱逐使用频率最少的键。

2、Redis是如何做到超时剔除的?


一个键过期了,Redis是如何知道?

首先,我们暂时不考虑Redis是如何实现的,我们先来考虑,超时删除策略可以由哪些?

  • 定时删除,超时立即删除(主动删除,并且是立即)
  • 定期删除,每隔一段时间就去删除一部分过期的Key(主动删除,但是不是立即,也可以再细分,定期删除全部还是定期删除部分,如果定义部分,就靠算法与使用场景了)
  • 惰性删除,获取的时候再去判断是否过期,判断过期再删除(被动删除,获取的时候才删除)

然后,我们在来看看,我们拥有哪些资源?CPU资源和内存资源。

这样,我们可以得出下面结论:

  • 主动删除会消耗更多的CPU资源,但是可以更好利用内存资源,而其中定时删除需要比定期删除更多的CPU资源
  • 被动删除会消耗更多的内存资源,但是不会额外消耗太多的CPU资源

对比于定时删除与惰性删除,定期删除无疑是一种”中庸之举“。

定时删除的实现,如果设置对某个时刻的过期键的删除的监听器,那么删除很有可能是O(n),并且经常需要占用比较大的CPU资源,在内存压力不大的时候,花费太多CPU资源在删除上,对服务器的响应时间是很不友好的,虽然Redis的性能上限是主要是内存与网络IO,但是不一定说CPU就没有影响。

惰性删除经常浪费了很多内存,很多过期的键依然存在内存中,造成了浪费。

现在可以说出Redis的使用的方式:定期删除+惰性删除

其中这个定期删除,使用的是,定期抽内存中抽取一部分出来进行判断是否过期然后删除,怎么抽取,一次抽取多少,抽取的频率,这些都需要Redis在内存资源与CPU资源之间进行取舍。而由于定期还会出现部分漏掉的过期Key,所以惰性删除为用户使用的最后一环提供了保障,这样用户就不会获取到已经过期的Key。

3、主动更新的时候,数据库与Redis缓存不一致怎么办?


你可能想说,同步更新不就好了?但是这个同步可没有原子性的保证!里面显然大有问题。

有一篇文章写得很好,这里就做一个推荐了,文章分析了各种可能会出现的问题,并给出解决方式 --> 如何保证数据库和缓存双写一致性?

二、有效的缓存


我们知道,在并发量大的情况下,普通查询关系数据库并不能满足我们对于并发量的需求,所以需要缓存来帮助我们快速获取到相应的数据,提高并发量,但是在一些情况下,未必能真的获取相应的数据,那么缓存也就失效了。

常见问题

  • 缓存穿透:大量请求的数据不在Redis中,只能被动查询数据库,导致数据库压力增大(本来就没有相应的Key
  • 缓存雪崩:key同时大量失效,或者redis服务器宕机,导致请求大量到达数据库(本来就有相应的Key,但是大量Key失效
  • 缓存击穿:热点key问题,高并发访问的key失效了,而重建业务复杂(本来就有相应的Key,但是少量key失效,而这些Key的并发访问量非常高

1、缓存穿透


这个问题导致的后果是,大量查询关系数据库,导致并发量大大下降,并且如果是被攻击了,所发送的请求都是无效的,也就是说无效请求使得我们的并发量大大下降,这是不被允许发生的。另外,如果是有效请求,缓存也被穿透了,这种情况也是有的,比较常见于服务刚启动时,所以这种情况下,我们需要缓存预热,提前往缓存里面加载数据,等到用户访问的时候就不需要再去查询关系数据库。

解决方式一:缓存空对象

虽然关系数据库中没有该数据,但是我们可以尝试缓存一个空对象,使得下一次对应的缓存进来,我们也暂时可以不用查询关系数据库,如果关系数据库更新了,那么这个Key将会被删除,下一次就可以缓存真正的对象,也就不用担心影响用户的实际使用。

  • 优点:实现简单。
  • 缺点:如果真的是被攻击,从而发起大量无效请求,这些无效请求未必是有重复的,所以空对象未必能有效发挥作用。
解决方式二:布隆过滤器

假设我们可以通过不查询关系数据库就能知道是否存在该数据,那么这个问题将会迎刃而解。怎么样才能做到这一点呢?

如果关系数据库中所有的数据都被分别计算出一串“密文”,那么如果我们存储了这个“密文”,当有请求进来的时候,我们只需要判断请求对应的密文是否存在即可,“密文”就起到了一个中间映射的关系:请求—密文—真实数据,需要注意的是,这个成立的前提是,计算请求得出来的“密文”与计算数据得出来的密文是一致的。

但是“密文”也需要存储,也需要去查找,真的能比查询真实数据会更快吗?如果密文存储在关系数据库,显然并不会,如果存储在缓存数据库,将会花费大量的内存资源。

那么,有没有一种,花费的内存少,查询速度快的方式呢?

回看我们上方的讲述,“密文”是如何得出的?是被计算出来的,用来做映射的。可以达到这个要求,我们第一个会想到什么?没错,就是Hash映射!

下面举个应用例子。

我们使用一个bit数组来存储密文。初始状态如下,起始位的下标默认为0:

| 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 |

现在我们有创建第一条数据——数据A,我们对它做Hash运算,比如说对“表名+ID”做一个Hash运算,得出一个数字为5,那么可以得出以下的bit数组:

| 0 0 0 0 | 1 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 | 0 0 0 0 |

此时有一个请求进来,需要查询某个表的某个ID的数据,我们可以对“表名+ID”做一个Hash运算,假设此时得出一个数字6,而我们发现bit数组中对应下标的是0,这说明没有数据库中根本没有这个数据!我们就可以直接把这个请求打回,就不用再查询数据库。

当然,你会说,Hash冲突问题,假设一个无效的请求也能通过Hash运算得出了一个5,那怎么办?

这个问题是无法解决的,但是可以缓解,使用多个Hash算法算出多个值,比如说分别算出了2,3,4,那么当一个请求进来,分别算出2,4,7,就如果下标为7的位置是0,说明这个请求对应的数据是不存在的,通过将bit数组的长度增大与使用多个Hash算法,能减少Hash冲突的发生。

事实上,这些漏网之鱼的请求是可以被忍受的,尤其是考虑了其占据的内存大小与查找的速度。

我们先看看1MB的bit数组有多长:

1MB = 1024 KB = 1024 * 1024 B = 1024 * 1024 * 8 bit = 8,388,608 bit

只需要1MB的内存,就可以轻松排除相当多的无效请求了。而查找速度呢?我们知道这个是数组,数组的查找的时间复杂度是O(1),也就是说,我们只需要付出少量的代价就可以过滤相当多的无效请求,从这个角度来思考,这个解决方式是最常用。

2、缓存雪崩与缓存击穿


我把这两个放在一起讲,是因为,这两个问题发生的原因是类似的,就是Key失效了,想要解决这个问题,我们首先需要知道,为什么出现了这个问题,然后思考如何达成两个目的:不让Key在同一时间内大量失效以及不让热点Key失效,前者是解决缓存雪崩达成的目标,后者是解决缓存击穿达成的目标。

出现的原因

回看我们上面所说的更新缓存的内容,可以发现,缓存失效无非就四个原因,三个是因为缓存更新,一个是因为服务器宕机。

四个原因

  1. 缓存的数据太多,部分Key被淘汰了
  2. 大量Key在同一时间超时
  3. 被使用者主动删除了
  4. 服务器宕机

分析出了问题,下面就可以开始解决问题了。

解决一:更换淘汰策略

回忆我们上面讲的八种缓存策略。

对于不让Key在同一时间内大量失效这个目的:

是无法通过缓存策略达成的,因为到达了缓存策略,就必然会淘汰部分Key,无论使用什么策略,都是一样的结果。

对于不让热点Key失效这个目的:

我们需要先定义什么是热点Key,最近使用过的Key?最近使用次数最多的Key?

对于前者,我们可以使用LRU策略,保存下来的就是最近使用过的Key,对于后者,我们可以使用LFU策略,保存下来的就是最近使用次数最多的Key。

另一方面,我们知道有些策略是可以设置是否只在配置了过期时间的Key中进行淘汰,所以,如果热点Key,不配置过期时间,策略也只选择了在配置了过期时间的Key中进行淘汰,那么就可以很大程度上保证热点Key不会失效。

解决二:配置随机过期时间或者不配置过期时间

前者可以解决缓存雪崩问题,后者可以解决缓存击穿问题。

缓存雪崩

害怕大量Key同一时间过期?

那就随机过期时间,大大降低同一时间过期的可能性。

缓存击穿

害怕热点Key过期?

那我干脆不设置过期时间,那样就不会过期了。

关于设置过期时间的特别的操作

只能通过Redis设置过期时间?我们能不能自己设置过期时间,由自己管理是否过期?

当然可以!这个就是逻辑过期的操作。

我们在Key-Value中的Value中存储着过期时间,等到获取的时候,我们就可以自己判断是否要删除这个Key去使它过期,假设我们的业务需要动态判断Key是否应该过期,这种操作就相当有用。

解决三、建立良好的工作流

防止误删。

解决四、使用集群

害怕服务器宕机?那就使用Redis集群,多台服务器共同保障服务进行。

万一,Key真的失效了呢?

万一真的失效了,我们要做的是什么?当然是重构啦!

现在进来一个A请求,发现D Key并不存在,所以需要重构D Key。
在重构的过程中,又进来一个B请求,也发现D Key不存在,所以也去重构D Key。

这个时候就可以发现,为什么一个Key,我需要重构两次?当然不需要啦!只需要一次就好,我们很容易就可以想到使用锁来处理这个问题。

在分布式应用中,请求A与请求B可能并不在一台主机上,所以我们需要分布式锁,这样就可以完成这个任务:所有请求进来只重构一次,而不是一个请求重构一次。

不过,也需要注意,经常被访问的Key才更需要这个操作,很长一段时间内只访问一两次的Key,多少是有些多此一举的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值