简言
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下标上对应的元素就会被重复遍历
问题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