redis面试题

Redis

基础概念:

1、Redis 是什么?为什么称之为高性能数据库

Redis 是一个开源、基于内存、键值对类型的非关系型数据库(NoSQL),支持数据持久化,同时提供主从复制、哨兵、集群等高可用方案,既可以当做缓存中间件,也可以承担部分业务数据存储。

高性能来源于 4 个核心因素:

  1. 绝大部分数据操作在内存完成,内存读写速度远高于磁盘;
  2. 命令执行采用单线程模型,规避多线程锁竞争、线程上下文切换的开销;
  3. 使用 IO 多路复用模型,单线程高效管理海量客户端连接;
  4. 底层全部采用经过高度优化的数据结构。

2、Redis 为什么快?四个核心原因

  1. 内存操作:所有热点数据存放内存,内存访问属于纳秒级,不存在磁盘 IO 耗时;
  2. 单线程执行命令:不需要加锁,没有多线程之间切换带来的 CPU 开销;
  3. IO 多路复用(epoll):单线程可以监听成千上万个客户端 socket 连接,没有空闲等待,最大化利用网络 IO;
  4. 底层高效数据结构:SDS、哈希表、跳表、压缩列表等结构,时间复杂度低,内存利用率高。

Redis6.0 引入的多线程,只用于 socket 网络数据的读写,命令的执行依旧是单线程串行执行,不存在并发安全问题。

3、单线程为什么还能扛住高并发?

首先分清两个概念:并发和并行不一样。

  • 并行:多个任务同一时刻在多个 CPU 上同时运行;
  • 并发:多个任务交替执行,宏观上同时处理。

Redis 单线程依靠 IO 多路复用,能够同时监听大量客户端连接。大部分请求瓶颈并不是 CPU 计算,而是网络等待。当某个客户端还没有发来数据的时候,线程不会阻塞等待,转而去处理其他就绪的客户端请求。 Redis 的命令执行速度极快,一条命令执行耗时非常短,可以快速处理完一个请求再处理下一个,因此单线程就可以支撑上万 QPS。

4、Redis 支持的数据类型

5 大基础类型 String、List、Hash、Set、ZSet(Sorted‑Set)

3 大特殊类型 Bitmap(位图)、HyperLogLog、Geo

高级类型 Stream(Redis5.0 推出,专门用于消息队列)

5、Redis 常用应用场景

  1. 热点缓存:减轻数据库压力,加速查询;
  2. 计数器:点赞数、浏览量,利用 String 原子自增;
  3. 分布式锁:跨服务进程之间互斥控制;
  4. 排行榜:ZSet 实现热搜、积分排行榜;
  5. 签到打卡:Bitmap 位图;
  6. UV 独立访客统计:HyperLogLog;
  7. 地理位置服务:附近的人、距离计算,Geo;
  8. 消息队列:List、Stream 实现异步解耦;
  9. 会话 Session 存储、限流。

基础数据结构:

1、String 底层结构、最大容量、使用场景

String 的底层实现是 SDS(Simple Dynamic String,简单动态字符串) ,是 Redis 自定义的字符串结构,源码定义在 sds.h 中。 SDS 结构由头部和数据区组成:头部包含 len(已使用字节数)、alloc(总分配字节数)、flags(编码类型标记)字段;尾部 buf 数组存储实际数据。根据字符串长度不同,SDS 会自动选择 sdshdr8、sdshdr16、sdshdr32、sdshdr64 等不同大小的头部,最大化节省内存。

相比 C 原生字符串,SDS 的核心优势:

  1. O (1) 获取长度 :直接读取 len 字段,无需遍历到结束符;
  2. 二进制安全 :不以 \0 作为字符串结束标志,可存储图片、序列化字节等二进制数据;
  3. 减少内存重分配:支持空间预分配(小于 1MB 时翻倍扩容,大于 1MB 时每次扩容 1MB)和惰性释放,避免频繁内存申请与拷贝。

最大容量 :单个 String 类型 value 的最大长度为 512MB,由源码硬性限制,超过该长度会直接报错。

典型使用场景:

  • 业务数据缓存,如 JSON 格式的用户信息、配置项;
  • 原子计数器,通过 INCR/DECR 实现浏览量、点赞数统计;
  • 分布式锁,基于 SET key value NX EX 原子命令实现;
  • 会话 Session 存储、接口限流令牌等。

2、List 底层 quicklist 原理

Redis 3.2 版本之后,List 类型的底层统一由 quicklist(快速列表) 实现,替代了之前 ziplist 和双向链表二选一的编码方案。

quicklist 的本质是**双向链表 + 压缩列表(ziplist)**的混合结构:

  • quicklist 本身是一个双向链表,链表的每个节点(quicklistNode)不存放单个元素,而是存放一个 ziplist 结构;
  • 每个 ziplist 是一块连续的内存空间,内部批量存储多个 List 元素。

这种设计是对内存占用和操作性能的折中平衡:

  1. 相比纯双向链表:消除了大量节点指针的内存开销,减少内存碎片,内存利用率更高;
  2. 相比纯 ziplist:避免了元素过多时,插入删除触发大规模内存拷贝的性能问题;
  3. 同时保留了双向链表头尾增删 O (1) 的优势,也通过分段 ziplist 保证了随机访问的性能。

可通过配置项 list-max-ziplist-size 控制每个节点中 ziplist 的最大大小,以此平衡内存占用和读写效率。List 常用来实现简单消息队列、时间轴列表、最新消息流等场景。

3、Hash 底层结构、为什么适合存对象

Hash 的底层有两种编码结构,会根据元素数量和 value 大小自动切换:

  1. ziplist(压缩列表) :当哈希的字段数 ≤ 512 个(hash-max-ziplist-entries 默认值),且每个 value 长度 ≤ 64 字节(hash-max-ziplist-value 默认值)时使用,采用连续内存紧凑存储,内存效率极高。
  2. dict(哈希表 / 字典):当字段数量或 value 长度超过阈值时,自动切换为哈希表实现,通过数组 + 链表解决哈希冲突,增删查改平均时间复杂度 O (1)。

Hash 适合存储对象的核心原因:支持字段级独立操作 。 如果用 String 存储对象,通常是存入序列化后的 JSON 字符串,只要修改一个字段,就需要把整个字符串读出、反序列化、修改、再序列化写回 Redis,全量操作开销很大。 而 Hash 可以直接对单个字段执行 HSET、HGET,只操作目标字段,不需要读写整个对象,开销更小、更灵活;小数据量下 ziplist 编码还能带来比 String 更高的内存利用率,非常适合存储用户信息、商品属性这类多字段的结构化数据。

