Redis面试题

Redis 数据结构整理

1. String(字符串)

  • 类型:简单的 key-value 结构,支持二进制存储,最大 512MB。
  • 底层实现
    • 短字符串(≤39字节):SDS(简单动态字符串)。
    • 长字符串:embstr(短)或 raw(长)。
  • 应用场景
    • 缓存、计数器、分布式锁(SET NX PX)。

2. Hash(哈希表)

  • 类型:存储键值对的集合,类似 Map<String, String>
  • 底层实现
    • 小数据量(字段少、值小):压缩列表(ziplist),节省内存。
    • 大数据量哈希表(hashtable),O(1) 查询。
  • 应用场景
    • 存储对象(如用户信息),减少 key 数量。

3. List(列表)

  • 类型:双向链表,可当作栈或队列使用。
  • 底层实现
    • 短列表(元素小于64字节,总数少于512)压缩列表(ziplist),占用更少内存。
    • 长列表双向链表(linkedlist),支持 O(1) 头尾操作。
  • 应用场景
    • 消息队列、任务队列、时间轴(微博)。

4. Set(集合)

  • 类型:无序、唯一元素集合,支持集合运算。
  • 底层实现
    • 元素个数少(≤512)且每个元素≤64字节整数数组(intset),节省空间。
    • 否则哈希表(hashtable),O(1) 查询。
  • 应用场景
    • 黑名单、唯一值存储、好友推荐(交集)。

5. Sorted Set(有序集合,ZSet)

  • 类型:Set 的变体,每个元素有一个 score 进行排序。
  • 底层实现
    • 小数据量压缩列表(ziplist)
    • 大数据量跳表(skiplist),O(logN) 查询。
  • 应用场景
    • 排行榜、延时队列、任务调度。

6. HyperLogLog(基数统计)

  • 类型:基于概率的去重计数,误差 0.81%。
  • 底层实现
    • 基于 稀疏矩阵 + 线性计数 进行基数估计,节省存储(12KB 统计亿级数据)。
  • 应用场景
    • 统计 UV、去重 ID。

7. Bitmaps(位图)

  • 类型:二进制位存储数据,可按位计算。
  • 底层实现
    • 直接基于 String 存储二进制位,1 字节存 8 个状态位。
  • 应用场景
    • 签到、活跃用户、布隆过滤器(结合 Bitmaps + 哈希)。

8. Geospatial(地理位置)

  • 类型:存储经纬度,可计算两点间距离和范围查询。
  • 底层实现
    • ZSet(跳表)+ GeoHash 编码 实现存储与查询。
  • 应用场景
    • LBS(最近商家、附近好友)。

HyperLogLog(基数统计)完善与补充

1. 什么是基数?

  • 基数(Cardinality) 是指集合中 不重复元素 的个数。
  • 例如:
    • A = {1, 2, 3, 4, 5},B = {3, 5, 6, 7, 9}
    • 基数 = 1, 2, 4, 6, 7, 9(去重后共有 6 个元素)

2. HyperLogLog 解决了什么问题?

  • 适用于 海量数据 的去重计数问题,例如:
    • 注册 IP 数
    • 每日访问 IP 数(UV 统计)
    • 在线用户数
    • 共同好友数
  • 核心特点
    • 计算近似 不重复元素个数,允许误差(标准误差 0.81%)。
    • 占用内存固定,仅需 12KB 即可统计 亿级数据
    • 适合 高并发、大规模数据 场景,不适合 小数据量高精度统计

3. HyperLogLog 工作原理

HyperLogLog 使用 概率算法 进行基数估算,主要原理如下:

  1. 哈希映射:将元素哈希为二进制值,例如 hash(“A”) = 110100…
  2. 前导零统计:找出哈希值二进制表示中最左侧的连续零的个数 k
  3. 存储最大 k:多个哈希桶存储不同元素的最大 k 值。
  4. 估算基数:基于 哈希桶的均值 + 统计学公式 计算基数。

HyperLogLog 牺牲精度换取超高的存储效率,是大数据统计中的重要技术之一。


4. HyperLogLog vs 其他计数方式

方法是否去重是否精确空间占用适用场景
HashSet✅ 是✅ 是O(n)小规模数据
Bitmap✅ 是✅ 是O(n/8)固定范围数据(如用户ID)
HyperLogLog✅ 是❌ 近似O(12KB)超大规模数据

5. HyperLogLog 的应用场景

  1. 统计 UV(独立访问用户数)
    • 适用于海量访问网站,如统计日活 DAU(Daily Active Users)。
  2. 日志分析
    • 统计不重复 IP、独立用户 ID 等数据。
  3. 搜索引擎
    • 统计搜索关键词总数(去重统计)。
  4. 社交平台
    • 计算共同好友数、活跃用户数。
  5. 广告反作弊
    • 统计唯一点击广告用户数,防止刷量。

