2024年 Java 面试八股文——Redis篇

发布于:2024-04-30 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

1、介绍下Redis Redis有哪些数据类型    难度系数:⭐

2、Redis提供了哪几种持久化方式    难度系数:⭐  

3、Redis为什么快    难度系数:⭐  

4、Redis为什么是单线程的    难度系数:⭐  

5、Redis服务器的的内存是多大    难度系数:⭐  

6、为什么Redis的操作是原子性的,怎么保证原子性的    难度系数:⭐  

7、Redis有事务吗    难度系数:⭐

8、Redis数据和MySQL数据库的一致性如何实现    难度系数:⭐⭐

9、缓存击穿,缓存穿透,缓存雪崩的原因和解决方案(或者说使用缓存的过程中有没有遇到什么问题,怎么解决的)    难度系数:⭐ 

10、哨兵模式是什么样的    难度系数:⭐⭐

11、Redis常见性能问题和解决方案    难度系数:⭐

12、MySQL里有大量数据,如何保证Redis中的数据都是热点数据    难度系数:⭐⭐


Redis(Remote Dictionary Server) 是一个开源的使用ANSI C语言编写的、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(string)、哈希(Hash)、列表(list)、集合(sets)、有序集合(sorted sets)等类型。

1、介绍下Redis Redis有哪些数据类型    难度系数:⭐

 Redis 支持多种数据类型,每种数据类型都有其特定的使用场景和优势。以下是 Redis 的主要数据类型:

  1. 字符串(String):(一个字符串类型最大存储容量为512M)
    • 字符串是 Redis 最基础的数据类型,你可以将任何数据存入字符串中,比如 JSON、XML 等。它不仅仅是简单的 key-value 存储,而且可以对 value 进行各种操作。
    • 常用的命令包括:SET、GET、MSET、MGET、INCR、DECR 等。
  2. 哈希(Hash):(类似于Map<string,string>)
    • Redis hash 是一个键值对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
    • 常用的命令包括:HSET、HGET、HMGET、HGETALL、HDEL 等。
  3. 列表(List):(可以重复的集合)
    • Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
    • 常用的命令包括:LPUSH、RPUSH、LPOP、RPOP、LRANGE 等。
  4. 集合(Set):(不可以重复的集合)
    • Redis 的集合是 string 类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。集合中不允许出现重复的元素。
    • 常用的命令包括:SADD、SMEMBERS、SISMEMBER、SDIFF、SINTER、SUNION 等。
  5. 有序集合(Sorted Set):(带分数的set)
    • Redis 有序集合和集合一样也是 string 类型元素的集合, 并且集合内的元素不重复。不同的是每个元素都会关联一个 double 类型的分数。Redis 正是通过分数来为集合中的元素进行从小到大的排序。有序集合的成员是唯一的,但分数(score)可以重复。
    • 常用的命令包括:ZADD、ZRANGE、ZREM、ZCARD 等。

这些数据类型使得 Redis 能够灵活地处理各种应用场景,无论是简单的缓存需求还是复杂的数据结构处理,Redis 都能提供高效且强大的支持。

2、Redis提供了哪几种持久化方式    难度系数:⭐  

  1. Redis 提供了两种持久化的方式,分别是 RDB(Redis DataBase)和 AOF(AppendOnly File)。
  2. RDB,简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上。
  3. AOF,则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
  4. RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。

3、Redis为什么快    难度系数:⭐  

