【搞定面试官】系列:Redis基础

引言

在互联网电商如火如荼发展的大背景下,传统的关系型数据库(MySQL、Oracle)已然无法满足高并发、限时秒杀等复杂的场景。此时NoSQL(非关系型数据库)应运而生,而Redis就是NoSQL大军里的一颗璀璨新星,在大厂面试中也是绝对绕不开的话题。

面试开始

同学你好,先简单做个自动介绍吧?

面试官您好,我叫少侠露飞,……,熟练运用Redis、RocketMQ等中间件。很期待加入贵部门。

好的,我看你自我介绍时提到了Redis,你们项目里为何采用Redis,或者说你们是基于什么场景应用Redis的呢?

什么?心里忍不住暗骂,这叫啥问题,大家都在用,我就是为了用而用。但是我们肯定不能把真实想法说出来,做人当然要有点内涵嘛。

于是认真答道:“闪闪发光”的面试官您好,因为传统的MySQL数据库已经不能适用所有的场景了,比如限时秒杀,大流量削峰等,这些场景瞬时流量可达到数万甚至数十万级,这些服务全部打到MySQL,数据库必然扛不住,很容易被打崩造成服务宕机,所以引入了缓存中间件。在缓存中间件领域有 Redis 和 Memcached 两个领头羊,但是Redis支持更多的数据类型,所以在综合考虑之后选择了Redis。

嗯,答得不错,你刚刚提到了数据类型,那介绍一下Redis有哪些数据类型及相应的使用场景吧?

string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)

数据类型特点使用场景
stringkey-value1.缓存功能:Redis作为缓存层,MySQL作为存储层,加速读写并降低后端压力;2.计数器功能,如视频点击量、文章浏览量等
hashkey-field-value假设要存储的对象有很多属性,那么为了表示该对象,必然有很多Key来描述该对象,但是接下来假如我修改某个值,还要把这个对象取出来再修改,比较麻烦,所以就有了Hash
listkey-value,在一个list首端或末端添加元素在朋友圈、微博等交友平台的发帖、回复、点赞、查看某一帖子点赞数等
set数据无序且不能重复set可以做并集、交集运算,所以在共同好友、二度好友推荐场景下运用较多
zset数据有序,给每个member一个分数值,可支持按分数排序微博等网站的排行榜

说明了这五种基本数据类型并详细分析了各自的应用场景,千万不要洋洋自得甚至都要被自己折服了,因为这仅仅是合格。
你想从数千个竞争者中脱颖而出,肯定要有些东西,你还需要补充说到这三种数据结构:HyperLogLog、Geo、Pub/Sub

这里我用一个实际的场景说明一下HyperLogLog。在电商领域,经常要统计某一页面的PV/UV(分别是浏览次数、浏览人数)。统计UV时肯定要考虑到去重,即同一个用户一天内无论点击多少次,都只算作一次。这个时候HyperLogLog就可以一展能为了。HyperLogLog是基于基数统计,通过hash碰撞实现,用户Id通过给定的hash算法每次都会得到同样的值,在哈希桶的同一索引位置。当然统计UV不用HyperLogLog也是可以的,就用string类型一样可行,需要用到setnx,这个不急,待会面试官就会问到。
需要注意的是,统计每天的UV,相关的键需要设置当天过期,不然你会发现之后每天的UV数据都是异常的。

嗯,小伙子对Redis的数据结构掌握的还不错(内心已经给你比起了大拇指),刚刚你有提到setnx,可以简单说说这个原理及用处么?

面试绝对不要让自己处于完全被动的地位。不知聪明的你们发现了没有,面试官问的Redis、setnx等问题都是我先在回答中提到的,也就是说面试官可以自由发问,我也可以主动的在回答中提到一些知识点,引导面试官询问相关,化被动为主动。这样就尽可能的把面试控制在我们的节奏中。

回答面试官的问题:setnx常常用作分布式锁,setnx核心思想在于争抢锁,抢到之后执行相关逻辑,注意执行完之后不要忘记用expire给锁设置一个过期时间。

接着面试官开始下一轮的攻势,问到如果在setnx后再执行expire期间进程意外crash或者服务重启维护了,那怎么办?

这个时候你要立即做出回应:当这种情况setnx的锁就永远不会被释放了,这是个危险的操作。
然后稍加思考并给出解决方案:Redis的set包含了丰富的指令参数,这两个命令可以合并成一条命令变为原子操作来执行。

面试官已经知道面前的小伙不简单,于是决定加大难度:如果Redis里面有十亿个key,其中有10w是以固定已知前缀开头的,如何将它们找出来?

使用keys指令可以扫出指定模式的key列表。
到这里你还可以更秀一点。
在这里插入图片描述
你接着补充到,但是由于Redis是单线程模型,如果Redis正在给线上提供服务,keys指令会导致线程阻塞一段时间,线上服务会停顿。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,将返回结果手动去重就行了。

这个时候面试官已经抑制不住激动的心情了,都学会抢答了,这个小伙子挺有料啊。既然刚刚提到了Redis的线程模型,可以说下么?

这个时候不要慌,稳稳的答道:Redis是单线程模型,底层是I/O多路复用(关于此知识点我将会在Nginx的事件驱动模块做详细介绍)。

此时面试官已经感到面前的小伙不一般了,于是继续开怼:Redis是一种内存数据库,数据如何持久化呢?

有两种方式:RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较长时间,难以做到实时持久化,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在Redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。
这里很好理解,把RDB理解为一整个表全量的数据,AOF理解为每次操作的日志就好了,服务器重启的时候先把表的数据全部塞进内存,但是他可能不完整,你再回访一下日志,数据不就完整了嘛。不过Redis本身的机制是 AOF持久化开启且存在AOF文件时,优先加载AOF文件;AOF关闭或者AOF文件不存在时,加载RDB文件;加载AOF/RDB文件之后,Redis启动成功; AOF/RDB文件存在错误时,Redis启动失败并打印错误信息。

