redis面试详解

redis的线程模型

image
  1. Server socket 监听到客户端发的请求,会产生一个AE_REABLE的事件
  2. IO多路复用程序将AE_REABLE事件压入队列中,依次执行
  3. 文件事件分派器从队列中取数据交给链接应答处理器
  4. 链接应答处理器创建与客户端的长链接socket,并将这个长链接和需要执行的命令给到命令请求处理器
  5. 命令请求处理器进行命令执行,执行完成之后将socket的长链接与命令回复处理器关联
  6. 文件事件分派器使用对应的socket关联与命令回复处理器的执行断开socket请求

线程模型的意义

  1. 使用非阻塞IO多路复用程序,所以支持高并发
  2. 使用的文件事件分派器是单线程的,所以redis是单线程的

redis的击穿(穿透)

出现原因

出现大量redis中的key不存在的请求,导致创建了太多的jdbc链接从而跳过了redis的缓存机制,给数据库带来太大的压力

解决办法

1. 增加数据规则的验证,只有符合规则的key才进行查询,防止人为恶意的进行请求
2. 数据库中查询为null的对应key值信息也进行缓存

redis的缓存雪崩

出现原因

同一时间点大量的缓存数据失效,导致透过redis直接请求数据库出现问题

解决办法

1. 均匀分配缓存的过期时间(业务逻辑角度)
2. 创建多级缓存来辅助修改(springBoot+redis+Ecache)
3. 使用分布式锁的逻辑,保证获取数据库资源有一个一定的上限,超过上限就进行等待知道锁释放

redis的主从、集群和哨兵

主从复制

image

产生的原因

1. 容错性(只有一台的情况,一旦挂掉就会影像业务逻辑)
2. 并发性(一台的并发能力必然很低,读的能力会很差)

使用主从的结果

1. 数据冗余
2. 读写分离

原理

1. 主节点负责读/写,从节点只能进行读操作
2. 可以一主多从
3. 数据同步:一般初次会讲所有主节点数据同步到从节点,后续都是补发更新的数据,并且主从节点有长链接的心跳机制

哨兵机制

image

产生的原因

在搭建主从之后,主节点只有一个,一旦发生错误之后就没有主节点信息了

原理

  1. 在主节点外层新增一套哨兵逻辑
  2. 暴露给外部的是哨兵的信息,相当于哨兵进行了一层封装
  3. 一旦主节点出现问题,哨兵机制会在剩下的从节点中通过选举算法选出一个节点充当主节点

哨兵原理

  1. 每个sentinel会以心跳机制请求master、slave进行PING命令,如果一个实例响应超过设置的时间.
  2. 当有足够数量的额sentinel确定master下线,就会认定下线,就会进行选举算法选举出新节点作为主节点

当使用哨兵模式之后,就不要再直接连redis的ip和端口了,而是访问哨兵的信息,由哨兵进行转发

redis淘汰策略

1.noeviction:禁止驱逐数据,当内存上限的时候,再添加数据会产生异常。从而保证数据不会丢失

2.allkeys-lru: 全体数据中找最近使用最少的数据进行淘汰

3.volatile-lru: 从有设置过期时间的数据中找最近使用最少的数据进行淘汰

4.allkeys-random: 全体数据随机淘汰

5.volatile-random: 设置过期时间的数据随机淘汰

6.volatile-ttl: 从设置过期时间的数据中,随机找一些数据淘汰。距离过期时间越短,淘汰优先级越高

Redis淘汰策略主要分为LRU淘汰、TTL淘汰、随机淘汰三种机制。

LRU淘汰

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,server.lrulock 的值是根据 server.unixtime 计算出来进行排序的,然后选择最近使用时间最久的数据进行删除。另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。每一次访问数据,会更新对应redisObject.lru。

在Redis中,LRU算法是一个近似算法,默认情况下,Redis会随机挑选5个键,并从中选择一个最久未使用的key进行淘汰。在配置文件中,按maxmemory-samples选项进行配置,选项配置越大,消耗时间就越长,但结构也就越精准。

