Redis详解 下篇
希望这一篇能搞定剩下的东西,如果搞不定,那咱们还有下下篇,这就和咱们开发拿到的需求一样,这就是最后一版需求了,一天后我这有个进化版、又一天后我这有个究极版、再过一天究极进化版如期而至。整个一数码宝贝的进化流程,吐槽下自己最近比较忙 没提前准备好今天要写的东西。主要还是农药S19赛季来了上分去了,对不住各位老板。
面试官:Redis的集群模式有哪些啊小韭菜?
1、主从模式(Master/Slave)
主从模式简介
- 一个Master可以有多个Slave
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离(默认Master节点可以进行读和写)
主从模式优点
- 可支持读写分离,降低Master的压力
- Master节点挂了以后,不影响slave节点的读(也有效地做了数据备份,防止特殊情况master无法恢复导致数据丢失)
- Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力
主从模式缺点
- Master挂了后不支持写操作且Slave不会从新选举出新的Master(光这一点就打死的单纯的主从模式)
- Master宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题。
2、哨兵模式(Sentinel)
哨兵模式简介
- 监控Master(主服务器)和Slave(从服务器)是否正常运行。
- Master(主服务器)出现故障时自动将Slave(从服务器)转换为Master(主服务器)。
哨兵模式优点
- 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。
- 主从可以自动切换,系统更健壮,可用性更高。
哨兵模式缺点
- 难以扩容,存储数据量较大一台服务器存放不下的情况时就凉凉
Redis cluster集群模式(这才是面试官会细问的)
Redis cluster简介
- Redis 3.0之后 支持 Redis集群(Redis cluster) 可以做到在多台机器上,部署多个Master(实例),每个Master存储一部分的数据,同时每个Master node可以挂载多个Slave node(从实例),那么如果Master挂掉,Redis cluster这套机制,就会自动将某个Slave切换成Master。
面试官:说的不错,那么如果Master挂掉,Redis的是是怎么从多个Slave选出新的Master?
- 选新的Master过程基于Raft协议选举方式来实现的,当从节点发现自己的主节点变成已下线状态时,从节点会广播一条消息,要求所有收到这条消息,并且具有投票权的主节点向这个从节点投票
- 如果一个主节点具有投票权,并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条消息,表示这个主节点支持从节点成为新的主节点
- 如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于集群N/2+1张支持票时,这个从节点就成为新的主节点
- 如果在一个配置纪元没有任何一个从节点能够收集到足够的支持票数,那么集群进入一个新的配置纪元,并再次进行选主,直到选出新的主节点为止
- 新的主节点出现后会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己(和狮群一样,新的王会占有旧狮王的妻子们)
- 新的主节点对集群进行广播PONG消息,告知其他节点已经成为新的主节点(新的狮王在领地留下自己的气味告诉邻居们这里是我的了)
- 新的主节点开始接收和处理槽相关的请求
面试官:选举这一块说的还不错,既然你之前聊到了每个Master存储一部分的数据,Redis数据怎么分到不同的节点?
采用虚拟槽分区的方式巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如Redis Cluster槽范围是 0 ~ 16383(2^14 = 16384)。槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽,
如增加节点就需要从现有节点获得部分槽分配到新增的节点上。如果想移除节点需要将节点中的槽移到其他节点上,然后将没有任何槽的节点从集群中移除即可。
面试官:小韭菜可以啊,那你知道为啥要用虚拟槽?一致性Hash算法有了解过吗
其实这个问题也就是问为啥Redis不用一致性Hash算法直接去做。
一致性哈希也就是将存储节点排列在首尾相接的Hash 环上,每个 key 在计算 Hash后会顺时针找到存储节点存放,如找到的节点不可用那就下再顺时针找下一个节点。而当有节点加入或退出时,仅影响该节点在 Hash 环上顺时针相邻的后续节点。
但是在实际运用中节点一般较少,一旦出现删除或增加某个节点对哈希环中的数据映射影响非常大,且无法保证负载均衡。除非增加一倍或减少一半的节点。
面试官:那假如我现在要新增一个节点,Redis如何保证在Slot迁移过程中的正常写入与修改
用个案例解答吧比较好理解:
假设从Node A迁移槽 1 位到Node B
迁移中会将 Node A的这槽 1 状态标记为Migrating(迁移)
Node B的状态为Importing (输入口)
这时候槽的映射关系不做修改,迁移过程中如果出现查询槽 1 的数据,
客户端会先去Node A上去查询数据,如果对应的数据还在本机上,那么直接返回,如果数据不在Node A上 则会返回 ASK 转向。
如果客户端接收到 ASK 转向, 那么将命令请求的发送对象调整为转向所指定的节点Node B。先发送一个 ASKING 命令,然后再发送真正的命令请求。
请求完成后不会更新客户端所记录的槽至节点的映射:槽 1 应该仍然映射到Node A , 而不是Node B 。
一旦Node A 针对槽 1 的迁移工作完成, Node A 在再次收到针对槽 1 的命令请求时,就会向客户端返回 MOVED 转向, 将关于槽 1 的命令请求长期地转向到Node B 。
到这里其实已经差不多了,但是没有问到你答不出来面试官怎能轻易停下,这个战场没有平手,必须挖到你的极限除非你说的面试官已经开始一愣一愣的。所以咱们继续大战300回合
面试官(忍住不要夸这个韭菜):看你之前还聊到过redis的主从,那你知不知道Redis如何保证主从数据一致的呀
首先Master可以执行读写的命令,从库只能执行读的命令(默认配置,好像可以修改支持写,但是没意义因为会被Master覆盖),
同步分两种情况
- 同步操作:当一个从服务器启动时,会向主服务器发送sync命令,主服务器接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来,从服务器在初次同步数据时则会阻塞不能处理客户端(client)的请求,Master不受影响。
- 命令传播:在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,主服务器会将自己执行的写命令送给从服务器执行,
- 心跳检测
在命令传播阶段,从服务器默认每秒一次的频率向主服务器发送命令
检测主从服务器连接是否正常
检测命令丢失,主服务器接收到从服务器的命令之后会检查从服务器的偏移量是否和主服务器的一致,如果不一致会把积压缓冲区中的从服务器偏移量后面的命令发送到从服务器.
面试官:Redis有哪几种数据淘汰策略?
- noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。
- allkeys-lru:所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
- volatile-lru:只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
- allkeys-random:所有key通用; 随机删除一部分 key。
- volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
- volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。
那又有哪几种过期键删除策略?
-
定时删除:
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作;
定时删除操作对于内存来说是友好的,内存不需要操作,而是通过使用定时器,可以保证尽快的将过期键删除,但是对于CPU来说不是友好的,如果过期键比较多的话,起的定时器也会比较多,删除的这个操作会占用到CPU的资源; -
惰性删除:
放任键过期不管,但是每次从键空间中获取键是,都检查取得的键的过期时间,如果过期的话,删除即可;
惰性操作对于CPU来说是友好的,过期键只有在程序读取时判断是否过期才删除掉,而且也只会删除这一个过期键,但是对于内存来说是不友好的,如果多个键都已经过期了,而这些键又恰好没有被访问,那么这部分的内存就都不会被释放出来; -
定期删除:
每隔一段时间,程序就对数据库进行一次检查,删除掉过期键;
定期删除是上面两种方案的折中方案,每隔一段时间来删除过期键,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响,除此之外,还有效的减少内存的浪费;但是该策略的难点在于间隔时长,这个需要根据自身业务情况来进行设置;
Redis怎么取出数据比较大的key
edis-cli的“bigkeys”
计算方式:String是使用STRLEN方法来计算,List是llen来计算
那在聊聊Redis里面最常用的String的底层数据结构
Redis中为了实现方便的扩展、安全和性能,自己定义了一个结构用来存储字符串。也就是SDS(simple dynamic string),
里面除了保存字符串buf,还保存了free(表示buf中剩余的空间)以及len(当前子字符串的长度)。
这种设计有几点好处:
- 获取字符串长度的复杂度为O(1)
- 通过判断剩余空间和修改字符串的新增长度来决定是否扩容,不会造成缓冲区溢出
- 每次扩容都是已两倍的长度扩容,减少修改字符串带来的内存重分配次数
- 二进制安全 因为是通过len来判断字符串是否结束而不是像C语言通过‘\0’作为判断字符串结束的标准
Redis先这样吧,休息一下准备下一个模块如果有兴趣可以留言看看更想看哪个模块的,我要会就先写。