4、Set 底层、特点、应用场景

Set 的底层采用双编码自动切换机制:

  1. intset(整数集合) :当集合内所有元素都是整数,且元素数量 ≤ 512 个(set-max-intset-entries 默认值)时使用,用连续数组存储,内存占用极低。
  2. dict(哈希表):当出现非整数元素,或者元素数量超过阈值时,自动切换为哈希表实现;元素作为哈希表的 key,value 统一为 null,利用哈希表的特性实现自动去重。

Set 的核心特点:元素无序、自动去重,原生支持交集、并集、差集等集合运算。

典型应用场景:

  • 数据去重,比如用户访问去重、标签去重;
  • 社交场景,比如计算两个用户的共同好友、共同关注(交集运算);
  • 抽奖活动,利用去重特性保证用户不会重复中奖;
  • 用户标签系统,通过集合运算筛选具备特定标签的用户。

5、ZSet 底层跳表 skiplist 原理、为什么用跳表不用红黑树

ZSet(有序集合)同样有两种编码:小数据量时使用 ziplist 紧凑存储;当元素数量超过 128 个(zset-max-ziplist-entries 默认值)或 member 长度超过 64 字节时,切换为 跳表(skiplist)+ 哈希表(dict) 的组合结构。 其中哈希表存储 member 到 score 的映射,实现 O (1) 时间查询成员分数;跳表负责按照 score 排序,支撑高效的排序、范围查询。

跳表原理: 跳表是一种多层有序链表结构。最底层(第 0 层)是完整的原始数据链表,包含全部元素;每往上一层都是下一层的索引层,节点数量逐层减少。查找时从顶层索引开始,逐层向下定位,类似二分查找,时间复杂度可达 O (log n)。

Redis 选择跳表而不用红黑树,主要有三个原因:

  1. 范围查询性能更优:ZSet 最核心的使用场景是范围遍历(比如排行榜取 TopN、分数区间查找)。跳表找到左边界后,只需沿底层链表向后线性遍历即可,实现简单且缓存局部性好;红黑树需要中序遍历 + 回溯,实现复杂且连续访问效率更低。
  2. 实现与维护更简单:跳表的插入、删除只需要修改相邻节点的指针,不需要红黑树的旋转、变色等平衡操作,代码实现、调试和维护成本更低。
  3. 单点查找的时间复杂度同为 O (log n),性能差距不大,但 ZSet 的核心诉求是有序范围操作,跳表在该场景下优势更明显。

特殊数据结构:

1、HyperLogLog 作用、误差范围、适用场景

HyperLogLog 是 Redis 提供的概率型基数统计数据结构,核心作用是统计集合中不重复元素的数量(基数),它不会存储原始元素本身,只维护统计结构,因此内存占用极低。

  • 误差范围 :标准估算误差约为 0.81%,统计结果是近似值,并非精确值。
  • 内存特性:单个 HyperLogLog 键只需要约 12KB 内存,就可以统计亿级别的基数,内存消耗和数据量无关。
  • 适用场景:海量数据场景下的独立访客(UV)统计、页面独立访问数统计等;只需要总数、不需要获取具体元素列表,且可以接受微小误差的场景。

2、Bitmap 位图原理、签到、统计活跃用户

Bitmap 本质上是 String 类型的扩展,它以二进制 bit 位为单位存储状态,每个 bit 位对应一个唯一标识(如用户 ID),bit 值为 0 代表未发生、1 代表已发生,是一种极致节省空间的存储方式。

  • 签到场景实现 :以用户 ID 作为 bit 位的偏移量,使用 SETBIT sign:日期 用户ID 1 即可标记用户当日签到;统计当日签到总人数直接通过 BITCOUNT 命令统计 bit 为 1 的数量。
  • 统计活跃用户 :按时间维度生成不同的 Bitmap 键(如 active:20260827),当日活跃用户对应 bit 位置 1;统计多日连续活跃用户可以通过位与(AND)运算,统计周期总活跃用户可以通过位或(OR)运算,效率极高。

3、Geo 地理位置实现原理

Redis Geo 的底层基于 ZSet(有序集合) 实现。 核心原理是通过 Geohash 编码算法,将经纬度坐标编码成一个 52 位的整数,把这个整数作为 ZSet 的 score 值,ZSet 的 member 存储位置标识。

借助 ZSet 本身按 score 排序、范围查询的能力,Geo 可以实现:

  1. 计算两个位置之间的距离;
  2. 查询指定坐标半径范围内的所有地点(附近的人、附近商家);
  3. 按距离对地点进行排序等地理位置服务。

4、Stream 消息队列原理、消费组、偏移量

Stream 是 Redis 5.0 推出的专业持久化消息队列结构,弥补了 List 做队列的功能缺陷。

  • 基本原理:Stream 以消息流的形式持久化存储消息,每条消息都有全局唯一的递增 ID(由时间戳 + 序号组成);消息消费后不会自动删除,会长期保留。
  • 消费组(Consumer Group): 可以将多个消费者划分到同一个消费组,同一条消息在组内只会分配给一个消费者,实现消费端的负载均衡;不同消费组之间相互独立,可以各自完整消费同一条消息流。
  • 偏移量(Offset): 每个消费组会维护一个消费偏移量,记录当前组已经消费到的消息位置;消费者重启后,可以从偏移量位置继续向后消费,实现断点续传,保证消息不丢失。 同时配套 ACK 确认机制,消费者处理完消息后需手动确认,消息才会标记为已处理。

5、BitMap 和 HyperLogLog 区别

二者都是用于统计的轻量化结构,但核心定位和能力差异很大:

维度 Bitmap HyperLogLog
统计精度 精确统计,状态完全准确 估算统计,标准误差约 0.81%
内存占用 和最大 ID 正相关,ID 越大内存越高 内存固定极低,单 key 约 12KB,与数据量无关
能力边界 可查询单个元素的状态,支持位运算做多维度统计 只能统计基数总数,无法查询单个元素是否存在,无法获取原始数据
适用场景 签到打卡、用户状态标记、小范围精确统计 海量数据基数统计,如亿级 UV 统计,不需要明细的场景

6、Redis 布隆过滤器原理、优缺点、误判问题、使用场景