持久化的时候突然断电了会怎样?

会丢失数据,但这取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如5s1次,这个时候最多就会丢失5s的数据。

面试官此时决定跟你卯上了,接着追问:RDB的原理呢?

这里有两个关键的步骤:fork和COW。fork是指Redis通过创建子进程来进行RDB操作,COW指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
注:回答这个问题的时候,如果你还能说出AOF和RDB的优缺点,我觉得面试官都会为你点赞的,关于此点我会在之后的博客继续补充。

既然在这类问题难不倒你,聪明的面试官决定换个方向问你:使用过Redis做异步队列么,怎么用的?

面试官想方设法的想问倒我们,我们就凭借满腹经纶化解一次次的危机,所谓振长策而于宇内,奥利给…
认真答道:Redis中一般用list数据结构作为异步队列,用rpush生产消息,lpop消费消息。当消费者lpop发现没有消息的时候要睡眠sleep一会再重试。
此时你再补充说道:除了使用sleep,list还有个指令叫blpop,在没有消息的时候,它会阻塞直到有新的消息到来。

这个时候面试官会觉得你这小伙子对Redis的见解很独到,在心里已经录用你100次了。但是,表面上还是要沉稳,毕竟是久经沙场的老技术人了。问答继续:像刚刚那种模式,数据blpop之后别的消费者就消费不了了,如何实现生产一次消费多次呢?

使用pub/sub主题订阅者模式,可以实现 1:N 的消息队列。

那么pub/sub主题订阅者模式的缺点呢?

该模式有个问题在于消费者下线的时候,生产的消息会丢失,得使用专业的消息队列RocketMQ或Kafka。

如果面试官还不罢休,Redis如何实现延时队列?

在这里插入图片描述
这一系列的连环发问,估计有耐心如你,也想把面试官打一顿:还有完没完了。疫情这么严重都不敢在外面吃,我得回家自己做饭,人家明天还要上班呢!
在这里插入图片描述
但是,为了大厂的offer,小伙子还是克制一下,然后神态自若的回答道:使用有序集合sortedset(zset),以消息内容作为key,用时间戳作为score来调用zadd命令生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行消费。

回答到这里,面试官已经为你竖起了大拇指,并且心里默默的给了你A+,但是他还不肯停止,筛不筛选人才的无所谓,主要就想问倒你。于是吹响了下一轮进攻的号角:知道pipeline吗?

可以将多次IO往返的时间缩减为一次,不过有个前提是pipeline执行的指令之间没有因果相关性。
注:实际上使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。

了解过Redis的同步机制么?

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。

使用过Redis的集群么?如何保证集群的高可用?

Redis Sentinal (Redis哨兵,这个我之后会单独写一篇博客介绍)着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

Redis Cluster 着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

面试结束

小伙子可以啊,我很满意,我们部门就缺你这种人才,要不今天就把入职手续办了吧?
这个时候,你千万要稳住,按捺一下激动无比的内心:这么急啊,疫情这么严重,房子不好找啊,要不下周一吧。
面试官一听,哎呀,这小伙子估计在手的offer不少啊,这种人才怎么能放过,不行,人事经理何在,加钱!!!
当这些问题你都一一回答上来了,你是不是都觉得自己很棒呢?

总结

在技术面试的时候,不管是Redis还是什么问题,如果你能举出实际开发过程的问题和收获会给面试官的印象分会加很多,回答逻辑性也要强一点,不要东一榔头西一棒子的,容易把自己都绕晕了。
并且面试不应该简单的一问一答,若能在问题之外扩散一些知识点,面试官会觉得你不只是一个会写代码的人,你逻辑清晰,你对技术选型,对中间件、对项目都有自己的理解和思考,心里自然会给你点赞的。

点点关注,不会迷路

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个基于sw::redis::RedisCluster和libevent异步订阅消息的示例代码: ```cpp #include <iostream> #include <string> #include <sw/redis++/redis++.h> #include <event2/event.h> using namespace std; using namespace sw::redis; void eventCallback(evutil_socket_t fd, short what, void *arg) { RedisCluster *redis = (RedisCluster *)arg; redis->cluster_recv(); } int main() { const string redis_cluster_address = "tcp://127.0.0.1:7000"; const string channel_name = "test_channel"; // 创建 RedisCluster 实例 auto redis = RedisCluster::create(); redis->connect(redis_cluster_address); // 订阅频道 auto callback = [](const string &channel, const string &msg) { cout << "Received message from channel " << channel << ": " << msg << endl; }; auto sub = redis->subscribe(channel_name, callback); // 创建 libevent 实例 auto event_base = event_base_new(); auto event = event_new(event_base, sub->fd(), EV_READ | EV_PERSIST, eventCallback, redis.get()); // 添加事件监听 event_add(event, nullptr); // 进入事件循环 event_base_dispatch(event_base); return 0; } ``` 这个示例代码中,首先创建了一个 RedisCluster 实例,然后调用其 connect 方法连接 Redis 集群。接着,调用 subscribe 方法订阅指定的频道,并传入一个回调函数来处理接收到的消息。 然后,创建了一个 libevent 实例,并使用 event_new 函数创建一个事件对象,将其绑定到 RedisCluster 实例的 socket 描述符上,并传入一个回调函数。最后,调用 event_base_dispatch 进入事件循环。 在事件循环中,libevent 会监听 Redis 集群返回的消息,当有消息到达时,会触发事件回调函数 eventCallback,在回调函数中调用 RedisCluster 实例的 cluster_recv 方法来处理接收到的消息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值