面试记录-redis

328 阅读23分钟

一、概念

1、持久化机制

redis是一个持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证持久化。

RDB:RDB是redis的默认持久化方式。

按照一定的时间周期策略把内存的数据已快照的形式保存到硬盘二进制文件。即snapshot快照存储,通过配置文件的save参数来定义快照周期。

缺点:

无法做到实时持久化,可能会造成数据丢失。

存储数据量较大时,效率较低/IO性能低

基于fork创建子进程,内存产生额外消耗

宕机带来数据丢失。

AOF:redis会将每一个写(增删改)数据命令通过Write函数追加到文件最后,类似于mysql的binlog。

当redis重启会通过重新执行文件中保存的写命令来在内存中重建整个数据。

优点:

AOF可以更好的保护数据,一般AOF会每隔1秒,最多丢失1秒钟的数据。

写入性能高,文件不容易破损

适合做灾难性的误删除的紧急恢复

缺点:

对于同一份文件来说AOF文件一般比RDB文件更大。

恢复速度慢。

使用场景:对数据敏感建议使用AOF,恢复速度快。阶段点数据恢复通常使用RDB.

2、缓存雪崩、缓存穿透,缓存击穿、缓存预热、缓存更新,缓存降级。

缓存雪崩:在高并发下,大量缓存Key在同一时间失效,大量请求落在db,导致DB宕机。

解决方案:

1.随机设置Key失效时间,避免大量key集体失效

2.集群部署,将热点数据分布在不同的Redis库中也能避免Key全部失效。

3.不设置过期时间

4.异步更新,在缓存失效前刷新缓存。

缓存穿透:reids缓存和Db中没有相关数据(例如id<=0)redis中没有这样的数据,直接穿透到db,导致db压力过大宕机

解决方案:

1.对不存在的数据设置Key,value=null,并设置较短的过期时间,避免时间过长影响正常用户。

2.对参数进行校验,不合法参数进行校验。

3.bloom过滤器,判断key是否存在

缓存击穿:某个热点key一直扛着高并发,当缓存key失效的一瞬间,大量并发请求进入,直接访问DB,导致DB宕机。

解决方案:

1.设置热点数据永不过期,

2.加互斥锁,多个线程同时访问DB的时候,可以在第一条查询上使用互斥锁锁住它,其他线程走到这

拿不到线程锁,等第一个线程查询到数据,放到redis中,后面线程从redis中查询即可。

缓存预热:系统上线后首先将相关缓存数据加载到缓存系统,避免用户请求的时候,大量先查数据库,再查数据缓存问题。

解决方案:

1.工具(页面,job)刷新缓存

2.更新缓存,除缓存服务器自带的默认6种策略外,还可以根据业务自定义缓存淘汰,常见策略有2种:

(1)定时去清理过期缓存

(2)当有用户请求时,先判断缓存是否过期,过期的就去底层系统得到新数据并更新缓存。

缓存降级:降级的最终目的是保证核心服务可用,即使是有损的。

3、热点数据与冷数据

热点数据缓存才有价值,对于冷数据而言,大部分可能环没有再次访问就已经失效了,价值不高。

4、Memcache与redis区别

(1).存储方式:memcache将数据全部存在内存中,断电后会挂掉,数据不能超过内存大小。redis有部分数据存在硬盘,redis可以持久化数据。

(2).支持数据类型:memcache所有的值均是简单的字符串,redis支持的数据类型有string、list、set、zset、hash等数据结构的存储。

(3).使用底层模型不同它们之间底层实现方式以及与客户端之间通信的应用协议不一样。redis直接构建了VM机制。

(4).value值大小不同:redis可以达到1gb,memcache只有1mb。

(5).redis速度比memcache快很多

(6).redis支持数据备份,即master-slave模式的数据备份。

5、单线程的redis为什么这么快

(1).纯内存操作

(2).单线程操作,避免了频繁的上下文切换。