布隆过滤器是一种概率型数据结构,核心由一个大的位数组 + 多个独立的哈希函数组成,用于快速判断一个元素是否存在于集合中。

  • 工作原理: 写入元素时,元素经过多个哈希函数计算,得到多个数组下标,将位数组中对应下标的 bit 位全部置为 1; 查询元素时,用同样的哈希函数计算下标,检查所有对应 bit 位是否都为 1。
  • 误判问题 : 不存在反向误判:只要有一个 bit 位为 0,元素一定不存在 ; 存在正向误判:如果所有 bit 位都为 1,元素可能存在(是其他元素哈希碰撞导致的误判);元素越多,误判率越高。
  • 优缺点: 优点:内存占用极低、插入和查询都是常数级时间,性能极高; 缺点:存在一定误判率;传统布隆过滤器不支持删除元素。
  • 使用场景: 缓存穿透防护(提前存入所有合法 key,拦截不存在的请求); 黑名单校验、爬虫 URL 去重、垃圾邮件过滤等。

缓存经典问题(必问)

1、缓存穿透是什么?产生原因 + 四种解决方案

缓存穿透是指客户端查询的数据在缓存和数据库中都不存在,导致请求永远无法命中缓存,直接穿透缓存层打到数据库;

大量这类无效请求持续涌入,会快速压垮数据库。

产生原因:

  1. 恶意攻击:爬虫、攻击者批量传入不存在的 ID、非法参数,刻意打穿缓存;
  2. 业务异常:数据被物理删除、数据未同步,且缓存中没有对应空值兜底;
  3. 批量查询:批量传入大量非法主键,绕过缓存直接访问数据库。

四种解决方案:

  1. 空值缓存:数据库查询为空时,在缓存中写入一个短过期时间的空值,后续相同请求直接命中缓存,避免重复打库;
  2. 布隆过滤器:提前将所有合法主键存入布隆过滤器,请求先经过过滤器校验,不存在的 key 直接拦截,不访问缓存和数据库;
  3. 入口参数校验:在接口层对参数做合法性校验,比如 ID 为负数、格式非法的请求直接返回,不进入后续流程;
  4. 黑名单限流:对频繁发起非法请求的 IP、用户标识进行限流或拉黑,从源头减少穿透攻击。

2、缓存击穿是什么?产生原因 + 解决方案(互斥锁、热点永不过期)

回答: 缓存击穿是指单个高并发访问的热点 Key,在缓存过期的瞬间,大量并发请求同时涌入,缓存全部失效,请求瞬间全部打到数据库,导致数据库压力骤增,甚至宕机。

产生原因: 热点 Key 设置了固定过期时间,过期时刻恰好有大量请求访问;或是热点流量突发,而缓存刚好到期失效。

两种核心解决方案:

  1. 互斥锁方案:缓存失效时,只允许一个线程获取分布式锁去查询数据库并更新缓存,其余线程等待缓存刷新完成后再读取数据。优点是保证数据库不会被打穿,缺点是会增加请求延迟,降低吞吐量。
  2. 热点 Key 逻辑永不过期:物理层面不设置缓存过期时间,由业务代码维护逻辑过期;后台启动异步线程定时刷新热点数据,缓存永远不会出现失效瞬间,从根源避免击穿。

3、缓存雪崩是什么?产生原因 + 事前 / 事中 / 事后解决方案

回答: 缓存雪崩是指大量缓存 Key 在同一时间集中过期失效,或者 Redis 服务整体宕机,导致海量请求全部直接打到数据库,引发数据库压力过载甚至崩溃,造成服务连锁故障。

产生原因:

  1. 过期时间集中:批量设置缓存时使用了相同的过期时间,到期同时失效;
  2. Redis 故障:Redis 节点宕机、集群网络分区,缓存服务整体不可用;
  3. 流量突增:大促、活动导致缓存访问量骤增,过期后集中击穿数据库。

分阶段解决方案:

  • 事前预防:
    1. 过期时间打散:给缓存过期时间增加随机偏移值,避免大量 key 同时过期;
    2. 高可用架构:部署 Redis 哨兵 / 集群,避免单点故障,保障缓存服务可用性;
    3. 热点数据永不过期:核心热点 key 设置逻辑永不过期,后台异步刷新;
    4. 多级缓存:本地缓存 + Redis 缓存二级架构,降低 Redis 故障的影响。
  • 事中控制:
    1. 服务限流熔断:接入层开启限流、熔断、降级机制,控制数据库的请求量;
    2. 分布式锁保护:通过分布式锁控制数据库并发访问数,避免数据库被打垮。
  • 事后补救:
    1. 缓存预热:Redis 重启后,提前批量加载热点数据到缓存,快速恢复服务;
    2. 故障恢复:快速重启 Redis 集群,通过持久化文件恢复数据,逐步恢复流量。

4、Reds大key问题(没啥招数,其实就是做拆分)

大 key 指单个 Key 对应的 Value 体积过大:比如 String 类型 value 超过 100KB,集合类型(List/Hash/Set/ZSet)包含数万甚至数十万元素。

核心危害: 删除、过期、迁移大 key 时会阻塞 Redis 单线程,导致其他命令卡顿超时;集群中大 key 集中在单个节点,会造成内存倾斜、节点负载不均;读取大 key 还会占用大量带宽,引发网络拥塞。

核心解决方案:拆分

  1. 大字符串拆分:将大对象按业务维度拆分为多个小 key 分块存储,读取时按块拼接;
  2. 大集合拆分:将一个大集合按 ID 哈希、时间分片等维度拆分为多个小集合,分散存储压力;
  3. 渐进式处理:删除大集合时,使用 SCAN 命令分批遍历删除,避免一次性 DEL 阻塞主线程;
  4. 业务优化:禁止一次性读取整个大集合,统一使用分页、分批查询。

5、Redis热key问题(去搜京东hotkey探测机制)

热 key 指单个 Key 在短时间内收到超高并发访问,访问压力集中在 Redis 集群的单个节点上,导致该节点 CPU、网卡打满,成为整个集群的性能瓶颈。

京东 HotKey 探测机制(JD-HotKey)

