JAVA工程师面试专题-《Redis》篇

目录

一、基础

1、Redis 是什么

2、说一下你对redis的理解

3、Redis 为什么这么快?

4、项目中如何使用缓存?

5、为什么使用缓存?

6、Redis key 和value 可以存储最大值分别多是多少?

7、Redis和memcache有什么区别?

8、说说redis常用应用场景

二、数据类型

1、redis有哪些数据类型,分别适用于什么场景?

2、说说Redis底层数据结构?

三、架构

1、redis 持久化机制

2、redis集群高可用

(1)Replication-Sentinel哨兵模式

(2)Redis-Cluster集群模式

3、redis事务怎么理解?

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

1.过期策略

2.内存淘汰机制:

5、布隆过滤器是什么?

6、如何保证数据库和缓存数据的一致性

1.延时双删

2.采用canal组件监控MySQL的binlog日志,把更新后的数据同步到redis里面

3.基于 RocketMQ 的可靠性消息通信,来实现最终一致性。

7、redis 分布式锁如何实现

四、场景

1、缓存穿透、缓存击穿、缓存雪崩

(2)缓存击穿

(3)缓存雪崩

2、怎么使用redis实现消息队列

3、什么是bigkey,有什么影响?

4、怎么处理热key?

5、缓存预热怎么做呢?

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


一、基础

1、Redis 是什么

Redis是一个开源的,基于内存的,也可进行持久化的,使用C语言编写的键值对存储数据库。

2、说一下你对redis的理解

(1)Redis 是一个高性能的基于 Key-Value 结构存储的 Nosql 开源数据库

(2)目前市面上绝大部分公司都采用 Redis 来实现分布式缓存,从而提高数据的检索效率

(3)Redis 之所以这么流行,主要有几个特点:

  • 它是基于内存存储,在进行数据 IO 操作时,能够 10WQPS
  • 提供了非常丰富的数据存储结构,如 String、List、Hash、Set、ZSet 等。

  • Redis 底层采用单线程实现数据的 IO,所以在数据算法层面并不需要要考虑并发安全性,所以底层算法上的时间复杂度基本上都是常量

  (4) Redis 虽然是内存存储,但是它也可以支持持久化,避免因为服务器故障导致数据丢失的问题

基于这些特点,Redis 一般用来实现分布式缓存,从而降低应用程序对关系型数据库检索带来的性能影响。除此之外,Redis 还可以实现分布式锁、分布式队列、排行榜、查找附近的人等功能,为复杂应用提供非常方便和成熟的解决方案

3、Redis 为什么这么快?

(1)基于内存实现

(2)高效的数据结构

SDS 简单动态字符串

Redis 是用 C 语言开发完成的,但在 Redis 字符串中,并没有使用 C 语言中的字符串,而是用一种称为SDS(Simple Dynamic String)的结构体来保存字符串。

哈希表(字典)

Redis 作为 k-v 型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比如 HashMap,通过 key 就可以直接获取到对应的value。而哈希表的特性,在O(1)时间复杂度就可以获得对应的值。

跳表

作为 Redis 中特有的数据结构-跳跃表,其在链表的基础上增加了多级索引来提升查找效率。这是跳跃表的简单原理图,每一层都有一条有序的链表,最底层的链表包含了所有的元素。这样跳跃表就可以支持在 O(logN) 的时间复杂度里查找到对应的节点。

下面这张是跳表真实的存储结构图:

网上对跳表的各种理论讲解都比较多,,其基本原理为添加多级索引来加快查找速度实现O(logN)的时间复杂度, 通过随机函数确定节点插入到几级索引中来防止跳表退化为单链表。

双向链表

列表 List 更多是被当作队列或栈来使用的。队列和栈的特性一个先进先出,一个先进后出。双向链表很好的支持了这些特性。

压缩列表

