Redis 面试题汇总

                                                       Redis 相关问题汇总

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它支持存储的类型包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。


为什么不推荐使用大Key ?

1.大key 在迁移的时候,数据太大,会导致卡顿

2.创建大key  ,string类似Java中的ArrayList 会导致扩容

3.删除key时,回收的时候,内存会一次性回收,也会导致卡顿

 

Redis 和Memached怎么选?

1.Redis 支持String hash,set,list zset等数据类型 Memcached 支持的String 对象等 不能满足复杂的需求

2.持久化 memcached 不支持持久化,只能选Redis

3.高可用,Redis 支持集群,可实现主从复制,读写分析,Menchched 原生不支持,需要二次开发

4.存储容量 memcached的value存储最大为1M,要更大的存储,只能选Redis

 

Redis有哪些数据结构?

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。

如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官就开始觉得你很不错啦。

 

Redis分布式锁是什么回事?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记释放。

这时候对方会告诉你说你回答得不错,然后接着会问如果在setnx之后执行expire之前进程意外crash或者要重启维护了,会怎么样呢?

 

这时候你要给予惊讶的反馈:啊,这个锁永远得不到释放了。接着你挠一挠自己的脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!

对方这时会显露笑容,心里开始默念:摁,这小子还不错。

 

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,怎样将它们全部找出来?

 

使用keys指令可以扫出指定模式的key列表。

 

对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会出现什么问题?

这个时候你要回答redis关键的一个特性:redis的单线程的。

keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。

这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

 

你是如何使用Redis做异步队列么的?

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

Redis 面试题汇总_第1张图片

队列空了怎么办?

客户端是通过队列的pop操作来获取消息的,然后进行处理,处理完再接着获取消息,可是如果队列空了,客户端会陷入pop死循环,不停的pop,没有数据,仍然pop,这样空轮询不断拉高了客户端的CPU ,redis的QPS会被拉高, 我们通常解决这个问题是使用sleep来解决这个问题,让先吃睡一会,睡1s就可以,不但客户的CPU能降下来,Redis的QPS也能降下来

对方追问可不可以不用sleep呢?

但是使用Redis 睡眠会带来一个问题,那就是睡眠会导致延迟增大,如果只有一个消费者,那么这个延迟就是1s

list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。可以把睡觉的时间缩短点,阻塞读在队列没有数据的时候,立刻进入休眠状态,一旦数据到来,就立刻醒来,消息的延迟几乎为零。用 blpop/brpop 替代前面的 lpop/rpop,就完美解决了上面的问题。 

对方追问能不能生产一次消费多次呢?

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

Redis 面试题汇总_第2张图片

对方追问pub/sub有什么缺点

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

对方接着问redis如何实现延时队列?

我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

import java.util.Calendar;
import java.util.Set;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Tuple;

public class DelayTest {
    private static final String ADDR = "172.31.1.135";
    private static final int PORT = 7000;
    private static JedisPool jedisPool = new JedisPool(ADDR, PORT);
    
    public static Jedis getJedis() {
       return jedisPool.getResource();
    }
    
    //生产者,生成5个订单放进去
    public void productionDelayMessage(){
        for(int i=0;i<5;i++){
            //延迟3秒
            Calendar cal1 = Calendar.getInstance();
            cal1.add(Calendar.SECOND, 3);
            int second3later = (int) (cal1.getTimeInMillis() / 1000);
            DelayTest.getJedis().zadd("OrderId", second3later,"OID0000001"+i);
            System.out.println(System.currentTimeMillis()+"ms:redis生成了一个订单任务:订单ID为"+"OID0000001"+i);
        }
    }
    
    //消费者,取订单
    public void consumerDelayMessage(){
        Jedis jedis = DelayTest.getJedis();
        while(true){
            Set items = jedis.zrangeWithScores("OrderId", 0, 1);
            if(items == null || items.isEmpty()){
                System.out.println("当前没有等待的任务");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                continue;
            }
            int  score = (int) ((Tuple)items.toArray()[0]).getScore();
            Calendar cal = Calendar.getInstance();
            int nowSecond = (int) (cal.getTimeInMillis() / 1000);
            if(nowSecond >= score){
                String orderId = ((Tuple)items.toArray()[0]).getElement();
                jedis.zrem("OrderId", orderId);
                System.out.println(System.currentTimeMillis() +"ms:redis消费了一个任务:消费的订单OrderId为"+orderId);
            }
        }
    }
    
    public static void main(String[] args) {
        DelayTest DelayTest =new DelayTest();
        DelayTest.productionDelayMessage();
        DelayTest.consumerDelayMessage();
    }
    
}

延时队列可以通过Redis的zset(有序列表)来实现,我们将消息序列化成一个字符串作为zset的value,这个消息到期处理的时间作为score

 

Redis如何做持久化的?

bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,优先使用aof来恢复内存的状态,如果没有aof日志,就会使用rdb文件来恢复。

 

如何删除整个Redis 数据?

Redis 并不总是可以将空闲内存立即归还给操作系统。
如果当前 Redis 内存有 10G,当你删除了 1GB 的 key 后,再去观察内存,你会发现
内存变化不会太大。原因是操作系统回收内存是以页为单位,如果这个页上只要有一个 key
还在使用,那么它就不能被回收。 Redis 虽然删除了 1GB 的 key,但是这些 key 分散到了
很多页面中,每个页面都还有其它 key 存在,这就导致了内存不会立即被回收。