TTL淘汰

也就是按照过期时间进行淘汰。距离过期时间越短,淘汰的优先级越高。

随机淘汰

在随机淘汰的场景下获取待删除的键值对,随机找hash桶再次hash指定位置的dictEntry即可。

Redis中的淘汰机制都是几近于算法实现的,主要从性能和可靠性上做平衡,所以并不是完全可靠,所以开发者们在充分了解Redis淘汰策略之后还应在平时多主动设置或更新key的expire时间,主动删除没有价值的数据,提升Redis整体性能和空间。

总结六种:

  1. 不淘汰
  2. 随机淘汰
  3. 有过期时间的随机淘汰
  4. 使用最少
  5. 有过期时间的使用最少
  6. 过期时间越短淘汰率越高

redis的八种数据结构

  • String,字符串类型
  • hash,对象类型
  • list,有序可重复类型
  • set,无需不可重复类型
  • zset,无需可重复类型
  • Geospatial,地理位置类型
  • bitmap,位图类型(key:value(1,2))
  • Hyperloglog,数学上的集合类型

队列模式:

list作为队列,rpush生产消息,lpop消费消息

redis持久化

不建议开启redis的持久化操作

RDB

原理

快照的方式保存数据,每隔一段时间有固定多少key值发生变更,将数据进行快照备份一次

由父进程fork开启一个子进程,使用子进程进行I/O操作将内存中的快照保存在硬盘上

缺点

耗时,耗性能

AOF

原理

将操作日志保存下来,做的是增量保存.在重启的时候执行所有操作一遍

缺点

文件体积大,恢复速度慢

现在一般采用两者集合的方式来进行持久化。底层对这两种方案做了融合处理

redis的事务

redis支持事务,但是整体redis集群不支持事务

1. 事务的一般执行流程

1. 开启事务`MULTI`
2. 执行操作,也就是执行你需要进行的操作set等
3. 提交事务`EXEC`,会执行一并所有的操作一起提交

2. 使用SpringBoot+redis执行事务

//1. 创建对应执行的RedisTemplate对象
@Autowired
StringRedisTemplate stringRedisTemplate
//2. 开启事务权限
stringRedisTemplate.setEnableTransationSupport(true);
//3. 开启事务
stringRedisTemplate.multi();
//4. 回滚
stringRedisTemplate.discard();
//5. 提交
stringRedisTemplate.exec();

如果开启事务,所有的命令只有在执行了exec。才会往redis中插入

redis实现分布式锁

原理

  1. redis是单线程的

如何实现

1.线程 A setnx(上锁的对象,超时时的时间戳 t1),如果返回 true,获得锁。

2.线程 B 用 get 获取 t1,与当前时间戳比较,判断是是否超时,没超时 false,若超时执行第 3 步;

3.计算新的超时时间 t2,使用 getset 命令返回 t3(该值可能其他线程已经修改过),如果

t1==t3,获得锁,如果 t1!=t3 说明锁被其他线程获取了。

4.获取锁后,处理完业务逻辑,再去判断锁是否超时,如果没超时删除锁,如果已超时,不用处理(防止删除其他线程的锁)。

核心

setnx关键字,将key对应的资源锁定,set成功返回1,失败返回0

对比redis与zookeeper实现分布式锁

edis实现分布式锁与Zookeeper实现分布式锁的区别

相同点

在集群的环境下,保证只允许有一个jvm进行执行

从技术上分析

Redis是nosql数据库,主要特点是缓存

Zookeeper是分布式协调工具,主要用户分布式解决方案

实现思路分析

获取锁

Zookeeper,多个客户端(jvm)会在Zookeeper上创建同一个临时节点,因为Zookeeper节点命名路径保证唯一,不允许出现重复,只要谁能创建成功就能获取这个锁

redis,多个jvm会在redis中使用setnx命令创建相同的一个key,因为redis的key保证唯一,不允许重复。只要谁先创建成功,谁就能获取锁

释放锁