京东开源了毫秒级热 key 探测框架,历经多次 618、双十一大促验证,核心是「客户端采集 + Worker 端计算 + etcd 全局同步 + 本地缓存兜底」的四层架构。

  1. 核心架构组成
    • etcd 集群:全局配置中心,存储热 key 判定规则、Worker 节点地址、已探测出的热 key 名单;提供监听订阅能力,保证配置和热 key 信息在所有服务节点间一致。
    • Client 端 Jar 包:嵌入业务服务,本地按时间窗口累加 key 访问次数,默认每 500ms 批量上报给 Worker;同时监听热 key 通知,收到后将热点数据缓存到本地 Caffeine 内存中。
    • Worker 端集群:独立部署的计算节点,接收各客户端上报的 key,通过滑动窗口统计访问频率;达到阈值则判定为热 key,推送给所有相关客户端并同步到 etcd。单台 16 核机器可稳定处理每秒 30 万以上的 key 探测。
    • Dashboard 控制台:可视化配置规则、查看热 key 列表、支持人工增删热 key。
  2. 判定逻辑:支持按业务维度配置差异化规则,比如商品 ID 1 秒内访问超 100 次、用户 ID 2 秒内超 20 次即判定为热 key。
  3. 热 key 处理方案
    • 本地二级缓存:热 key 数据缓存到应用本地内存,请求直接命中本地,绕过 Redis;
    • 多副本分流:将热点 key 复制多份分布到集群不同节点,分散访问压力;
    • 请求打散:同一热 key 生成多个副本 key,请求哈希到不同节点,均衡流量。

缓存与数据库一致性

1、缓存和数据库双写一致性几种策略

缓存与数据库双写一致性,核心是解决数据更新时,缓存和数据库两个独立存储之间的数据不同步问题。业界主流有四种策略,绝大多数业务场景下缓存只保证最终一致性,强一致性实现成本极高,不适用于缓存架构。

四种主流策略分别是:

  1. 先更新数据库,再删除缓存(业界最主流方案)
  2. 先删除缓存,再更新数据库
  3. 延时双删策略
  4. 基于 Canal 订阅 binlog 的异步缓存更新方案

2、先更新数据库再删缓存、先删缓存再更新数据库 优缺点

方案一:先更新数据库,再删除缓存(业界推荐)

优点:

  1. 并发脏数据概率极低。唯一可能产生脏数据的极端场景:缓存刚好失效时,读线程读到旧数据正要写回缓存,此时写线程更新完数据库并删除缓存,读线程随后把旧值写入缓存。但写数据库速度远慢于读,该场景发生概率极低。
  2. 实现简单,步骤少,业务侵入性低,出错概率小。

缺点:

  1. 如果删除缓存的操作失败,缓存会残留旧数据,导致持续不一致;
  2. 高并发写场景下频繁删除缓存,会导致缓存命中率下降。

方案二:先删除缓存,再更新数据库

优点:

  1. 逻辑直观,先让缓存失效,再更新底层数据,理论上后续读请求会自动拉取最新值。

缺点:

  1. 并发脏数据概率高。线程 A 先删除缓存,还未更新完数据库;此时线程 B 查询,缓存未命中,读取数据库旧值并写回缓存;等线程 A 更新完数据库,缓存中就会长期留存旧数据,不一致窗口非常大。
  2. 无法解决数据库更新期间的并发读问题,数据不一致风险远高于前者。

3、延时双删策略原理

延时双删是对「先删缓存,再更新数据库」方案的优化,目的是降低并发脏数据的概率,属于最终一致性方案。

执行步骤

  1. 第一步:先删除缓存;
  2. 第二步:更新数据库;
  3. 第三步:线程休眠一小段时间(通常几百毫秒到 1 秒,根据业务读耗时设定),然后第二次删除缓存。

核心原理

休眠时间需要略大于一次普通业务读请求的耗时。 目的是等待「数据库更新过程中,其他并发线程读取旧数据并写入缓存」这个过程完成;第二次删除缓存,就可以把这段时间内产生的脏缓存清理掉,避免脏数据长期停留在缓存中。

注意点

  • 只能降低不一致概率,无法完全消除;如果第二次删除失败,依然会残留脏数据;
  • 同步休眠会阻塞写线程、降低吞吐量,生产环境通常异步执行第二次删除。

4、如何保证强一致性?Canal 订阅 binlog 方案

首先明确:缓存天生不适合严格强一致性场景。如果要求绝对强一致,最可靠的方式是直接读写数据库,不走缓存;缓存架构通常只追求秒级以内的最终一致性。

Canal 订阅 binlog 方案原理

Canal 是阿里巴巴开源的 MySQL binlog 订阅工具,它模拟 MySQL 从节点协议,实时接收并解析数据库的二进制日志。

完整方案流程:

  1. 业务服务只负责操作数据库,不直接操作缓存,业务代码与缓存逻辑完全解耦;
  2. Canal 服务实时监听 MySQL 的 binlog 日志,解析出数据的增删改变更;
  3. 消费端接收到变更事件后,异步去删除或更新对应的 Redis 缓存。

方案优势

  1. 业务层无感知,缓存更新逻辑和业务解耦,避免业务代码里到处穿插缓存操作;
  2. 基于 binlog 保证变更事件不丢失,缓存更新的可靠性更高;
  3. 一致性延迟低,通常在毫秒到秒级,是工业界非常成熟的最终一致性方案。

关于强一致性的补充

如果必须实现缓存与数据库的强一致,需要引入分布式事务(如 2PC、TCC),同时包裹数据库操作和缓存操作,保证二者同时成功或同时回滚;但性能损耗极大、复杂度极高,几乎不会在普通缓存场景使用。

过期/淘汰策略

1、Redis 键过期删除三种策略:定时删除、惰性删除、定期删除

Redis 针对设置了过期时间的 key,设计了三种过期删除策略,最终采用「惰性删除 + 定期删除」的组合方案。

  1. 定时删除 为每个过期 key 创建定时器,过期时间一到立刻删除 key 释放内存。 优点:内存释放及时; 缺点:大量定时器会占用大量 CPU 资源,严重影响 Redis 主流程性能,Redis 没有采用该策略。
  2. 惰性删除 key 过期后不主动删除,等客户端访问这个 key 时,再检查是否过期,过期则删除并返回空。 优点:几乎不占用 CPU 额外开销,对性能影响极小; 缺点:如果过期 key 永远不被访问,就会一直占用内存,造成内存浪费。
  3. 定期删除 每隔固定时间,从过期 key 集合中随机抽取一批 key 检查,删除其中过期的 key。 优点:通过控制执行频率和时长,折中平衡 CPU 开销和内存回收效率; 缺点:无法精准控制过期 key 的内存回收速度。