6. HyperLogLog 的局限性

  • 不能获取去重后的具体元素,仅统计基数。
  • 误差 0.81%,不适合对精度要求极高的场景。
  • 仅支持集合合并(PFMERGE),不能删除单个元素。

总结

HyperLogLog 是 Redis 提供的 高效基数统计工具,用于近似去重计数,适用于大数据量对精度要求不高的场景。它的固定存储占用(12KB)使其在高并发、海量数据环境中表现优异。

Bitmap(位图)完善与补充

1. 什么是 Bitmap?

  • Bitmap(位图)是一种使用 二进制位(0 或 1)表示集合中元素的方法。
  • 每个元素只占用 1 bit,相比 HashSet 或 List,大幅减少存储空间。

2. 位图的存储方式

  • Bitmap 基于 Redis String 类型,但按 位(bit) 操作。
  • 每个 bit 仅存储 01,可用于表示二元状态(如在线/离线)。
  • 1 字节(Byte)= 8 个 bit,因此:
    • 1000 个用户的状态仅需 1000 / 8 = 125 字节

3. Bitmap 的应用场景

  1. 用户状态存储
    • 1 代表 在线0 代表 离线
    • 应用示例
      • 统计 7 天内活跃用户(每日 1 bit)。
      • 标记 用户是否完成某个任务
  2. 签到系统(连续签到统计)
    • 每天 1 bit,1 代表签到,0 代表未签到。
    • 可以快速计算 连续签到天数缺勤次数
  3. 访问记录(UV 统计)
    • 统计 某天是否访问过某个页面(如 1 代表访问过)。
  4. 布隆过滤器(Bloom Filter)
    • 位图可用于 快速判断元素是否存在,提高查询效率。

4. Bitmap vs 其他数据结构

数据结构占用空间适用场景
HashSet适用于小规模数据
BitSetJVM 内存中的位存储
Bitmap适用于海量二元状态存储

5. Bitmap 的优缺点

优点

  • 节省空间:每个 bit 仅占 1/8 字节,适用于海量数据存储。
  • 支持快速查询:位操作时间复杂度 O(1)。
  • 适用于二元状态:签到、活跃、访问记录等。

缺点

  • 不支持直接删除元素(只能手动清 0)。
  • 仅适用于固定范围的数据(如 用户ID 连续时)。
  • 无法存储非 0/1 状态(如评分、积分等需用更复杂结构)。

总结

Bitmap 适用于 存储二元状态的海量数据(如活跃用户、签到、访问记录等),极大节省存储空间,在高效数据分析和统计场景中广泛应用。

Geospatial(地理位置)完善与补充

1. 什么是 Geospatial(地理位置)?

  • Redis 3.2 版本引入,用于存储地理位置(经纬度)并执行地理计算。
  • 核心功能
    • 存储 经纬度坐标(longitude, latitude)。
    • 计算 两点之间的距离
    • 进行 范围查询(如查找附近的人或店铺)。

2. Geospatial 底层实现

  • 数据结构ZSet(跳表)+ GeoHash 编码
    • GeoHash经纬度 编码成 一个字符串,再存入 Sorted Set
    • score 代表 地理位置的 GeoHash 值,可以快速范围查找
    • ZSet 的特性使得 查询附近位置的效率极高(O(logN))

3. Geospatial 的应用场景

1. 查找附近的人(LBS 位置服务)

  • 共享单车、外卖、打车软件等应用。
  • 例如:查找 3 公里内的骑手5 公里内的餐厅

2. 物流 & 路线规划

  • 计算 两个城市之间的距离,评估 最优路线

3. 线下门店推荐

  • 例如:查找 10 公里范围内的加油站商店电影院

4. 位置打卡 & 地理围栏

  • 例如:用户进入某区域(商场、景点)后触发特定行为。

4. Geospatial 主要命令(示例)

🔹 添加地理位置(GEOADD)

GEOADD china:city 121.4737 31.2304 "shanghai" 120.5853 31.2989 "suzhou"

存储多个城市的经纬度

🔹 计算两地之间的距离(GEODIST)

GEODIST china:city suzhou shanghai km

✅ 计算苏州与上海之间的距离,单位 km(可用 m、mi、ft)。

🔹 查找某范围内的地点(GEORADIUS – 已废弃,使用 GEOSEARCH)

GEORADIUS china:city 121.49295 31.22337 30 km

✅ 查找 以(121.49295, 31.22337)为中心,半径 30 km 内的地点
Redis 6.2 之后推荐使用 GEOSEARCH,支持更丰富的查询方式。

🔹 返回某个地点的经纬度(GEOPOS)

GEOPOS china:city shanghai

✅ 获取 上海的经纬度(可用于地图展示)。

🔹 返回 GeoHash 编码(GEOHASH)

GEOHASH china:city shanghai

✅ 获取 上海的 GeoHash 编码,用于快速查询相邻区域。


5. Geospatial 优缺点