ziplist 是 Redis 为了节约内存而开发的,是由一系列特殊编码的连续内存块(而不是像双端链表一样每个节点是指针)组成的顺序型数据结构 ,如下图:

压缩列表是经过特殊编码,专门为了提升内存使用效率设计的。所有的操作都是通过指针与解码出来的偏移量进行的。并且压缩列表的内存是连续分配的,遍历的速度很快

(3)IO多路复用

(4)合理的线程模型,单线程避免线程上下文切换、IO多路复用

(5)渐进式rehash、缓存时间戳

4、项目中如何使用缓存?

变动频率低、查询频率高的数据做缓存,减轻DB压力

5、为什么使用缓存?

高性能:数据库查询耗时,缓存查询极快

高并发:mysql单机QPS 2000左右,redis轻松几万

6、Redis key 和value 可以存储最大值分别多是多少?

  • key 的大小上限为 512M,但一般建议 key 大小不要超过 1KB,这样既可以节约存储空间,又有利于 Redis 进行检索

  • value 的最大值也是 512M。对于 String 类型value 值上限为 512M,而集合、链表、哈希等 key 类型,单个元素 value 上限也为 512M

7、Redis和memcache有什么区别?

  • Redis 支持复杂的数据结构

Redis 相比 Memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, Redis 会是不错的选择。

  • Redis 原生支持集群模式

在 Redis3.x 版本中,便能支持 cluster 模式,而 Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。

  • 性能对比

由于 Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis。虽然Redis 最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色

8、说说redis常用应用场景

  • 缓存

  • 排行榜

  • 计数器

  • 分布式会话

  • 分布式锁

  • 社交网络

  • 消息队列

  • 位操作

二、数据类型

1、redis有哪些数据类型,分别适用于什么场景?

String

这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。场景:缓存、计数、限流等

set college szu

Hash

这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这 个对象没嵌套其他的对象)给缓存在 Redis 里,然后每次读写缓存的时候,可以就操作 hash里的某个字段。场景:用户信息、商品信息等

hset person name bingo

hset person age 20

hset person id 1

hget person name

person = {

"name": "bingo",

"age": 20,

"id": 1

}

List

Lists 是有序列表,这个可以玩儿出很多花样。

比如可以通过 list 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。

比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很

棒的一个功能,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东

西,性能高,就一页一页走

# 0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。

lrange mylist 0 -1

比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。

lpush mylist 1

lpush mylist 2

lpush mylist 3 4 5

# 1

rpop mylist

Set

Sets 是无序集合,自动去重,可以基于 set 玩儿交集、并集、差集的操作。常用于进行标签管理等

#-------操作一个set-------

# 添加元素

sadd mySet 1

# 查看全部元素

smembers mySet

# 判断是否包含某个值

sismember mySet 3

# 删除某个/些元素

srem mySet 1

srem mySet 2 4

# 查看元素个数

scard mySet# 随机删除一个元素

spop mySet

#-------操作多个set-------

# 将一个set的元素移动到另外一个set

smove yourSet mySet 2

# 求两set的交集

sinter yourSet mySet

# 求两set的并集

sunion yourSet mySet

# 求在yourSet中而不在mySet中的元素

sdiff yourSet mySet

Sorted Set

Sorted Sets 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。排行榜。

zadd board 85 zhangsan

zadd board 72 lisi

zadd board 96 wangwu

zadd board 63 zhaoliu

2、说说Redis底层数据结构?

Redis有动态字符串(sds)链表(list)字典(ht)跳跃表(skiplist)整数集合(intset)压缩列表(ziplist) 等底层数据结构。 Redis并没有使用这些数据结构来直接实现键值对数据库,而是基于这些数据结构创建了一个对象系统,来表示所有的key-value

我们常用的数据类型和编码对应的映射关系:

三、架构

1、redis 持久化机制

(1)Redis 的持久化机制

