Redis面试18问,看完,把面试官按在地上暴打摩擦!

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 面试官:今天要不来聊聊Redis吧?候选者:好候选者:我个人是这样理解的:无论Redis也好、MySQL也好、HDFS也好、HBase也好,他们都是存储数据的地方候选者:因为它们的设计理念的不同,我们会根据不同的应用场景使用不同的存储候选者:像Redis一般我们会把它用作于缓存候选者:当然啦,日常有的应用场景比较简单,用个HashMap也能解决很多的问题了,没必要上Redis候选者:这就好比,有的单机限流可能应对某些场景就够用了,也没必要说一定要上分布式限流把系统搞得复杂

面试官:今天要不来聊聊Redis吧?

候选者:好

候选者:我个人是这样理解的:无论Redis也好、MySQL也好、HDFS也好、HBase也好,他们都是存储数据的地方

候选者:因为它们的设计理念的不同,我们会根据不同的应用场景使用不同的存储

候选者:像Redis一般我们会把它用作于缓存候选者:当然啦,日常有的应用场景比较简单,用个HashMap也能解决很多的问题了,没必要上Redis候选者:这就好比,有的单机限流可能应对某些场景就够用了,也没必要说一定要上分布式限流把系统搞得复杂

面试官:你在项目里有用到Redis吗?怎么用的?

候选者:Redis肯定是用到的,我负责的项目几乎都会有Redis的踪影

候选者:我举几个我这边项目用的案例呗?

面试官:嗯

候选者:我这边负责消息管理平台,简单来说就是发消息的

候选者:那发完消息肯定我们是得知道消息有没有下发成功的,是吧?

候选者:于是我们系统有一套完整的链路追踪体系候选者:其中实时的数据我们就用Redis来进行存储,有实时肯定就会有离线的嘛(离线的数据我们是存储到Hive的)

候选者:对消息进行实时链路追踪,我这边就用了Redis好几种的数据结构

候选者:分别有Set、List和Hash

面试官:嗯….

候选者:我再稍微铺垫下链路追踪的背景吧~

候选者:要在消息管理平台发消息,首先得在后台新建一个「模板」,有模板自然会有一个模板ID

候选者:对模板ID进行扩展,比如说加上日期和固定的业务参数,形成的ID可以唯一标识某个模板的下发链路

候选者:在系统上,我这边叫它为UMPID

候选者:在发送入口处会对所有需要下发的消息打上UMPID,然后在关键链路上打上对应的点位

面试官:嗯,你继续吧

候选者:接下来的工作就是清洗出统一的模型,然后根据不同维度进行处理啦。比如说:

候选者:我要看某一天下发的所有模板有哪些,那只要我把清洗出来后数据的,将对应UMPID扔到了Set就好了

候选者:我要看某一个模板的消息下发的整体链路情况,那我以UMPID为Key,Value是Hash结构,Key是state,Value则是人数

候选者:这里的state我们在下发的过程中打的关键点位,比如接收到消息打个51,消息被去重了打个61,消息成功下发了打个81…

候选者:以UMPID为Key,Hash结构的Key(State)进行不断的累加,就可以实现某一个模板的消息下发的整体链路情况

候选者:我要看某个用户当天下发的消息有哪些,以及这些消息的整体链路是如何。

候选者:这边我用的是List结构,Key是userId,Value则是UMPID+state(关键点位)+processTime(处理时间)

面试官:嗯….

候选者:简单来说,就是通过Redis丰富的数据结构来实现对下发消息多个维度的统计候选者:不同的应用场景选择不同的数据结构,再等到透出做处理的时候,就变得十分简单了

候选者:消息下发过程中去重或者一般正常的场景就直接Key-Value就能符合需求了候选者:像bitmap、hyperloglogs、sortset、steam等等这些数据结构在我所负责的项目用得是真不多