Zookeeper直接关闭 临时节点session会话连接,因为临时节点生命周期session会话绑定在一起,如果session会话连接关闭的话,就会使这个临时节点被删除

然后客户端使用事件监听,监听到临时节点被删除,就释放锁

redis释放锁的时候,为了保证是锁的一致性问题,在删除锁的时候需要判断对应的value是否是对应创建的那个业务的id

死锁解决

Zookeeper使用会话有效方式解决死锁的现象

redis是对key设置有效期来解决死锁现象

性能上:

因为redis是nosql,所以redis比Zookeeper性能好

可靠性

Zookeeper更加可靠。因为redis有效求不是很好控制,可能会产生延迟

其余面试问题

7、一个字符串类型的值能存储最大容量是多少?

512M

8、为什么 Redis 需要把所有数据放到内存中?

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。

所以 redis 具有快速和数据持久化的特征,如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。

在内存越来越便宜的今天,redis 将会越来越受欢迎, 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

11、MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?

使用的淘汰策略可以是LRU策略,可以实现淘汰最近最少使用的数据淘汰掉

22、Redis 中的管道有什么用?

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应,这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。

这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多 POP3 协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

25、Redis key 的过期时间和永久有效分别怎么设置?

EXPIRE 和 PERSIST 命令

26、Redis 如何做内存优化?

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。

比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面。

27、Redis 回收进程如何工作的?

一个客户端运行了新的命令,添加了新的数据。Redi 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。

所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

29.锁互斥机制

那么在这个时候,如果客户端 2 来尝试加锁,执行了同样的一段 lua 脚本,会咋样呢?很简单,第一个 if 判断会执行“exists myLock”,发现 myLock 这个锁 key 已经存在了。接着第二个 if 判断,判断一下,myLock 锁 key 的 hash 数据结构中,是否包含客户端 2 的 ID,但是明显不是的,因为那里包含的是客户端 1 的 ID。

所以,客户端 2 会获取到 pttl myLock 返回的一个数字,这个数字代表了 myLock 这个锁 key的剩余生存时间。比如还剩 15000 毫秒的生存时间。此时客户端 2 会进入一个 while 循环,不停的尝试加锁。

30.watch dog 自动延期机制

客户端 1 加锁的锁 key 默认生存时间才 30 秒,如果超过了 30 秒,客户端 1 还想一直持有这把锁,怎么办呢?

简单!只要客户端 1 一旦加锁成功,就会启动一个 watch dog 看门狗,他是一个后台线程,会每隔 10 秒检查一下,如果客户端 1 还持有锁 key,那么就会不断的延长锁 key 的生存时间。

缓存与数据库不一致怎么办

假设采用的主存分离,读写分离的数据库,

如果一个线程 A 先删除缓存数据,然后将数据写入到主库当中,这个时候,主库和从库同步没有完成,线程 B 从缓存当中读取数据失败,从从库当中读取到旧数据,然后更新至缓存,这个时候,缓存当中的就是旧的数据。

发生上述不一致的原因在于,主从库数据不一致问题,加入了缓存之后,主从不一致的时间被拉长了

处理思路:在从库有数据更新之后,将缓存当中的数据也同时进行更新,即当从库发生了数据更新之后,向缓存发出删除,淘汰这段时间写入的旧数据。

主从数据库不一致如何解决场景描述,对于主从库,读写分离,如果主从库更新同步有时差,就会导致主从库数据的不一致

1、忽略这个数据不一致,在数据一致性要求不高的业务下,未必需要时时一致性

2、强制读主库,使用一个高可用的主库,数据库读写都在主库,添加一个缓存,提升数据读取的性能。

3、选择性读主库,添加一个缓存,用来记录必须读主库的数据,将哪个库,哪个表,哪个主键,作为缓存的 key,设置缓存失效的时间为主从库同步的时间,如果缓存当中有这个数据,直接读取主库,如果缓存当中没有这个主键,就到对应的从库中读取。

46.Redis 如何实现延时队列

你可能感兴趣的:(redis面试详解)