(3).采用了非阻塞I/O多路复用机制,可以有更多的连接。

6、redis的数据类型,以及每种的使用场景

(1).string:常规的get/set操作,value可以是string也可以是数字。一般做一些复杂的计数功能的缓存。

(2).hash:value存放的是结构化对象,比较方便的就是操作其中某个字段。单点登录,cookie 30s。

(3).list:使用list结构,可以做简单的消息队列功能。还可以利用lrange命令,做基于redis的分页功能。

(4).set:set因为放的是不重复的集合。可以做全局的去重功能,

(5).sorted set(有序集合),既支持单个元素查询,有支持按照范围查询。跳表是一种典型的以空间换时间的数据结构。

由许多层元素组成,每一层都是一个有序链表,最底层的链表包含了所有元素,如果一个元素出现在了Level i的链表中,则它在Level i之下的链表也会出现。

7、redis的过期策略及内存淘汰机制

redis采用的是定期删除+惰性删除策略。

为什么不用定时删除策略?

定时删除,用一个定时器来负责监视Key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。

在大并发请求下,CPU要将时间应用在处理请求,而不是删除Key,因此没采用这一策略。

定期删除:(占用大量CPU清理数据,影响响应和吞吐量)

redis默认每隔100ms检查,是否有过期的可以删除,有过期的key则删除。需要说明的是redis不是每隔

100ms将所有的key都检查一遍,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。

因此,如果只采用定期删除策略,会导致很多key到时间没有删除。于是,惰性删除就排上了用场。

也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间,那么是否过期了,如果过期了

此时就会删除。

惰性删除:(节省了CPU资源,但是占用了大量内存,大量key没有清除)

只有当访问一个key时,才会判断该key是否已过期,过期则清除

定期删除可能会导致很多过期key到了时间并没有被删除。所以就有了惰性删除。假如你的过期key靠定期删除没有删除,

还停留在内存里,除非你去系统中查一下那个key,才会被redis删除掉。

如果大量过期key,定期删除没有清掉,多行删除也没有去查,也没有删除,那么大量堆积在内存里,没有被删除,导致redis内存耗尽。

这个时候就又有redis内存淘汰机制。

8、redis数据淘汰机制

Redis内存淘汰策略是指Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据

内存淘汰策略用于处理内存不足时的需要申请额外空间的数据,过期策略用于处理过期的缓存数据。

全局的键空间选择性移除

noeviction:当内存不足时容纳新写入数据时,新写入操作会报错。

allkey-lru:当内存空间不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个最常用)

allkey-random:当内存不足以容纳新新入数据时,在键空间中,随机移除某个key。

设置过期时间的键空间选择性移除

voliate-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key

voliate-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

voliate-ttl:当内存空间不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

9、redis为什么是单线程的

因为redis是基于内存操作的,CPU不是redis的瓶颈,redis的瓶颈最有可能是机器内存的大小或者网络带宽,既然单线程容易实现

而且CPU不会成为瓶颈,那就顺理成章的采用单线程方案了。

redis利用队列技术将并发访问变成串行访问

(1).绝大部分请求是纯粹的内存操作(非常快速)

(2).采用单线程避免了不必要的上下文切换和竞争操作。

(3).非阻塞IO优点:

(a).速度快,因为数据在内存中,类似于hashmap,hashmap的优势就是查找和操作的时间复杂度为O(1)

(b).支持丰富的数据类型,string、list、hash、set、sorted set。

(c).支持事务,操作都是原子性,所谓的原子性指,对数据的操作要么全部执行要么全部不执行。

(d).丰富的特性,可用于缓存、消息、按key设置过期时间,过期后自动删除。

10、redis常见性能问题和解决方案

(1).master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2).如果数据比较重要,某个slave开启AOF备份,策略设置每秒同步一次。

(3).为了主从复制的速度和链接的稳定性,最好两个在同一个局域网内。