候选者:要是我有机会去到贵公司,贵公司有相关的应用场景,我相信我也很快就能掌握

候选者:这些数据结构底层都由对应的object来支撑着,object记录对应的「编码」

候选者:其实就是会根据key-value存储的数量或者长度来使用选择不同的底层数据结构实现

候选者:比如说:ziplist压缩列表这个底层数据结构有可能上层的实现是list、hash和sortset

候选者:Hash结构的底层数据结构可能是hash和ziplist

候选者:在节省内存和性能的考量之中切换

候选者:Redis还是有点屌的啊

面试官:就你上面那个实时链路场景,可以用其他的存储替代吗?

候选者:嗯,理论上是可以的(或许可以尝试用HBase),但总体来说没这么好吧

候选者:因为Redis拥有丰富的数据结构,在透出的时候,处理会非常的方便。

候选者:如果不用Redis的话,还得做很多解析的工作候选者:并且,我那场景的并发还是相当大的(就一条消息发送,可能就产生10条记录)

候选者:监控峰值命令处理数会去到20k+QPS,当然了,这场景我肯定用了Pipeline的(不然处理会慢很多)

候选者:综合上面并发量和实时性以及数据结构,用Redis是一个比较好的选择

面试官:嗯….你觉得为什么Redis可以这么快?

候选者:首先,它是纯内存操作,内存本身就很快候选者:其次,它是单线程的,Redis服务器核心是基于非阻塞的IO多路复用机制,单线程避免了多线程的频繁上下文切换问题

