Java面试—Redis篇

1、什么是Redis?

  1. Redis是一种基于键值对(key-value)的NoSQL数据库

NoSQL数据库是一种非关系型数据库,它不使用关系型数据库的传统基于表的结构。

  1. 基于C语言开发
  2. Redis中的数据是存储在内存中的,读写速度非常快
  3. Redis比一般键值对强大之处是,其中的value支持String、Hash、Sorted Set、BitMap、GEO等

2、Redis可以运用在哪些地方呢?

  1. 缓存:我相信大多数人接触到Redis一开始都是做缓存的吧,读写速度快,减少关系型数据的访问压力,还有存储session,存储序列化等等
  2. 计数器、排行榜、赞/踩:Redis有天然的计数功能来做计数器,采用合适的结构去构建排行榜体系(本人是在黑马点评中都见识过)
  3. 消息队列:Redis提供了发布订阅和阻塞队列的功能,也可以用,但是对于这个功能还是交给专业的RocketMq、RibbitMq和Kafka这些东西去做
  4. 分布式锁:在分布式环境下,利用Redis去做分布式锁,提到这个就要了解一下Redisson

3、Redis为什么那么快?

  1. 是基于C语言开发的,其中用到的几种数据结构都是经过优化的,性能很高
  2. 是完全基于内存的操作,内存的访问速度是磁盘的上千倍
  3. 使用的是单线程和IO多路复用,高效的事件处理模型

4、Redis有哪些基本数据结构?

描述 使用场景
string 字符串是最基础的数据结构,但是值最大不能超过512MB 缓存、计数、共享session、限速
hash 本身又是key-value的键值对 缓存用户信息、缓存对象
list 可以存储多个有序的字符串 消息队列、文章列表
set 用来保存多个字符串元素,集合中不允许有重复元素并且无序 标签、共同关注
Sorted Set 带权的set,通过权可以进行排序 用户点赞排序、用户排序

还有三种特殊的数据类型:

geospatial 可以推算出地理位置信息,两地之间的距离
hyperloglog 数据上集合的元素个数,是不能重复的,常用于统计网站的UV
bitmap 就是通过最小的单位 bit 来进行0或者1的设置,表示某个元素对应的值或者状态

5、Redis的底层数据机构?

  1. String:底层数据结构是由简单动态字符串(SDS)直接存储,但是编码方式可以是int、raw、embstr
    1. int编码:字符串保存的是整数值,会直接保存在redisObject的ptr里面,并将编码设置成int
    2. raw编码:保存大于32字节的字符串,会使用SDS结构,并将编码设置为raw,此时内存分配次数为两次,创建redisObject和sdshdr
    3. embstr编码:保存小于等于32字节的字符串,也是SDS结构,结构做了优化,内存只用分配一次,分配一块连续的空间即可

在Redis中,存储long、double类型的浮点数是先转换为字符串再进行存储的;int编码、embstr编码如果继续做字符串追加操作的话,一定条件下会转换成raw编码的

  1. List:底层数据结构是由ziplistlinkedlist组成的。
    1. ziplist看起来是一块连续的内存,本质上就是一个字节数组,为了节约内存而设计的一种线性结构,可以包含多个元素,每个元素可以是一个字节数组或一个整数
    2. linkedlist:就和普通那个差不多

当list对象保存的字符串元素长度都小于64字节,保存的元素个数小于512个,使用ziplist编码,其余使用linkedlist编码

  1. hash:底层数据结构是由ziplisthashtable组成的
    1. ziplist:保存同一键值对的两个节点紧靠相邻,键key在前,值value在后,先保存的键值对在压缩列表的表头方向,后来在表尾方向
    2. hashtable:用字典键值对保存,字典的键为字符串对象,保存键key,字典的值也为字符串对象,保存键值对的值

当list对象保存的字符串元素长度都小于64字节,保存的元素个数小于512个,使用ziplist编码,其余使用hashtable编码,这两个条件的上限值可以进行配置文件修改

  1. set:底层数据结构是由intsethashtable
    1. intset:使用整数集合作为底层实现,set对象包含的所有对象都被保存在intset整数集合中
    2. hashtable:字典key包含一个set元素,value都为null

当set对象保存的所有元素都是整数值,set对象保存的元素数量不超过512个,使用intset编码,其余使用hashtable编码

  1. zset:底层数据结构是ziplistskiplist
    1. ziplist:每个集合元素使用相邻的两个压缩列表节点保存,一个保存元素成员,一个保存元素的分值,然后根据分数进行从小到大的排序
    2. skiplist:使用了zset结构,包含一个字典和一个跳跃表