优点

  • 查询效率高:基于 ZSet + GeoHash,查询复杂度 O(logN)。
  • 存储紧凑:相比传统数据库地理计算,Redis 占用更少。
  • 支持多个单位(m/km/mi/ft),适用于不同需求。

缺点

  • 不支持路线规划(仅计算两点直线距离,无法考虑道路)。
  • GeoHash 存储精度有限(误差随 GeoHash 长度变化)。

总结

Redis Geospatial 适用于 LBS(基于位置的服务),可实现 附近查找、距离计算、位置存储
基于 ZSet + GeoHash,查询 高效、存储紧凑,适用于 共享单车、外卖、地图导航 等应用。

Redis 缓存穿透

缓存穿透 指的是:用户查询数据库中不存在的数据,缓存也不会存储这些无效查询,导致每次查询都需要访问数据库,进而造成 数据库压力过大

解决方案

方案一:缓存空值(NULL 缓存)

原理:

  • 如果数据库查询结果为空(null),仍然将 null 结果存入 Redis,并设置短期过期时间(如 5~60 分钟)。
  • 下次查询时,直接返回 null,避免访问数据库。

优点:
✅ 实现简单,无需额外组件。
✅ 适用于业务中查询 key 较为固定的场景。

缺点:
占用额外缓存,但可以通过短期过期时间控制内存开销。
❌ 可能引发缓存与数据库不一致,可通过数据库更新时删除缓存来缓解。


方案二:布隆过滤器(Bloom Filter)

原理:

  • 预加载数据库中的 key布隆过滤器(Bloom Filter)。
  • 查询时,先在布隆过滤器中判断 key 是否可能存在
    • 不存在 → 直接返回,避免查询数据库。
    • 可能存在 → 继续查询 Redis 或数据库。

优点:
占用内存极少,适用于大规模数据过滤。
无需存储无效数据,不会污染 Redis。

缺点:
❌ 存在 误判(可能会认为某个不存在的 key 存在)。
删除困难(传统布隆过滤器无法移除元素,但可用计数布隆过滤器(Counting Bloom Filter)解决)。


方案三:限流与黑名单

原理:

  • 对恶意 IP 进行封禁,防止爬虫或攻击请求大量无效 key。
  • 限流策略(Rate Limiting)
    • 固定窗口限流(如 10s 内最多允许 10 次请求)。
    • 令牌桶算法(控制请求速率,防止短时间内突发流量)。

优点:
✅ 可减少恶意流量对数据库的压力。
✅ 适用于爬虫拦截、恶意用户封禁等场景。

缺点:
❌ 需要额外开发限流逻辑,不适用于业务侧的正常查询。


方案四:数据预加载(Warm Up)

原理:

  • 将热门 key 预加载到 Redis,避免初次查询缓存未命中。
  • 数据库新增数据时,主动更新 Redis

优点:
适用于热点数据,减少首次查询的数据库压力。
可与缓存淘汰策略(LFU/LRU)结合,提高命中率。

缺点:
❌ 只适用于 热点数据,对随机查询作用不大。


3. 方案对比

方案优点缺点适用场景
缓存空值实现简单,快速响应占用内存,可能数据不一致业务 key 数量有限,更新频率低
布隆过滤器节省内存,无需存储无效 key误判问题,删除困难适用于大规模数据
限流+黑名单可减少恶意流量无法根本解决缓存穿透适用于防爬虫、恶意攻击
数据预加载提高缓存命中率仅适用于热点数据适用于固定范围的查询

4. 总结

  • 对于少量无效查询,可以 缓存空值 解决。
  • 对于 海量随机查询,建议使用 布隆过滤器 过滤掉不存在的 key。
  • 对于恶意攻击,可以 限流+黑名单 机制拦截。
  • 对于热点数据,可以 预加载,提升缓存命中率。

通常,综合使用多种方案 能够更好地防止 Redis 缓存穿透 问题。

Redis 缓存击穿

1. 什么是缓存击穿?

缓存击穿指的是:一个热点 key 过期的瞬间,大量并发请求同时查询该 key,导致请求全部打到数据库,瞬间压垮数据库

2. 解决方案

方案一:互斥锁(setnx)

原理:

  1. 当多个线程同时查询 Redis,发现 key 过期时,只有一个线程可以获取互斥锁,其他线程等待。
  2. 线程 1 查询数据库,并将数据写回 Redis。
  3. 其他线程稍后重新查询缓存,避免大量请求同时访问数据库。

优缺点:
优点

  • 无额外的内存消耗,保证数据一致性。
  • 逻辑清晰,实现简单。

缺点

  • 线程等待锁,影响性能。
  • 需要注意 死锁 问题(可用 setnx + expire 机制避免)。

方案二:逻辑过期(Lazy Rebuild)

原理:

  1. 不让 Redis 的 key 物理过期,而是为 key 添加逻辑过期时间
  2. 每次查询 Redis 时,若发现逻辑时间已过期,则由后台线程异步更新缓存,但仍然返回旧数据。

