Redis八股文(大厂面试真题)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生内存数据库 Tair,内存型 2GB
简介: Redis八股文(大厂面试真题)

🌏号:tutou123com
​我是小宋编码,Java程序员 ,只熬夜但不秃头。关注我,带你轻松过面试。提升简历亮点
关注可了解更多的面试及源码技巧。问题或建议,请公众号留言;
如果你觉得对你有帮助,欢迎关注[1]



内容目录


1、说说redis,了解redis源码吗?2.聊聊:为什么使用redis(阿里一面)3.聊聊:redis 都有哪些数据类型?分别在哪些场景下使用比较合适?(1-3)4.redis 的过期策略是啥?5.Redis为什么快呢?1.基于内存实现2.高效的数据结构3.IO多路复用模型4.避免上下文切换6.Redis为什么最开始被设计成单线程的?7.redis 的IO处理线程模型8.为什么Redis 6.0 引入多线程9.Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?10.聊聊:Redis如何做持久化的?bgsave做全量持久化aof做增量持久化11.聊聊:系统化的介绍一下,Redis持久化的机制?1.Redis的持久化配置2.RDB持久化功能3.AOF持久化功能4.RDB和AOF的选择12.Redis 集群了解吗?13.数据分片(sharding)的基本类型?大致的原理?1.为什么要进行数据分片?2.分片方式详解14..能说说Redis集群的原理吗?集群创建故障转移15..说说集群的伸缩?16.如何保证本地缓存和分布式缓存的一致?17.怎么处理热key?18.缓存预热怎么做呢?19.热点key重建?问题?解决?20.了解Bigkey 吗?1.什么是BIgKey?2.如何解决BigKey3.来个Redis 的String 分析4.SpringBoot BigKey的scan扫描实操21.说说redis 架构22.redis 优化 了解不,你做过哪些?1. 使用短的 Key 和精简的 Value2. 避免存储过大的数据3. 慎用复杂度高的命令4. 数据压缩5. 设置 Key 的有效期6. 内存管理策略7. 使用连接池8. 内存优化操作9. 关闭持久化(视业务需求)10. 使用管道(Pipeline)11. 配置系统参数12. 性能分析13. 其他优化措施


1、说说redis,了解redis源码吗?



redis 是什么,有哪些特点,架构是,如何实现高并发的,应用场景是?redis源码宏观分为两大部分。



1.Redis是一种开源的、基于内存的数据库系统,它可以用作数据库、缓存和消息中间件。
2.Redis特点是 内存中存储: , 支持数据持久化:RDB和AOF, 支持事务:原子性, 丰富的数据类型:字符串、列表、集合、有序集合、散列、位图等。 发布/订阅功能主从复制备份和读写分离, 哨兵系统集群:数据分片和分布式存储, 单线程:IO多路


1.简介Redis


Redis是一种高性能的开源key-value内存数据库。Redis的特点是速度快,可以处理非常大的数据集,而且具有很强的可扩展性和数据持久化功能。
Redis采用单线程模型,通过异步IO和事件驱动机制实现高并发。其架构包括网络层、客户端请求处理、命令执行引擎、键空间与过期管理、持久化、复制、Sentinel(哨兵)以及集群等模块。


2.Redis特点:


1.内存中存储:数据存储在内存中,读写速度快。



  1. 支持数据持久化:通过RDB和AOF机制,可以将内存中的数据保存到磁盘。

  2. 支持事务:提供事务功能,确保操作的原子性。

  3. 丰富的数据类型:支持字符串、列表、集合、有序集合、散列、位图等。

  4. 发布/订阅功能:支持消息发布和订阅机制。

  5. 主从复制:支持数据的备份和读写分离。

  6. 哨兵系统:监控主服务器状态,自动故障转移。

  7. 集群:支持数据分片和分布式存储。

  8. 单线程模型:尽管是单线程,但通过IO多路复用技术实现高并发。


3.Redis架构:


1.网络层:处理客户端的TCP连接和请求。



  1. 事件循环:使用epoll或kqueue等IO多路复用技术,处理文件事件和时间事件。

  2. 命令处理:解析和执行Redis命令。

  3. 数据存储:内存中的数据结构和持久化到磁盘的机制。

  4. 复制和集群:实现数据的备份和分布式存储。

  5. 持久化模块:RDB和AOF持久化机制的实现。

  6. 哨兵模块:监控和故障转移的实现。


4.Redis如何实现高并发:



  1. 单线程事件循环:Redis使用单线程模型,通过IO多路复用技术(如epoll)来处理大量的并发连接。

  2. 内存中操作:数据存储在内存中,减少了磁盘I/O操作,提高了处理速度。

  3. 高效的数据结构:使用高效的数据结构来优化数据操作的性能。

  4. 无锁编程:避免了多线程编程中的锁竞争问题,减少了上下文切换的开销。


5.Redis应用



  1. 缓存:Redis可以作为缓存系统,用于存储热点数据,提高应用程序的响应速度。

  2. 数据库:Redis可以用作数据库,用于存储数据,支持多种数据结构和事务。

  3. 消息队列:Redis可以作为消息队列,用于异步消息的传递和处理。

  4. 分布式系统:Redis支持分布式架构,可以用于实现分布式数据存储和分布式计算。

  5. 监控和日志:Redis可以用于存储监控数据和日志,支持数据的快速查询和分析。

  6. 排行榜和计数器:实现实时更新的排行榜。

  7. 实时分析:进行实时数据统计和分析。

  8. 全页缓存:缓存整个页面或页面片段。

  9. 分布式锁:实现跨多个应用实例的锁机制。

  10. 地理位置服务:存储和查询地理位置数据。


