首页 > 数据库 >redis反杀面试官之10问

redis反杀面试官之10问

时间:2022-12-14 11:35:17浏览次数:52  
标签:10 面试官 遍历 CLOCK redis LRU key lruclock


简言

1. 笔者近几年来一直使用redis,也对redis有过仔细的研究,不敢说精通,熟悉至少是有的

2. redis越来越火,网上相应的文章,总结,面试问题也有很多,但大多是应付简单面试用的,如果面试官再深入一些,恐怕大多数人都hold不住

3. 所以特在这里总结了一些有难度的问题,若你能认真学习研究,不但能大幅提高对redis的理解程度,反杀面试官也是轻轻松松

4. 问题不止10个,随时想到随时更新,有时间就会把答案补上来

问题和答案

问题1:持久化的混合存储模式(前面RDB+后面AOF),它的实现原理和好处

答:

问题2:scan命令,第三个参数应该怎么填

答:先看下scan命令的格式,第1个参数是下标,第2个参数是匹配模式,第三个参数是限制个数

1          2               3 
SCAN cursor [MATCH pattern] [COUNT count]

但是这个count限制并不是必定能起效,受多种因素影响,大致如下

1. 当redis发现用hscan,zscan,scan命令遍历的对象是压缩列表实现的,说明对象内元素比较少,会全部遍历筛选后返回

2. 如果你不传count参数,那么redis会默认设置为10

3. redis是对数组里面单个下标里面的元素遍历,如果某个下标里面符合match格式的元素很多,会超出count值,单个下标遍历完后才会判断是否超出count值,所以可能已经超过了count

4. 如果单次遍历过程中耗时过长,为防止redis卡顿,即使符合条件的元素有很多,也会提前返回,此时个数会少于count值

综上:具体使用时要根据你对结果值的处理操作(元素数据总体量大小,单个元素的操作耗时等)来决定一批筛选多少个元素,redis已经为我们做好了防护

具体scan命令的实现原理可参考笔者的这篇博客​​redis的scan命令的源码分析,实现原理_papaya的博客

问题3:scan命令,返回的游标值在redis中代表什么,是槽位吗,这个槽位和redis集群的槽位概念有关系吗

答:redis中的数据是以hash表的方式组织的,scan返回的游标值是hash的下标,从0开始,这个值和槽位无关。

由于scan遍历时采用高位遍历,所以返回的下标值可能忽大忽小。只需谨记:只要游标值不为0说明遍历未结束

问题4:scan命令,返回的key中,有一定概率会出现重复的情况,能说下到底什么情况下会出现重复吗?为什么,这种重复能优化吗

答:redis缩容的时候会出现,因为rehash采用高位加法,举例:遍历到下图中的橙色下标(110)时发生了缩容,前面的000,100,010都已经遍历过了,其中000,100节点会被rehash到新的00节点上,不会重复遍历。但是010,110节点都会rehash到新的10下标(第一行绿色的)上,那么010下标上对应的元素就会被重复遍历

redis反杀面试官之10问_redis

问题5:redis的读写分离模式中,因为主从同步需要时间,所以有一定概率会出现主redis刚刚写入,从redis读取不到进而认为该key不存在的情况,如何优化

答:

问题6:redis命令查看单个key的大小有哪些命令,哪些是准确的,哪些是不准确的,比如--bigkeys命令的原理

答:

问题7:redis集群选举时是使用raft算法还是paxos算法,知道raft算法和paxos算法的区别吗

答:

问题8:rehash为什么要用高位加法,而不是我们通常的低位加法,好处是什么

答:采用高位进位加法,无论是扩容还是缩容,rehash 后的槽位在遍历顺序上是相邻的,这也是scan命令能保证全盘遍历的精妙之处,详见这篇博客:​​redis之rehash原理_papaya的博客-博客_redis rehash原理​​

问题9:redis对象中有个字段叫lru,只有24位,在LRU模式下,存储的是最后一次访问时间戳。由于只有24位,能表示的最大时间只有194天,超过194天的key怎么办,如何才能计算出它真正的空闲时间?

答:超过194天的key,lru方式计算key的空闲时间不准确

计算redis对象空闲时间的代码如下

// 计算对象的空闲时间,也就是没有被访问的时间,返回结果是毫秒
unsigned long long estimateObjectIdleTime(robj* o)
{
unsigned long long lruclock = LRU_CLOCK();
// 获取Redis时钟,也就是server.lruclock的值,单位:秒
if (lruclock >= o->lru)
{
// 正常递增时直接减即可(LRU_CLOCK_RESOLUTION的值默认是1000)
return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
}
else
{
// 折返了,则加一轮最大值后再减(LRU_CLOCK_MAX表示一轮的最大值,即2^24 - 1)
return (lruclock + LRU_CLOCK_MAX - 0->lru) * LRU_CLOCK_RESOLUTION;
}
}