候选者:至于这个单线程,其实官网也有过说明(:表示使用Redis往往的瓶颈在于内与和网络,而不在于CPU

面试官:了解

面试官:今天要不来聊聊Redis的持久化机制吧?

候选者:嗯,没问题的

候选者:在上一次面试已经说过了Redis是基于内存的

候选者:假设我们不做任何操作,只要Redis服务器重启(或者中途故障挂掉了),那内存的数据就会丢掉

候选者:我们作为使用方,肯定是不想Redis里头的数据会丢掉

候选者:所以Redis提供了持久化机制给我们用,分别是RDB和AOF

候选者:RDB指的就是:根据我们自己配置的时间或者手动去执行BGSAVE或SAVE命令,Redis就会去生成RDB文件

候选者:这个RDB文件实际上就是一个经过压缩的二进制文件,Redis可以通过这个文件在启动的时候来还原我们的数

据候选者:而AOF则是把Redis服务器接收到的所有写命令都记录到日志中候选者:Redis重跑一遍这个记录下的日志文件,就相当于还原了数据

面试官:那我就想问了,你上次不是说Redis是单线程吗

面试官:那比如你说的RDB,它会执行SAVE或BESAVE命令,生成文件面试官:那不是非常耗时的吗,那如果只有一个线程处理,那其他的请求不就得等了?

候选者:嗯,没错,Redis是单线程的。

候选者:以RDB持久化的过程为例,假设我们在配置上是定时去执行RDB存储Redis有自己的一套事件处理机制,主要处理文件事件(命令请求和应答等等)和时间事件(RDB定时持久化、清理过期的Key等的)

候选者:所以,定时的RDB实际上就是一个时间事件

候选者:线程不停地轮询就绪的事件,发现RDB的事件可执行时,则调用BGSAVE命令候选者:而BGSAVE命令实际上会fork出一个子进程来进行完成持久化(生成RDB文件

候选者:在fork的过程中,父进程(主线程)肯定是阻塞的。

候选者:但fork完之后,是fork出来的子进程去完成持久化。处理请求的进程该干嘛的就干嘛

候选者:所以说啊,Redis是单线程,理解是没错的,但没说人家不能fork进程来处理事情呀,对不对

候选者:还有就是,其实Redis在较新的版本中,有些地方都使用了多线程来进行处理候选者:比如说,一些删除的操作(UNLINK、FLUSHALL ASYNC等等)还有Redis 6.x 之后对网络数据的解析都用了多线程处理了。

候选者:只不过,核心的处理命令请求和响应还是单线程。

面试官:那AOF呢?AOF不是也要写文件吗?难道也是fork 了个子进程去做的?

候选者:emm,不是的。AOF是在命令执行完之后,把命令写在buffer缓冲区的(直接追加写)

候选者:那想要持久化,肯定得存盘嘛。Redis提供了几种策略供我们选择什么时候把缓冲区的数据写到磁盘

候选者:我记得有:每秒一次/每条命令都执行/从不存盘;一般我们会选每秒一次

候选者:Redis会启一个线程去刷盘,也不是用主线程去干的

面试官:那如果把执行过的命令都存起来

面试官:等启动的时候是可以再把这些写命令再执行一遍,达到恢复数据的效果面试官:这样会有什么样的问题吗?

候选者:嗯,问题就是,如果这些写入磁盘的「命令集合」不做任何处理,那该「命令集合」就会一直膨胀

候选者:其实就是该文件会变得非常大

候选者:Redis当然也考虑了这一点,它会fork个子进程会对「原始」命令集合进行重写

候选者:说白了就是会压缩,压缩完了之后只要替换原始文件就好了

面试官:那我又想问了,既然它是fork一个进程来对AOF进行重写的

面试官:前面你也提到了再fork时,主进程是阻塞的,但fork后,主进程会继续接收命令

面试官:你是说重写完(压缩)会进行文件覆盖

面试官:那这样不会丢数据吗?毕竟主进程在fork之后是一直会接收命令的

候选者:哦,我明白你的意思了。候选者:其实做法很简单啊,在fork子进程之后,把新接收到命令再写到另一个缓冲区不就好了吗

面试官:可以

面试官:那AOF和RDB用哪一个呢?

候选者:主要是看业务场景吧,我们这边是基于Redis自研了一套key-value存储

面试官:自研的?你们的Redis架构是什么?

候选者:别别别,当我没说。就是开源的,开源的。我们回到RDB和AOF上吧。

候选者:在新增namespace(实例) 的时候也会让你选择对应的使用场景

候选者:就是会让你通过不同的应用场景进行配置选择

候选者:比如说,业务上是允许重启时部分数据丢失的,那RDB就够用了(:

候选者:RDB在启动的时候恢复数据会比AOF快很多

候选者:在Redis4.0以后也支持了AOF和RDB混合

候选者:在官网是不建议仅仅只使用AOF的,如果对数据丢失容忍度是有要求的,建议是开启AOF+RDB一起用

候选者:总的来说,不同的场景使用不同的持久化策略吧

面试官:了解

面试官:顺便我想问下,假如Redis的内存满了,但业务还在写数据,会怎么样?

候选者:嗯,这个问题我也遇到过

候选者:一般来说,我们会淘汰那些「不活跃」的数据,然后把新的数据写进去

候选者:更多情况下,还是做好对应的监控和容量的考量吧

候选者:等容量达到阈值的时候,及时发现和扩容

面试官:你这懂得有点多啊

本文总结:

Redis持久化机制:RDB和AOF


RDB持久化:定时任务,BGSAVE命令 fork一个子进程生成RDB文件(二进制)


AOF持久化:根据配置将写命令存储至日志文件中,顺序写&&异步刷盘(子线程),重写AOF文件也是需要 fork 子进程。Redis4.0之后支持混合持久化,用什么持久化机制看业务场景

面试官:要不你来讲讲你最近在看的点呗?可以拉出来一起讨论下(今天我也不知道要问什么)

候选者:最近在看「Redis」相关的内容

面试官:嗯,我记得已经问过Redis的基础和持久化了

面试官:要不你来讲讲你公司的Redis是什么架构的咯?

候选者:我前公司的Redis架构是「分片集群」,使用的是「Proxy」层来对Key进行分流到不同的Redis服务器上

候选者:支持动态扩容、故障恢复等等

面试官:那你来聊下Proxy层的架构和基本实现原理?

候选者:抱歉,这块由中间件团队负责,具体我也没仔细看过

候选者:…

面试官:….

候选者:不过,我可以给你讲讲现有常见开源的Redis架构(:

面试官:那只能这样了,好吧,你开始吧

候选者:那我从基础讲起吧?

候选者:在之前提到了Redis有持久化机制,即便Redis重启了,可以依靠RDB或者AOF文件对数据进行重新加载

候选者:但在这时,只有一台Redis服务器存储着所有的数据,此时如果Redis服务器「暂时」没办法修复了,那依赖Redis的服务就没了

候选者:所以,为了Redis「高可用」,现在基本都会给Redis做「备份」:多启一台Redis服务器,形成「主从架构」

候选者:「从服务器」的数据由「主服务器」复制过去,主从服务器的数据是一致的候选者:如果主服务器挂了,那可以「手动」把「从服务器」升级为「主服务器」,缩短不可用时间

面试官:那「主服务器」是如何把自身的数据「复制」给「从服务器」的呢?

候选者:「复制」也叫「同步」,在Redis使用的是「PSYNC」命令进行同步,该命令有两种模型:完全重同步和部分重同步

候选者:可以简单理解为:如果是第一次「同步」,从服务器没有复制过任何的主服务器,或者从服务器要复制的主服务器跟上次复制的主服务器不一样,那就会采用「完全重同步」模式进行复制

候选者:如果只是由于网络中断,只是「短时间」断连,那就会采用「部分重同步」模式进行复制候选者:(假如主从服务器的数据差距实在是过大了,还是会采用「完全重同步」模式进行复制)

面试官:那「同步」的原理过程可以稍微讲下嘛?

候选者:嗯,没问题的候选者:主服务器要复制数据到从服务器,首先是建立Socket「连接」,这个过程会干一些信息校验啊、身份校验啊等事情候选者:然后从服务器就会发「PSYNC」命令给主服务器,要求同步(这时会带「服务器ID」RUNID和「复制进度」offset参数,如果从服务器是新的,那就没有)

候选者:主服务器发现这是一个新的从服务器(因为参数没带上来),就会采用「完全重同步」模式,并把「服务器ID」(runId)和「复制进度」(offset)发给从服务器,从服务器就会记下这些信息。

面试官:嗯…候选者:随后,主服务器会在后台生成RDB文件,通过前面建立好的连接发给从服务器

候选者:从服务器收到RDB文件后,首先把自己的数据清空,然后对RDB文件进行加载恢复

候选者:这个过程中,主服务器也没闲着(继续接收着客户端的请求)

面试官:嗯…

候选者:主服务器把生成RDB文件「之后修改的命令」会用「buffer」记录下来,等到从服务器加载完RDB之后,主服务器会把「buffer」记录下的命令都发给从服务器

候选者:这样一来,主从服务器就达到了数据一致性了(复制过程是异步的,所以数据是『最终一致性』)

面试官:嗯…

面试官:那「部分重同步」的过程呢?

候选者:嗯,其实就是靠「offset」来进行部分重同步。每次主服务器传播命令的时候,都会把「offset」给到从服务器

候选者:主服务器和从服务器都会将「offset」保存起来(如果两边的offset存在差异,那么说明主从服务器数据未完全同步)

候选者:从服务器断连之后,就会发「PSYNC」命令给主服务器,同样也会带着RUNID和offset(重连之后,这些信息还是存在的)

面试官:嗯…

候选者:主服务器收到命令之后,看RUNID是否能对得上,对得上,说明这可能以前就复制过一部分了

候选者:接着检查该「offset」是否在主服务器记录的offset还存在候选者:(这里解释下,因为主服务器记录offset使用的是一个环形buffer,如果该buffer满了,会覆盖以前的记录)

候选者:如果找到了,那就把从缺失的一部分offer开始,把对应的修改命令发给从服务器

候选者:如果从环形buffer没找到,那只能使用「完全重同步」模式再次进行主从复制了

面试官:主从复制这块我了解了,那你说到现在,Redis主库如果挂了,你还是得「手动」将从库升级为主库啊

面试官:你知道有什么办法能做到「自动」进行故障恢复吗?

候选者:必须的啊,接下来就到了「哨兵」登场了

面试官:开始你的表演吧。

候选者:「哨兵」干的事情主要就是:监控(监控主服务器的状态)、选主(主服务器挂了,在从服务器选出一个作为主服务器)、通知(故障发送消息给管理员)和配置(作为配置中心,提供当前主服务器的信息)

候选者:可以把「哨兵」当做是运行在「特殊」模式下的Redis服务器,为了「高可用」,哨兵也是集群架构的。

候选者:首先它需要跟Redis主从服务器创建对应的连接(获取它们的信息)

候选者:每个哨兵不断地用ping命令看主服务器有没有下线,如果主服务器在「配置时间」内没有正常响应,那当前哨兵就「主观」认为该主服务器下线了

候选者:其他「哨兵」同样也会ping该主服务器,如果「足够多」(还是看配置)的哨兵认为该主服务器已经下线,那就认为「客观下线」,这时就要对主服务器执行故障转移操作。

面试官:嗯…

候选者:「哨兵」之间会选出一个「领头」,选出领头的规则也比较多,总的来说就是先到先得(哪个快,就选哪个)

候选者:由「领头哨兵」对已下线的主服务器进行故障转移

面试官:嗯…

候选者:首先要在「从服务器」上挑选出一个,来作为主服务器

候选者:(这里也挑选讲究,比如:从库的配置优先级、要判断哪个从服务器的复制offset最大、RunID大小、跟master断开连接的时长…)

候选者:然后,以前的从服务器都需要跟新的主服务器进行「主从复制」候选者:已经下线的主服务器,再次重连的时候,需要让他成为新的主服务器的从服务器

面试官:嗯…我想问问,Redis在主从复制的和故障转移的过程中会导致数据丢失吗

候选者:显然是会的,从上面的「主从复制」流程来看,这个过程是异步的(在复制的过程中:主服务器会一直接收请求,然后把修改命令发给从服务器)

候选者:假如主服务器的命令还没发完给从服务器,自己就挂掉了。这时候想要让从服务器顶上主服务器,但从服务器的数据是不全的(:

候选者:还有另一种情况就是:有可能哨兵认为主服务器挂了,但真实是主服务器并没有挂( 网络抖动),而哨兵已经选举了一台从服务器当做是主服务器了,此时「客户端」还没反应过来,还继续写向旧主服务器写数

据候选者:等到旧主服务器重连的时候,已经被纳入到新主服务器的从服务器了…所以,那段时间里,客户端写进旧主服务器的数据就丢了

候选者:上面这两种情况(主从复制延迟&&脑裂),都可以通过配置来「尽可能」避免数据的丢失

候选者:(达到一定的阈值,直接禁止主服务器接收写请求,企图减少数据丢失的风险)

面试官:要不再来聊聊Redis分片集群?

候选者:嗯…分片集群说白了就是往每个Redis服务器存储一部分数据,所有的Redis服务器数据加起来,才组成完整的数据(分布式)

候选者:要想组成分片集群,那就需要对不同的key进行「路由」(分片)候选者:现在一般的路由方案有两种:「客户端路由」(SDK)和「服务端路由」(Proxy)

候选者:客户端路由的代表(Redis Cluster),服务端路由的代表(Codis)

面试官:要不来详细讲讲它们的区别呗?

候选者:今天有点儿困了,要不下次呗?

本文总结:

Redis实现高可用:AOF/RDB持久化机制主从架构(主服务器挂了,手动由从服务器顶上)引入哨兵机制自动故障转义


主从复制原理:PSYNC命令两种模式:完全重同步、部分重同步完全重同步:主从服务器建立连接、主服务器生成RDB文件发给从服务器、主服务器不阻塞(相关修改命令记录至buffer)、将修改命令发给从服务器部分重同步:从服务器断线重连,发送RunId和offset给主服务器,主服务器判断offset和runId,将还未同步给从服务器的offset相关指令进行发送


哨兵机制:哨兵可以理解为特殊的Redis服务器,一般会组成哨兵集群哨兵主要工作是监控、告警、配置以及选主当主服务器发生故障时,会「选出」一台从服务器来顶上「客观下线」的服务器,由「领头哨兵」进行切换


数据丢失:Redis的主从复制和故障转移阶段都有可能发生数据丢失问题(通过配置尽可能避免)

面试官:聊下Redis的分片集群,先聊 Redis Cluster好咯?

面试官:Redis Cluser是Redis 3.x才有的官方集群方案,这块你了解多少?

候选者:嗯,要不还是从基础讲起呗?

候选者:在前面聊Redis的时候,提到的Redis都是「单实例」存储所有的数据。

候选者:1. 主从模式下实现读写分离的架构,可以让多个从服务器承载「读流量」,但面对「写流量」时,始终是只有主服务器在抗。

候选者:2. 「纵向扩展」升级Redis服务器硬件能力,但升级至一定程度下,就不划算了。

候选者:纵向扩展意味着「大内存」,Redis持久化时的”成本”会加大(Redis做RDB持久化,是全量的,fork子进程时有可能由于使用内存过大,导致主线程阻塞时间过长)

候选者:所以,「单实例」是有瓶颈的

候选者:「纵向扩展」不行,就「横向扩展」呗。

候选者:用多个Redis实例来组成一个集群,按照一定的规则把数据「分发」到不同的Redis实例上。当集群所有的Redis实例的数据加起来,那这份数据就是全的

候选者:其实就是「分布式」的概念(:只不过,在Redis里,好像叫「分片集群」的人比较多?

候选者:从前面就得知了,要「分布式存储」,就肯定避免不了对数据进行「分发」(也是路由的意思)

候选者:从Redis Cluster讲起吧,它的「路由」是做在客户端的(SDK已经集成了路由转发的功能)

候选者:Redis Cluster对数据的分发的逻辑中,涉及到「哈希槽」(Hash Solt)的概念候选者:Redis Cluster默认一个集群有16384个哈希槽,这些哈希槽会分配到不同的Redis实例中

候选者:至于怎么「瓜分」,可以直接均分,也可以「手动」设置每个Redis实例的哈希槽,全由我们来决定

候选者:重要的是,我们要把这16384个都得瓜分完,不能有剩余!

候选者:当客户端有数据进行写入的时候,首先会对key按照CRC16算法计算出16bit的值(可以理解为就是做hash),然后得到的值对16384进行取模候选者:取模之后,自然就得到其中一个哈希槽,然后就可以将数据插入到分配至该哈希槽的Redis实例中

面试官:那问题就来了,现在客户端通过hash算法算出了哈希槽的位置,那客户端怎么知道这个哈希槽在哪台Redis实例上呢?

候选者:是这样的,在集群的中每个Redis实例都会向其他实例「传播」自己所负责的哈希槽有哪些。这样一来,每台Redis实例就可以记录着「所有哈希槽与实例」的关系了(:

候选者:有了这个映射关系以后,客户端也会「缓存」一份到自己的本地上,那自然客户端就知道去哪个Redis实例上操作了

面试官:那我又有问题了,在集群里也可以新增或者删除Redis实例啊,这个怎么整?

候选者:当集群删除或者新增Redis实例时,那总会有某Redis实例所负责的哈希槽关系会发生变化

候选者:发生变化的信息会通过消息发送至整个集群中,所有的Redis实例都会知道该变化,然后更新自己所保存的映射关系

候选者:但这时候,客户端其实是不感知的(:

候选者:所以,当客户端请求时某Key时,还是会请求到「原来」的Redis实例上。而原来的Redis实例会返回「moved」命令,告诉客户端应该要去新的Redis实例上去请求啦

候选者:客户端接收到「moved」命令之后,就知道去新的Redis实例请求了,并且更新「缓存哈希槽与实例之间的映射关系」

候选者:总结起来就是:数据迁移完毕后被响应,客户端会收到「moved」命令,并且会更新本地缓存

面试官:那数据还没完全迁移完呢?

候选者:如果数据还没完全迁移完,那这时候会返回客户端「ask」命令。也是让客户端去请求新的Redis实例,但客户端这时候不会更新本地缓存

面试官:了解了

面试官:说白了就是,如果集群Redis实例存在变动,由于Redis实例之间会「通讯」

面试官:所以等到客户端请求时,Redis实例总会知道客户端所要请求的数据在哪个Redis实例上面试官:如果已经迁移完毕了,那就返回「move」命令告诉客户端应该去找哪个Redis实例要数据,并且客户端应该更新自己的缓存(映射关系)

面试官:如果正在迁移中,那就返回「ack」命令告诉客户端应该去找哪个Redis实例要数据

候选者:不愧是你…

面试官:那你知道为什么哈希槽是16384个吗?

候选者:嗯,这个。是这样的,Redis实例之间「通讯」会相互交换「槽信息」,那如果槽过多(意味着网络包会变大),网络包变大,那是不是就意味着会「过度占用」网络的带宽

候选者:另外一块是,Redis作者认为集群在一般情况下是不会超过1000个实例

候选者:那就取了16384个,即可以将数据合理打散至Redis集群中的不同实例,又不会在交换数据时导致带宽占用过多

面试官:了解了

面试官:那你知道为什么对数据进行分区在Redis中用的是「哈希槽」这种方式吗?而不是一致性哈希算法

候选者:在我理解下,一致性哈希算法就是有个「哈希环」,当客户端请求时,会对Key进行hash,确定在哈希环上的位置,然后顺时针往后找,找到的第一个真实节点

候选者:一致性哈希算法比「传统固定取模」的好处就是:如果集群中需要新增或删除某实例,只会影响一小部分的数据

候选者:但如果在集群中新增或者删除实例,在一致性哈希算法下,就得知道是「哪一部分数据」受到影响了,需要进行对受影响的数据进行迁移面试官:嗯…

候选者:而哈希槽的方式,我们通过上面已经可以发现:在集群中的每个实例都能拿到槽位相关的信息

候选者:当客户端对key进行hash运算之后,如果发现请求的实例没有相关的数据,实例会返回「重定向」命令告诉客户端应该去哪儿请求

候选者:集群的扩容、缩容都是以「哈希槽」作为基本单位进行操作,总的来说就是「实现」会更加简单(简洁,高效,有弹性)。过程大概就是把部分槽进行重新分配,然后迁移槽中的数据即可,不会影响到集群中某个实例的所有数据。

面试官:那你了解「服务端 路由」的大致原理吗?

候选者:嗯,服务端路由一般指的就是,有个代理层专门对接客户端的请求,然后再转发到Redis集群进行处理

候选者:上次最后面试的时候,也提到了,现在比较流行的是Codis

候选者:它与Redis Cluster最大的区别就是,Redis Cluster是直连Redis实例的,而Codis则客户端直连Proxy,再由Proxy进行分发到不同的Redis实例进行处理

候选者:在Codis对Key路由的方案跟Redis Cluster很类似,Codis初始化出1024个哈希槽,然后分配到不同的Redis服务器中

候选者:哈希槽与Redis实例的映射关系由Zookeeper进行存储和管理,Proxy会通过Codis DashBoard得到最新的映射关系,并缓存在本地上

面试官:那如果我要扩容Codis Redis实例的流程是怎么样的?

候选者:简单来说就是:把新的Redis实例加入到集群中,然后把部分数据迁移到新的实例上

候选者:大概的过程就是:1.「原实例」某一个Solt的部分数据发送给「目标实例」。2.「目标实例」收到数据后,给「原实例」返回ack。3.「原实例」收到ack之后,在本地删除掉刚刚给「目标实例」的数据。4.不断循环1、2、3步骤,直至整个solt迁移完毕

候选者:Codis也是支持「异步迁移」的,针对上面的步骤2,「原实例」发送数据后,不等待「目标实例」返回ack,就继续接收客户端的请求。

候选者:未迁移完的数据标记为「只读」,不会影响到数据的一致性。如果对迁移中的数据存在「写操作」,那会让客户端进行「重试」,最后会写到「目标实例」上

候选者:还有就是,针对 bigkey,异步迁移采用了「拆分指令」的方式进行迁移,比如有个set元素有10000个,那「原实例」可能就发送10000条命令给「目标实例」,而不是一整个bigkey一次性迁移(因为大对象容易造成阻塞)

面试官:了解了。

本文总结:

分片集群诞生理由:写性能在高并发下会遇到瓶颈&&无法无限地纵向扩展(不划算)

分片集群:需要解决「数据路由」和「数据迁移」的问题

Redis Cluster数据路由:Redis Cluster默认一个集群有16384个哈希槽,哈希槽会被分配到Redis集群中的实例中Redis集群的实例会相互「通讯」,交互自己所负责哈希槽信息(最终每个实例都有完整的映射关系)当客户端请求时,使用CRC16算法算出Hash值并模以16384,自然就能得到哈希槽进而得到所对应的Redis实例位置

为什么16384个哈希槽:16384个既能让Redis实例分配到的数据相对均匀,又不会影响Redis实例之间交互槽信息产生严重的网络性能开销问题

Redis Cluster 为什么使用哈希槽,而非一致性哈希算法:哈希槽实现相对简单高效,每次扩缩容只需要动对应Solt(槽)的数据,一般不会动整个Redis实例

Codis数据路由:默认分配1024个哈希槽,映射相关信息会被保存至Zookeeper集群。Proxy会缓存一份至本地,Redis集群实例发生变化时,DashBoard更新Zookeeper和Proxy的映射信息

Redis Cluster和Codis数据迁移:Redis Cluster支持同步迁移,Codis支持同步迁移&&异步迁移把新的Redis实例加入到集群中,然后把部分数据迁移到新的实例上(在线)

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
1月前
|
存储 NoSQL Java
可能是最漂亮的Redis面试基础详解
我是南哥,相信对你通关面试、拿下Offer有所帮助。敲黑板:本文总结了Redis基础最常见的面试题!包含了Redis五大基本数据类型、Redis内存回收策略、Redis持久化等。相信大部分Redis初学者都会忽略掉一个重要的知识点,Redis其实是单线程模型。我们按直觉来看应该是多线程比单线程更快、处理能力更强才对,比如单线程一次只可以做一件事情,而多线程却可以同时做十件事情。但Redis却可以做到每秒万级别的处理能力,主要是基于以下原因:(1)Redis是基于内存操作的,Redis所有的数据库状态都保存在
可能是最漂亮的Redis面试基础详解
|
1月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
1月前
|
NoSQL 算法 Redis
Redis面试篇
Redis面试篇
36 5
|
1月前
|
缓存 NoSQL Java
Java中redis面试题
Java中redis面试题
37 1
|
21天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
1月前
|
缓存 NoSQL 算法
面试题:Redis如何实现分布式锁!
面试题:Redis如何实现分布式锁!
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
10天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
12天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
37 4