实现方式:在SpringBoot框架里自然可以配置

优缺点:
优点

  • 请求无阻塞,查询即返回旧值,不影响用户体验。
  • 减少数据库压力,由后台异步更新数据。

缺点

  • 数据可能不一致(查询可能返回旧数据)。
  • 额外的 Redis 内存消耗(存储过期时间信息)。
  • 实现复杂度较高,需要后台线程维护更新任务。

3. 方案对比

方案优点缺点适用场景
互斥锁(setnx)确保一致性,简单易懂可能阻塞请求,影响性能适用于数据一致性要求高的场景
逻辑过期(Lazy Rebuild)低延迟,不阻塞可能返回旧数据,实现复杂适用于热点数据、更新频率不高的场景

4. 总结

  • 互斥锁 适用于数据一致性要求高的业务(如订单、支付等)。
  • 逻辑过期 适用于热点数据,可降低数据库压力,提高查询性能。
  • 两者可以结合:热点数据使用逻辑过期,非热点数据使用互斥锁

Redis 缓存雪崩

1. 什么是缓存雪崩?

缓存雪崩指的是:在同一时刻,大量缓存 key 同时失效,或者 Redis 整体宕机,导致数据库瞬间承受巨量请求,可能直接崩溃

常见触发场景:

  • 大量 key 设置了相同 TTL,导致它们同时过期。
  • Redis 宕机,所有缓存数据瞬间失效。
  • 流量突增,导致缓存未命中,请求全打到数据库。

2. 解决方案

方案一:为 key 设置不同 TTL,避免同时失效

原理:

  • 给 key 设定随机过期时间,避免大量 key 同时过期。

实现:

int expireTime = 60 + new Random().nextInt(30); // 60~90 秒随机过期
redis.setex("key", expireTime, value);

优点:实现简单,有效缓解缓存雪崩。
缺点:适用于缓存过期导致的雪崩,但无法解决 Redis 宕机的问题。


方案二:使用 Redis 集群,分散请求压力

原理:

  • 采用 主从架构分片集群,让多个 Redis 实例共同分担压力,避免单点故障。

实现方式:

  • 主从架构(Replication): 读写分离,Redis 只读实例承载查询请求。
  • 分片集群(Cluster): 把数据分布到多个 Redis 节点,减少单个节点压力。

优点:保证 Redis 高可用,即使部分节点故障,也能继续提供服务。
缺点:增加系统复杂性,需要额外运维成本。


方案三:添加熔断、限流、降级机制

原理:

  • 通过熔断、限流、降级等手段,防止数据库被击穿。

实现方式:

  1. 限流(如 Redis + Guava RateLimiter)
    • 限制高并发请求,防止短时间内请求暴增。
  2. 熔断(如 Sentinel、Hystrix)
    • 当数据库压力过大时,直接返回默认数据,避免拖垮系统。
  3. 降级
    • 若 Redis 和数据库都不可用,则返回默认数据或错误提示。

优点:防止数据库直接崩溃,保证系统可用性。
缺点:部分用户可能获取不到最新数据,体验受影响。


方案四:使用多级缓存(L1 + L2)

原理:

  • L1(本地缓存,Caffeine/Guava) + L2(Redis 缓存),分层存储数据,减少 Redis 依赖。

实现方式:

  1. 本地缓存(Caffeine/Guava):存储热点数据,降低 Redis 压力。
  2. Redis 作为二级缓存,存储大部分数据。
  3. Redis 失效时,本地缓存仍可提供部分数据。

优点:提升访问速度,降低对 Redis 依赖,减少雪崩影响。
缺点:本地缓存占用应用服务器内存,适用于热点数据


3. 方案对比

方案优点缺点适用场景
不同 TTL实现简单,避免缓存同时失效不能解决 Redis 宕机问题大量 key 过期时
Redis 集群保证高可用,减少单点故障复杂度高,增加运维成本大规模分布式缓存
熔断 + 限流 + 降级防止数据库被拖垮可能影响部分用户体验高并发场景
多级缓存(L1 + L2)降低 Redis 依赖,提高性能额外占用服务器内存热点数据

4. 总结

  • 如果是大量 key 同时过期引发雪崩TTL 随机化
  • 如果 Redis 整体宕机采用 Redis 集群
  • 如果流量过大导致雪崩熔断 + 限流 + 降级
  • 如果需要进一步优化采用多级缓存

项目中为什么要用 Redis?


1. 高性能(读写快)

  • 数据存储在内存,读取速度远远快于 磁盘数据库(如 MySQL)
  • 示例:
    • 第一次访问数据:从 数据库 读取(慢)。
    • 后续访问:直接从 Redis 缓存 获取(快)。
    • 数据库更新 时,同步修改缓存,保证一致性。

对比数据库查询

存储方式访问速度
MySQL(磁盘)毫秒级(ms)
Redis(内存)微秒级(μs),比数据库快 100 倍