其中用到的LRU_CLOCK()函数,是获取当前节点示例的lru时间戳,代码如下

unsigned int LRU_CLOCK(void)
{
unsigned int lruclock;
if (1000 / server.hz <= LRU_CLOCK_RESOLUTION)
{
// 原子操作通常会走这里,我们只需注意这里
atomicGet(server.lruclock, lruclock);
}
else
{
// 直接通过系统调用获取时间戳,hz 配置得太低(一般不会这么干),lruclock 更新不及时,需要实时获取系统时间戳
lruclock = getLRUClock(); // 系统调用获取时间
}
return lruclock;
}

其中用到的 getLRUClock()函数,代码如下

unsigned int getLRUClock(void) {
// 系统毫秒数 / 1000,得到秒数,最后做 & 运算,其中LRU_CLOCK_MAX的值为2的24次方 -1
return (mstime()/LRU_CLOCK_RESOLUTION) & LRU_CLOCK_MAX;
}

上面代码中我已经尽量添加了注释,由上面代码可知,服务器的 lruclock是秒数时间戳,值是介于0 ~ 2的24次方之间,差不多194天,存在折返的可能性

若服务器lruclock未折返,则服务器lruclock的值大于robj的lru,相减即可得空闲时间

若服务器lruclock折返了,则服务器lruclock的值可能大于robj的lru,也可能小于robj的lru,此时

服务器lruclock + LRU_CLOCK_MAX - robj.lru 并不准确,因为可能需要加一个LRU_CLOCK_MAX ,也有可能需要加多个LRU_CLOCK_MAX

也就说,当服务器节点时间开启超过194天时,redis对象的空闲时间可能计算不准确

问题10:redis事务的watch有没有ABA问题,是如何解决的?

答:没有ABA问题。redis会为每个要监听的key维护一个监听的client列表,任何key发生变化时都会检测一下是否是watch中的key。若是watch中的key,则把监控这个key的所有client都标记为REDIS_DIRTY_CAS,意思是为该client的所有CAS操作都“dirty”了。当服务器收到该client的事务执行命令(即exec命令时),会检测是否有REDIS_DIRTY_CAS标记,若有,则直接返回,不再执行事务

详细分析可以见笔者的这篇博客:

​​redis的watch没有ABA的问题_papaya的博客​

问题11:布隆过滤器怎么删除元素呢?

答:经典的布隆过滤器不支持删除元素

如果一定要删除元素,业内普遍有两种做法

方法1:定时异步重建布隆过滤器,比如每隔3天把所有元素重新hash,建立新的布隆过滤器,重建完后再删掉旧的布隆过滤器。

方法2:使用计数型的布隆过滤器。因为经典的布隆过滤器的一个位只能为0或1,为1时不能记录有多少个元素引用了该位,一旦删除,数据就乱了。但计数型的布隆过滤器每个位是一个数字,记录了有多少个元素引用了该位,删除一个元素时只需对其hash对应的位的数字进行减1即可。

问题12:一般情况下redis cluster中key和slot的映射是通过算法(原理:crc16(key)%16384)对应的,知道如何强制把key映射到指定的slot呢?

答:在要操作的key中添加 {xxx} ,键哈希标签原理,源码如下

unsigned int keyHashSlot(char *key, int keylen) {
undefined int s, e;

// 找到 { 的index
for (s = 0; s < keylen; s++)
{
if (key[s] == '{')
break;
}
// 没找到就计算crc16()的值
if (s == keylen)
{
return crc16(key,keylen) & 0x3FFF;
}
// 再往后面找 } 的index
for (e = s+1; e < keylen; e++)
{
if (key[e] == '}')
break;
}
// 没找到 } 或者{}之间为空,仍然计算crc16()的值
if (e == keylen || e == s+1)
{
return crc16(key,keylen) & 0x3FFF;
}

// 获取{}之间的字符串进行crc16()计算
return crc16(key+s+1,e-s-1) & 0x3FFF;
}

问题13:redis7.0中新加的multipart-aof的优点

答:

问题14:redis cluster中最多16384个节点,16384也就是2的14次方,占14个位,redis中用2个字节来表示,2个字节也就是16位,为什么不全部使用呢?剩余的2个位用来存储什么内容?

答:

标签:10,面试官,遍历,CLOCK,redis,LRU,key,lruclock
From: https://blog.51cto.com/u_15912066/5936270

相关文章