共计 9796 个字符,预计需要花费 25 分钟才能阅读完成。
>> 面试常问问题一
redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?理解一致性 hash 算法吗?
1、面试官心理剖析
在前几年,redis 如果要搞几个节点,每个节点存储一部分的数据,得借助一些中间件来实现,比如说有 codis,或者 twemproxy,都有。有一些 redis 中间件,你读写 redis 中间件,redis 中间件负责将你的数据分布式存储在多台机器上的 redis 实例中。
这两年,redis 一直在倒退,redis 也一直有新的版本,当初的 redis 集群模式,能够做到在多台机器上,部署多个 redis 实例,每个实例存储一部分的数据,同时每个 redis 主实例能够挂 redis 从实例,主动确保说,如果 redis 主实例挂了,会主动切换到 redis 从实例上来。
当初 redis 的新版本,大家都是用 redis cluster 的,也就是 redis 原生反对的 redis 集群模式,那么面试官必定会就 redis cluster 对你来个几连炮。要是你没用过 redis cluster,失常,以前很多人用 codis 之类的客户端来反对集群,然而起码你得钻研一下 redis cluster 吧。
如果你的数据量很少,次要是承载高并发高性能的场景,比方你的缓存个别就几个 G,单机就足够了,能够应用 replication,一个 master 多个 slaves,要几个 slave 跟你要求的读吞吐量无关,而后本人搭建一个 sentinel 集群去保障 redis 主从架构的高可用性。
redis cluster,次要是针对海量数据 + 高并发 + 高可用的场景。redis cluster 撑持 N 个 redis master node,每个 master node 都能够挂载多个 slave node。这样整个 redis 就能够横向扩容了。如果你要撑持更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能寄存更多的数据了。
2、面试题分析
redis cluster 介绍
主动将数据进行分片,每个 master 上放一部分数据
提供内置的高可用反对,局部 master 不可用时,还是能够持续工作的
在 redis cluster 架构下,每个 redis 要放开两个端口号,比方一个是 6379,另外一个就是 加 1w 的端口号,比方 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的货色,cluster bus 的通信,用来进行故障检测、配置更新、故障转移受权。cluster bus 用了另外一种二进制的协定,gossip 协定,用于节点间进行高效的数据交换,占用更少的网络带宽和解决工夫。
3、节点间的外部通信机制
1)根本通信原理
集群元数据的保护有两种形式:集中式、Gossip 协定。redis cluster 节点间采纳 gossip 协定进行通信。
集中式是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据畛域的 storm。它是分布式的大数据实时计算引擎,是集中式的元数据存储的构造,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储保护。
redis 保护集群元数据采纳另一个形式,gossip 协定,所有节点都持有一份元数据,不同的节点如果呈现了元数据的变更,就一直将元数据发送给其它的节点,让其它节点也进行元数据的变更。
集中式的益处在于,元数据的读取和更新,时效性十分好,一旦元数据呈现了变更,就立刻更新到集中式的存储中,其它节点读取的时候就能够感知到;不好在于,所有的元数据的更新压力全副集中在一个中央,可能会导致元数据的存储有压力。
gossip 益处在于,元数据的更新比拟扩散,不是集中在一个中央,更新申请会陆陆续续打到所有节点下来更新,升高了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。
10000 端口:每个节点都有一个专门用于节点间通信的端口,就是本人提供服务的端口号 +10000,比方 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 ping 音讯,同时其它几个节点接管到 ping 之后返回 pong。替换的信息:信息包含故障信息,节点的减少和删除,hash slot 信息等等。
2)gossip 协定
gossip 协定蕴含多种音讯,蕴含 ping、pong、meet、fail 等等。
meet:某个节点发送 meet 给新退出的节点,让新节点退出集群中,而后新节点就会开始与其它节点进行通信。
redis-trib.rb add-node
其实外部就是发送了一个 gossip meet 音讯给新退出的节点,告诉那个节点去退出咱们的集群。
ping:每个节点都会频繁给其它节点发送 ping,其中蕴含本人的状态还有本人保护的集群元数据,相互通过 ping 替换元数据。
pong:返回 ping 和 meeet,包含本人的状态和其余信息,也用于信息播送和更新。fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其余节点,告诉其余节点说,某个节点宕机啦。
3)ping 音讯深刻
ping 时要携带一些元数据,如果很频繁,可能会减轻网络累赘。
每个节点每秒会执行 10 次 ping,每次会抉择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 cluster_node_timeout / 2,那么立刻发送 ping,防止数据交换延时过长,落后的工夫太长了。
比如说,两个节点之间都 10 分钟没有替换数据了,那么整个集群处于重大的元数据不统一的状况,就会有问题。所以 cluster_node_timeout 能够调节,如果调得比拟大,那么会升高 ping 的频率。
每次 ping,会带上本人节点的信息,还有就是带上 1/10 其它节点的信息,发送进来,进行替换。至多蕴含 3 个其它节点的信息,最多蕴含 总节点数减 2 个其它节点的信息。
4)分布式寻址算法
hash 算法(大量缓存重建)
一致性 hash 算法(主动缓存迁徙)+ 虚构节点(主动负载平衡)
redis cluster 的 hash slot 算法
5)hash 算法
来了一个 key,首先计算 hash 值,而后对节点数取模。
而后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有申请过去,都会基于最新的残余 master 节点数去取模,尝试去取数据。
这会导致大部分的申请过去,全副无奈拿到无效的缓存,导致大量的流量涌入数据库。
6)一致性 hash 算法
一致性 hash 算法将整个 hash 值空间组织成一个虚构的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(应用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的地位。
来了一个 key,首先计算 hash 值,并确定此数据在环上的地位,从此地位沿环顺时针“行走”,遇到的第一个 master 节点就是 key 所在位置。
在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。减少一个节点也同理。
燃鹅,一致性哈希算法在节点太少时,容易因为节点散布不平均而造成缓存热点的问题。为了解决这种热点问题,一致性 hash 算法引入了虚构节点机制,即对每一个节点计算多个 hash,每个计算结果地位都搁置一个虚构节点。这样就实现了数据的均匀分布,负载平衡。
7)redis cluster 的 hash slot 算法
redis cluster 有固定的 16384 个 hash slot,对每个 key 计算 CRC16 值,而后对 16384 取模,能够获取 key 对应的 hash slot。
redis cluster 中每个 master 都会持有局部 slot,比方有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的减少和移除很简略,减少一个 master,就将其余 master 的 hash slot 挪动局部过来,缩小一个 master,就将它的 hash slot 挪动到其余 master 下来。挪动 hash slot 的老本是非常低的。客户端的 api,能够对指定的数据,让他们走同一个 hash slot,通过 hash tag 来实现。
任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。
4、redis cluster 的高可用与主备切换原理
redis cluster 的高可用的原理,简直跟哨兵是相似的。
1)判断节点宕机
如果一个节点认为另外一个节点宕机,那么就是 pfail,主观宕机。如果多个节点都认为另外一个节点宕机了,那么就是 fail,主观宕机,跟哨兵的原理简直一样,sdown,odown。
在 cluster-node-timeout 内,某个节点始终没有返回 pong,那么就被认为 pfail。如果一个节点认为某个节点 pfail 了,那么会在 gossip ping 音讯中,ping 给其余节点,如果超过半数的节点都认为 pfail 了,那么就会变成 fail。
2)从节点过滤
对宕机的 master node,从其所有的 slave node 中,抉择一个切换成 master node。查看每个 slave node 与 master node 断开连接的工夫,如果超过了,那么就没有资格切换成 master。
3)从节点选举
每个从节点,都依据本人对 master 复制数据的 offset,来设置一个选举工夫,offset 越大(复制数据越多)的从节点,选举工夫越靠前,优先进行选举。
所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点能够切换成 master。
从节点执行主备切换,从节点切换为主节点。
与哨兵比拟:
整个流程跟哨兵相比,十分相似,所以说,redis cluster 功能强大,间接集成了 replication 和 sentinel 的性能。
>> 面试常问问题二
理解什么是 redis 的雪崩、穿透和击穿?redis 解体之后会 怎么样?零碎该如何应答这种状况?如何解决 redis 的穿透?
1、面试官心理剖析
其实这是问到缓存必问的,因为缓存雪崩和穿透,是缓存最大的两个问题,要么不呈现,一旦呈现就是致命性的问题,所以面试官肯定会问你。
2、面试题分析
1)缓存雪崩
对于零碎 A,假如每天高峰期每秒 5000 个申请,原本缓存在高峰期能够扛住每秒 4000 个申请,然而缓存机器意外产生了全盘宕机。缓存挂了,此时 1 秒 5000 个申请全副落数据库,数据库必然扛不住,它会报一下警,而后就挂了。
此时,如果没有采纳什么特地的计划来解决这个故障,DBA 很着急,重启数据库,然而数据库立马又被新的流量给打死了。
这就是缓存雪崩。
大概在 3 年前,国内比拟出名的一个互联网公司,曾因为缓存事变,导致雪崩,后盾零碎全副解体,事变从当天下午继续到早晨凌晨 3~4 点,公司损失了几千万。
缓存雪崩的事先事中预先的解决方案如下。- 事先:redis 高可用,主从 + 哨兵,redis cluster,防止全盘解体。- 事中:本地 ehcache 缓存 + hystrix 限流 & 降级,防止 MySQL 被打死。- 预先:redis 长久化,一旦重启,主动从磁盘上加载数据,疾速复原缓存数据。
用户发送一个申请,零碎 A 收到申请后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的后果,写入 ehcache 和 redis 中。
限流组件,能够设置每秒的申请,有多少能通过组件,残余的未通过的申请,怎么办?走降级!能够返回一些默认的值,或者情谊提醒,或者空白的值。
益处:- 数据库相对不会死,限流组件确保了每秒只有多少个申请能通过。- 只有数据库不死,就是说,对用户来说,2/5 的申请都是能够被解决的。- 只有有 2/5 的申请能够被解决,就意味着你的零碎没死,对用户来说,可能就是点击几次刷不进去页面,然而多点几次,就能够刷进去一次。
2)缓存穿透
对于零碎 A,假如一秒 5000 个申请,后果其中 4000 个申请是黑客收回的歹意攻打。黑客收回的那 4000 个攻打,缓存中查不到,每次你去数据库里查,也查不到。
举个栗子。
数据库 id 是从 1 开始的,后果黑客发过来的申请 id 全部都是正数。这样的话,缓存中不会有,申请每次都“视缓存于无物”,间接查询数据库。这种歹意攻打场景的缓存穿透就会间接把数据库给打死。
解决形式很简略,每次零碎 A 从数据库中只有没查到,就写一个空值到缓存里去,比方 set -999 UNKNOWN。而后设置一个过期工夫,这样的话,下次有雷同的 key 来拜访的时候,在缓存生效之前,都能够间接从缓存中取数据。
3)缓存击穿
缓存击穿,就是说某个 key 十分热点,拜访十分频繁,处于集中式高并发拜访的状况,当这个 key 在生效的霎时,大量的申请就击穿了缓存,间接申请数据库,就像是在一道屏障上凿开了一个洞。
解决形式也很简略,能够将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,期待第一个申请构建完缓存之后,再开释锁,进而其它申请能力通过该 key 拜访数据。
>> 面试常问问题三:
如何保障缓存与数据库的双写一致性?
1、面试官心理剖析
你只有用缓存,就可能会波及到缓存与数据库双存储双写,你只有是双写,就肯定会有数据一致性的问题,那么你如何解决一致性问题?
2、面试题分析
一般来说,如果容许缓存能够略微的跟数据库偶然有不统一的状况,也就是说如果你的零碎不是严格要求“缓存 + 数据库”必须放弃一致性的话,最好不要做这个计划,即:读申请和写申请串行化,串到一个内存队列里去。
串行化能够保障肯定不会呈现不统一的状况,然而它也会导致系统的吞吐量大幅度降低,用比失常状况下多几倍的机器去撑持线上的一个申请。
1)Cache Aside Pattern
最经典的缓存 + 数据库读写的模式,就是 Cache Aside Pattern。- 读的时候,先读缓存,缓存没有的话,就读数据库,而后取出数据后放入缓存,同时返回响应。- 更新的时候,先更新数据库,而后再删除缓存。
为什么是删除缓存,而不是更新缓存?
起因很简略,很多时候,在简单点的缓存场景,缓存不单单是数据库中间接取出来的值。
比方可能更新了某个表的一个字段,而后其对应的缓存,是须要查问另外两个表的数据并进行运算,能力计算出缓存最新的值的。
另外更新缓存的代价有时候是很高的。是不是说,每次批改数据库的时候,都肯定要将其对应的缓存更新一份?兴许有的场景是这样,然而对于比较复杂的缓存数据计算的场景,就不是这样了。
如果你频繁批改一个缓存波及的多个表,缓存也频繁更新。然而问题在于,这个缓存到底会不会被频繁拜访到?
举个栗子,一个缓存波及的表的字段,在 1 分钟内就批改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;然而这个缓存在 1 分钟内只被读取了 1 次,有大量的冷数据。
实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就从新计算一次而已,开销大幅度降低。用到缓存才去算缓存。
其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思维,不要每次都从新做简单的计算,不论它会不会用到,而是让它到须要被应用的时候再从新计算。像 mybatis,hibernate,都有懒加载思维。
查问一个部门,部门带了一个员工的 list,没有必要说每次查问部门,都外面的 1000 个员工的数据也同时查出来啊。80% 的状况,查这个部门,就只是要拜访这个部门的信息就能够了。先查部门,同时要拜访外面的员工,那么这个时候只有在你要拜访外面的员工的时候,才会去数据库外面查问 1000 个员工。
2)最高级的缓存不统一问题及解决方案
问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就呈现了不统一。
解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不统一。因为读的时候缓存没有,所以去读了数据库中的旧数据,而后更新到缓存中。
3)比较复杂的数据不统一问题剖析
数据产生了变更,先删除了缓存,而后要去批改数据库,此时还没批改。一个申请过去,去读缓存,发现缓存空了,去查询数据库,查到了批改前的旧数据,放到了缓存中。随后数据变更的程序实现了数据库的批改。
完了,数据库和缓存中的数据不一样了……
为什么上亿流量高并发场景下,缓存会呈现这个问题?
只有在对一个数据在并发的进行读写的时候,才可能会呈现这种问题。其实如果说你的并发量很低的话,特地是读并发很低,每天访问量就 1 万次,那么很少的状况下,会呈现方才形容的那种不统一的场景。
然而问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只有有数据更新的申请,就可能会呈现上述的数据库 + 缓存不统一的状况。
解决方案如下:
更新数据的时候,依据数据的惟一标识,将操作路由之后,发送到一个 jvm 外部队列中。读取数据的时候,如果发现数据不在缓存中,那么将从新读取数据 + 更新缓存的操作,依据惟一标识路由之后,也发送同一个 jvm 外部队列中。
一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,而后一条一条的执行。这样的话一个数据变更的操作,先删除缓存,而后再去更新数据库,然而还没实现更新。此时如果一个读申请过去,没有读到缓存,那么能够先将缓存更新的申请发送到队列中,此时会在队列中积压,而后同步期待缓存更新实现。
这里有一个优化点,一个队列中,其实多个更新缓存申请串在一起是没意义的,因而能够做过滤,如果发现队列中曾经有一个更新缓存的申请了,那么就不必再放个更新申请操作进去了,间接期待后面的更新操作申请实现即可。
待那个队列对应的工作线程实现了上一个操作的数据库的批改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,而后写入缓存中。如果申请还在等待时间范畴内,一直轮询发现能够取到值了,那么就间接返回;如果申请期待的工夫超过肯定时长,那么这一次间接从数据库中读取以后的旧值。
4)高并发的场景下,该解决方案要留神的问题
读申请长时阻塞
因为读申请进行了十分轻度的异步化,所以肯定要留神读超时的问题,每个读申请必须在超时工夫范畴内返回。该解决方案,最大的危险点在于说,可能数据更新很频繁,导致队列中积压了大量更新操作在外面,而后读申请会产生大量的超时,最初导致大量的申请间接走数据库。务必通过一些模仿实在的测试,看看更新数据的频率是怎么的。
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因而须要依据本人的业务状况进行测试,可能须要部署多个服务,每个服务摊派一些数据的更新操作。
如果一个内存队列里竟然会挤压 100 个商品的库存批改操作,每隔库存批改操作要消耗 10ms 去实现,那么最初一个商品的读申请,可能期待 后,能力失去数据,这个时候就导致读申请的长时阻塞。
肯定要做依据理论业务零碎的运行状况,去进行一些压力测试,和模仿线上环境,去看看最忙碌的时候,内存队列可能会挤压多少更新操作,可能会导致最初一个更新操作对应的读申请,会 hang 多少工夫,如果读申请在 200ms 返回,如果你计算过后,哪怕是最忙碌的时候,积压 10 个更新操作,最多期待 200ms,那还能够的。
如果一个内存队列中可能积压的更新操作特地多,那么你就要加机器,让每个机器上部署的服务实例解决更少的数据,那么每个内存队列中积压的更新操作就会越少。
其实依据之前的我的项目教训,一般来说,数据的写频率是很低的,因而实际上失常来说,在队列中积压的更新操作应该是很少的。像这种针对读高并发、读缓存架构的我的项目,一般来说写申请是非常少的,每秒的 QPS 能到几百就不错了。
咱们来理论粗略测算一下。
如果一秒有 500 的写操作,如果分成 5 个工夫片,每 200ms 就 100 个写操作,放到 20 个内存队列中,每个内存队列,可能就积压 5 个写操作。每个写操作性能测试后,个别是在 20ms 左右就实现,那么针对每个内存队列的数据的读申请,也就最多 hang 一会儿,200ms 以内必定能返回了。
通过方才简略的测算,咱们晓得,单机撑持的写 QPS 在几百是没问题的,如果写 QPS 扩充了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。
读申请并发量过高
这里还必须做好压力测试,确保凑巧碰上上述情况的时候,还有一个危险,就是忽然间大量读申请会在几十 毫秒的延时 hang 在服务上,看服务能不能扛的住,须要多少机器能力扛住最大的极限状况的峰值。
然而因为并不是所有的数据都在同一时间更新,缓存也不会同一时间生效,所以每次可能也就是多数数据的缓存生效了,而后那些数据对应的读申请过去,并发量应该也不会特地大。
多服务实例部署的申请路由
可能这个服务部署了多个实例,那么必须保障说,执行数据更新操作,以及执行缓存更新操作的申请,都通过 Nginx 服务器路由到雷同的服务实例上。
比如说,对同一个商品的读写申请,全副路由到同一台机器上。能够本人去做服务间的依照某个申请参数的 hash 路由,也能够用 Nginx 的 hash 路由性能等等。
热点商品的路由问题,导致申请的歪斜
万一某个商品的读写申请特地高,全部打到雷同的机器的雷同的队列外面去了,可能会造成某台机器的压力过大。
就是说,因为只有在商品数据更新的时候才会清空缓存,而后才会导致读写并发,所以其实要依据业务零碎去看,如果更新频率不是太高的话,这个问题的影响并不是特地大,然而确实可能某些机器的负载会高一些。
>> 面试常问问题四
redis 的并发竞争问题是什么?如何解决这个问题?理解 redis 事务的 CAS 计划吗?
1、面试官心理剖析
这个也是线上十分常见的一个问题,就是多客户端同时并发写一个 key,可能原本应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,批改值之后再写回去,只有程序错了,数据就错了。
而且 redis 本人就有人造解决这个问题的 CAS 类的乐观锁计划。
2、面试题分析
某个时刻,多个零碎实例都去更新某个 key。能够基于 zookeeper 实现分布式锁。每个零碎通过 zookeeper 获取分布式锁,确保同一时间,只能有一个零碎实例在操作某个 key,他人都不容许读和写。
你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保留一个工夫戳,从 mysql 查出来的时候,工夫戳也查出来。
每次要写之前,先判断一下以后这个 value 的工夫戳是否比缓存里的 value 的工夫戳要新。如果是的话,那么能够写,否则,就不能用旧的数据笼罩新的数据。
>> 面试常问问题五
生产环境中的 redis 是怎么部署的?
1、面试官心理剖析
看看你理解不理解你们公司的 redis 生产集群的部署架构,如果你不理解,那么的确你就很尽职了,你的 redis 是主从架构?集群架构?用了哪种集群计划?有没有做高可用保障?有没有开启长久化机制确保能够进行数据恢复?线上 redis 给几个 G 的内存?设置了哪些参数?压测后你们 redis 集群承载多少 QPS?
兄弟,这些你必须是门儿清的,否则你的确是没好好思考过。
2、面试题分析
redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写顶峰 qps 可能能够达到每秒 5 万,5 台机器最多是 25 万读写申请 /s。
机器是什么配置?32G 内存 + 8 核 CPU + 1T 磁盘,然而调配给 redis 过程的是 10g 内存,个别线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。5 台机器对外提供读写,一共有 50g 内存。
因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会主动故障迁徙,redis 从实例会主动变成主实例持续提供读写服务。
你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的申请量。
其实大型的公司,会有基础架构的 team 负责缓存集群的运维。