2. 高并发(减轻数据库压力)

  • 直接访问缓存,避免 数据库高并发查询,提升系统吞吐量。
  • 应用场景
    • 热点数据(如商品详情页、热门文章)。
    • 排行榜(访问量大的数据不必每次查询数据库)。

3. 支持多种数据结构(灵活应用)

  • String:用户信息、计数器。
  • Hash:存储对象(如用户信息:id、name、email)。
  • List:消息队列、社交动态(微博、朋友圈)。
  • Set:标签、推荐系统(去重)。
  • Sorted Set:排行榜、积分系统。

4. 分布式锁(保证并发安全)

  • 使用 SET NX EX 实现分布式锁,防止多个线程同时修改同一资源。
  • 应用场景
    • 秒杀系统(防止超卖)。
    • 任务调度(防止任务重复执行)。

5. 过期策略(自动清理数据)

  • 支持 TTL(Time to Live),可自动删除过期数据。
  • 应用场景
    • 限时优惠券(10 分钟后自动失效)。
    • 用户登录状态(Session 过期)。

6. 消息队列(提高系统解耦能力)

  • Redis Pub/SubList 可用作 消息队列,用于异步处理任务。
  • 应用场景
    • 订单处理:用户下单后,异步通知库存系统。
    • 日志收集:大数据分析系统。

总结

高性能(内存读写,速度快)
高并发(缓解数据库压力)
数据结构丰富(适配不同场景)
分布式锁(保证并发安全)
消息队列(提升系统解耦)

Redis 适用于 缓存、分布式锁、排行榜、队列、限流 等高性能场景,在项目中发挥重要作用!

Redis 为什么快?

(1)基于内存(无磁盘 I/O 开销)

  • Redis 直接 在内存中读写,无需像 MySQL 进行磁盘读写,速度快 100 倍+

(2)单线程,避免 CPU 线程切换开销

  • 无锁争抢,所有请求按 FIFO(先进先出) 执行,避免 死锁

(3)I/O 多路复用(非阻塞高效处理请求)

  • epoll + 事件驱动,Redis 单线程也能处理大量连接,高效利用 CPU。

(4)优化的数据结构

  • 跳表(Sorted Set)、哈希表(Hash)、压缩列表(List)、整数集合(Set),内存占用低,访问速度快。

(5)Pipeline 机制(减少 RTT 开销)

  • 支持批量命令执行,减少 TCP 往返延迟(RTT),提高吞吐量。

(6)高效的持久化机制(AOF + RDB)

  • AOF 采用增量日志,防止数据丢失。
  • RDB 定期快照,减轻内存负担,恢复速度快。

总结

🔹 Redis 为什么快:纯内存操作、单线程无锁、I/O 多路复用、优化数据结构、Pipeline 批处理、高效持久化。

阻塞 I/O(Blocking I/O) vs 非阻塞 I/O(Non-blocking I/O) vs 异步 I/O(Asynchronous I/O)


在计算机网络和操作系统中,I/O(输入输出)是非常关键的概念。不同的 I/O 模型会影响系统的并发性能和资源使用效率。下面我们详细对比 阻塞 I/O非阻塞 I/O异步 I/O 三种模式的区别和特点。


1. 阻塞 I/O(Blocking I/O)

定义

  • 阻塞 I/O 模型中,当一个线程发起 I/O 操作(如读取文件或网络数据)时,它会 阻塞当前线程,直到操作完成后才返回。也就是说,线程在发起 I/O 操作时会 等待,直到操作完成(例如,读取完数据或接收到网络响应)。

特点

  • 阻塞等待:当线程发起 I/O 操作时,必须等待操作完成才能继续执行后续代码。线程被阻塞在 I/O 操作上。
  • 简单易懂:编程模型简单,线程直接发起 I/O 操作并等待结果返回,程序流程自然。
  • 资源消耗:每个 I/O 操作都占用一个线程,并且线程处于阻塞状态,不能做其他任务。对于大量 I/O 请求时,系统性能容易受到影响,线程池可能迅速耗尽。

适用场景

  • 低并发、低负载:对于 I/O 请求较少的场景,阻塞 I/O 足够使用。
  • 简单应用:如果系统负载不高且不需要处理高并发请求,使用阻塞 I/O 足够。

缺点

  • 资源浪费:线程在等待 I/O 完成时无法进行其他任务,浪费 CPU 资源。
  • 高并发性能差:当并发请求增加时,阻塞 I/O 导致线程过多,从而增加上下文切换和资源消耗,性能下降。

示意

线程 -> 发起 I/O 请求 -> 等待 I/O 完成 -> 继续执行后续代码

2. 非阻塞 I/O(Non-blocking I/O)

定义

  • 非阻塞 I/O 模型指的是,当线程发起 I/O 操作时,如果数据 没有准备好(例如文件或网络数据还没有到达),线程不会被阻塞,立即返回一个错误或者状态指示,线程可以继续执行其他任务。当数据准备好时,线程可以重新检查或再次发起 I/O 操作。