6.Redis的源码
Redis由两大部分构成:服务器和客户端,它们通过TCP协议进行通信。



  • Redis服务器是用C语言编写的,负责底层数据存储和处理逻辑。(下面详解)

  • Redis客户端可以由不同的编程语言实现,提供了与Redis服务器交互的接口。


Redis源码的架构: 核心代码## 辅助模块## 性能优化## 安全性## 可维护性## 测试## 文档和社区支持


7.Redis服务器源码包括以下几个部分:
核心模块



  • 协议栈:处理客户端连接和基于RESP的通信。

  • 内存管理:高效的内存分配和释放,内存碎片整理。

  • 数据结构:实现多种数据类型,如字符串、列表、集合等。

  • 命令执行:解析和执行命令,保证原子性和一致性。


辅助模块 (Auxiliary Modules)核心模块



  • 持久化:RDB和AOF两种持久化机制。

  • 复制:主从复制机制,支持数据备份和扩展读操作。

  • 哨兵:Redis Sentinel监控和自动故障转移。

  • 集群:Redis Cluster支持数据分片和分布式存储。

  • Lua脚本:服务器端Lua脚本执行。

  • 发布/订阅:消息传递和通知的发布/订阅模式。

  • 事务:支持批量命令的原子性执行。

  • 模块系统:动态加载和卸载模块,便于功能扩展。


Redis服务器源码非常庞大和复杂,如果要完整地研究和理解Redis的源码,需要有较强的编程能力和C/C++语言基础。


2.聊聊:为什么使用redis(阿里一面)


核心概念为:并发+性能


在面试中被问到为什么使用Redis,特别是在涉及并发和性能的话题时,通常有几个关键的理由可以作为回答的核心:
1.性能: 我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样, 后面的请求就去缓存中读取,使得请求能够迅速响应。
2.并发:
在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis 做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。



3.聊聊:redis 都有哪些数据类型?分别在哪些场景下使用比较合适?(1-3)



4.redis 的过期策略是啥?



5.Redis为什么快呢?


Redis的速度非常快,单机的Redis可以支撑每秒十几万的并发,相对于MySQL来说,性能是MySQL的几十倍。速度快的原因主要有以下几点:



  • 1. 完全基于内存操作

  • 2. 使用单线程,避免了线程切换和竞态产生的消耗

  • 3. 基于非阻塞的IO多路复用机制

  • 4. C语言实现,优化过的数据结构


1.基于内存实现

Redis是基于内存的数据库,与磁盘数据库(如MySQL)相比,其访问速度要快得多。MySQL是关系型数据库,主要用于存放持久化数据,将数据存储在硬盘中,读取速度较慢,每次请求访问数据库时,都存在I/O操作。
如果反复频繁访问数据库,会导致高负载。而Redis作为基于内存的缓存数据库,用于存储频繁使用的数据,从而减少访问数据库的次数,提高运行效率。


2.高效的数据结构

image.pngimage.png
Redis底层数据结构一共有六种:简单动态字符串(SDS)、双向链表、压缩列表、哈希表、跳表和整数数组。它们和数据类型的对应关系如下:


2.1 SDS(简单动态字符串)


Redis使用SDS(Simple Dynamic String)结构体来保存字符串,而不是C语言中的字符串。SDS与C字符串的区别主要有:

image.pngimage.png

  • 字符串长度:SDS中保存着字符串的长度,可以在常数时间内获取字符串长度。

  • 拒绝缓冲区溢出:SDS在修改时会检查空间是否足够,避免缓冲区溢出。

  • 减少字符串修改时的内存重新分配次数:SDS实现了空间预分配和惰性空间释放两种优化策略。

  • 二进制安全:SDS可以存储二进制数据,不会受到特殊字符的影响。


2.2 哈希表(字典)
Redis作为k-v型内存数据库,所有的键值对是用字典来存储。哈希表的特性使得可以在O(1)时间复杂度内获得对应的值。


2.3 跳表
跳表在链表的基础上增加了多级索引来提升查找效率。跳表可以在O(logN)的时间复杂度里查找到对应的节点。

image.pngimage.png

2.4 双向链表


列表(List)更多是被当作队列或栈来使用的。双向链表支持先进先出和先进后出的特性,链表中的每个节点都带有两个指针,可以在O(1)时间复杂度内获取到前后节点。


2.5 压缩列表

image.pngimage.png
压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。压缩列表的内存是连续分配的,遍历速度很快。



3.IO多路复用模型

IO多路复用(I/O Multiplexing)是一种在单个线程内同时监控多个文件描述符(如网络连接、文件等)的方法。 当任何一个文件描述符就绪(例如有数据到达可以读,或者可以写数据)时,程序就可以处理相应的IO操作。常见的IO多路复用机制包括select、poll和epoll等。


4.避免上下文切换

Redis使用单线程模型避免了多线程的上下文切换问题。多线程在执行过程中需要进行CPU的上下文切换,而Redis基于内存操作,没有上下文切换,效率更高。多次读写都在一个CPU上,对于内存来说是最佳方案。


6.Redis为什么最开始被设计成单线程的?


Redis 是一个成熟的分布式缓存框架,由多个模块组成,包括网络请求模块、索引模块、存储模块、高可用集群支撑模块和数据操作模块等。


1.单线程与多线程


在 Redis 中,只有网络请求模块和数据操作模块是单线程的,而其他如持久化存储模块和集群支撑模块是多线程的。单线程意味着网络请求模块使用一个线程处理所有网络请求,不需考虑并发安全性,而其他模块则使用多个线程。