Redis 的持久化机制有:RDB、AOF、混合持久化(RDB+AOF,Redis 4.0引入)。RDB 和 AOF 都是 Redis 里面提供的持久化机制,RDB 是通过快照方式实现持久化、AOF 是通过命令追加的方式实现持久化

  • RDB

RDB 持久化机制会根据快照触发条件,把内存里面的数据快照写入到磁盘,以二进制的压缩文件进行存储

RDB 快照的触发方式有很多,比如

  • 执行 bgsave 命令触发异步快照,执行 save 命令触发同步快照,同步快照会阻塞客户端的执行指令。

  • 根据 redis.conf 文件里面的配置,自动触发 bgsave

  • 主从复制的时候触发

  • AOF

AOF持久化机制是近乎实时的方式来完成持久化的,就是客户端执行一个数据变更的操作,Redis Server 就会把这个命令追加到 aof 缓冲区的末尾,然后再把缓冲区的数据写入到磁盘的 AOF 文件里面,至于最终什么时候真正持久化到磁盘,是根据刷盘的策略来决定的

为了避免追加的方式导致 AOF 文件过大的问题,Redis 提供了 AOF 重写机制(如图),也就是说当 AOF 文件的大小达到某个阈值的时候,就会把这个文件里面相同的指令进行压缩

AOF三种策略

为了控制Redis服务器在遇到意外停机时丢失的数据量,Redis为AOF持久化提供了appendfsync选项,这个选项的值可以是always,everysec或者noappendfsync

  • always:总是写入aof文件,并通过事件循环磁盘同步,即使Redis遭遇意外停机时,最多只丢失一事件循环内的执行的数据

  • appendfsync everysec:每一秒写入aof文件,并完成磁盘同步,即使Redis遭遇意外停机时,最多只丢失一秒钟内的执行的数据

  • appendfsync no:服务器不主动调用fdatasync,由操作系统决定任何将缓冲区里面的命令写入到硬盘里,这种模式下,服务器遭遇意外停机时,丢失的命令的数量是不确定的

(2)RDB和AOF 比较

  • RDB 是每隔一段时间触发持久化,因此数据安全性低,AOF 可以做到实时持久化,数据安全性较高

  • RDB 文件默认采用压缩的方式持久化,AOF 存储的是执行指令,所以 RDB 在数据恢复的时候性能比 AOF 要好

(3)混合持久化

只发生于 AOF 重写过程。使用了混合持久化,重写后的新 AOF 文件前半段是 RDB 格式的全量数据,后半段是 AOF 格式的增量数据。

2、redis集群高可用

Redis高可用常见的有三种方式:

(1)Replication-Sentinel哨兵模式

Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。

Redis sentinel 其中三个特性:

  • 监控(Monitoring):

Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。

  • 提醒(Notification):

当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

  • 自动故障迁移(Automatic failover):

当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

哨兵本身也有单点故障的问题,可以使用多个哨兵进行监控,哨兵不仅会监控redis集群,哨兵之间也会相互监控。

每一个哨兵都是一个独立的进程,作为进程,它会独立运行。

特点:

  • 保证高可用

  • 监控各个节点

  • 自动故障迁移

缺点:

  • 主从模式,切换需要时间丢数据

  • 没有解决 master 写的压力

Redis哨兵系统是怎么实现自动故障转移的?

1.认定主节点主观下线

因为每隔2s,哨兵节点会给主节点发送PING命令,如果在一定时间间隔内,都没有收到回复,那么哨兵节点就认为主节点主观下线。

2.认定主节点客观下线

哨兵节点认定主节点主观下线后,会向其他哨兵节点发送sentinel is-master-down-by-addr命令,获取其他哨兵节点对该主节点的状态,当认定主节点下线的哨兵数量达到一定数值时(这个阀值是Sentinel配置中quorum参数的值,通常我们设置为哨兵总节点数的1/2),就认定主节点客观下线。

3.进行领导者哨兵选举