特点

  • 立即返回:线程发起 I/O 操作后,如果操作不能立即完成(如数据不可用),线程会返回一个状态信息(通常是 EAGAIN 错误),表示无法完成当前操作。
  • 不阻塞线程:线程不会被阻塞,而是返回继续执行其他任务,直到数据可用。
  • 轮询或事件驱动:线程通常需要使用轮询(不断检查是否有数据)或者通过事件驱动来管理 I/O 操作的完成。

适用场景

  • 高并发、低延迟要求:非阻塞 I/O 适合处理大量并发请求(如 Web 服务器、游戏服务器),能避免线程因为等待 I/O 操作而被阻塞。

缺点

  • 复杂性高:程序需要不断检查 I/O 操作的状态,编程模型复杂,代码通常需要使用多线程或事件驱动模型(如 epoll、select)来处理。
  • CPU 消耗:如果程序使用轮询来检查 I/O 状态(如不断查询数据是否准备好),可能会浪费 CPU 时间。

示意

线程 -> 发起 I/O 请求 -> 数据未准备好 -> 返回(不阻塞)-> 继续执行其他代码 -> 再次发起请求,直到数据准备好

3. 异步 I/O(Asynchronous I/O)

定义

  • 异步 I/O 模型中,线程发起 I/O 操作后,立即返回,不等待操作完成,而是将 I/O 操作交给操作系统或内核去处理。等到 I/O 操作完成后,操作系统会通知线程或回调函数,以便线程可以处理结果。这意味着线程 不需要等待 操作完成,可以执行其他任务。

特点

  • 操作系统处理:I/O 操作是由操作系统或内核线程处理的,线程并不需要等待操作完成,可以继续处理其他任务。
  • 回调机制:线程发起 I/O 操作后,操作系统会在 I/O 完成时通知线程,通常通过 回调函数事件通知 来触发后续操作。
  • 极低的资源消耗:系统通过异步处理 I/O,可以显著减少对线程的需求,避免了阻塞和轮询操作,节省了大量资源。

适用场景

  • 极高并发、高负载:异步 I/O 非常适合高并发、高性能的系统,如大型 Web 服务、数据流处理等场景。

缺点

  • 编程复杂度高:编写异步 I/O 程序通常比阻塞或非阻塞 I/O 更复杂,需要理解回调、事件通知等机制。错误处理和状态管理也更加繁琐。
  • 依赖操作系统:实现异步 I/O 通常依赖操作系统提供的异步 I/O 支持,这在不同平台之间可能存在差异。

示意

线程 -> 发起 I/O 请求 -> 立即返回 -> 继续执行其他代码 -> 操作系统处理 I/O -> I/O 完成后触发回调函数

4. 阻塞 I/O、非阻塞 I/O、异步 I/O 的比较

特性阻塞 I/O非阻塞 I/O异步 I/O
线程行为线程等待 I/O 完成线程立即返回,继续执行线程立即返回,I/O 操作由内核处理
程序模型简单,线性执行复杂,需要轮询或事件驱动复杂,依赖回调或事件通知
CPU 使用I/O 操作期间阻塞,浪费 CPUI/O 操作期间不阻塞,但可能会轮询,浪费 CPU无需轮询,由内核管理,CPU 使用高效
适用场景简单应用、低并发高并发、实时系统高并发、大量请求的高效系统
性能对于低并发较好,但高并发性能差性能高,但编程复杂极高性能,能处理海量并发
编程复杂度高,需要轮询或事件驱动高,需要回调或事件通知

总结

  • 阻塞 I/O:线程在 I/O 操作上阻塞,适合简单的应用或低并发场景。
  • 非阻塞 I/O:线程立即返回,适合高并发场景,但编程复杂度较高,需要使用轮询或事件驱动模型。
  • 异步 I/O:线程无需等待 I/O 操作完成,由操作系统处理,适合处理大量并发请求的高效系统,但编程复杂度较高。

每种 I/O 模型都有其适用的场景,选择合适的 I/O 模型可以根据系统的需求来优化性能和资源利用。

Redis 的持久化方式:RDB 和 AOF

Redis 提供了两种持久化机制来保证数据在断电、重启或其他故障后能够恢复,分别是 RDB(Redis DataBase)AOF(Append Only File)。它们的原理、优势和适用场景有所不同,可以根据不同的需求选择合适的持久化方式,甚至可以同时启用。


1. RDB(Redis DataBase)持久化

原理

  • RDB 持久化的核心思想是 定期生成数据快照。它会在设定的时间间隔内,将 Redis 内存中的所有数据保存到磁盘中,生成一个 RDB 文件(通常命名为 dump.rdb)。
  • 当 Redis 重启时,它会加载 RDB 文件中的数据来恢复内存中的状态。