Redis之所以快,主要归因于以下几个关键因素:

  1. 内存存储:Redis将数据主要存储在内存中,而内存读写速度远快于硬盘。因此,Redis能够快速地读取和写入数据,实现高性能的响应。

  2. 数据结构优化:Redis支持多种数据类型,每种数据类型都经过精心设计,以实现高效的操作。例如,Redis的字符串类型采用了SDS(简单动态字符串)实现,不仅支持修改字符串长度,还避免了C语言字符串操作中的频繁内存分配和释放。此外,Redis还针对每种数据类型提供了丰富的操作命令,这些命令都经过优化,以最小化时间复杂度。

  3. 单线程模型:Redis采用单线程模型来处理客户端请求。虽然这看似限制了Redis的并发性能,但实际上由于Redis的操作大多是基于内存的,且避免了多线程的上下文切换和锁竞争,使得Redis在处理单个请求时非常高效。此外,Redis通过IO多路复用技术(如epoll)来同时处理多个客户端连接,进一步提高了并发性能。

  4. 高效的数据编码:Redis对不同的数据类型采用不同的编码方式,以最大化性能和空间利用率。例如,对于小整数,Redis使用共享对象来减少内存占用;对于字符串,Redis会根据长度选择不同的编码方式;对于哈希、列表等复杂数据类型,Redis也采用了紧凑的存储结构和高效的操作算法。

  5. 持久化机制:虽然Redis主要关注性能,但它也提供了持久化机制,以防止数据丢失。Redis支持RDB快照和AOF日志两种持久化方式,可以根据实际需求选择。这些持久化方式都经过优化,以最小化对性能的影响。

4、Redis为什么是单线程的    难度系数:⭐  

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了Redis利用队列技术将并发访问变为串行访问

1)绝大部分请求是纯粹的内存操作

2)采用单线程,避免了不必要的上下文切换和竞争条件

5、Redis服务器的的内存是多大    难度系数:⭐  

在Redis的配置文件中,通过maxmemory参数来指定Redis实例能够使用的最大内存量。参数的值是以字节为单位的。

例如,在Redis的配置文件redis.conf中,你可以这样设置最大内存限制:

maxmemory 1gb

或者,如果你想要设置为500MB,可以这样写:

maxmemory 524288000

一旦设置了maxmemory,当Redis使用的内存接近或达到这个限制时,它会根据配置的淘汰策略(eviction policy)来自动移除一些键,以确保不会超出内存限制。淘汰策略可以通过maxmemory-policy参数来配置,常见的策略包括:

  • noeviction:不删除任何数据,拒绝写入操作并返回一个错误。
  • allkeys-lru:根据LRU(Least Recently Used)算法删除键。
  • volatile-lru:只删除设置了过期时间的键,使用LRU算法。
  • allkeys-random:随机删除键。
  • volatile-random:只随机删除设置了过期时间的键。
  • volatile-ttl:只删除设置了过期时间的键,并且优先删除剩余时间(TTL)较短的键。

6、为什么Redis的操作是原子性的,怎么保证原子性的    难度系数:⭐  

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

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

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

多个命令在并发中也是原子性的吗?

不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.

7、Redis有事务吗    难度系数:⭐

Redis中的事务(transaction)是一组命令的集合。事务同命令一样都是Redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。Redis事务的实现需要用到 MULTI  EXEC 两个命令,事务开始的时候先向Redis服务器发送 MULTI 命令,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。

8、Redis数据和MySQL数据库的一致性如何实现    难度系数:⭐⭐

Redis数据和MySQL数据库的一致性可以通过以下几种方式实现:

  1. 事务支持:MySQL支持事务,通过事务可以保证数据库操作要么全部执行成功,要么全部失败回滚,从而确保数据的一致性。在事务中,所有的修改必须符合相关约束,这样才能保证一致性。
  2. 唯一约束和外键约束:MySQL可以通过设置唯一约束和外键约束来保证数据一致性。唯一约束保证某列或者几列的取值都是唯一的,外键约束可以保证参照完整性,确保关联表之间的数据一致性。
  3. 使用消息队列:一种常见的做法是先更新MySQL数据库,然后通过发送操作缓存的消息到消息队列,进行更新Redis缓存操作。这里还需要利用消息队列的重试机制,保证缓存能够更新成功。如果多次消费失败,可能是由于网络原因或者Redis服务挂了,此时可以添加告警处理。
  4. 延时双删策略:这是在使用Redis时保持数据一致性的流行解决方案之一。其主要思想是,在更新数据库后,等待一段时间再删除Redis中的旧数据。这样做的目的是确保在更新数据库和删除Redis数据之间,如果有新的读请求,它仍然会读取到旧的但已经过时的Redis数据,而不是直接从数据库中读取新的数据。然后,当延时时间过去后,再删除Redis中的旧数据,此时新的读请求就会直接从数据库中读取新的数据。需要注意的是,经常修改的数据表不适合使用Redis,因为双删策略执行的结果是把Redis中保存的那条数据删除了,以后的查询就都会去查询数据库。

