本文已参与「新人创作礼」活动,一起开启掘金创作之路
21、Redis有哪些部署方案?
单机版:单机部署,单机redis能够承载的 QPS 大概就在上万到几万不等。这种部署方式很少使用。存在的问题:1、内存容量有限 2、处理能力有限 3、无法高可用。
主从模式:一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。master 节点挂掉后,需要手动指定新的 master,可用性不高,基本不用。
哨兵模式:主从复制存在不能自动故障转移、达不到高可用的问题。哨兵模式解决了这些问题。通过哨兵机制可以自动切换主从节点。master 节点挂掉后,哨兵进程会主动选举新的 master,可用性高,但是每个节点存储的数据是一样的,浪费内存空间。数据量不是很多,集群规模不是很大,需要自动容错容灾的时候使用。
Redis cluster:服务端分片技术,3.0版本开始正式提供。Redis Cluster并没有使用一致性hash,而是采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。主要是针对海量数据+高并发+高可用的场景,如果是海量数据,如果你的数据量很大,那么建议就用Redis cluster,所有主节点的容量总和就是Redis cluster可缓存的数据容量。
22、主从架构
单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。
Redis的复制功能是支持多个数据库之间的数据同步。主数据库可以进行读写操作,当主数据库的数据发生变化时会自动将数据同步到从数据库。从数据库一般是只读的,它会接收主数据库同步过来的数据。一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
主从复制的原理
①当启动一个从节点时,它会发送一个 PSYNC 命令给主节点;
②如果是从节点初次连接到主节点,那么会触发一次全量复制。此时主节点会启动一个后台线程,开始生成一份 RDB 快照文件;
③同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, 主节点会将RDB文件发送给从节点,从节点会先将RDB文件写入本地磁盘,然后再从本地磁盘加载到内存中;
④接着主节点会将内存中缓存的写命令发送到从节点,从节点同步这些数据;
⑤如果从节点跟主节点之间网络出现故障,连接断开了,会自动重连,连接之后主节点仅会将部分缺失的数据同步给从节点。
23、哨兵Sentinel
主从复制存在不能自动故障转移、达不到高可用的问题。哨兵模式解决了这些问题。通过哨兵机制可以自动切换主从节点。
客户端连接Redis的时候,先连接哨兵,哨兵会告诉客户端Redis主节点的地址,然后客户端连接上Redis并进行后续的操作。当主节点宕机的时候,哨兵监测到主节点宕机,会重新推选出某个表现良好的从节点成为新的主节点,然后通过发布订阅模式通知其他的从服务器,让它们切换主机。
工作原理
①每个Sentinel以每秒钟一次的频率向它所知道的Master,Slave以及其他 Sentinel实例发送一个 PING命令。
②如果一个实例距离最后一次有效回复 PING 命令的时间超过指定值, 则这个实例会被 Sentine 标记为主观下线。
③如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel要以每秒一次的频率确认Master是否真正进入主观下线状态。
④当有足够数量的 Sentinel(大于等于配置文件指定值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。若没有足够数量的 Sentinel同意 Master 已经下线, Master 的客观下线状态就会被解除。 若 Master重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
⑤哨兵节点会选举出哨兵 leader,负责故障转移的工作。
⑥哨兵 leader 会推选出某个表现良好的从节点成为新的主节点,然后通知其他从节点更新主节点信息。
24、Redis cluster
哨兵模式解决了主从复制不能自动故障转移、达不到高可用的问题,但还是存在主节点的写能力、容量受限于单机配置的问题。而cluster模式实现了Redis的分布式存储,每个节点存储不同的内容,解决主节点的写能力、容量受限于单机配置的问题。
Redis cluster集群节点最小配置6个节点以上(3主3从),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
Redis cluster采用虚拟槽分区,所有的键根据哈希函数映射到0~16383个整数槽内,每个节点负责维护一部分槽以及槽所映射的键值数据。
工作原理:
①通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384 个槽位
②每份数据分片会存储在多个互为主从的多节点上
③数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
④同一分片多个节点间的数据不保持一致性
⑤读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
⑥扩容时时需要需要把旧节点的数据迁移一部分到新节点
在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
优点:
①无中心架构,支持动态扩容;
②数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布;
③高可用性。部分节点不可用时,集群仍可用。集群模式能够实现自动故障转移(failover),节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色转换。
缺点:
①不支持批量操作(pipeline)。
②数据通过异步复制,不保证数据的强一致性。
③事务操作支持有限,只支持多key在同一节点上的事务操作,当多个key分布于不同的节点上时无法使用事务功能。
④key作为数据分区的最小粒度,不能将一个很大的键值对象如hash、list等映射到不同的节点。
⑤不支持多数据库空间,单机下的Redis可以支持到16个数据库,集群模式下只能使用1个数据库空间。
⑥只能使用0号数据库。
25、哈希分区算法有哪些?
节点取余分区。使用特定的数据,如Redis的键或用户ID,对节点数量N取余:hash(key)%N计算出哈希值,用来决定数据映射到哪一个节点上。 优点是简单性。扩容时通常采用翻倍扩容,避免数据映射全部被打乱导致全量迁移的情况。
一致性哈希分区。为系统中每个节点分配一个token,范围一般在0 ~ 232,这些token构成一个哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点。 这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响。
虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据。Redis Cluser采用虚拟槽分区算法。
26、过期键的删除策略?
①被动删除。在访问key时,如果发现key已经过期,那么会将key删除。
②主动删除。定时清理key,每次清理会依次遍历所有DB,从db随机取出20个key,如果过期就删除,如果其中有5个key过期,那么就继续对这个db进行清理,否则开始清理下一个db。
③内存不够时清理。Redis有最大内存的限制,通过maxmemory参数可以设置最大内存,当使用的内存超过了设置的最大内存,就要进行内存释放, 在进行内存释放的时候,会按照配置的淘汰策略清理内存。
27、内存淘汰策略有哪些?
当Redis的内存超过最大允许的内存之后,Redis 会触发内存淘汰策略,删除一些不常用的数据,以保证Redis服务器正常运行。
Redisv4.0前提供 6 种数据淘汰策略:
①volatile-lru:LRU(Least Recently Used),最近使用。利用LRU算法移除设置了过期时间的key
②allkeys-lru:当内存不足以容纳新写入数据时,从数据集中移除最近最少使用的key
③volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
④volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
⑤allkeys-random:从数据集中任意选择数据淘汰
⑥no-eviction:禁止删除数据,当内存不足以容纳新写入数据时,新写入操作会报错
Redisv4.0后增加以下两种:
①volatile-lfu:LFU,Least Frequently Used,最少使用,从已设置过期时间的数据集中挑选最不经常使用的数据淘汰。
②allkeys-lfu:当内存不足以容纳新写入数据时,从数据集中移除最不经常使用的key。
内存淘汰策略可以通过配置文件来修改,相应的配置项是maxmemory-policy,默认配置是noeviction。
28、如何保证缓存与数据库双写时的数据一致性?
先删除缓存再更新数据库
①进行更新操作时,先删除缓存,然后更新数据库,后续的请求再次读取时,会从数据库读取后再将新数据更新到缓存。
②存在的问题:删除缓存数据之后,更新数据库完成之前,这个时间段内如果有新的读请求过来,就会从数据库读取旧数据重新写到缓存中,再次造成不一致,并且后续读的都是旧数据。
先更新数据库再删除缓存
①进行更新操作时,先更新MySQL,成功之后,删除缓存,后续读取请求时再将新数据回写缓存。
②存在的问题:更新MySQL和删除缓存这段时间内,请求读取的还是缓存的旧数据,不过等数据库更新完成,就会恢复一致,影响相对比较小。
异步更新缓存
①数据库的更新操作完成后不直接操作缓存,而是把这个操作命令封装成消息扔到消息队列中,然后由Redis自己去消费更新数据,消息队列可以保证数据操作顺序一致性,确保缓存系统的数据正常。
以上几个方案都不完美,需要根据业务需求,评估哪种方案影响较小,然后选择相应的方案。
29、缓存常见问题
缓存穿透
缓存穿透是指查询一个不存在的数据,由于缓存是不命中时被动写的,如果从DB查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了。
缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重挂掉。
缓存击穿
缓存击穿是指大量的请求同时查询一个 key 时,此时这个 key 正好失效了,就会导致大量的请求都落到数据库。缓存击穿是查询缓存中失效的 key,而缓存穿透是查询不存在的 key。
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
30、缓存穿透怎么解决?
①缓存空值,不会查数据库。
②采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,查询不存在的数据会被这个bitmap拦截掉,从而避免了对DB的查询压力。
布隆过滤器的原理:当一个元素被加入集合时,通过K个哈希函数将这个元素映射成一个位数组中的K个点,把它们置为1。查询时,将元素通过哈希函数映射之后会得到k个点,如果这些点有任何一个0,则被检元素一定不在,直接返回;如果都是1,则查询元素很可能存在,就会去查询Redis和数据库。
布隆过滤器一般用于在大数据量的集合中判定某元素是否存在。