2.为什么网络操作和数据存储模块最初没有使用多线程?


多线程的目的是通过并发提升 I/O 和 CPU 的利用率。 Redis 不需要提升 CPU 利用率,因为它的操作主要基于内存,CPU 资源不是性能瓶颈。因此,通过多线程提升 Redis 的 CPU 利用率是没有必要的。虽然多线程可以提升 I/O 利用率,但也带来了并发问题和性能开销。因此,Redis 选择了多路复用 I/O 技术而非多线程技术来提升 I/O 利用率。


3.官方解释


官方 FAQ 提供了进一步解释。一个程序在执行过程中,主要进行读写操作(I/O 操作,包括网络 I/O 和磁盘 I/O)和计算操作(CPU 操作)。因为 Redis 基于内存操作,CPU 成为瓶颈的情况很少见。Redis 的瓶颈更可能是内存大小或网络限制。如果要最大化利用 CPU,可以在一台机器上 启动多个 Redis 实例。


4.Redis 4.0 之后的多线程支持


在 Redis 4.0 之后,除了主线程外,Redis 还引入了后台线程来处理一些较为缓慢的操作,例如清理脏数据、释放无用连接和删除大 Key 等等。


7.redis 的IO处理线程模型


Redis 内部使用文件事件处理器(file event handler),这是其单线程模型的核心。这种模型与 Netty 的 Reactor 反应器模型类似,它利用了 IO 多路复用机制来同时监听多个 socket。这种机制允许 Redis 将产生事件的 socket 压入内存队列中,然后由事件分派器根据 socket 上的事件类型,选择相应的事件处理器进行处理。


1.文件事件处理器结构


文件事件处理器结构由以下四个主要部分组成:



  1. 多个 socket:Redis 可以同时监听多个 socket。

  2. IO 多路复用程序:用于监听这些 socket,并将事件放入队列。

  3. 文件事件分派器:负责从队列中取出 socket,并根据事件类型进行分派。

  4. 事件处理器:包括连接应答处理器、命令请求处理器、命令回复处理器等,它们负责具体的事件处理工作。


尽管多个 socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但 IO 多路复用程序能够高效地将这些事件统一管理。事件分派器每次从队列中取出一个 socket,然后根据其事件类型,交由相应的事件处理器进行处理。



8.为什么Redis 6.0 引入多线程


Redis 6.0引入多线程主要是为了解决一些性能瓶颈问题。以下是引入多线程的几个关键原因:



  1. 网络IO瓶颈:尽管Redis使用单线程模型,但网络IO处理成为了性能瓶颈。在高并发情况下,单线程处理网络请求可能会变得不够高效。

  2. 多路复用技术的局限:Redis之前使用的IO多路复用技术,如select,本质上是同步阻塞型的。这意味着在等待数据时,它们会阻塞线程,限制了处理能力。

  3. 多核CPU优势未充分利用:现代服务器通常配备多个CPU核心,但Redis的单线程模型并未充分利用这些核心,导致CPU资源没有得到最佳利用。

  4. 提升性能:通过引入多线程,Redis可以并发处理网络请求,减少网络I/O等待时间,从而提升性能。

  5. 保持核心操作的单线程:Redis 6.0中,数据的读写命令仍然由单线程处理,以保持操作的原子性和避免并发问题。多线程主要用于网络请求的接收、解析和响应发送。

  6. 整体性能提升:通过这种方式,Redis 6.0旨在提高网络IO处理效率,同时利用多核CPU的优势,从而整体提升性能。


image.pngimage.png
简而言之,这样做的⽬的是因为Redis的性能瓶颈在于⽹络IO⽽⾮CPU,使⽤多线程能提升IO读写的效率,从⽽整体提⾼Redis的性能。


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



使用keys指令可以扫出指定模式的key列表。
如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
这个时候你要回答redis关键的一个特性:
redis的单线程的。
keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。
这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复
概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。






10.聊聊:Redis如何做持久化的?


Redis通过以下两种方式实现持久化:



  • bgsave 做镜像全量持久化(rdb)

  • aof 做增量持久化




由于bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,因此需要AOF来配合使用。在Redis实例重启时,优先使用AOF来恢复内存的状态,如果没有AOF日志,就会使用RDB文件来恢复。


bgsave做全量持久化

RDB是Redis中数据集的时间点快照,在Redis内完成RDB持久化的方法有rdbSaverdbSaveBackground两个函数方法(源码文件rdb.c中),两者差别如下:



  • rdbSave:同步执行的方法,调用后立刻启动持久化流程。由于Redis是单线程模型,持久化过程中会阻塞,Redis无法对外提供服务。

  • rdbSaveBackground:异步执行的方法,会fork出子进程,真正的持久化过程在子进程中执行(调用rdbSave),主进程继续提供服务。


RDB持久化的触发方式分为手动和自动。手动触发是指通过Redis客户端发起持久化备份指令,常用的指令有savebgsave


在整个持久化过程中,主进程不进行任何IO操作,全程由子进程完成,确保了极高的性能。如果需要大规模数据恢复且对数据恢复的完整性要求不高,那么RDB方式比AOF方式更高效。但RDB的缺点是最后一次持久化的数据可能会丢失。



aof做增量持久化

AOF(Append Only File)以日志形式记录每个写操作,将Redis执行的所有写操作指令记录下来(读操作不记录)。文件只许追加,不可改写,Redis重启时会读取该文件重新构建数据。AOF默认保存的文件是appendonly.aof,该文件具有可读性。