(4).尽量避免在压力很大的主库上做从库。

(5).主从复制尽量不要使用图状结构,使用单向链表结构更为稳定,即Master<-slave1<-slave2

11、为什么redis操作是原子性的,怎么保证的原子性。

对于redis而言,命令的原子性是指:一个操作的不可再分,要么执行,要么不执行。

redis的操作之所以是原子性的,因为redis是单线程的。

redis本身提供的所有API操作都是原子性的,redis的事务其实是保证批量操作的原子性。

多个命令在并发操作下也是原子性的吗?不一定,将get和set改成单命令操作,incr.

12、Redis线程模型

Reids基于Reactor模式开发了网络时间处理器,这个处理器被称为文件事件处理器器。它的组成结构为4部分:多个套接字,IO多路复用程序,文件事件分配器,

事件处理器。因为文件事件分派器队列的消费是单线程的,所以redis才叫单线程模型。

12、redis事务

redis的事务功能是通过MULT,EXEC、DISCARD和WATCH四个原语实现的,redis会将一个事务中的所有命令序列化,然后按顺序执行。

(1).redis不支持事务回滚,redis在事务失败时不进行事务回滚,而是继续执行余下的命令,所以redis内部可以保持简单且快速。

(2).如果在一个事务中的命令出现错误,那么所有的命令都不会执行。

(3).如果在一个事务中出现错误,那么正确的命令会被执行。

(1).MULT用于开启一个事务,他总是返回OK。MULT执行后,客户端可以继续发送多条命令,这些命令不会立即执行,而是被

放在一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。

(2).EXEC执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令的执行先后顺序排列。当操作被打断,返回nil.

(3).调用DISCARD命令,客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务中退出。

(4).WATCH命令可以为redis提供compare-and-set(CAS)行为。可以监控一个或多个建,一但其中

有一个键被修改或者删除,之后的事务不会被执行,监控一直只需到EXEC命令。

13、应用场景

缓存、共享Session、消息队列、分布式锁、限流

14、zset跳表的数据结构

跳表(skip list),增加了向前指针的链表叫做跳表,跳表是一个随机化的动态数据结构,实质就是可以进行二分查找的有序链表。

跳表在原有的有序链表上面增加了多级索引,通过多级索引来实现快速查找。跳表不仅能提高搜索性能,还可以提高插入和删除操作性能。

原理:

跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。首先在最高索引上查找最后一个小于当前查找元素的位置,然后

再次跳到次高级索引继续查找,直到跳到最底层为止。

跳表是一种典型的以空间换时间的数据结构。

由许多层元素组成,每一层都是一个有序链表,最底层的链表包含了所有元素,如果一个元素出现在了Level i的链表中,则它在Level i之下的链表也会出现。

为什么使用跳表(skip list)

zset加强版跳表:

Reids中队SkipLIst做了些改造:增加了后驱指针,同时记录了value和score,且score可以重复,且通过score对成员进行从小到大排序。

应用场景:

延时队列:zset会按照score进行排序,如果score代表想要执行时间的时间戳。在某个时间点将数据插入zset集合,就会按照时间戳大小进行排序,也就是对执行时间先后顺序进行排序。

起一个死循环线程不断地进行取第一个Key值,如果当前时间戳大于或者等于score就将它取出来进行消费删除。

排行榜:经常浏览社区的话,对“1小时最热门不陌生”,已当前小时的时间戳作为zset的key,把贴在ID作为member(obj),点击数评论数作为score,当score发生变化时更新score。

限流:滑动窗口是常见的一种策略,如果把一个用户的ID作为Key来定义zset,member或者score都为访问时的时间戳。只需要统计某个key在指定时间戳区内的个数,

就能得到这个用户滑动窗口访问频次,与最大通过次数比较,来决定是否允许通过。将时间窗口外的记录全部清理掉,只保留窗口内的记录

