架构师考点扩展:redis

redis在历年试题中都涉及,但侧重点不同,为防止再考(2021年),特记录关于 redis 的各方面。

基础知识

分布式锁,主从复制。哨兵,集群。

单线程的Redis为什么这么快?
纯内存操作,单线程操作,避免了频繁的上下文切换,采用了非阻塞I/O多路复用机制。

场景:
并发,秒杀实验。

redis的5种数据类型:
string 字符串(可以为整形、浮点型和字符串,统称为元素)
list 列表(实现队列,元素不唯一,先入先出原则)
hash hash散列值(hash的key必须是唯一的)
set 集合(各不相同的元素)
sort set 有序集合

类型

memcached

memcache是一个高性能的分布式的内存对象缓存系统,用于动态Web应用以减轻数据库负担。它通过在内存中缓存数据和对象,来减少读取数据库的次数。从而提高动态、数据库驱动网站速度。
memcache通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。memcache主要用于分担数据库负的压力,memcache将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。 
memcache是这个项目的名称,而memcached是它服务器端的主程序文件名。

与memcache对比

三种模式

持久化(2018年案例)

高阶知识

过期策略以及内存淘汰机制(2020年案例)

redis中缓存的数据是有过期时间的,当缓存数据失效时,redis会删除过期数据以节省内存。如何删除,涉及淘汰机制。
Redis在设置缓存数据时指定了过期时间,到了过期时间数据就失效了。
过期策略有2种:”定期删除+惰性删除”。

定期删除:每100毫秒检查一次,随机抽取部分设置有过期时间的key,如果到了过期时间,则删除(注:是先抽取,再判断是否要删除)。配置参数为hz 10,即1秒检测10次(即100毫秒)。删除的数量由maxmemory-samples 5指定。如果全部都检测一次,会消耗CPU。由于随机抽取,如果有大量过期数据,内存占用还是高,因为再引入“惰性删除策略”。
惰性删除:不主动删除,在获取某个key时,redis先检测该key是否已经过期,如无则返回,如是,则删除。定期删除是集中处理,惰性删除是零散处理。
“定期删除+惰性删除”能保证过期的key最终一定会被删掉,但只保证最终一定会被删除,如果定期删除后,还遗漏大量过期key,但后面又不访问(根据惰性删除原则,不访问不会删除),则这些key还是在内在中的。因此引入淘汰机制。

淘汰机制:
当redis内存占用过高时,会进行内存淘汰(即删除一部分key),以保证redis的内存占用率不会过高。当前版本(5.0)提示了8种内存淘汰策略,由maxmemory-policy noeviction指定,配置文件示例:

1
2
3
4
5
6
7
8
9
10
11
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select one from the following behaviors:
#
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key having an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.

解释:
(1)volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
(2)volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
(3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
(4)volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰。
(5)allkeys-lru:从数据集中挑选最近最少使用的数据淘汰。
(6)allkeys-lfu:从数据集中挑选使用频率最低的数据淘汰。
(7)allkeys-random:从数据集中任意选择数据淘汰。
(8)noenviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失。
大体分为4种类型,lru、lfu、random、ttl。注意相同策略有针对设置过期时间的(volatile),也有针对所有key的。

redis最大内存由maxmemory指定,超过时执行内存淘汰策略。
淘汰策略的执行过程
客户端执行一条命令,导致Redis需要增加数据(比如set key value);
Redis会检查内存使用情况,如果内存使用超过 maxmemory,就会按照配置的置换策略maxmemory-policy删除一些key;
再执行新的数据的set操作。

注:其它场景,如生成快照RDB、载入RDB等未写。

缓存雪崩

数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。

导致缓存整体或大量失效的场景一般有:
1、缓存服务宕机,如Redis集群彻底崩溃;
2、在某个集中的时间段内,系统预加载的缓存集中失效了;

解决:缓存的高可用性(如使用Redis Sentinel 和 Redis Cluster)、缓存降级、Redis备份和快速预热、提前演练。
在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
总结:过期时间不同,集群。

缓存穿透(2019年案例)

缓存穿透是指查询一个一不存在的数据。例如:从缓存redis没有命中,需要从mysql数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。失去了缓存的意义。

缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。

解决:
如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。设置一个过期时间或者当有值的时候将缓存中的值替换掉即可。
可以给key设置一些格式规则,然后查询之前先过滤掉不符合规则的Key。

规范key过滤
规范key的命名,并且统一缓存查询的入口,在入口处对key的命名格式进行检测,过滤掉不规范key的访问,这样可以过滤掉大部分的恶意攻击。如约定项目中Redis缓存key的前缀都是以”公司名项目名REDIS_”开头,不符合这个约定的key在一开始就过滤掉。

缓存空值
如果查询DB返回的数据为空,我们仍然把这个空值放到Redis缓存中,只是将它的过期时间设置的很短,另外为了避免不必要的内存消耗,可以定期清理空值的key。

加锁
根据key从缓存中获取到的value为空时,先锁上,再去查DB将数据加载到缓存,若其它线程获取锁失败,则等待一段时间后重试,从而避免了大量请求直接打到DB。单机可以使用synchronized或ReentrantLock加锁,分布式环境需要加分布式锁,如Redis分布式锁。

布隆过滤器
布隆过滤器是一个叫“布隆”的人提出的,本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure)。它本身是一个很长的二进制向量,特点是高效地插入和查询,可以用来确定 “某一条数据一定不存在或者可能存在一个集合中”。
只能判断元素是否一定不存在,而无法判断数据是否一定存在。

缓存击穿

缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
总结:不过期,加锁。

缓存与数据库双写一致

(最终一致性和强一致性)
如果对数据有强一致性要求,不能放缓存。

读请求: 不要求强一致性的读请求,走redis,要求强一致性的直接从mysql读取

写请求: 数据首先都写到数据库,之后更新redis。

三种更新策略:

  • 先删除缓存,再更新数据库;
  • 先更新数据库,再更新缓存;
  • 先更新数据库,再删除缓存;
  • 不要先写redis再写数据库,因为写数据库可能会失败。

延时双删。1)先删除缓存 2)再写数据库 3)休眠500毫秒(根据具体的业务时间来定) 4)再次删除缓存。

1
2
3
4
5
6
public void write(String key,Object data){
trueredis.delKey(key);
truedb.updateData(data);
trueThread.sleep(1000);
trueredis.delKey(key);
}

参考:https://coolshell.cn/articles/17416.html

一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。

  • 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大
  • 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型

redis hash一致性

N台服务器做缓存,通过哈希算法,将缓存固定分配到某台服务器,最简单的理解:以ID为key,对N取模,即ID%N,得到余数,即为存放的服务器。如果添加或减少服务器,用旧的哈希算法,则得不到正确的缓存位置。因此,产生哈希一致性算法。

redis使用的是 Consistent hashing 算法:

  • Consistent hashing 是一致性hash算法
  • Consistent hashing 算法早在1997年就在论文《Consistent hashing and random trees》中提出

环形hash空间。范围:0~2^32-1

Hash倾斜性:节点分布不均衡,引入虚拟节点,使得分布均衡

数据分片技术(2020年论文)

数据库

mysql备份方式:mysqldump、mysqlbinlog。