缓存Redis使用分享

作者:老夏

概述

天下武功,无坚不摧,唯快不破!软件也是。

72A07D3E-FC60-4488-A13D-1CE8B9379428.png

通常我们为了获得更快的读取速度会使用缓存,缓存是利用空间换时间较为常用的架构设计模式,我们在开发中也经常会使用这种模式,例如 当数据量达到一定数量时 用 HashMap 取代遍历 List,虽然 HashMap 占用更大的空间,但是 HashMap 查询时间复杂度是O(1)(理想状态下)。Redis 是我们比较常用的缓存,接下来我会分享几个 我所见所闻的坑。

一,不要图快什么数据都存 Redis

我之前听过一个笑话,开发人员因为在循环里查库,导致接口请求时间过长,为了解决查询慢的问题,开发人员选择使用 Redis,开发人员会把查过数据存放在 Redis ,并且设置了失效时间,在开发自测环节,以及测试环节,都算比较平稳,但是上线不久后就遇到了业务高峰期,服务器居然宕机!!!原本服务器空闲 10G左右,现在居然宕机了。后边经过排查发现,服务器 64G,Redis 快占了近20G,原因就是上述接口放了太多数据。所以在使用Redis 时,应限制其对内存的使用量设置 maxmemory 参数;其次,我们应清晰什么数据应该放在 Redis,我们要把使用频繁修改少的数据放在 Redis,不是“快”就用 Redis。

那么想一下都有什么数据需要放 Redis ?思考十秒钟,我来合理安排一下时间:

截屏2022-04-03 20.56.36.png

热点数据,例如 Token,通用的数据字典等,其他的数据还要根据实际使用情况去判断,例如通过日志分析,不过我们一定要注意一下两点:

第一,缓存数据一定是有原始数据来源,且允许缓存数据丢失。当数据丢失后,我们可以从原始数据重新加载数据。

第二,Redis 缓存的数据量一定是小于原始数据的。首先,我们应该限制 Redis 对内存的使用量,也就是设置 maxmemory 参数;其次,设置失效时间和淘汰策略。

下面我粘贴常用的几个策略:

1,allkeys-lru : Keeps most recently used keys; removes least recently used (LRU) keys

2,allkeys-lfu : Keeps frequently used keys; removes least frequently used (LFU) keys

3,volatile-lru : Removes least recently used keys with the expire field set to true.

4,volatile-ttl : Removes least frequently used keys with expire field set to true and the shortest remaining time-to-live (TTL) value.

5,volatile-lfu : Removes least frequently used keys with the expire field set to true.

对应中文翻译:

1,allkeys-lru,针对所有 Key,优先删除最近最少使用的 Key;

2,allkeys-lfu(Redis 4.0 以上),针对所有 Key,优先删除最少使用的 Key;

3,volatile-lru,针对带有过期时间的 Key,优先删除最近最少使用的 Key;

4,volatile-ttl,针对带有过期时间的 Key,优先删除即将过期的 Key(根据 TTL 的值);

5,volatile-lfu(Redis 4.0 以上),针对带有过期时间的 Key,优先删除最少使用的 Key。

有兴趣的小伙伴可以查阅一下官方文档https://redis.io/docs/manual/eviction/

二,注意缓存雪崩问题

由于缓存系统的IOPS (Input/Output Operations Per Second) 比数据库高很多,因此要特别小心短时间内大量缓存失效的情况。这种情况一旦发生,会造成瞬间有大量的请求直接怼到数据库,数据库造成极大的压力,甚至数据库直接炸了。这种情况就是我们常说的缓存失效,也叫作缓存雪崩。

缓存雪崩产生原因分为两种:

1)缓存挂了导致所有的请求直接怼到数据库,缓存雪崩。

2)大量的 key 在同一时间失效大量请求怼到数据库,缓存雪崩。

解决办法:

1)差异化缓存失效时间,不让缓存在同一时间失效。有些数据是在初始化的时候加载的,当设置这些数据失效时间的时候,做到差异化,例如 失效时间 = 30 + (0 -10 随机数), 这样就不会造成同一时间失效。

2)让批量的缓存数据不主动过期,后台启动一个线程去维护更新缓存数据。

读到这里,休息一下,我来合理安排一下时间:

6b327631013f01a0d1c28a8352226357.jpeg

三,注意缓存穿透问题

上一点我们讲的是缓存雪崩问题,因为缓存挂了,或者缓存数据同一时间失效造成大量请求怼到数据库。你有没有考虑过,如果数据库压根就没有一条数据,所以缓存没有属于正常,当请求过来,缓存没有,请求直接怼到数据库,这种情况,就是我们常说的缓存穿透。

例如,数据库中不存在 id 小于 0 的数据,而接口频繁请求 id = -122 的数据,这就每次请求缓存都查不到,然后再去查数据库,造成了很多资源的浪费,甚至造成系统瘫痪。

解决方法:

1)对于不存在的数据,同样设置缓存,数据内容可以是“NODATA”这种,并且设置合理的实效时间,下次请求时就能命中缓存了。单该方法有个致命的问题,如果接口请求大量不存在的数据,那缓存就有可能被这些无用的数据占满。那布隆过滤器做前置过滤可能是更好的选择。

2)使用布隆过滤器做前置过滤。

布隆过滤器是一种概率型数据库结构,由一个很长的二进制向量和一系列随机映射函数组成。它的原理是,当一个元素被加入集合时,通过 k 个散列函数将这个元素映射成一个 m 位 bit 数组中的 k 个点,并置为 1。检索时,我们只要看看这些点是不是都是 1 就(大概)知道集合中有没有它了。如果这些点有任何一个 0,则被检元素一定不在;如果都是 1,则被检元素很可能在。

原理如下图所示:

截屏2022-04-04 14.42.33.png

布隆过滤器不保存原始值,空间效率很高,平均每一个元素占用 2.4 字节就可以达到万分之一的误判率。这里的误判率是指,过滤器判断值存在而实际并不存在的概率。我们可以设置布隆过滤器使用更大的存储空间,来得到更小的误判率。

你可以把所有可能的值保存在布隆过滤器中,从缓存读取数据前先过滤一次:

如果布隆过滤器认为值不存在,那么值一定是不存在的,无需查询缓存也无需查询数据库;

对于极小概率的误判请求,才会最终让非法 Key 的请求走到缓存或数据库。

四,注意缓存击穿问题

相对上边说的几个注意,缓存击穿比较容易理解:

当一个 key 属于极端热点数据且并发量很大,突然这个 key 过期了,这个时候大量请求进来,直接怼到数据库。这种情况就叫缓存击穿或者缓存并发问题。

这里需要注意,缓存击穿 和 缓存穿透 的区别:

a,缓存穿透是指,缓存没有起到压力缓冲的作用;

b,缓存击穿是指,缓存失效时瞬时的并发打到数据库。

对于高并发的缓存 Key 回源问题,可以使用锁来限制回源并发数。

{{o.name}}
{{m.name}}

猜你喜欢

转载自my.oschina.net/u/3620858/blog/5510275
今日推荐