1.Redis有哪些数据结构
字符串String、字典Hash、列表List、集合Set、有序集合Zset。
除此之外还有:
HyperLogLog,Pub/Sub发布订阅,布隆过滤器,Bitmap位图,Geo等
(要能说出他们的使用场景和特点原理才行)
String类型可以用于:普通的kv缓存,计数器(incr/decr),分布式锁(setex/setnx/watch/事务),限流(incr/setex)
hash类型可以用于缓存mysql中的行数据或者对象数据,一个hash key就mysql表中的一行数据。
List类型可以用于:消息队列(lpush/brpop),分页,还有类似粉丝列表,文章列表(将文章id存到列表中,文章内容标题作者存到hash中)
Set类型可以用于:求交集并集差集,去重(利用不重复性),抽奖(无序性)
Zset类型可以用于:排行榜,延时队列
Bitmap类型:实现大数据量的统计(能够节省大量内存)
HyperLogLog类型:实质还是字符串,可以以极小的空间完成独立数据的统计。
BloomFilter类型:实质还是一个位图类型,可用于在一个大数据集中准确的找到一个小数据集,具体应用有去重,防止缓存穿透。
pub/sub类型:由一个生产者发布消息,多个消费者订阅频道后可以接受到消息,用于消息广播。
2.如果有大量的key需要设置同一时间过期,一般需要注意什么?
可能会出现缓存雪崩的问题(即大量key在同一时刻失效,数据请求在这一刻全打在DB上导致数据库崩溃)。可以通过给每一个key设置的过期时间加一个随机数分散过期时间来解决。
3.如何实现Redis分布式锁?
在修改数据前,使用setnx拿到锁,再用expire给锁加上过期时间防止死锁。在修改数据后用delete释放锁。
为了防止setnx和expire不是一个原子操作而造成的可能发生的死锁,可以使用一个可以同时结合setnx和expire的指令,像python的redis客户端就提供了一个接口:redis.set(key,value,ex=expire,nx=True)的命令保证setnx和expire是一个原子操作。
4.假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
使用 keys 前缀*
但是keys命令是一个长指令,而redis在执行命令上是一个单线程,这就会阻塞其他客户端命令的执行导致慢查询。
此时可以使用scan指令无阻塞的提取出指定模式的key列表。
# SCAN cursor [MATCH pattern] [COUNT count]
scan 0 count 20 // 从 游标0(开始偏移) 开始遍历,遍历20个key
scan 0 match key1111* count 20
scan指令是一个基于游标的迭代器,是一个增量式命令, SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用于下一次scan迭代。当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
scan不会阻塞redis主线程。
scan可能会返回零个元素, 但只要命令返回的游标不是0, 应用程序就不应该将迭代视作结束。
如果在scan过程中,有某一个新key写入到redis,该key可能无法被本轮scan扫描到,而且也可能导致已经被扫描过的key被重复扫描到(因为新写入的key导致其他key的游标偏移量改变)。因此 scan 扫描得到的key有一定的偏差。
其他scan命令:
SCAN 命令用于迭代当前数据库中的数据库键。
SSCAN 命令用于迭代集合键中的元素。
HSCAN 命令用于迭代哈希键中的键值对。
ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)。
5.如何使用redis做消息队列?
使用list类型,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。如果希望在没有消息是不使用sleep进行阻塞,可以用blpop命令。
如果希望生产一次消费多次,可以用pub/sub发布订阅,可以实现 1:N 的消息队列。但是缺点是:在消费者下线的情况下,生产的消息会丢失。
6.如何用redis实现延时队列?
首先什么是延时队列,就是具有延时功能的消息队列。典型的应用场景就是:30分钟内没有付款的订单自动取消。
如果用redis来实现,可以使用zset类型。以订单自动取消为例,将订单数据存在hash结构中,同时订单的id和下单时间放到zset中,其中id作为zset的value,下单时间作为zset的score。
消费者用zrangebyscore指令获取30分钟之前的订单数据轮询(使用定时任务一分钟执行一次轮询)进行处理。
7.Redis是怎么持久化的?
两种方式:rdb快照 和 aof日志
RDB会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。
8.RDB的原理是什么?
你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
回答这个问题的时候,如果你还能说出AOF和RDB的优缺点,我觉得我是面试官在这个问题上我会给你点赞,两者其实区别还是很大的,而且涉及到Redis集群的数据同步问题等等。
9.Pipeline有什么好处,为什么要用pipeline?
可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。
10.是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
11.Redis的同步机制了解么(主从复制的过程)?
Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。
12.为啥Redis那么快?
a.完全基于内存,绝大部分请求是纯粹的内存操作
b.采用单线程,避免了上下文切换,也避免线程间的切换而消耗CPU,不存在加锁和释放锁的操作。
c.使用多路I/O复用模型,非阻塞IO
追问:啥是上下文切换?
我的理解是,多线程并发运行的时候每一个线程都有自己的一段时间片,当一个线程运行完自己时间片内的时间是就会将CPU让给下一个线程运行。此时CPU会将上一个线程的工作环境和进度状态保存下来,从寄存器写入到内存中。等这个线程重新拿到CPU开始工作的时候(从就绪状态回到运行状态),CPU会从内存中读取他上一次工作的进度和环境(也就是上下文环境),这样才能从上一次断开的地方继续工作。
举个通俗的例子就是:我要看10本书,看了一本书的一半的时候,用书签标记一下读到了哪里,然后我就去看另一本书去了。下次就直接从这本书的书签处继续往下读。
13.redis是单线程的,我们现在服务器都是多核的,那不是很浪费?
可以在单机上部署多个redis节点。例如redis集群,主从复制。
14.redis的内存淘汰机制
Redis的过期策略,是有定期删除+惰性删除两种。
定期好理解,默认每隔一段时间(100秒)就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了。
为啥不扫描全部设置了过期时间的key呢?
假如Redis里面所有的key都有过期时间,都扫描一遍?那太恐怖了,而且我们线上基本上也都是会设置一定的过期时间的。全扫描跟你去查数据库不带where条件不走索引全表扫描一样,100s一次,Redis累都累死了。
如果一直没随机到很多key,里面不就存在大量的无效key了?
此时就用到了惰性删除:
即当用户查询到某个key的时候,redis会查看这个key是否过期。过期则不返回给用户,直接删掉。
当内存溢出时,redis会根据maxmemory-policy设定的策略对key进行删除直到空出足够的空间。
15.有没有考虑过多个服务器同时(并发)请求redis带来的数据问题?
以秒杀超卖为例,ABC三个服务器同时抢一个库存量只有1的商品,可能3个用户都拿到库存为1(例如redis的命令队列中3个get命令在3个decr命令之前的情况),于是都减库存,导致库存变为-2。
面对这种情况,我们可以使用redis分布式锁,控制用户有序的依次请求这个key,例如A先拿到锁的话,他就可以先将库存-1,然后B再拿到锁,查询库存为0于是抢购失败。
总结:只需设置锁使得多个系统顺序访问,同一时间只有一个用户请求到redis即可,当然get查询和decr减库存都得在锁的保护之内,使其成为一个原子操作,不然锁就是去了意义。
16.你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
我们知道,读数据的时候,基本流程是缓存有就读缓存,缓存没有就读DB,然后顺便写入缓存。
但是如果是写数据呢?请注意,我们说的写数据大多数场景不是说用户修改数据,而是后台人员修改数据。例如管理员在后台修改商品价格,更新文章内容等等,此时管理员是直接写入到mysql,但是redis的数据还没有更新。
此时有两种策略:
A. 最简单的做法就是等缓存的有效期过期,下次用户访问到Db时自动生成新的缓存。如果怕并发量大,把Db打崩,你可以给key的过期时间加随机数分散key的过期时间。
另一种方式是先更新数据库,再删除缓存。
为什么是删除缓存,而不是更新缓存?因为我要等用户访问它的时候让他自动更新。对于频繁修改但很少读取的数据,这种做法可以大幅降低开销。
所以你要考虑一点:这个缓存到底会不会被频繁访问到?
其实删除缓存,而不是更新缓存,就是一个懒加载的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。
如果是数据先写入redis,再同步到Db的话,建议不要每次修改redis时都同步到Db,因为数据可能会被频繁的修改,如果每次修改redis都同步到Db那么系统就会卡顿,Db也吃不消这么频繁的磁盘io。还有就是考虑到用户读的时候不是读你的Db而是先读redis,所以Db也没有必要更新的那么频繁。我们可以在早上将用户修改过的key放到一个队列中,等深夜系统空闲的时候用一个定时任务统一将队列中的数据更新到mysql中。
17.redis和memcached的区别
从数据结构上讲:
Redis 相比 Memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。
从集群来讲:
Redis 原生支持集群模式,而 Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据(得由客户端实现分片算法)。
从性能来讲:
Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis,虽然 Redis 最近也在存储大数据的性能上进行优化,但是比起 Remcached,还是稍有逊色。
redis是单线程,避免了线程的切换上下文切换,锁的性能消耗;memcache是多线程。
从持久化来说:
redis支持持久化,而memcache不支持
18. 请详细描述redis主从复制的过程
A. 在配置文件中配置好主从节点,从节点的配置文件要写好是同步哪个主节点(replicaof 127.0.0.1 6379)以及设置从节点只读(replica-read-only yes)
B. 先后启动主从节点,内部开始了同步的流程
C. 第一次同步,主节点会先对从节点进行全量复制:主节点会fork出一个子进程执行bgsave生成rdb快照,并且通过网络发送给从节点。从节点接收到rdb快照后先清空自己的所有数据(flush)并将快照加载到自己的内存。这个过程中,用户向主节点写入的新命令会被写入到主节点的复制缓冲区,等从节点完成了rdb快照的加载之后,主节点会将复制缓冲区中的新命令发送给从节点让它执行。
D. 完成第一次同步之后,主节点每接收到用户的写命令请求都会发送给从节点。
E. 如果出现网络闪断导致从节点在一小段时间内没有和主节点同步,主节点会对从节点做一个部分复制,根据从节点的偏移量和自己的偏移量作对比,如果缺失的命令没有超过复制缓冲区中记录的命令,那么会将复制缓冲区的相应内容发给从节点,让从节点同步回来。
19.redis的哨兵的工作流程
A. 先搭建redis主从复制结构,开启主从节点
B. 在配置文件中配置好sentinel,指定要监控的主节点(不用指定从节点),用redis-sentinel命令开启所有的sentinel节点
此时sentinel内部会执行3个定时任务:
第一个定时任务:每10秒每个sentinel对master节点执行info命令,目的是发现和获取从节点的信息,然后再对slave节点执行info,目的是为了发现从节点下是否还有从节点
第二个定时任务:每2秒每个sentinel以客户端的身份在master节点上建一个__sentinel__频道,发布消息。同时每个sentinel都订阅这个频道接收消息。目的是让哨兵与哨兵之间进行通信,完成如判断客观下线和领导者选举的过程
第三个定时任务:每1秒每个sentinel对其他sentinel和master/slave节点发送ping消息(心跳检测),目的是为了监控主节点或从节点是否下线。
C. 当master发生故障,sentinel不会马上认为master主观下线,而是在30秒内都无法和master通信才认为它主观下线。当超过半数sentinel认为master主观下线后,就认为master客观下线(slave如果下线,sentinel不会对其进行故障转移,除非slave下面还有slave)。接下来,在正式的故障转移之前,sentinel会进行领导者选举,选出1台sentinel进行故障转移。
D. 选举的过程:每个sentinel节点会向其他的sentinel节点发送命令,要求将自己设置为领导者。每个sentinel第一次接收到这样的请求后都会给对方发送同意的响应,之后再接收到这样的请求会拒绝。当sentinel节点发现自己持有的票数过半则成为领导者,停止选举。
E. 领导者会选择一台从节点对它执行slaveof no one晋升为主节点。选择的依据是:选优先级最高的slave晋升(配置文件中设置slave_priority xxx,slave-priority值越小,优先级越高),如果优先级相同则选择偏移量最大(与主节点数据最接近)的slave,如果偏移量都一样则选择runid最小(启动最早)的slave节点。
F.从节点晋升主节点之后,其他从节点要对这个从节点进行同步,这个过程是一个部分复制。
G.如果发生故障的主节点恢复了,它会作为从节点对当前主节点进行同步。这个同步是一个全量复制。
20. 请描述redis分片的原理、过程和常见问题
原理:
redis使用虚拟槽分区算法进行分片,总的槽范围是0~16383,redis会将这16384个槽平均分配给每一个redis节点;当执行读写命令的时候,redis会对key进行hash运算,得到的结果对16383进行取余得到值就是要放入的槽的编号。根据槽的编号找到对应的redis节点进行命令的执行。
流程:
(redis分片要求集群中的每个redis节点都要有一个slave从节点以备故障转移)
先开启所有的主从节点;
再执行meet操作让一台节点meet其他所有节点,这样集群中所有节点两两之间就可以相互通信(cluster meet),meet的过程中redis会自动完成主从同步让每一台节点认另一台节点为主节点;
最后需要给所有的主节点分配槽范围(cluster addslots);
这样redis分布式集群就搭建好了。
常见问题:
集群规模大的时候,带宽消耗巨大;
数据倾斜和请求倾斜的问题;
数据迁移问题(从单节点迁移到集群);
主从节点无法进行读写分离;
21、如何实现redis主从架构不影响用户服务的情况下平滑迁移到新的机器
例如,A为主节点,B 和 C 为从节点,要将数据迁移到性能更好内存容量更高的 E(新主节点) 和 F 和 G(新从节点)。
方案一:让 E 作为 A 的从节点进行 slaveof 操作,将 E 的 read-only 配置改为no,让E节点可以接收写请求。再让 F 和 G 对 E 进行slaveof操作。
当同步完成以后,修改web服务中的redis配置,将读写流量从A转移到E,并平滑重启web服务。
最后让 E 断开与 A 的同步。
方案二:使用redis-shake工具进行迁移。
22、如何实现 redis分布式集群 平滑迁移到新的机器。
使用redsi-shake工具进行迁移。
redis-shake工具是阿里云自研的开源工具,支持对Redis数据进行解析(decode)、恢复(restore)、备份(dump)、同步(sync/rump)。
可以参考文章:redis集群平滑迁移方案
参考链接:
https://www.cnblogs.com/aobing/p/11811194.html
https://blog.csdn.net/qq_35190492/article/details/102958250
https://juejin.im/post/6844903991562747912