👉 Redis 实际采用:惰性删除 + 定期删除结合,既保证主线程性能,又尽可能回收过期内存。

2、Redis 内存满后八大淘汰策略分别是什么?

当 Redis 内存占用达到 maxmemory 上限,再写入新数据时会触发内存淘汰,共 8 种策略,分为「全量 key 范围」和「仅过期 key 范围」两大类。

  1. noeviction(默认策略):不淘汰任何 key,新的写请求直接返回错误,只读操作正常执行。
  2. allkeys-lru:在所有 key 中,淘汰最近最少使用(Least Recently Used)的 key。
  3. volatile-lru:只在设置了过期时间的 key 中,淘汰最近最少使用的 key。
  4. allkeys-lfu:在所有 key 中,淘汰访问频次最低(Least Frequently Used)的 key。
  5. volatile-lfu:只在设置了过期时间的 key 中,淘汰访问频次最低的 key。
  6. allkeys-random:在所有 key 中,随机挑选 key 淘汰。
  7. volatile-random:只在设置了过期时间的 key 中,随机挑选淘汰。
  8. volatile-ttl:只在设置了过期时间的 key 中,淘汰剩余存活时间最短的 key。

3、LRU 底层实现原理、Redis 近似 LRU 怎么做的

传统 LRU 原理

标准 LRU(最近最少使用)算法底层是双向链表 + 哈希表:链表头部是最近访问的节点,尾部是最久未访问的节点;每次访问 key 就把节点移到头部;内存满时删除尾部节点。 缺点:需要维护完整的链表结构,额外内存开销大,节点移动操作频繁,对 Redis 性能影响较大。

Redis 近似 LRU 实现

Redis 没有实现精确 LRU,而是采用采样淘汰的近似方案: 每次淘汰时,随机采样 5~10 个 key,计算每个 key 的闲置时间,只淘汰采样集合中闲置最久的 key。 通过多次采样迭代,最终淘汰效果接近精确 LRU,但性能开销远低于标准 LRU,牺牲极小的准确率换取极高的执行性能。

4、过期键会不会主动占用内存?主从间过期怎么同步

过期键的内存占用:

过期键会继续占用内存。 因为 Redis 采用惰性 + 定期删除策略:只有 key 被访问触发惰性删除、或者被定期删除抽中时,才会被真正删除释放内存;如果过期 key 既没被访问、也没被定期抽样抽到,就会一直占用内存空间。

主从间的过期同步:

主从架构中,过期删除的主动权完全在主节点:

  1. 主节点检测到 key 过期并删除后,会向所有从节点发送一条 DEL 命令,同步删除从节点上的对应 key;
  2. 从节点自身不会主动删除过期 key,即使键已经过期,从节点也不会自行处理,必须等待主节点的同步命令;
  3. 客户端访问从节点时,Redis 会检查键是否过期,过期则返回空结果,但不会在从节点本地删除该键。