9、缓存击穿,缓存穿透,缓存雪崩的原因和解决方案(或者说使用缓存的过程中有没有遇到什么问题,怎么解决的)    难度系数:⭐ 

1. 缓存穿透

问题描述:查询一个不存在的数据,由于缓存和数据库都未命中,导致每次请求都要查询数据库,失去了缓存的意义。

解决方案

  • 空结果缓存:将查询结果为空的对象也进行缓存,并设置一个较短的过期时间(例如几分钟)。
  • 布隆过滤器:使用布隆过滤器快速判断一个元素是否存在于集合中,对于不存在的数据直接返回,不查询数据库。Redisson等框架提供了布隆过滤器的实现。
  • 应用层限流:对于频繁查询不存在的数据,可以在应用层进行限流,防止恶意攻击。

2. 缓存雪崩

问题描述:由于大量缓存同时失效,导致所有请求都转发到数据库,数据库瞬时压力过大。

解决方案

  • 随机过期时间:在设置缓存过期时间时,增加一个随机值,降低缓存集体失效的概率。
  • 预热缓存:在系统上线前或低峰时段,预先加载一些热点数据到缓存中,避免上线时大量请求导致缓存失效。
  • 熔断降级:当数据库压力过大时,可以暂时关闭缓存的更新功能,或者将部分请求降级处理,减轻数据库压力。

3. 缓存击穿

问题描述:对于设置了过期时间的热点数据,如果这些数据在过期时刻被大量并发请求访问,会导致所有请求都转发到数据库。

解决方案

  • 分布式锁:使用Redis的setnx命令、RedLock算法、Zookeeper的临时顺序节点等方式实现分布式锁,确保同一时刻只有一个请求去查询数据库并更新缓存。
  • 本地缓存:对于热点数据,可以在应用层使用本地缓存(如Guava Cache、Caffeine等)进行缓存,减少对远程缓存和数据库的访问。
  • 异步更新:当缓存失效时,可以启动一个异步任务去更新缓存,而不是等待同步查询数据库的结果。

10、哨兵模式是什么样的    难度系数:⭐⭐

redis一共有3中集群模式,1、主从模式。2、哨兵模式。3、Cluster模式

哨兵模式是为了解决主从复制模式的缺点的,即哨兵模式还是基于主从复制模式,只不过多了一个“哨兵”,当master挂掉之后,哨兵就会在在所有的从节点竞选出新的主节点

 当master挂掉之后,哨兵会自动从slave中选一个作为master,若master重新启动,master则会转化为现有的master下的一个slave,当slave切换时,会通过发布订阅方式,将slave所对应的master更改。

哨兵本身也有单点故障的问题,所以在一个一主多从的Redis系统中,可以使用多个哨兵进行监控,哨兵不仅会监控主数据库和从数据库,哨兵之间也会相互监控。每一个哨兵都是一个独立的进程,作为进程,它会独立运行

11、Redis常见性能问题和解决方案    难度系数:⭐

1. 内存占用过高

问题描述:Redis使用了过多的内存,可能导致系统资源紧张,影响性能。

解决方案

  • 优化数据结构和存储:使用更节省内存的数据结构,例如使用哈希表代替列表或集合。
  • 配置maxmemory:设置Redis实例的最大内存使用量,防止无限制地占用内存。
  • 使用LRU算法:当内存达到上限时,Redis会使用LRU(Least Recently Used)算法自动删除一些不常用的键。

2. 读写性能下降

问题描述:Redis的读写操作变得缓慢,响应时间增加。

解决方案

  • 优化数据结构:选择合适的数据结构来存储数据,避免不必要的转换和计算。
  • 管道技术:使用管道(pipelining)技术将多个命令打包发送,减少网络往返时间。
  • 关闭持久化:对于写密集型的场景,可以考虑关闭或优化Redis的持久化配置,以减少写操作的开销。

3. 阻塞操作