特点

  • 快照式持久化:RDB 会在指定时间间隔内,按照某个条件(如一定数量的写操作或指定的时间)将数据存储为一个快照。
  • 性能较好:由于 RDB 是在后台进行快照操作的,它对 Redis 主进程的性能影响较小,适合处理大规模的 Redis 实例。
  • 数据丢失风险:RDB 生成的快照是一个时间点的快照,如果 Redis 在上一次快照后崩溃,可能会丢失这期间的数据(具体丢失量取决于配置的保存频率)。

优点

  • 更快的恢复速度:RDB 文件通常比较小,而且 Redis 重启时从 RDB 文件加载数据的速度较快。
  • 节省资源:RDB 持久化不需要每个写操作都记录,而是定期进行快照,节省了 I/O 资源。
  • 适合备份:RDB 文件可以用作备份,便于在生产环境中进行定期的数据备份。

缺点

  • 数据丢失:如果 Redis 崩溃且 RDB 没有生成快照或快照未完成,可能会丢失大量数据,尤其是在高并发场景下。
  • 不适合实时性要求高的应用:由于 RDB 生成快照有一定的延迟,因此不适用于对数据持久化要求非常实时的应用场景。

RDB 配置

通过配置文件(redis.conf)中的 save 参数来设置快照的生成条件。例如:

save 900 1   # 900秒内至少有 1 次写操作,则生成快照
save 300 10  # 300秒内至少有 10 次写操作,则生成快照
save 60 10000 # 60秒内至少有 10000 次写操作,则生成快照

2. AOF(Append Only File)持久化

原理

  • AOF 持久化的核心思想是将 Redis 执行的 所有写命令追加到 AOF 文件(通常命名为 appendonly.aof)中。每次 Redis 执行写操作时,会将该操作记录到 AOF 文件中。
  • Redis 重启时,会重新执行 AOF 文件中的所有命令,从而恢复数据。

特点

  • 日志式持久化:AOF 会记录 Redis 执行的所有写命令,并且这些命令会按顺序追加到文件末尾。
  • 较慢的性能:由于每个写操作都需要记录到 AOF 文件,因此对性能有一定的影响,尤其是在高频写入的场景中。
  • 数据安全性更高:AOF 可以确保数据的持久化完整性,理论上不易丢失数据,特别是在通过设置合适的写入策略时。

优点

  • 高数据安全性:AOF 文件可以记录所有写操作,可以确保数据恢复的完整性,适用于要求较高的数据一致性。
  • 更精确的持久化:AOF 持久化方式记录的是每个写操作,因此可以非常精确地恢复 Redis 数据。
  • 支持重写机制:随着 AOF 文件不断增长,Redis 提供了 AOF 重写功能,能够定期压缩 AOF 文件,减小文件体积。

缺点

  • 性能开销:每次写操作都会被记录到 AOF 文件中,这会影响 Redis 的性能,尤其是在高写入负载的环境下。
  • AOF 文件大小:随着时间推移,AOF 文件会逐渐增大,可能需要通过重写机制定期清理。
  • 恢复速度较慢:相比 RDB 文件,AOF 恢复数据的速度较慢,因为需要依次执行文件中的所有命令。

AOF 配置

可以通过 appendonly 参数启用 AOF 持久化:

appendonly yes   # 启用 AOF 持久化
appendfsync everysec  # 每秒同步一次

appendfsync 配置有三种选择:

  • always:每个写操作都同步到 AOF 文件(性能最差,但数据最安全)。
  • everysec:每秒同步一次 AOF 文件(常用配置,性能与安全平衡)。
  • no:不进行同步,由操作系统控制(性能最好,但数据安全性最低)。

3. RDB 和 AOF 的组合使用

Redis 允许同时启用 RDB 和 AOF,这种组合使用方式可以充分利用两种持久化方式的优点。

  • 数据恢复优先使用 AOF:在 Redis 重启时,会优先使用 AOF 文件来恢复数据,因为 AOF 文件能够保证更高的恢复精度。
  • RDB 快照加速启动:如果同时启用 RDB,Redis 在启动时会先加载 RDB 快照,从而加速启动过程,减少恢复时间。
  • AOF 文件的重写:为了避免 AOF 文件过大,Redis 提供了 AOF 文件的重写机制,将旧的 AOF 文件压缩成一个新的文件,从而减少文件大小和 I/O 开销。

4. RDB 与 AOF 的对比

特性RDBAOF
持久化方式快照式持久化日志式持久化
性能性能较好,适合高并发应用相对较慢,尤其是在写操作频繁时
数据安全性相对较低,存在数据丢失的风险较高,记录所有写操作,数据恢复更精确
恢复速度快速恢复较慢,恢复时需要执行所有写命令
文件大小文件较小,适合备份文件随着写操作增大,需要定期重写
适用场景高性能要求的应用,较少数据丢失容忍的场景对数据持久化要求严格的应用,如银行等

总结

组合使用:可以同时启用 RDB 和 AOF,在性能和数据恢复之间找到合适的平衡。