认定主节点客观下线后,各个哨兵之间相互通信,选举出一个领导者哨兵,由它来对主节点进行故障转移操作。

选举使用的是Raft算法,基本思路是所有哨兵节点A会向其他哨兵节点发送命令,申请成为该哨兵节点B的领导者,如果B还没有同意过其他哨兵节点,那么就同意A成为领导者,最终得票超过半数以上的哨兵节点会赢得选举,如果本次投票,没有选举出领导者哨兵,那么就开始新一轮的选举,直到选举出哨兵节点(实际开发中,最先判定主节点客观下线的哨兵节点,一般就能成为领导者。)

  1. 领导者哨兵进行故障转移

领导者哨兵节点首先会从从节点中选出一个节点作为新的主节点。选择的规则是:

  • 1.首先排除一些不健康的节点。(下线的,断线的,最近5s没有回复哨兵节点的INFO命令的,与旧的主服务器断开连接时间较长的)

  • 2.然后根据优先级,复制偏移量,runid最小,来选出一个从节点作为主节点。

向这个从节点发送slaveof no one命令,让其成为主节点,通过slaveof 命令让其他从节点成为它的从节点,将已下线的主节点更新为新的主节点的从节点,将其他从节点的复制目标改为新的主节点,将旧的主服务器改为从服务器。

(2)Redis-Cluster集群模式

redis在3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的数据。

cluster模式为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。

RedisCluster 是 Redis 的亲儿子,它是 Redis 作者自己提供的 Redis 集群化方案。

相对于 Codis 的不同,它是去中心化的,如图所示,该集群有三个 Redis 节点组成, 每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样。这三个节点相 互连接组成一个对等的集群,它们之间通过一种特殊的二进制协议相互交互集群信息。

如上图,官方推荐,集群部署至少要 3 台以上的master节点,最好使用 3 主 3 从六个节点的模式。

Redis Cluster 将所有数据划分为 16384 的 slots,它比 Codis 的 1024 个槽划分得更为精细,每个节点负责其中一部分槽位。槽位的信息存储于每个节点中,它不像 Codis,它不 需要另外的分布式存储来存储节点槽位信息。 Redis Cluster是一种服务器Sharding技术(分片和路由都是在服务端实现),采用多主多从,每一个分区都是由一个Redis主机和多个从机组成,片区和片区之间是相互平行的。

Redis Cluster集群采用了P2P的模式,完全去中心化。

3 主 3 从六个节点的Redis集群(Redis-Cluster) Redis 集群是一个提供在多个Redis节点间共享数据的程序集。下图以三个master节点和三个slave节点作为示例。

Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。集群的每个节点负责一部分hash槽,如图中slots所示。

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有1-n个从节点。例如master-A节点不可用了,集群便会选举slave-A节点作为新的主节点继续服务。

3、redis事务怎么理解?

multi 开启事务

exec 提交事务

discard 回滚事务

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

1.过期策略
  • 定期删除:每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除

  • 惰性删除:获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西

2.内存淘汰机制
  • noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错

  • allkeys-lru:移除最近最少使用的 key

  • allkeys-random:随机移除某个 key

  • volatile-lru:在设置了过期时间的键空间中,移除最近最少使用的 key

  • volatile-random:在设置了过期时间的键空间中,随机移除某个 key

  • volatile-ttl:设置了过期时间的键空间中,有更早过期时间的 key 优先移除

5、布隆过滤器是什么?

布隆过滤器可以理解为一个有误差的set结构,使用布隆过滤器来判断元素是否存在其中时,如果返回结果是存在,实际可能存在也可能不存在,返回结果不存在时,实际结果肯定是不存在。

布隆过滤器实际上是一个大型的位数组,添加key时,通过几个hash函数对key计算得到多个hash值,将每个hash值与布隆过滤器的位数组的size取模得到下标,然后将数组中这些下标位置的值都设置为1。

创建key为userid的布隆过滤器,0.01是误判率,10000是初始大小