AOF的工作原理类似于MySQL的binlog日志语句复制。AOF文件同步有三种同步策略:



  1. 修改同步

  2. 每秒同步

  3. 不主动调用fsync同步


AOF优缺点:



  • 利用appendfsync持久化机制,异步操作每秒记录,数据完整性高于RDB。如果一秒宕机,有可能丢失1秒数据。

  • AOF文件要远大于RDB文件,恢复速度要慢于RDB。每秒同步策略效率较好,不同步效率和RDB相同。


在Redis实例重启时,优先使用AOF来恢复内存的状态,如果没有AOF日志,就会使用RDB文件来恢复。



11.聊聊:系统化的介绍一下,Redis持久化的机制?



1.Redis的持久化配置

Redis提供两种持久化机制:RDB和AOF。用于在崩溃后恢复数据。RDB和AOF持久化的区别如下:



  • RDB持久化:将数据集生成快照并保存为二进制文件.rdb。优点是单个文件便于备份,恢复速度快。缺点是数据实时性低,可能会丢失最后一次持久化的数据。

  • AOF持久化:以日志形式记录每个写操作。优点是数据实时性高,通过append模式写文件,确保数据一致性。缺点是文件大,恢复速度慢于RDB。



2.RDB持久化功能

RDB可以通过以下三种方式创建:



  1. 使用SAVE命令手动同步创建RDB文件

  2. 使用BGSAVE命令异步创建RDB文件

  3. 自动创建RDB文件,通过配置文件设定触发条件


配置文件中的自动持久化配置如下:


save 900 1
save 300 10
save 60 10000

这些配置表示在指定时间内数据发生一定次数的改动时自动执行BGSAVE命令。



3.AOF持久化功能

AOF持久化通过记录每个写操作的日志文件实现。AOF文件同步策略有三种:



  1. appendfsync always:每次有数据修改时都写入AOF文件,最安全但性能较低。

  2. appendfsync everysec:每秒同步一次,性能适中,最多丢失1秒的数据。

  3. appendfsync no:不主动调用fsync,由操作系统决定何时写入硬盘,性能最好但数据丢失量不确定。


推荐将appendfsync选项设定为everysec,在Redis做缓存时,即使数据丢失也不会造成影响。



4.RDB和AOF的选择

一般来说,如果需要数据安全性高,可以同时使用RDB和AOF持久化。在Redis重启时会优先载入AOF文件恢复数据,因为AOF保存的数据集通常比RDB完整。如果可以接受数分钟的数据丢失,可以只使用RDB持久化。





12.Redis 集群了解吗?


数据分区:
数据分区 (或称数据分片) 是集群最核心的功能。
集群将数据分散到多个节点,一方面 突破了 Redis 单机内存大小的限制,存储容量大大增加;另一方面 每个主节点都可以对外提供读服务和写服务,大大提高了集群的响应能力。 数据分片是高扩展的基础。


高可用:
集群支持主从复制和主节点的 自动故障转移 (与哨兵类似),当任一节点发生故障时,集群 仍然可以对外提供服务。



13.数据分片(sharding)的基本类型?大致的原理?


数据分片,也称为数据分区,是一种将全量数据根据特定规则分散存储到多个数据库或表的技术。


数据分片主要有以下几种基本类型:



  • 范围分片:根据数据的范围将数据分配到不同的节点。

  • Key(ID)取模分片:通过Key的数值对节点数量进行取模来分配数据。

  • 哈希取余分片:使用哈希算法后对节点数量取余来分配数据。

  • 一致性哈希分片:使用一致性哈希算法来分配数据,减少节点变化时的数据迁移。

  • 虚拟槽分片:Redis Cluster使用的分片方式,使用固定的哈希槽来分配数据。



1.为什么要进行数据分片?

在处理大规模数据时,单节点可能无法满足性能和存储需求,数据分片可以提高系统的扩展性和可用性。



2.分片方式详解

2.1 Range 分片


按照连续的数据范围来分配数据,适用于按时间或数据顺序访问的场景,但需注意数据倾斜问题。
2.2 ID(Key)取模分片


常用于关系型数据库,通过数据ID对节点数量取模来均匀分配数据。


2.3 哈希取余分片


通过哈希算法分散数据,然后根据节点数量取余数来分配数据,保证数据分布均匀。


优点:配置简单,易于实现。
缺点:节点伸缩时会引起大量数据迁移。


2.4 一致性哈希分片


使用一致性哈希算法,将数据分布在一个虚拟的token环上,每个节点负责一定范围的数据。



  • 优点:节点伸缩时,只影响邻近节点的数据迁移。

  • 缺点:在数据规模较小时,可能导致某些节点空闲。


2.5 虚拟槽分片


Redis Cluster使用的一种分片方式,通过预设的哈希槽来分配数据。


映射步骤



  1. 预设16384个哈希槽,平均分配给各个节点。

  2. 对每个Key进行CRC16哈希运算。

  3. 哈希结果对16383取余,得到槽编号。

  4. 根据槽编号将数据发送到相应节点。

  5. 节点接收数据,如果在自己的槽编号范围内,则保存数据;否则转发到正确节点。


优势



  • 节点扩容或缩容时,只需重新分配哈希槽,数据不会丢失。


选择合适的数据分片策略需要根据应用场景、数据特性和系统需求综合考虑,以实现最优的系统性能和可扩展性。



14..能说说Redis集群的原理吗?


Redis集群通过数据分区来实现数据的分布式存储,通过自动故障转移实现高可用。


集群创建

数据分区是在集群创建的时候完成的。


