中高级开发面试必问的Redis面试题,看这篇就够了!

在这里插入图片描述

hset hash-key sub-key1 value1

(integer) 1

hset hash-key sub-key2 value2

(integer) 1

hset hash-key sub-key1 value1

(integer) 0

hgetall hash-key

  1. “sub-key1”

  2. “value1”

  3. “sub-key2”

  4. “value2”

hdel hash-key sub-key2

(integer) 1

hdel hash-key sub-key2

(integer) 0

hget hash-key sub-key1

“value1”

hgetall hash-key

  1. “sub-key1”

  2. “value1”

ZSET


在这里插入图片描述

zadd zset-key 728 member1

(integer) 1

zadd zset-key 982 member0

(integer) 1

zadd zset-key 982 member0

(integer) 0

zrange zset-key 0 -1 withscores

  1. “member1”

  2. “728”

  3. “member0”

  4. “982”

zrangebyscore zset-key 0 800 withscores

  1. “member1”

  2. “728”

zrem zset-key member1

(integer) 1

zrem zset-key member1

(integer) 0

zrange zset-key 0 -1 withscores

  1. “member0”

  2. “982”

需要更多大厂面试资料的话也可以点击直接进入,免费获取!暗号:优快云

三、数据结构


字典


dictht 是一个散列表结构,使用拉链法保存哈希冲突。

/* This is our hash table structure. Every dictionary has two of this as we

  • implement incremental rehashing, for the old to the new table. */

typedef struct dictht {

dictEntry **table;

unsigned long size;

unsigned long sizemask;

unsigned long used;

} dictht;

typedef struct dictEntry {

void *key;

union {

void *val;

uint64_t u64;

int64_t s64;

double d;

} v;

struct dictEntry *next;

} dictEntry;

Redis 的字典 dict 中包含两个哈希表 dictht,这是为了方便进行 rehash 操作。在扩容时,将其中一个 dictht 上的键值对 rehash 到另一个 dictht 上面,完成之后释放空间并交换两个 dictht 的角色。

typedef struct dict {

dictType *type;

void *privdata;

dictht ht[2];

long rehashidx; /* rehashing not in progress if rehashidx == -1 */

unsigned long iterators; /* number of iterators currently running */

} dict;

rehash 操作不是一次性完成,而是采用渐进方式,这是为了避免一次性执行过多的 rehash 操作给服务器带来过大的负担。

渐进式 rehash 通过记录 dict 的 rehashidx 完成,它从 0 开始,然后每执行一次 rehash 都会递增。例如在一次 rehash 中,要把 dict[0] rehash 到 dict[1],这一次会把 dict[0] 上 table[rehashidx] 的键值对 rehash 到 dict[1] 上,dict[0] 的 table[rehashidx] 指向 null,并令 rehashidx++。

在 rehash 期间,每次对字典执行添加、删除、查找或者更新操作时,都会执行一次渐进式 rehash。

采用渐进式 rehash 会导致字典中的数据分散在两个 dictht 上,因此对字典的查找操作也需要到对应的 dictht 去执行。

/* Performs N steps of incremental rehashing. Returns 1 if there are still

  • keys to move from the old to the new hash table, otherwise 0 is returned.

  • Note that a rehashing step consists in moving a bucket (that may have more

  • than one key as we use chaining) from the old to the new hash table, however

  • since part of the hash table may be composed of empty spaces, it is not

  • guaranteed that this function will rehash even a single bucket, since it

  • will visit at max N*10 empty buckets in total, otherwise the amount of

  • work it does would be unbound and the function may block for a long time. */

int dictRehash(dict *d, int n) {

int empty_visits = n * 10; /* Max number of empty buckets to visit. */

if (!dictIsRehashing(d)) return 0;

while (n-- && d->ht[0].used != 0) {

dictEntry *de, *nextde;

/* Note that rehashidx can’t overflow as we are sure there are more

  • elements because ht[0].used != 0 */

assert(d->ht[0].size > (unsigned long) d->rehashidx);

while (d->ht[0].table[d->rehashidx] == NULL) {

d->rehashidx++;

if (–empty_visits == 0) return 1;

}

de = d->ht[0].table[d->rehashidx];

/* Move all the keys in this bucket from the old to the new hash HT */

while (de) {

uint64_t h;

nextde = de->next;

/* Get the index in the new hash table */

h = dictHashKey(d, de->key) & d->ht[1].sizemask;

de->next = d->ht[1].table[h];

d->ht[1].table[h] = de;

d->ht[0].used–;

d->ht[1].used++;

de = nextde;

}

d->ht[0].table[d->rehashidx] = NULL;

d->rehashidx++;

}

/* Check if we already rehashed the whole table… */

if (d->ht[0].used == 0) {

zfree(d->ht[0].table);

d->ht[0] = d->ht[1];

_dictReset(&d->ht[1]);

d->rehashidx = -1;

return 0;

}

/* More to rehash… */

return 1;

}

跳跃表


是有序集合的底层实现之一。

跳跃表是基于多指针有序链表实现的,可以看成多个有序链表。

在这里插入图片描述

在查找时,从上层指针开始查找,找到对应的区间之后再到下一层去查找。下图演示了查找 22 的过程。

在这里插入图片描述

与红黑树等平衡树相比,跳跃表具有以下优点:

  • 插入速度非常快速,因为不需要进行旋转等操作来维护平衡性;

  • 更容易实现;

  • 支持无锁操作。

需要更多大厂面试资料的话也可以点击直接进入,免费获取!暗号:优快云

四、使用场景


计数器