127.0.0.1:6379> bf.reserve userid 0.01 100000

调用exist指令判断181920是否存在于布隆过滤器,如果返回0,不存在,那么说明一定不存在,如果返回1,代表可能存在,也可能不存在。

127.0.0.1:6379> bf.add userid '181920' (integer) 1

布隆过滤器也有一些缺点:

  1. 它在判断元素是否在集合中时是有一定错误几率,因为哈希算法有一定的碰撞的概率。

  2. 不支持删除元素。

6、如何保证数据库和缓存数据的一致性

1.延时双删
  1. 先删除缓存

  2. 更新数据库

  3. 线程等待 N秒(等待时间根据具体业务来判断)

  4. 再删除缓存

2.采用canal组件监控MySQL的binlog日志,把更新后的数据同步到redis里面
  1. 更新数据库

  2. 更新redis的数据

  3. canal组件从binlog加载数据,然后同步到redis

3.基于 RocketMQ 的可靠性消息通信,来实现最终一致性。

7、redis 分布式锁如何实现

1、加锁时要设置过期时间SET lock_key unique_value EX expire_time NX

2、操作共享资源

3、释放锁:Lua脚本,先GET判断锁是否归属自己,再DEL释放锁

集群模式+Redlock实现高可靠的分布式锁

让客户端和多个独立的Redis实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。

https://blog.csdn.net/fuzhongmin05/article/details/119251590

四、场景

1、缓存穿透、缓存击穿、缓存雪崩

(1)缓存穿透

缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。

解决方案:参数校验、缓存空对象及布隆过滤器

(2)缓存击穿

在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

解决方案:永不过期或分布式互斥锁

(3)缓存雪崩

缓存层宕机或者大量缓存同时失效,请求直接到达存储层,存储层压力过大导致系统雪崩。

解决方案:缓存高可用、避免缓存同时失效及采用多级缓存

2、怎么使用redis实现消息队列

  1. 基于List的LPUSH+BRPOP实现

  2. 基于Zset

  3. PUB/SUB

  4. 基于Stream类型

3、什么是bigkey,有什么影响?

  • 字符串类型:单个value值很大,一般超过10KB就是bigkey

  • 非字符串类型:体现在元素过多

危害:

  1. 内存空间不均匀,redis cluster中,bigkey造成节点内存空间使用不均与

  2. 超时阻塞,redis单线程,操作bigkey比较好使,阻塞redis的可能性增大

  3. 网络拥塞,每次获取bigkey产生的网络流量较大

4、怎么处理热key?

  • 什么是热Key?所谓的热key,就是访问频率比较的key。

  • 怎么处理热key?

对热key的处理,最关键的是对热点key的监控,可以从这些端来监控热点key:

  1. 客户端 客户端其实是距离key“最近”的地方,因为Redis命令就是从客户端发出的,例如在客户端设置全局字典(key和调用次数),每次调用Redis命令时,使用这个字典进行记录。

  2. 代理端 像Twemproxy、Codis这些基于代理的Redis分布式架构,所有客户端的请求都是通过代理端完成的,可以在代理端进行收集统计。

  3. Redis服务端 使用monitor命令统计热点key是很多开发和运维人员首先想到,monitor命令可以监控到Redis执行的所有命令。

只要监控到了热key,对热key的处理就简单了:

  1. 把热key打散到不同的服务器,降低压⼒

  2. 加⼊⼆级缓存,提前加载热key数据到内存中,如果redis宕机,⾛内存查询

5、缓存预热怎么做呢?

所谓缓存预热,就是提前把数据库里的数据刷到缓存里,通常有这些方法:

  1. 直接写个缓存刷新页面或者接口,上线时手动操作

  2. 数据量不大,可以在项目启动的时候自动进行加载

  3. 定时任务刷新缓存

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

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

对方接着追问:

如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

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

redis的单线程的。

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

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

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农滴自我修养

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值