设置节点Redis集群一般由多个节点组成,节点数量至少为6个才能保证组成完整高可用的集群。每个节点需要开启配置cluster-enabled yes,让Redis运行在集群模式下。 节点握手节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信, 达到感知对方的过程。节点握手是集群彼此通信的第一步,由客户端发起命 令:cluster meet{ip}{port}。完成节点握手之后,一个个的Redis节点就组成了一个多节点的集群。
分配槽(slot)Redis集群把所有的数据映射到16384个槽中。每个节点对应若干个槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。通过 cluster addslots命令为节点分配槽。

分配槽


故障转移

Redis集群的故障转移和哨兵的故障转移类似,但是Redis集群中所有的节点都要承担状态维护的任务。
故障发现Redis集群内节点通过ping/pong消息实现节点通信,集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong 消息作为响应。如果在cluster-node-timeout时间内通信一直失败,则发送节 点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。

当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当 半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流程。
故障恢复
故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它 的从节点中选出一个替换它,从而保证集群的高可用。

故障恢复流程



  1. 资格检查 每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障 的主节点。

  2. 准备选举时间 当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该 时间后才能执行后续流程。

  3. 发起选举 当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程。

  4. 选举投票 持有槽的主节点处理故障选举消息。投票过程其实是一个领导者选举的过程,如集群内有N个持有槽的主节 点代表有N张选票。由于在每个配置纪元内持有槽的主节点只能投票给一个 从节点,因此只能有一个从节点获得N/2+1的选票,保证能够找出唯一的从节点。

  5. 替换主节点 当从节点收集到足够的选票之后,触发替换主节点操作。


部署Redis集群至少需要几个物理节点?
在投票选举的环节,故障主节点也算在投票数内,假设集群内节点规模是3主3从,其中有2 个主节点部署在一台机器上,当这台机器宕机时,由于从节点无法收集到 3/2+1个主节点选票将导致故障转移失败。这个问题也适用于故障发现环节。因此部署集群时所有主节点最少需要部署在3台物理机上才能避免单点问题。


15..说说集群的伸缩?


Redis集群提供了灵活的节点扩容和收缩方案,可以在不影响集群对外服务的情况下,为集群添加节点进行扩容也可以下线部分节点进行缩容。

其实,集群扩容和缩容的关键点,就在于槽和节点的对应关系,扩容和缩容就是将一部分槽和数据迁移给新节点。
例如下面一个集群,每个节点对应若干个槽,每个槽对应一定的数据,如果希望加入1个节点希望实现集群扩容时,需要通过相关命令把一部分槽和内容迁移给新节点。 缩容也是类似,先把槽和数据迁移到其它节点,再把对应的节点下线。



16.如何保证本地缓存和分布式缓存的一致?


PS:这道题面试很少问,但实际工作中很常见。
在日常的开发中,我们常常采用两级缓存:本地缓存+分布式缓存。
所谓本地缓存,就是对应服务器的内存缓存,比如Caffeine,分布式缓存基本就是采用Redis。
那么问题来了,本地缓存和分布式缓存怎么保持数据一致?

Redis缓存,数据库发生更新,直接删除缓存的key即可,因为对于应用系统而言,它是一种中心化的缓存。
但是本地缓存,它是非中心化的,散落在分布式服务的各个节点上,没法通过客户端的请求删除本地缓存的key,所以得想办法通知集群所有节点,删除对应的本地缓存key。
可以采用消息队列的方式:



  1. 采用Redis本身的Pub/Sub机制,分布式集群的所有节点订阅删除本地缓存频道,删除Redis缓存的节点,同事发布删除本地缓存消息,订阅者们订阅到消息后,删除对应的本地key。但是Redis的发布订阅不是可靠的,不能保证一定删除成功。

  2. 引入专业的消息队列,比如RocketMQ,保证消息的可靠性,但是增加了系统的复杂度。

  3. 设置适当的过期时间兜底,本地缓存可以设置相对短一些的过期时间。


17.怎么处理热key?


什么是热Key?所谓的热key,就是访问频率比较的key。
比如,热门新闻事件或商品,这类key通常有大流量的访问,对存储这类信息的 Redis来说,是不小的压力。
假如Redis集群部署,热key可能会造成整体流量的不均衡,个别节点出现OPS过大的情况,极端情况下热点key甚至会超过 Redis本身能够承受的OPS。
怎么处理热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宕机,⾛内存查询


18.缓存预热怎么做呢?


所谓缓存预热,就是提前把数据库里的数据刷到缓存里,通常有这些方法:
1、直接写个缓存刷新页面或者接口,上线时手动操作
2、数据量不大,可以在项目启动的时候自动进行加载
3、定时任务刷新缓存.


19.热点key重建?问题?解决?


开发的时候一般使用“缓存+过期时间”的策略,既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。
但是有两个问题如果同时出现,可能就会出现比较大的问题:



  • 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。

  • 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次IO、多个依赖等。在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。


怎么处理呢?
要解决这个问题也不是很复杂,解决问题的要点在于:



  • 减少重建缓存的次数。

  • 数据尽可能一致。

  • 较少的潜在危险。


所以一般采用如下方式:



  1. 互斥锁(mutex key) 这种方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。

  2. 永远不过期 “永远不过期”包含两层意思:



  • 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。

  • 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。



20.了解Bigkey 吗?



什么是bigkey? 如何解决bigkey?springboot的使用?




1.什么是BIgKey?