当有序集合保存的元素数量小于128个,有序集合保存的所有元素的长度都小于64字节,使用ziplist,否则使用skiplist,这两个条件的上限值可以进行配置文件修改

6、Redis的过期策略和淘汰策略?

  1. 我们可以通过给缓存数据设置过期时间,它有助于缓解内存的消耗,有利于某些应用场景(短信验证码)
  2. Redis通过一个叫做过期字典来保存你设置过数据的过期时间,实际上就是一个hash表,当我们查询一个key时,Redis先会查找这个key是否存在于过期字典中,如果不在,就正常取值即可,如果在的话,会获取key的过期时间,然后与系统时间做对比来判断是否过期
  3. 过期策略:
    1. 惰性删除:只会取出key的时候才会对数据进行过期性检查,对cpu友好
    2. 定期删除:每隔一段时间就会抽取一批key执行删除过期key的操作,对内存更加友好

所以Redis一般采用的是:定期删除+惰性删除的过期策略

  1. 淘汰策略:当 Redis 的运行内存已经超过 Redis 设置的最大内存之后,则会使用内存淘汰策略删除符合条件的 key,以此来保障 Redis 高效的运行。
内存淘汰策略 作用
volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random 从已设置过期时间的数据集中任意选取数据淘汰
allkeys-lru 当内存不足时,在键空间中,移除最近最少使用的key(最常用)
allkeys-random 从数据集中任意选择数据淘汰
no-eviction 禁止驱逐数据,当内存不足时,禁止写入数据,会报错
vloatile-lfu 从已设置过期时间的数据集中挑选最不经常使用的数据淘汰
allkeys-lfu 当内存不足时,在键空间中,移除最不经常使用的key

7、Redis持久化方式有哪些?有什么区别?

Redis的持久化方案分为RDB和AOF:

扫描二维码关注公众号,回复: 15424550 查看本文章
  1. RDB:RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发
    1. 手动触发:save和bgsave命令,第一个是阻塞当前Redis,RDB完成后恢复正常;第二个是创建一个子线程,RDB持久化和Redis服务器线程并行
    2. 自动触发:使用save m n 命令,在m秒内数据集存在n次修改;对节点进行全量复制,主节点执行bgsave生成RDB文件发送给从节点;执行reload命令重新加载Redis时,也会触发save操作;默认下执行shutdown,如果没有开启AOF持久化功能则自动执行bgsave
  2. AOF:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性。
    1. 所有的写入命令会追加到aof_buf(缓冲区)中
    2. AOF缓冲区根据对应的策略向硬盘做同步操作
    3. 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的
    4. 当Redis服务器重启的时候,可以加载AOF文件进行数据恢复
  3. 区别
RDB AOF
优点 只有一个dump.rdb文件,适合备份、复制;灾容性好;恢复速度快,远快于AOF 实时性好;通过append模式写文件,即使中途服务器宕机,也可以通过redis-check-aof工具解决数据一致性问题
缺点 实时性低,隔一段时间持久化一次,没办法做到实时持久化;存在兼容问题 AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比RDB启动效率低

8、什么是缓存击穿、缓存穿透、缓存雪崩?怎么解决?

缓存击穿 一个并发量比较大的key在某个时间过期,导致所有的请求直接打到DB,可能直接给搞宕机了。
缓存穿透 指的是查询的数据既不在缓存中也不在数据库中
缓存雪崩 某一时刻发生大规模的缓存失效的情况,导致大量的请求都直接打到了数据库上,对数据库造成了巨大的压力
  1. 解决缓存击穿:
    1. 基于互斥锁方式解决缓存击穿问题,加锁后去查数据库
    2. 使用逻辑过期方式,获取缓存数据的时候,先判断是否是逻辑过期,没过期就获取,过期了就加锁重建
  2. 解决缓存穿透:
    1. 当数据库中未命中的时候,就设置一个带过期时间的空值保存到缓存中,之后就会从这个缓存中获取数据
    2. 布隆过滤器
  3. 解决缓存雪崩:
    1. 提高缓存的可用性:集群部署和多级缓存
    2. 过期时间:均匀过期时间、热点数据永不过期
    3. 服务降级:当出现大量缓存失效,系统中暂时舍弃一些非核心的接口和数据的请求,直接返回一个准备好的fallback错误处理信息
    4. 服务熔断:当缓存服务器宕机或者超时响应的时候,暂停业务服务访问缓存系统

9、Redis的主从复制原理?主从复制的方式?