可以对 String 进行自增自减运算,从而实现计数器功能。

Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。

缓存


将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。

查找表


例如 DNS 记录就很适合使用 Redis 进行存储。

查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。

消息队列


List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息

不过最好使用 Kafka、RabbitMQ 等消息中间件。

会话缓存


可以使用 Redis 来统一存储多台应用服务器的会话信息。

当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。

分布式锁实现


在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。

可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

其它


Set 可以实现交集、并集等操作,从而实现共同好友等功能。

ZSet 可以实现有序性操作,从而实现排行榜等功能。

五、Redis 与 Memcached


两者都是非关系型内存键值数据库,主要有以下不同:

数据类型


Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。

数据持久化


Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。

分布式


Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。

Redis Cluster 实现了分布式的支持。

内存管理机制


  • 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。

  • Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。

六、键的过期时间


Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。

对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。

七、数据淘汰策略


可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。

Redis 具体有 6 种淘汰策略:

在这里插入图片描述

作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并非针对所有 key,而是抽样一小部分并且从中选出被淘汰的 key。

使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。

Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。

需要更多大厂面试资料的话也可以点击直接进入,免费获取!暗号:优快云

八、持久化


Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的

数据持久化到硬盘上。

RDB 持久化


将某个时间点的所有数据都存放到硬盘上。

可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。

如果系统发生故障,将会丢失最后一次创建快照之后的数据。

如果数据量很大,保存快照的时间会很长。

AOF 持久化


将写命令添加到 AOF 文件(Append Only File)的末尾。

使用 AOF 持久化需要设置同步选项,从而确保写命令什么时候会同步到磁盘文件上。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项:

在这里插入图片描述

  • always 选项会严重减低服务器的性能;

  • everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;

  • no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。

随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。

九、事务


一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。

事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。

Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。

十、事件


Redis 服务器是一个事件驱动程序。

文件事件


服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。

Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器。

在这里插入图片描述

时间事件


服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。

时间事件又分为:

  • 定时事件:是让一段程序在指定的时间之内执行一次;

  • 周期性事件:是让一段程序每隔指定时间就执行一次。

Redis 将所有时间事件都放在一个无序链表中,通过遍历整个链表查找出已到达的时间事件,并调用相应的事件处理器。

事件的调度与执行


服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能一直监听,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。

事件调度与执行由 aeProcessEvents 函数负责,伪代码如下:

def aeProcessEvents():

获取到达时间离当前时间最接近的时间事件

time_event = aeSearchNearestTimer()

计算最接近的时间事件距离到达还有多少毫秒

remaind_ms = time_event.when - unix_ts_now()

如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0

if remaind_ms < 0:

remaind_ms = 0

根据 remaind_ms 的值,创建 timeval

timeval = create_timeval_with_ms(remaind_ms)

阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定

aeApiPoll(timeval)

处理所有已产生的文件事件

procesFileEvents()

处理所有已到达的时间事件

processTimeEvents()

将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下:

def main():

初始化服务器

init_server()

一直处理事件,直到服务器关闭为止

while server_is_not_shutdown():

aeProcessEvents()

服务器关闭,执行清理操作

clean_server()

从事件处理的角度来看,服务器运行流程如下:

在这里插入图片描述

十一、复制


通过使用 slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。

一个从服务器只能有一个主服务器,并且不支持主主复制。

连接过程

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后我们该如何学习?

1、看视频进行系统学习

这几年的Crud经历,让我明白自己真的算是菜鸡中的战斗机,也正因为Crud,导致自己技术比较零散,也不够深入不够系统,所以重新进行学习是很有必要的。我差的是系统知识,差的结构框架和思路,所以通过视频来学习,效果更好,也更全面。关于视频学习,个人可以推荐去B站进行学习,B站上有很多学习视频,唯一的缺点就是免费的容易过时。

另外,我自己也珍藏了好几套视频资料躺在网盘里,有需要的我也可以分享给你:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

2、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

Spring源码深度解析:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Mybatis 3源码深度解析:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Redis学习笔记:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

Spring Boot核心技术-笔记:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

3、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了

只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。

人生短暂,别稀里糊涂的活一辈子,不要将就。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
可以分享给你:

[外链图片转存中…(img-ReaXgAaq-1713569285538)]

2、读源码,看实战笔记,学习大神思路

“编程语言是程序员的表达的方式,而架构是程序员对世界的认知”。所以,程序员要想快速认知并学习架构,读源码是必不可少的。阅读源码,是解决问题 + 理解事物,更重要的:看到源码背后的想法;程序员说:读万行源码,行万种实践。

Spring源码深度解析:

[外链图片转存中…(img-Dq5s2U06-1713569285539)]

Mybatis 3源码深度解析:

[外链图片转存中…(img-KkMtN25o-1713569285540)]

Redis学习笔记:

[外链图片转存中…(img-SjjWSeM4-1713569285541)]

Spring Boot核心技术-笔记:

[外链图片转存中…(img-hRouIWtf-1713569285542)]

3、面试前夕,刷题冲刺

面试的前一周时间内,就可以开始刷题冲刺了。请记住,刷题的时候,技术的优先,算法的看些基本的,比如排序等即可,而智力题,除非是校招,否则一般不怎么会问。

关于面试刷题,我个人也准备了一套系统的面试题,帮助你举一反三:

[外链图片转存中…(img-SGKQJaRI-1713569285544)]

只有技术过硬,在哪儿都不愁就业,“万般带不去,唯有业随身”学习本来就不是在课堂那几年说了算,而是在人生的旅途中不间断的事情。

人生短暂,别稀里糊涂的活一辈子,不要将就。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值