通俗易懂的讲,Big Key就是某个key对应的value很大,占用的redis空间很大,本质上是大value问题。
key往往是程序可以自行设置的,value往往不受程序控制,因此可能导致value很大。
redis中这些Big Key对应的value值很大,在序列化/反序列化过程中花费的时间很大,因此当我们操作Big Key时,通常比较耗时,这就可能导致redis发生阻塞,从而降低redis性能。
BigKey指以Key的大小和Key中成员的数量来综合判定,用几个实际的例子对大Key的特征进行描述:



  • Key本身的数据量过大:一个String类型的Key,它的值为5MB

  • Key中的成员数过多:一个ZSET类型的Key,它的成员数量为10000个

  • Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1000个但这些成员的Value值总大小为100MB


在实际业务中,大Key的判定仍然需要根据Redis的实际使用场景、业务场景来进行综合判断。通常都会以数据大小与成员数量来判定。


2.如何解决BigKey


Big Key问题指的是在Redis中单个键(Key)对应的值(Value)过大,这可能导致内存使用效率低下,以及影响Redis的性能和响应速度。以下是几种常见的解决方法:


1. 对大Key进行拆分
将一个Big Key拆分成多个小Key,每个小Key的成员数量或大小都保持在合理范围内。这样做可以降低单个操作的内存和性能负担。


实施步骤:
- 确定合理的拆分标准,例如每个小Key包含的元素数量或大小。
- 重构应用程序逻辑,以支持通过多个小Key进行数据存取。


2. 对大Key进行清理
定期检查并删除不再需要的大Key,释放内存资源。


实施步骤:
- 使用UNLINK命令逐步删除大Key,避免阻塞Redis主线程。
- 监控Key的使用情况,及时清理无用或过期的数据。


3. 监控Redis的内存和性能指标
通过监控系统来跟踪Redis的内存使用情况和其他性能指标,以便及时发现潜在的大Key问题。


实施步骤:
- 设置内存使用率和增长速度的警告阈值。
- 使用Redis的监控工具,如INFO命令或第三方监控系统。


4. 定期清理失效数据
对于不断增长但具有时效性的数据,定期清理可以防止大量失效数据的堆积。


实施步骤:
- 利用Redis的过期策略,如设置Key的过期时间。
- 通过定时任务清理那些不再需要或过期的数据。


5. 压缩value
使用序列化和压缩技术减小value的大小,但要注意这可能会增加CPU的负担。


实施步骤:
- 选择合适的序列化格式,如JSON、MessagePack等。
- 应用压缩算法,如gzip,但要权衡压缩与解压的性能开销。


6. 优化数据结构
根据数据的使用模式优化数据结构,例如使用更适合的数据类型或结构来减少内存占用。


实施步骤:
- 分析数据访问模式,选择最合适的Redis数据类型。
- 重构数据模型以减少冗余和提高内存效率。


7. 使用更高效的数据编码
利用Redis提供的不同类型的内部编码方式,如使用intset代替hashtable存储小整数集合。


实施步骤:
- 根据数据特性选择合适的内部编码。
- 调整Redis配置以优化特定类型的数据存储。


8. 避免过度使用持久化
过度的持久化操作可能会影响性能,特别是在处理大Key时。


实施步骤:
- 根据业务需求调整RDB和AOF的持久化策略。
- 考虑在低峰时段进行持久化操作。


结论
解决Big Key问题需要综合考虑数据模型、访问模式、性能监控和系统配置等多个方面。通过上述方法,可以有效地管理和减小Big Key带来的影响,从而提高Redis的整体性能和稳定性。


3.来个Redis 的String 分析

Redis string 的命令 只能一次设置/查询一个键值对,这样虽然简单,但是效率不高。 为了提高命令的执行效率, Redis 提供了可以批量操作多个字符串的读写命令 MSET/MGET(“M”代表“Many”), 它们允许你一次性设置或查询多个键值对,这样就有效地减少了网络耗时。


Redis 使用标准 C 语言编写,但在存储字符时,Redis 并未使用 C 语言的字符类型,
为了存储字符串,Redis 自定义了一个属于特殊结构 SDS(Simple Dynamic String)即简单动态字符串),
SDS 是一个可以修改的内部结构,非常类似于 Java 的 ArrayList。


1.SDS动态字符串
SDS 的结构定义如下:


struct sdshdr{
   
   
     //记录buf数组中已使用字符的数量,等于 SDS 保存字符串的长度
     int len;
     //记录 buf 数组中未使用的字符数量
     int free;
     //字符数组,用于保存字符串
     char buf[];

从上述结构体可以看出,Redis string 将字符串存储到字符类型的buf[] 、len、free


2.分配冗余空间
string 采用了预先分配冗余空间的方式来减少内存的频繁分配,如下图所示:


如图 所示,Redis 每次给 string 分配的空间都要大于字符串实际占用的空间,这样就在一定程度上提升了 Redis string 存储的效率,比如当字符串长度变大时,无需再重新申请内存空间。
当字符串所占空间小于 1MB 时,Redis 对字符串存储空间的扩容是以成倍的方式增加的;而当所占空间超过 1MB 时,每次扩容只增加 1MB。Redis 字符串允许的最大值字节数是 512 MB。



4.SpringBoot BigKey的scan扫描实操

SpringBoot 应用中,可是经过用scan,咱们就能够指定有共性的key,并指定一次性查询条件。
演示的代码如下:


@Autowired
private StringRedisTemplate redisTemplate;

public void scanForBigkeys({
    long cursor = ScanOptions.SCAN_POINTER_START;
    Set<String> bigkeys = new HashSet<>();
    do {
        ScanOptions options = ScanOptions.scanOptions()
                .match("your_key_pattern"// 指定key的模式
                .count(1000// 每次迭代扫描的键的数量
                .build();
        Cursor<String> scanCursor = redisTemplate.executeWithStickyConnection(
            (RedisConnection connection, Object[] args) -> connection.scan(cursor, (ScanOptions) args[0])
        );

        while (scanCursor.hasNext()) {
            String key = scanCursor.next();
            long sizeInBytes = redisTemplate.getConnectionFactory().getConnection().strlen(key);
            if (sizeInBytes > YOUR_BIGKEY_SIZE_THRESHOLD) {
                bigkeys.add(key);
            }
        }
        cursor = scanCursor.getPos();
    } while (!cursor.equals(0L));

    // 处理发现的bigkeys
    if (!bigkeys.isEmpty()) {
        sendAlert(bigkeys);
    }
}

private void sendAlert(Set<String> bigkeys{
    // 发送邮件或钉钉消息
}

这里例子中,是 以 大于 50个字节,就计算为 bigkey.
这个阈值,仅仅是为了演示方便,生产场景,可以设置一个大的阈值,比如,一个String类型的Key,它的阈值为5MB


执行的结果
启动应用,可以得到执行的结果


实验完美成功
生产场景的bigkey 扫描
结合scan + 定时任务的方式, 在 吞吐量的低峰期,进行扫描
发现了bigkey, 可以及时的进行 运维 告警, 发送 邮件通知或者 钉钉企业信息


类似场景,对大量key进行扫描的cluster
在线上有时候,须要对大量key进行扫描(如删除)操做,有几个风险点:



  1. 一次性查询所指定的key,
    如果是使用keys,数量较大可能形成redis服务卡顿,Redis是单线程程序,顺序执行全部指令,其它指令必须等到当前的 keys 指令执行完了才能够继续。

  2. 从海量的 key 中找出知足特定前缀的 key
    上面的场景中,都可以用scan,咱们就能够指定有共性的key,并指定一次性查询条件。


要点是:使用SCAN命令扫描key替代KEYS避免redis服务器阻塞,无坑!



21.说说redis 架构


1.Redis单机: 首先,在使用最简单的单机版 Redis 时,我们遇到了 Redis 故障宕机后数据无法恢复的问题,因此我们引入了「数据持久化」,将内存中的数据保存到磁盘上,以便 Redis 重启后能快速恢复数据。
2.加入AOF/RDB: 在进行数据持久化时,我们面临如何更高效地将数据保存到磁盘的问题。后来我们发现 Redis 提供了 RDB 和 AOF 两种方案,分别对应数据快照和实时命令记录。当对数据完整性要求不高时,可以选择 RDB 持久化方案;如果对数据完整性要求较高,可以选择 AOF 持久化方案。
3.混合使用:但是我们又发现,AOF 文件体积会随着时间增长变得越来越大,此时我们想到的优化方案是,使用 AOF rewrite 的方式对其进行瘦身,减小文件体积,再后来,我们发现可以结合 RDB 和 AOF 各自的优势,在 AOF rewrite 时使用两者结合的「混合持久化」方式,又进一步减小了 AOF 文件体积。
4.加上副本:接着,我们发现虽然可以通过数据恢复的方式还原数据,但恢复数据仍需要花费时间,这意味着业务应用仍会受到影响。我们进一步优化,采用「多副本」的方案,让多个实例保持实时同步,当一个实例故障时,可以手动把其他实例提升上来继续提供服务。
5.哨兵模式:但是这样也有问题,手动提升实例上来,需要人工介入,人工介入操作也需要时间,我们开始寻找方法使这个流程自动化,因此我们引入了「哨兵」集群。哨兵集群通过互相协商的方式,发现故障节点,并可以自动完成切换,从而大幅降低对业务应用的影响。
6.分片集群:最后,我们将关注点放在如何支持更大的写流量上,因此引入了「分片集群」来解决这个问题,让多个 Redis 实例分担写压力。面对更大的流量,我们还可以添加新的实例进行横向扩展,进一步提高集群性能。


通过这些步骤,我们的 Redis 集群能够长期稳定、高性能地为我们的业务提供服务。
在架构演进的过程中, 围绕着「架构设计」的核心思想:



  • 高性能:读写分离、分片集群

  • 高可用:数据持久化、多副本、故障自动切换

  • 易扩展:分片集群、横向扩展

  • 高可靠: 写后日志、数据快照


image.pngimage.png
在进行软件架构设计时,您面临的场景是发现问题、分析问题、解决问题,逐步优化和升级您的架构,最终在性能和可靠性方面达到平衡。
尽管各种软件层出不穷,但架构设计的理念不会改变,希望您真正吸收的是这些思想,这样才能做到以不变应万变。





22.redis 优化 了解不,你做过哪些?


Redis 是一个高效的内存数据库,但为了充分发挥其性能和可靠性,进行合理的优化是非常必要的。以下是对 Redis 进行优化的一些关键建议和措施:



1. 使用短的 Key 和精简的 Value


  • 短 Key:减少内存占用,缩短哈希表查找时间。虽然要保持 Key 的可读性,但尽量缩短长度。
    - 精简 Value:对于可以使用数字或短字符串表示的数据,如性别、状态等,使用数字代替字符串。



2. 避免存储过大的数据


  • 大数据分片:避免单个 Key 存储过多数据,可以使用分片技术,将数据分散到多个 Key 中存储。



3. 慎用复杂度高的命令


  • Keys :尽量避免使用 keys * 这种复杂度为 O(n) 的命令。替代方法是使用 SCAN 命令进行迭代查询。
    - 其他复杂命令:参考 Redis 命令复杂度](https://redis.io/commands)) 文档,尽量避免使用高复杂度命令。



4. 数据压缩


  • 内部编码:Redis 对不同的数据类型有不同的编码方式,自动根据情况调整。可以通过配置参数进行调优,比如 hash-max-ziplist-entrieshash-max-ziplist-value



5. 设置 Key 的有效期


  • 过期时间:为临时数据设置过期时间,减少内存占用。避免大量数据同时过期,可以使用不同的过期时间。



6. 内存管理策略


  • 回收策略:根据需求选择合适的内存回收策略。常用策略包括:
    - volatile-lru:对设置了过期时间的 Key 进行 LRU 回收。
    - allkeys-lru:对所有 Key 进行 LRU 回收。
    - 其他策略:volatile-randomallkeys-randomvolatile-ttlnoeviction



7. 使用连接池


  • 连接池:减少连接创建和释放的开销,提高并发处理能力。



8. 内存优化操作


  • Bit 操作:使用 GETRANGE、SETRANGE、GETBIT、SETBIT 进行位级别操作,减少内存占用。
    - Hash 操作:尽量使用 Hash 结构进行存储,特别是在有多个字段需要存储时。



9. 关闭持久化(视业务需求)


  • 无持久化:在不需要数据持久化的场景下,可以关闭 RDB 和 AOF,获得更高性能。



10. 使用管道(Pipeline)


  • 批量操作:使用管道一次性发送多条命令,减少网络开销和 RTT(Round Trip Time)。
    java
    @Test
    public void usePipeline() throws Exception {
    Jedis jedis = JedisUtil.getJedis();
    long starttime = System.currentTimeMillis();
    Pipeline pipelined = jedis.pipelined();
    for (int i = 0; i < 10000; i++) {
    pipelined.set("cc
    " + i, i + "");
    }
    pipelined.sync();
    System.out.println(System.currentTimeMillis() - start_time);
    }¨C111C



11. 配置系统参数


  • TCP Backlog:增大 /proc/sys/net/core/somaxconn 的值。
    bash
    echo 511 &gt; /proc/sys/net/core/somaxconn

    - 内存分配策略:设置 vm.overcommit_memory = 1。
    bash
    echo 'vm.overcommit_memory = 1' &gt;&gt; /etc/sysctl.conf
    sysctl -p

    - 关闭透明大页(THP)
    bash
    echo never &gt; /sys/kernel/mm/transparent_hugepage/enabled



12. 性能分析


  • 慢日志
    bash
    CONFIG SET slowlog-log-slower-than 10000
    CONFIG SET slowlog-max-len 1000
    SLOWLOG GET 10

    - 基准测试
    bash
    redis-cli --intrinsic-latency 120



13. 其他优化措施


  • 避免 Big Key:对于大 Key 采用异步删除,防止阻塞主线程。
    - 合理配置 AOF:根据业务需要调整 AOF 的同步策略,减少 IO 压力。
    - 控制内存占用:避免 Redis 使用 Swap,必要时增加物理内存。
    - 多核 CPU 优化:绑定 Redis 进程到物理 CPU 核,提高处理能力。
    - 集群配置:在使用主从集群时,控制每个实例的数据量,避免因复制带来的性能问题。


通过这些优化措施,可以显著提升 Redis 的性能和可靠性,确保在高并发和大数据量的场景下仍然能够高效运行。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
10天前
|
缓存 NoSQL Redis
Java面试之redis篇
Java面试之redis篇
29 0
|
10天前
|
存储 缓存 前端开发
Java八股文面试之多线程篇
Java八股文面试之多线程篇
21 0
Java八股文面试之多线程篇
|
18天前
|
存储 NoSQL Redis
《面试官之你说我听》:简明的图解Redis RDB持久化、AOF持久化
《面试官之你说我听》:简明的图解Redis RDB持久化、AOF持久化
|
20天前
|
存储 缓存 NoSQL
经验大分享:Redis简单总结及常见面试题
经验大分享:Redis简单总结及常见面试题
16 1
|
25天前
|
存储 NoSQL Redis
redis面试题库
redis面试题库
|
18天前
|
缓存 NoSQL 数据库
分布式系统面试全集通第一篇(dubbo+redis+zookeeper----分布式+CAP+BASE+分布式事务+分布式锁)
分布式系统面试全集通第一篇(dubbo+redis+zookeeper----分布式+CAP+BASE+分布式事务+分布式锁)
26 0
|
存储 缓存 移动开发
redis 面试总结
前段时间找工作搜索 golang 面试题时,发现都是比较零散或是基础的题目,覆盖面较小。而自己也在边面试时边总结了一些知识点,为了方便后续回顾,特此整理了一下。
162 0
redis 面试总结
|
存储 NoSQL 数据库
|
存储 NoSQL 数据库
Redis面试总结
什么是redis? Redis 是一个基于内存的高性能key-value数据库。 (有空再补充,有理解错误或不足欢迎指正) Reids的特点 Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。
1580 0
|
10天前
|
NoSQL Java Redis
软件开发常见流程之宝塔初始化安装环境配置,Lam前面不选,直接跳商城,在宝塔内点击软件商城,安Mysql5.7,安java项目管理器,安Ngnix最新版,安Redis
软件开发常见流程之宝塔初始化安装环境配置,Lam前面不选,直接跳商城,在宝塔内点击软件商城,安Mysql5.7,安java项目管理器,安Ngnix最新版,安Redis

相关产品

  • 云数据库 Redis 版