执行flushdb ,可以看到内存被回收了,原因是所有的key都被干掉了,大部分

如果再问aof文件过大恢复时间过长怎么办?

你告诉面试官,Redis会定期做aof重写,压缩aof文件日志大小。如果面试官不够满意,再拿出杀手锏答案,Redis4.0之后有了混合持久化的功能,将bgsave的全量和aof的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。这个功能甚至很多面试官都不知道,他们肯定会对你刮目相看。

 

如果对方追问那如果突然机器掉电会怎样?

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

 

Pipeline有什么好处,为什么要用pipeline?

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

 

Redis的同步机制了解么?

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

在了解主从同步之前,可以先了解下CAP 原理

C - Consistent , 一致性
A - Availability , 可用性
P - Partition tolerance , 分区容忍性

 

CAP 理论意味着  三者只能选其二,不能同时满足

分布式系统的节点往往都是分布在不同的机器上进行网络隔离的,意味着会有网络断开的风险, 这个 网络断开的

场景叫做网络分区,当网络分区发生时,两个节点之间无法通讯,就 无法满足 数据 一致性,除非我们牺牲可用性,暂停分布式节点服务,不提供数据修改服务,直到网络状态恢复正常才对外提供服务

什么是缓存穿透?

 

 

缓存是淘汰还是修改?

看场景

案例1

假设,缓存里存了某一个用户uid=123的余额是money=100元,业务场景是,购买了一个商品pid=456。
分析:如果修改缓存,可能需要:
(1)去db查询pid的价格是50元
(2)去db查询活动的折扣是8折(商品实际价格是40元)
(3)去db查询用户的优惠券是10元(用户实际要支付30元)
(4)从cache查询get用户的余额是100元
(5)计算出剩余余额是100 - 30 = 70
(6)到cache设置set用户的余额是70
为了避免一次cache miss,需要额外增加若干次db与cache的交互,得不偿失。

结论:此时,应该淘汰缓存,而不是修改缓存。

案例2

假设,缓存里存了某一个用户uid=123的余额是money=100元,业务场景是,需要扣减30元。

分析:如果修改缓存,需要:
(1)从cache查询get用户的余额是100元
(2)计算出剩余余额是100 - 30 = 70
(3)到cache设置set用户的余额是70
为了避免一次cache miss,需要额外增加若干次cache的交互,以及业务的计算,得不偿失。
结论:此时,应该淘汰缓存,而不是修改缓存。

 

案例3

假设,缓存里存了某一个用户uid=123的余额是money=100元,业务场景是,余额要变为70元。

 

分析:如果修改缓存,需要:
(1)到cache设置set用户的余额是70
修改缓存成本很低。

 

结论:此时,可以选择修改缓存。当然,如果选择淘汰缓存,只会额外增加一次cache miss,成本也不高。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

问题1:你的兴趣在哪,技术专家,还是带团队?
有没有想过自己五年后在做什么,届时是否工作得开心?



做技术专家,带团队做事情,还是自己创业,搞清楚自己想要什么最重要。



问问自己,做管理对你有什么吸引力?如果只是一心只想挣钱的话,说不定技术专家赚到的钱更多。如果只是觉得工作会轻松一点,那你肯定错了,管好自己与管好多个人,费心程度怎么可能一样呢?



问题2:你害怕面对冲突吗?
职场中发生冲突是不可避免的,如果你正考虑转型一个管理岗位,很可能处理和协调冲突,会占用你较多的时间。



管理者要有开放的心态,通过坦诚的沟通,去处理和解决工作中的困难,这是管理者一项必备技能。一旦处理不好冲突,就会有挫败感,毕竟大多数人都希望尽量避免冲突。



问题3:你善于激励别人吗?
作为技术专家,唯一管理对象是自己,而激励自己是一件相对容易的事儿,毕竟你最了解你自己。



然而,当管理一个团队时,你会发现自己面对的是不同性格的人,激励方法也因人而异。对于一线管理者来说,同步战略,确定目标,确保大家对战略与目标的理解一致,让员工们知道自己可以怎样为公司的成功做贡献,让做的好的员工不断升迁,员工的动力就会增加。



而如果你很难做到,很可能你是一个努力的管理者,但团队的士气和员工对团队的认可度,并不一定会符合预期。



问题4:你能做到客观公正吗?
好的管理者,对待员工要一视同仁,统一标准,必须公平地对待所有员工,不能亲疏有别,厚此薄彼。



营造一种公平和客观的环境与氛围,是管理者必须要做到的。如果你过于感性,带队未必适合。



问题5:你是一个容易放权的人吗?
你可能已经习惯了独立完成一个事情,并沉浸在技术设计与落地的喜悦之中。事实上,作为一个管理者,所有的事情都事必躬亲,未必是一件好事。



大胆放权而不是自己包办一切,可能很难,但委托和授权是合格管理者必须做到的。授权给员工一定程度上可以增加他们的内在动力,激发他们的自主性和创造性,给他们更多自由发挥的空间,一定会带来积极的影响。



同时,你还必须定期关注员工们执行的结果,发现潜在的问题,帮助员工解决这些问题,让他们能持续的成长和提高。授人以渔,有时候可能比你亲自上阵抓鱼要来的更难一些。



问了自己这些问题,不知道你的想法有没有什么改变。如果你没有做好准备,也许走一条不同的道路对你和公司来说,都可能是一个更好的选择。

 

 

你可能感兴趣的:(Redis 面试题汇总)