问题描述:Redis的某些操作(如大键值的操作、长时间的Lua脚本等)可能导致阻塞,影响整体性能。

解决方案

  • 避免大键值操作:尽量拆分大键值操作,减少阻塞时间。
  • 优化Lua脚本:减少Lua脚本的执行时间,避免长时间占用Redis资源。
  • 使用监控工具:使用监控工具(如Redis-stat、Redis-cli等)监控Redis的性能指标,及时发现并处理潜在的阻塞问题。

4. 主从复制延迟

问题描述:在Redis主从复制的场景下,从库可能存在延迟,导致读取不一致。

解决方案

  • 优化复制策略:调整复制参数(如repl-backlog-size、repl-timeout等),优化复制性能。
  • 读写分离读写:使用读写分离读写的架构,将读请求分散到多个从库上,减轻主库压力。
  • 监控复制状态:使用监控工具监控主从复制的状态和延迟情况,及时发现并处理问题。

5. 网络问题

问题描述:网络延迟或不稳定可能导致Redis性能下降。

解决方案

  • 优化网络连接:确保Redis服务器与客户端之间的网络连接稳定且延迟低。
  • 使用连接池:使用连接池技术来管理Redis连接,减少连接建立和断开的开销。

12、MySQL里有大量数据,如何保证Redis中的数据都是热点数据    难度系数:⭐⭐

要实现MySQL中的大量数据只保证Redis中存储热点数据,可以结合多种策略和技术。以下是一些具体的实现方式:

1. 基于访问频率的热点数据识别

  • 使用Redis的ZSET(有序集合): 利用Redis的有序集合(Sorted Set)结构,每个元素都会关联一个分数,可以用来表示访问频率或最近访问时间。
  • 实时更新访问频率: 当MySQL中的数据被访问时,更新Redis中对应元素的分数。分数可以是访问次数或时间戳。
  • 定期清理非热点数据: 通过设置较低的分数阈值,定期清理ZSET中分数较低(即访问频率较低)的元素。

2. 缓存预热

  • 分析历史数据: 分析MySQL的历史访问记录,识别出常规的热点数据。
  • 预加载热点数据: 在系统启动或低峰期,将这些热点数据预先加载到Redis中。

3. 使用消息队列实现数据同步

  • 设置数据库触发器: 在MySQL中设置触发器,当数据发生变化(如INSERT、UPDATE、DELETE)时,将变化的信息发送到消息队列(如Kafka、RabbitMQ)。
  • 消费队列消息: Redis的消费者服务订阅消息队列,当接收到消息时,根据消息内容判断是否为热点数据,并同步到Redis中。

4. 定期审查和优化

  • 监控和分析: 使用监控工具(如Redis监控插件、Prometheus等)实时监控Redis中数据的访问情况。
  • 定期调整: 根据监控数据,定期调整缓存策略,如修改淘汰策略、调整分数阈值等。

5. 结合LRU和LFU策略

  • 使用Redis的LRU策略: Redis默认使用LRU作为内存淘汰策略,可以确保长时间未使用的数据被优先淘汰。
  • 自定义LFU策略: 如果需要更精细的控制,可以结合Redis的TTL(Time To Live)和自定义脚本实现LFU策略。例如,每次访问时更新TTL并增加访问计数器。

6. 限制Redis存储大小

  • 配置maxmemory: 在Redis配置中设置maxmemory参数,限制Redis使用的最大内存。
  • 选择合适的淘汰策略: 根据业务需求选择合适的淘汰策略,如volatile-lru(仅淘汰带有过期时间的键中使用最少的)、allkeys-lru(淘汰所有键中使用最少的)等。

7. 分布式缓存

  • 使用Redis集群: 如果热点数据量非常大,可以考虑使用Redis集群来扩展缓存容量和性能。
  • 数据分片: 通过合理的数据分片策略,将热点数据分布到多个Redis节点上,提高系统的并发处理能力。

8. 避免大值缓存

  • 拆分大值对象: 对于过大的对象,考虑将其拆分成多个小对象进行缓存。
  • 使用压缩算法: 对缓存的数据进行压缩,减少存储空间的占用。

网站公告

今日签到

点亮在社区的每一天
去签到