image.png

  1. 保存主节点信息:从节点保存主节点的ip和port
  2. 主从建立socket连接:从节点尝试和主节点建立网络连接
  3. 发送ping命令:连接建立成功后,会通过ping进行首次通讯,检测网络是否可用
  4. 权限验证:从节点必须通过主节点要求的密码验证
  5. 同步数据集:通讯正常,主节点就会把数据都发送给从节点进行同步
  6. 命令持续复制

支持全量复制和部分复制,全量复制用于初次复制场景,会将数据一次性同步过去,网络开销大;部分复制应对全量复制的高开销做的优化措施

10、Redis主从复制有哪些问题?什么是哨兵?原理是什么?

  1. 存在的问题:
    1. 一旦主节点发生故障,要人工干预才能进行故障转移
    2. 主节点的写能力收到单机的限制
    3. 主节点的存储能力受到单机的限制
  2. 哨兵:用来解决主从复制中没办法完成自动故障转移的问题
    1. 组成:
      1. 哨兵节点:特殊的Redis节点,不存储数据,对数据节点进行监控
      2. 数据节点:主节点和从节点都是数据节点
    2. 功能:
      1. 监控:不断检查主节点和从节点是否正常运作
      2. 自动故障转移:当主节点挂了的时候,哨兵会将其他一个从节点升级为主节点
      3. 配置提供者:通过链接哨兵来获得当前Redis服务的主节点地址
      4. 通知:哨兵可以将故障转移的结果发送给客户端
  3. 原理:
    1. 定时监控:对各个节点进行监控
    2. 主观下线和客观下线:主观下线就是哨兵节点会定期去给其他节点发送消息,如果没有及时回复的话,就认为它主观下线了;客观下线指当一个哨兵节点认为主节点主观下线了,就询问其他哨兵节点对该主节点的判断,超过一定数量的判断结果,就认为它有问题,就是客观下线了
    3. 哨兵节点领导者节点选举:故障转移只需要一个哨兵节点来完成,所以要进行哨兵节点领导者的选举过程,每个哨兵向其他哨兵发送请求,收到该请求的哨兵如果没有同意过其他的哨兵的请求则同意,否则拒绝,当同意数量大于一定数量时,它就成了领导者
    4. 故障转移:领导者主持故障转移
      1. 通过一些规则去选取一个主节点
      2. 领导者哨兵让其执行命令成为主节点
      3. 让其余的节点,让他们成为新主节点的从节点
      4. 将旧主节点切换为从节点,对其进行监控,当它恢复了就去复制新的主节点

11、Redis集群了解吗?集群中数据如何分区?

哨兵解决了高可用的问题,集群解决了高可用和分布式的问题。

  1. 数据分区:数据分区是集群核心的功能。
    1. 集群将数据分散到多个节点,突破了Redis单机内存大小的限制,存储容量大大增加
    2. 每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力
  2. 高可用:集群仍然支持自动故障转移
  3. 如何分区:
    1. 节点取余分区:通过某些特定的数据对响应的hash值取余,来确定数据映射到哪一个节点上
    2. 一致性哈希分区
    3. 虚拟槽分区

12、Redis如何实现分布式锁?

Redis实现分布式锁的本质就是只允许一个客户端占用这个锁,其他的客户端就进不来了。
image.png

  • 最基础的就是上面图中,使用setnx命令去获取锁,执行完操作后,再释放锁,但是这样有一个弊端,如果在释放锁的前面客户端挂了的话,就成了死锁了,就G了

image.png

  • 在锁和执行中间加上超时时间,当客户端挂了的话,到了时间锁就会自动释放,但是如果在设置锁和设置超时时间之间挂的话,又变成了死锁了,原因就是获取锁和设置超时时间这两个操作不具有原子性。

image.png

  • 通过Redis提供的一些原子性操作可以避免这个问题。

image.png

  • 当执行时间(15s)大于超时时间(10s),再没有执行完操作,就将锁释放了,其他的客户端就会获取锁,进行执行,比如它刚执行了1s,但是之前15s的执行执行完成后,释放了刚才这个客户端的锁,又出现了新的问题

image.png

  • 通过对锁加标识,执行释放锁之前判断锁是不是自己的锁,再删除,通过lua脚本保证原子性,还要一个问题就是过期时间和业务执行时间不匹配,有的执行时间长,有的执行时间短,及其的影响效率

image.png

  • 通过上面的守护线程可以解决了这个超时时间和执行时间的问题,最终我们发现自己实现一个Redis分布式锁是十分复杂的,所以说我们可以使用现成的Redisson、RedLock。

猜你喜欢

转载自blog.csdn.net/weixin_52487106/article/details/131154491