zset支持随机的插入和删除,不宜使用数组。

(1).性能考虑:高并发情况下,树新结构需要rebalance树的操作,相对于调鱼鳔变化只涉及局部。

(2).实现考虑:在复杂度和红黑树相同的情况下,跳表实现简单,更加直观

15、redis设置过期时间

redis中对存储的key可以设置一个过期时间,作为缓存数据库,非常实用。

16、怎么保证缓存和db的一致性

合理设置缓存的过期时间

新增,更改,删除db操作时同步更新redis,可以使用事务保证数据一致性。(一般可以先更新db,然后删除缓存Key)

缓存失败时增加重试机制。

17、redis实现分布式锁

使用setex(set if not exists)命令,只允许被一个程序占用,使用完调用del释放锁。

配合expire key seconds 自动释放锁。过期时间,总有程序执行的时间可能不固定导致并发锁中设置的过期时间,有可能已过期但程序没有执行完成。

更好的优化,可以使用redission实现,redisssion中使用watchdog自动延期机制,每隔10秒检查一下,如果客户端还持有锁,就会不断的延长锁的有效时间。

三、集群方案

1、redis单实例

不适合生产,如果宕机严重影响系统

2、redis master-slave(主从架构)

作用:

读写分离:master写,slave读,提高服务器的读写负载能力。

负载均衡:基于主从结构,配合读写分离,有slave负担master负载,并根据需求的变化,改变slave的数量,通过多个节点分单数据读取负载,大大提高了

redis服务器并发量与数据吞吐量。

故障恢复:当master出现问题时,有slave提供服务,实现快速故障恢复。

数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式。

高可用基石:基于主从复制,构建哨兵模式与集群,实现redis的高可用方案。

过程:

复制/数据同步过程分为2个阶段

1.全量复制:slave接收到master生成的RDB文件,先清空自身的旧数据,然后进行RDB恢复过程,然后告知master已经恢复完毕。

2.部分复制(增量复制):主节点发送数据给从节点过程中,主节点还会收到一些写操作,这时候的数据存储在复制缓冲区中。

master把自己复制缓冲区的数据发送到slave,slave接收到AOF指令后执行重写操作,恢复数据。

存在问题:

1.一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。

2.主节点的写受到单机的限制。

3.主节点的存储能力收到单机的限制。

3、redis哨兵(2.8及以上)

哨兵是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择出新的master并将所有slave连接到新的master。

作用:

监控:不断的检查master和slave是否正常,master存活检测,master与slave运行情况检测。

通知(提醒):当被监控的服务器出现故障时,向其他(哨兵,客户端)发送通知。

自动故障转移:断开master与slave的链接,选取一个slave作为master,讲其他slave链接到新的master,并告知客户端新的服务地址。

配置中心:如果故障转移发生了,通知Client客户端新的master地址。

哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

故障转移时,判断一个master node是都宕机了,需要大部分的哨兵同意才行,涉及到了分布式选举问题。

即时部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,就很坑了。

核心知识:

哨兵至少需要3个实例,来保障自己的健壮性。

哨兵+redis主从的部署结构,是不保证数据零丢失的,只能保证redis集群的高可用。

对于哨兵+redis主从复杂部署,尽量进行充分的测试和演练。

4、redis cluster(集群,3.0)

Redis Cluster是一种服务端Sharding技术,3.0版本正式提供。Redis Cluster并没有使用一致性hash,而是采用sloat(槽)的概念,一共分成16384个槽。将请求发送到任意节点,

接收到请求的节点会将查询请求发送到正确的节点上执行。

通常来说单台redis执行10W/S,基本上满足了也无需求,随着业务增加,遇到单节点内存、并发、流线瓶颈,采用cluster方案实现负载均衡。

cluster主要解决分片问题,即把整个数据按照规则分成多个子集存储在多个不同的节点上,每个节点负责自己整个数据的一部分。