持久化 RDB & AOF(超级高频

1、RDB 是什么?原理、触发方式、优缺点

RDB 是 Redis 的快照式持久化 机制,指在某个指定时间点,将 Redis 内存中的全量数据生成一份二进制格式的快照文件(默认文件名 dump.rdb)写入磁盘,实现数据落盘持久化。

核心原理:生成内存数据的完整二进制镜像,以紧凑的格式存储,恢复时直接加载到内存即可完成数据还原。

触发方式分为四类:

  1. save 命令:主线程阻塞式执行快照生成,期间无法响应客户端请求,生产环境禁用。
  2. bgsave 命令:fork 出一个子进程,由子进程后台生成 RDB 文件,主进程继续处理请求,是最常用的触发方式。
  3. 配置自动触发:通过 save m n 配置规则,m 秒内发生 n 次写操作则自动触发 bgsave。
  4. 被动触发:Redis 正常关闭(shutdown)、主从复制全量同步时,都会自动生成 RDB 快照。

优缺点

  • 优点:文件体积小、数据恢复速度极快;适合冷备份、全量数据迁移。
  • 缺点:两次快照间隔内宕机会丢失全部增量数据;fork 子进程时会短暂阻塞主线程,内存越大阻塞时间越长。

2、AOF 是什么?日志刷写策略三种

AOF(Append Only File)是 Redis 的增量日志式持久化 机制,以文本形式记录每一条对数据有修改的写命令,追加写入到 appendonly.aof 文件中;宕机后通过逐条回放日志中的命令恢复数据。

三种日志刷盘策略(appendfsync):

  1. always:每执行一条写命令,就立刻调用 fsync 刷写到磁盘。 特点:数据安全性最高,最多丢失一条命令的数据;但磁盘 IO 开销极大,性能最差。
  2. everysec(默认配置):每秒执行一次 fsync 刷盘。 特点:性能与安全性的折中,最多丢失 1 秒的数据,是生产环境的默认选择。
  3. no:不主动执行刷盘,完全交由操作系统自行决定刷盘时机。 特点:性能最高,但数据丢失风险最大、不可控。

3、AOF 重写机制原理、为什么要重写

为什么要重写

随着 Redis 运行时间增长,AOF 日志文件会不断膨胀,文件中存在大量冗余命令(比如对同一个 key 多次 set、多次 incr),既占用大量磁盘空间,也会大幅拖慢宕机后的数据恢复速度。AOF 重写的核心目的就是压缩 AOF 文件体积,提升恢复效率。

重写原理

AOF 重写不会读取和解析旧的 AOF 文件 ,而是直接读取当前 Redis 内存中的数据,为每一个 key 生成一条对应的最简写命令,写入新的 AOF 文件。 重写执行期间,新的写命令会同时写入旧 AOF 文件 和重写缓冲区;重写完成后,将缓冲区中的增量命令追加到新 AOF 文件末尾,最后用新文件原子替换旧的 AOF 文件。

重写可通过 bgrewriteaof 手动触发,也可通过配置文件大小阈值自动触发。

4、RDB 和 AOF 对比区别、各自适用场景

对比维度 RDB AOF
存储形式 全量二进制快照 增量写命令日志
数据完整性 较差,丢失两次快照间隔的所有数据 较好,默认最多丢失 1 秒数据
恢复速度 极快,直接加载二进制镜像 较慢,需要逐条回放所有命令
文件体积 小,二进制压缩存储 大,重写后可大幅压缩
性能开销 低,仅快照时 fork 子进程 相对较高,持续写日志 + 定期刷盘

适用场景

  • RDB:适合全量冷备份、数据迁移、可接受少量数据丢失的场景,常作为辅助持久化手段。
  • AOF:适合数据安全性要求高、不能丢失大量业务数据的场景,是生产环境的主流持久化方案。

5、混合持久化原理(RDB+AOF 混合)

回答: 混合持久化是 Redis 4.0 推出的特性,结合了 RDB 和 AOF 各自的优势,在 AOF 重写阶段生效。

核心原理: AOF 重写时,不再生成纯命令格式的日志,而是先将当前内存中的全量数据以 RDB 二进制格式写入新 AOF 文件的开头,后续新增的写命令再以 AOF 日志格式追加到文件末尾。

核心优势:

  1. 恢复速度快:开头的 RDB 部分可直接加载,无需逐条回放全量命令,恢复速度接近纯 RDB;
  2. 数据安全性高:增量部分保留 AOF 日志,最多丢失 1 秒数据,安全性与纯 AOF 一致; 兼顾了恢复效率和数据可靠性,是目前生产环境推荐的持久化方案。

6、Redis 宕机后数据恢复流程

Redis 重启恢复数据时,会按照「优先 AOF、次之 RDB」的优先级执行:

  1. 首先检查是否开启了 AOF 持久化:
    • 如果开启了 AOF,优先加载 AOF 文件恢复数据;因为 AOF 的数据完整性更高,丢失的数据更少。
  2. 如果未开启 AOF,才会加载 RDB 快照文件恢复数据。
  3. 如果 AOF 和 RDB 文件都不存在,则 Redis 以空数据库启动。

补充:若开启了混合持久化,加载 AOF 文件时会先解析开头的 RDB 部分快速加载全量数据,再回放后面的增量 AOF 命令补齐数据。

并发 & 分布式锁

1、Redis 分布式锁基础实现原理(SET NX EX)

Redis 分布式锁的核心是利用 SET key value NX EX seconds 原子命令实现,保证「加锁 + 设置过期时间」操作的原子性。

  • 核心参数含义:
    • NX:只有当 key 不存在时才能设置成功,保证同一时刻只有一个客户端能加锁成功;
    • EX:给 key 设置过期时间,防止客户端异常崩溃后锁永远无法释放,避免死锁。
  • value 设计:value 一般设为唯一标识(如 UUID + 线程 ID),解锁时校验 value 是否匹配,避免误删除其他客户端持有的锁。
  • 为什么不用 SETNX + EXPIRE 两条命令:两条命令不具备原子性,如果 SETNX 成功后客户端宕机,来不及设置过期时间,锁就会永久存在,造成死锁。

2、分布式锁超时释放问题怎么解决?

回答: 超时释放问题指:业务执行时长超过了锁的过期时间,锁被 Redis 自动释放,此时其他客户端可以获取到锁,引发并发安全问题;同时原业务线程执行结束后,还可能误删除其他线程的锁。

解决方案:

  1. 合理预估过期时间:根据业务执行耗时设置过期时间,预留一定冗余,降低超时概率;
  2. 锁续约机制:引入后台定时线程,业务未执行完成时自动延长锁的过期时间(即看门狗机制);
  3. 解锁身份校验:解锁时必须校验锁的持有者标识,只能释放自己加的锁,避免误删他人的锁;
  4. 业务兜底校验:锁超时后,业务执行结果需要做并发校验,避免脏数据写入。

3、锁续约 / 续命怎么做?Redisson 看门狗原理

锁续约的核心是通过后台定时线程,动态延长锁的过期时间,保证业务执行完成前锁不会提前释放。Redisson 中的标准实现就是看门狗(WatchDog)机制。

看门狗原理:

  1. 加锁成功后,Redisson 会启动一个后台定时线程(看门狗线程);
  2. 默认锁的过期时间为 30 秒,看门狗每隔 1/3 过期时间(即 10 秒) 检查一次:如果当前线程仍然持有锁,就重新设置锁的过期时间为 30 秒,完成续命;
  3. 当业务执行完成、主动释放锁后,看门狗线程会被停止,不再续期。

作用:彻底解决业务执行耗时超过锁过期时间导致的锁提前释放问题,无需人工精准预估超时时间。

4、主从架构下分布式锁锁失效问题

在 Redis 主从 + 哨兵架构下,存在锁失效的典型场景: 客户端在 Master 主节点加锁成功后,锁数据还未同步到 Slave 从节点,此时 Master 宕机;哨兵执行故障转移,将一个 Slave 升级为新的 Master;由于新主节点上没有这把锁的数据,其他客户端就可以再次加锁成功,导致同一时间存在两把锁,锁失效。

解决方案:

  1. Redlock 红锁算法:部署多个相互独立、无主从关系的 Redis 节点,客户端向所有节点请求加锁,超过半数节点加锁成功才算真正获取锁;故障时单个节点宕机不影响整体锁状态,可靠性更高,但性能开销大。
  2. 容忍最终一致:如果业务可以接受极低概率的锁冲突,主从架构的锁失效概率很低,可通过业务兜底校验兼容。
  3. 集群架构同理:Redis Cluster 集群每个槽位同样有主从,锁失效逻辑一致,强一致场景同样推荐红锁方案。

5、Redisson 可重入锁原理

可重入锁指同一个线程可以多次获取同一把锁,不会出现自己阻塞自己的情况。Redisson 可重入锁底层基于 Redis Hash 结构 实现。

实现原理:

  1. Hash 的 key 是锁的名称,Hash 的 field 是锁持有者的唯一标识(线程 ID + 客户端 UUID),value 是该线程的重入次数。
  2. 加锁逻辑:
    • 锁不存在:直接加锁成功,重入次数设为 1,同时启动看门狗;
    • 锁已存在且 field 匹配当前线程:重入次数 +1,重置锁过期时间;
    • 锁已存在且持有者不是当前线程:加锁失败,阻塞等待。
  3. 解锁逻辑:
    • 校验持有者身份,匹配成功则重入次数 -1;
    • 当重入次数递减到 0 时,真正删除锁的 key,停止看门狗线程。

主从复制 & 集群

1、Redis 主从复制原理、全量同步 / 增量同步

回答: 主从复制是 Redis 高可用的基础,主节点负责写,从节点负责读,实现读写分离和数据备份。复制分为全量同步和增量同步两种模式。

全量同步:一般发生在从节点初次连接、或断线后无法增量同步的场景。

  1. 从节点向主节点发送同步请求;
  2. 主节点执行 bgsave 生成 RDB 快照文件,同时将快照期间的新写命令写入复制缓冲区;
  3. 主节点将 RDB 文件发送给从节点,从节点清空本地数据并加载 RDB;
  4. 主节点再把缓冲区的增量命令发送给从节点,从节点执行命令补齐数据,同步完成。

增量同步:正常运行阶段的持续同步。 主节点每执行一条写命令,就会异步将命令发送给所有从节点,从节点执行命令,保证主从数据一致。

2、主从复制复制积压缓冲区作用

复制积压缓冲区(repl_backlog)是主节点维护的一个固定大小的环形缓冲区,用来存储最近一段时间的写命令以及对应的复制偏移量。

核心作用:

  1. 支持断线增量同步:主从短暂断线重连后,不需要重新全量同步,只需要根据从节点上报的偏移量,从缓冲区中取出缺失的命令补发即可,大幅提升断线恢复效率。
  2. 缓冲复制延迟:从节点网络延迟、阻塞时,命令可以暂存在缓冲区,避免主节点阻塞。

缓冲区大小由 repl-backlog-size 配置,太小会导致断线后经常触发全量同步;太大则浪费内存。

3、主从同步断线后怎么增量同步

主从断线重连后的增量同步流程如下:

  1. 从节点重新连接主节点后,向主节点上报自己当前的复制偏移量;
  2. 主节点校验该偏移量是否还存在于复制积压缓冲区中;
  3. 如果偏移量仍在缓冲区内:主节点将偏移量之后的所有写命令发送给从节点,从节点执行后完成增量同步;
  4. 如果偏移量已经被新数据覆盖(缓冲区是环形的,旧数据被覆盖):说明断线时间过长,无法增量同步,主节点会触发全量同步流程。

4、哨兵 Sentinel 作用、原理、故障转移流程

哨兵是独立运行的进程,组成哨兵集群,实现 Redis 主从的自动故障转移,解决主节点单点故障问题。

三大核心作用:

  1. 监控:持续检测所有主、从节点是否存活;
  2. 通知:节点故障时通知管理员和客户端;
  3. 自动故障转移:主节点宕机后,自动选举一个从节点升级为新主节点,保证服务可用。

故障转移完整流程:

  1. 单个哨兵检测到主节点无响应,标记为主观下线;
  2. 多个哨兵都确认主节点下线,达成多数共识后,标记为客观下线;
  3. 哨兵集群内部投票选举出一个领头哨兵,负责执行故障转移;
  4. 领头哨兵从存活的从节点中选出最优节点,升级为新的主节点;
  5. 命令其余从节点切换到新主节点进行复制;
  6. 通知客户端主节点变更,同时将旧主节点标记为下线,恢复后自动变为新主的从节点。

5、哨兵怎么选主、投票机制

哨兵选举分为两部分:领头哨兵选举 和 新主节点选举。

领头哨兵选举(基于 Raft 协议思想)

  1. 哨兵发现主节点客观下线后,会发起选举,拉票请求其他哨兵投票给自己;
  2. 每个哨兵在一轮选举中只能投一票,先到先得;
  3. 获得超过半数哨兵投票的节点,成为领头哨兵,负责执行故障转移;
  4. 如果选举失败,等待超时后开启新一轮选举。

新主节点选举规则(优先级从高到低)

  1. 从节点优先级 :slave-priority 配置值越小,优先级越高;
  2. 复制偏移量:偏移量越大,数据越完整越新,优先选;
  3. 运行 ID:以上都相同时,选 runid 更小的从节点。

6、Redis Cluster 集群原理、哈希槽 16384

Redis Cluster 是无中心的分布式分片集群,解决单机内存上限问题,实现数据分布式存储和高可用。

核心原理是哈希槽分片:

  1. 整个集群预划分 16384 个哈希槽,编号 0 ~ 16383;
  2. 每个主节点负责管理其中一部分哈希槽;
  3. 写入数据时,对 key 做 CRC16(key) % 16384 计算,得到对应的槽号,直接路由到负责该槽的主节点执行。

每个主节点可以挂载多个从节点,主节点故障时,集群自动将对应从节点升级为主,实现分片级别的高可用。

7、集群槽位分配、分片规则

分片规则: 数据不直接绑定节点,而是通过哈希槽做中间层。key → CRC16 → 哈希槽 → 节点,实现数据和节点的解耦,方便后续扩容缩容、槽位迁移。

注意:多键批量操作(如 MGET、MSET)要求所有 key 必须在同一个哈希槽,否则会报错。

槽位分配规则:

  1. 集群初始化时,槽位可以自动或手动分配,尽量均匀分布到所有主节点,保证各节点数据量均衡;
  2. 每个哈希槽有且仅属于一个主节点,不允许交叉;
  3. 只有全部 16384 个槽都有节点负责,集群才处于正常可用状态。

8、集群扩容、缩容原理

回答: 集群扩容和缩容的核心都是哈希槽迁移,数据跟随槽位移动,过程中集群持续对外提供服务。

扩容原理:

  1. 新的主节点加入集群,成为空节点;
  2. 从现有各个主节点上,分别迁移一部分哈希槽到新节点;
  3. 迁移过程中,数据逐步移动,槽位归属逐步更新;
  4. 所有槽迁移完成后,新节点正式承担读写服务,扩容完成。

缩容原理:

  1. 将待下线主节点的所有哈希槽,全部迁移到其他存活的主节点;
  2. 所有槽迁移完毕、数据完全转移后,将该节点从集群中移除;
  3. 缩容完成,剩余节点继续提供服务。

9、为什么是 16384 个哈希槽,不是更大?

主要有两个核心原因:

  1. 心跳包大小控制 集群节点之间的心跳消息会携带完整的槽位图,槽位数量对应位图大小。16384 个 bit = 2KB,心跳包体积很小,网络开销低;如果扩大到 65536 个槽,位图就变成 8KB,节点越多心跳带宽占用越高,得不偿失。
  2. 集群规模匹配 Redis 集群官方推荐节点数量上限约 1000 个,16384 个槽已经足够让上百个节点均匀分片,槽数量过多没有实际收益,反而增加管理开销。

补充:Redis 作者也明确说明,16k 槽对于 1000 节点规模的集群已经完全够用,是性能和分片粒度的最佳平衡。

10、集群高可用、脑裂问题怎么解决

集群高可用实现: 每个主节点都配置至少一个从节点作为备份;当主节点故障下线时,集群通过类似哨兵的故障转移机制,自动将对应从节点升级为新主节点,接管该分片的所有槽位,继续对外提供服务,实现分片级高可用。

脑裂问题与解决方案 脑裂指网络分区后,旧主节点和集群其他节点断开,但自身仍在运行,客户端继续向其写入数据;网络恢复后,旧主节点变为从节点,分区期间写入的数据会全部丢失。

解决方案:通过配置 min-replicas-to-write 参数。 设置主节点必须至少拥有 N 个在线的从节点,才能接收写请求。网络分区后,主节点失去从节点连接,数量不满足阈值,就会自动拒绝写入,避免脑裂产生脏数据,牺牲部分可用性保证数据一致性。

网络模型 & 底层原理

1、Redis 多路复用 IO 模型:epoll 原理

Redis 是单线程事件驱动模型,核心依靠 IO 多路复用(epoll) 同时监听成千上万条客户端连接,实现高性能网络 IO。

epoll 原理:

epoll 是 Linux 下多路复用模型,核心思想:单线程监听多个文件描述符(客户端 socket),只在连接就绪(可读 / 可写)的时候才去处理事件,不需要轮询遍历所有连接。 分为三步:

  1. epoll_create:创建一个 epoll 实例;
  2. epoll_ctl:把所有客户端 socket 连接注册添加到 epoll 监听列表;
  3. epoll_wait:阻塞等待事件就绪;当某个客户端发来请求、socket 可读,epoll 就主动返回就绪的连接列表。

Redis 使用 epoll 带来的好处:

  1. 不需要开辟大量线程去处理每一个客户端,避免线程切换开销;
  2. 连接即使空闲,也几乎不消耗 CPU;
  3. 支持海量并发连接,所以 Redis 可以做到几万客户端同时连接。

Redis 单线程只是执行命令是单线程,网络 IO 靠 epoll 多路复用来管理大量连接。

2、Redis 6.0 多线程做了什么?网络多线程、命令还是单线程

Redis6.0 引入的多线程仅仅用来处理网络 IO,执行命令仍然是单线程串行执行。

6.0 多线程分工

  1. IO 线程(多线程):负责网络数据的读写。
    • 从 socket 中读取客户端发来的请求数据;
    • 将命令执行后的响应结果写回 socket;
    • 多线程并发分担网络 IO 读写的压力。
  2. 主线程(单线程) :拿到解析好的命令,串行执行所有 Redis 命令。 SET、GET、LPOP 等操作依然只能主线程跑,命令执行不会并发。

为什么命令不做多线程?

  1. Redis 命令很多操作都是对共享数据修改,多线程就要加锁,锁竞争反而降低性能;
  2. 单线程无锁,代码简单,不容易出现并发安全问题。

一句话总结:IO 多线程负责收包发包;命令执行依旧单线程。

3、Pipeline 管道作用、原理、适用场景

回答: Pipeline(管道)用来批量一次性发送多条 Redis 命令,减少多次网络往返 RTT 延迟。

原理

普通模式:客户端发一条命令 → 等待 Redis 返回结果,一次网络往返。多条命令就要多次网络来回,网络开销很大。 Pipeline:客户端一次性把 N 条命令打包,发给 Redis;Redis 收到全部命令,依次串行执行,最后一次性把所有结果打包返回客户端。

Pipeline 不保证原子性,命令之间可以被其他客户端命令插队。

作用

大幅降低网络往返次数,提升批量操作吞吐量。

适用场景

  1. 大批量简单读写操作,比如一次性写入上千条数据;
  2. 不需要上一条命令返回结果,才能执行下一条;
  3. 离线批量导入数据。

不适用场景

  1. Redis‑Cluster 集群环境,key 跨不同槽位,Pipeline 无法使用;
  2. 需要多条命令原子执行(该场景选 Lua 脚本)。

4、Lua 脚本作用、原子性、批量操作

Redis 支持执行 Lua 脚本,把多条命令写到一段 Lua 代码里发送给 Redis 服务端执行。

核心特性:原子性

Redis 执行 Lua 脚本期间,不会插入其他客户端的命令,整个脚本一次性串行跑完,具备原子性。 脚本执行没结束,其他请求全部阻塞等待,要么脚本所有命令全部成功,要么失败。

注意:脚本执行中报错,已经执行过的命令不会回滚,Redis 没有事务回滚机制。

作用

  1. 原子批量操作:多条命令需要一起执行、不可被打断,比如分布式锁的释放;
  2. 减少网络开销:多条逻辑命令封装成一次请求;
  3. 实现复杂业务逻辑:服务端完成判断 + 修改的组合逻辑;

经典使用场景

  1. 安全释放分布式锁(先判断 value,再删除 key,两条命令原子执行);
  2. 限流;
  3. 复杂的批量原子操作。

5、Lua 脚本 vs Pipeline 对比

对比项 Pipeline 管道 Lua 脚本
原子性 不原子,命令可被插队 原子执行
执行位置 客户端打包发送,redis 逐条跑 Redis 服务端执行
能否依赖上一条结果 不能 可以,脚本内部可以拿到前一条命令返回值
相关推荐
GeW10 小时前
产业新优势基础软件的人才底盘,从Linux与数据库开始
linux·数据库
SelectDB10 小时前
Doris Arrow Flight SQL 高速读取协议实战:与 Snowflake 客户端取数链路对照
大数据·数据库·数据分析
曼岛_10 小时前
[图解密码学]第4章 古典密码
java·数据库·密码学
一枕槐安48710 小时前
RAG 服务扛住高并发:缓存、队列、限流三件套
java·spring·缓存·异步任务·rag·断路器
Wang's Blog10 小时前
Java 项目实战: 交友项目 - 动态查询开工:昨日回顾与今日任务
java·python·交友
云和数据.ChenGuang10 小时前
光哥说Python 异常深度解析:从异常模型到工程化实践
java·前端·人工智能·python·langchain
SL_staff11 小时前
APS动态重排实践:在多品种小批量场景下实现5分钟插单响应
java·开源·产品
瀚高PG实验室11 小时前
HGDB V9.0.5 .pgpass 免密登录提示 IsEncryptNon-base64 encoding 警告处理
数据库·postgresql·瀚高数据库
龙腾AI白云11 小时前
大模型检索增强生成(RAG)的技术原理与架构设计
数据库·python·机器学习·知识图谱
captain37611 小时前
Spring MVC(4)-响应
java·spring boot·后端