RDB:适合用在对性能要求较高的场景,如缓存系统,数据丢失可以接受且恢复速度要求快的场景。

AOF:适合用在对数据完整性要求高的场景,如需要保证每个写操作都被持久化的应用,但会影响性能。

Redis 4.0 引入的 RDB-AOF 混合持久化模式

在 Redis 4.0 之前,持久化方式主要是 RDB(快照)AOF(日志)
RDB 适用于高效存储和快速恢复,但可能会丢失部分数据。
AOF 能提供更高的数据安全性,但写入和恢复速度较慢,尤其是在 AOF 文件过大时恢复变得更加耗时。

为了结合两者的优点,Redis 4.0 引入了 RDB-AOF 混合持久化模式(Hybrid Persistence)。这种模式结合了 RDB 和 AOF,使得持久化更加高效,同时兼顾数据安全性和恢复速度。


1. RDB-AOF 混合模式的原理

在混合模式下,Redis 生成 AOF 文件的方式有所不同:

  • 触发 AOF 重写(Rewrite)时,Redis 会先 生成一个 RDB 快照 并写入 AOF 文件。
  • 之后,所有新增的写操作 以 AOF 日志的方式追加 到这个 RDB 快照之后。

这样做的好处

  • 文件更小:相比传统的 AOF 文件,RDB-AOF 文件的大小大大减少,避免了 AOF 过大带来的问题。
  • 恢复更快:在 Redis 重新启动时,先加载 RDB 快照(速度快),然后再回放 AOF 日志(数据更完整)。
  • 写入效率更高:RDB 作为起始状态,减少了 AOF 需要存储的命令数量,从而减少了 I/O 开销,提高了性能。

2. 具体流程

  1. Redis 触发 AOF 重写(bgrewriteaof)
    • 传统 AOF 方式是遍历现有数据,并重新生成更精简的 AOF 日志文件。
    • 混合模式下,Redis 先生成一个新的 RDB 快照,并将其写入 AOF 文件的头部。
  2. 持续追加 AOF 命令
    • RDB 快照写入后,Redis 开始以 AOF 方式记录新的写命令,并追加到该文件后面。
  3. Redis 重启
    • 重新启动 Redis 时,优先从 RDB 部分加载数据(相比传统 AOF,速度更快)。
    • 然后回放 AOF 命令,确保数据完整性。

3. RDB-AOF 持久化模式的优势

特性传统 AOF(仅日志)RDB(仅快照)RDB-AOF 混合模式
数据完整性高,能保证数据不丢失可能丢失最近修改的数据高,优先加载 RDB,再回放 AOF
写入性能较低,所有写操作都记录日志高,定期快照,开销较小适中,RDB 初始化,AOF 追加
恢复速度慢,需要回放所有命令快,只需加载快照快,加载快照后再回放少量日志
文件大小大,存储所有操作命令小,只存储数据快照适中,存储快照+少量日志
适用场景需要高可靠性,要求所有数据可恢复适用于对数据完整性要求不高的缓存场景平衡数据安全性和性能的场景

4. 如何启用 RDB-AOF 混合模式?

要启用混合持久化,需要修改 redis.conf 配置文件:

appendonly yes   # 开启 AOF 持久化
aof-use-rdb-preamble yes  # 启用 RDB-AOF 混合模式
  • appendonly yes:开启 AOF 机制。
  • aof-use-rdb-preamble yes:开启 RDB-AOF 混合模式。

Redis 运行时,也可以通过 命令行 开启:

CONFIG SET appendonly yes
CONFIG SET aof-use-rdb-preamble yes

5. 适用场景

适用于

需要数据完整性(但不希望 AOF 影响性能的场景)。
数据恢复速度要求高(避免 AOF 过大导致恢复慢)。
需要兼顾性能和数据安全性的应用(如社交平台、金融系统、日志分析等)。

不适用于

对 AOF 日志全量存储有特殊需求的场景(如严格审计场景)。
极端高并发、对性能要求极致的应用(如纯缓存模式,直接使用 RDB 更高效)。


6. 总结

  • Redis 4.0 引入 RDB-AOF 混合持久化,结合了 RDB 的高效存储和 AOF 的高数据安全性。
  • 采用 RDB 作为 AOF 的起点,使得 AOF 文件更小,数据恢复更快,写入更高效。
  • 适用于 对数据一致性要求高,同时希望提高持久化性能 的场景。
  • 开启方式:在 redis.conf 里设置 aof-use-rdb-preamble yes

这一机制大大优化了 Redis 的持久化能力,使其更适用于 高并发、低延迟的业务场景

RDB-AOF 混合模式默认启用吗?

Redis 4.0 之后,RDB-AOF 混合持久化(Hybrid Persistence)被引入,允许 Redis 在 AOF 持久化模式下同时存储 RDB 快照和 AOF 日志,以提高数据恢复速度。

但是,默认情况下,该模式是未启用的,需要手动修改 Redis 配置文件(redis.conf)或者在运行时动态设置。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注