redis clusetr采用哈希分区中的虚拟槽分区。虚拟槽分区巧妙的使用哈希空间,使用分散度良好的哈希函数把所有的数据映射到一个固定范围内的

整数集合,整数定义为槽(slot).

redis cluster槽的范围是0~16383。槽是集群内数据管理和迁移的基本单位。采用大范围槽的目的是为了方便数据的拆分和集群的扩展,每个节点负责

一部分数据的槽。redis cluster采用虚拟槽分区,所有的键根据哈希函数映射到0~16383,计算公式slot=CRC16(key)&16384.每个几点负责维护一部分槽以及

槽所映射的键值数据。

节点间内部通信机制:基本通信原理,集群元数据的维护有两种方式:集中式,Gossip协议。

分布式寻址算法:hash算法(大量缓存重建),一致性hash算法(自动缓存迁移)+虚拟节点(自动负载均衡),redis cluster的hash slot算法。

优点:无中心架构,支持动态扩容,对业务透明;具备Sentinel的监控和自动Failover(故障转移)能力;客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;高性能,客户端直连redis服务,免去proxy代理耗损。

缺点:运维复杂,数据迁移需要人工干预,不支持批量操作,分布式逻辑和存储模块耦合等。

5、基于代理服务器分片

客户端发送请求到一个代理组件,代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端

四、分区

1、Redis是单线程的,如何提高多核CPU的利用率

可以在同一个服务器部署多个Redis的实例,并把他们当做不同的服务来使用,在某些时候,无论如何一个服务器是不够的的,所以如果你想使用多个CPU,可以考虑下分片。

2、为什么做Redis分区

分区可以让redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存,分区使Redis的计算能力通过简单地增加计算机得到成倍提升,redis的事务功能是通过MULT

的网络宽带也会随着计算机和网卡的增加而成倍增长。

客户端分区

代理分区

查询路由

五、分布式

1、redis实现分布式锁

Redis为单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的链接并不存在竞争关系,Redis中可以使用SetNX命令实现分布式锁

当且仅当Key不存在,将key的值设为value。若给定的key已经存在,则setNx不错任何操作

SETNX(SET if Not Exists),成功返回1,失败返回0

为了防止获取锁后程序异常,导致其他线程/进程调用setNx命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间释放锁,使用DEL命令蒋锁数据删除。

2、如何解决Redis的并发竞争Key问题

分布式锁(zookeeper和redis都可以实现分布式锁)

基于zookeeper临时有序节点可以实现的分布式锁。

3、分布式Redis什么时候做合理

既然redis是轻量的,为防止以后扩容,最好的办法是一开始就启动较多实例。

4、redission

使用redission实现,redisssion中使用watchdog自动延期机制,每隔10秒检查一下,如果客户端还持有锁,就会不断的延长锁的有效时间

5、redLock

Redis官方提出了一种基于redis实现分布式锁的方式叫redLock,此种方式比原先的先单节点方法更安全。

安全特性:互斥访问,即永远只有一个client能拿到锁

避免死锁:最终client都可能拿到锁,不会出现死锁的情况,

容错性:只要大部分redis节点存活就可以正常提供服务。

6、分布式限流

(1)incr+expire

(2)令牌算法,可以滑动

六、常用工具

Redis支持的java客户端有

Redisson、Jedis、lettuce等等,官方推荐使用Redisson。

Redisson是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。

Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

七、常见问题

1、字符串类型的值最大容:512M。

2、redis里面1亿个Key,其中10Wkey是固定已知前缀开头如何找出

使用Keys指令

redis是单线程的,keys指令会导致线程阻塞一段时间,线上服务会停顿,知道到指令执行完毕,服务才能恢复。这个是可以用scan指令,scan指令可以无阻塞的提取出制定模式的key列表,但是会有重复,客户端需要去重

3、redis做异步队列

list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop。

4、redis回收使用LRU算法