作者:没有四次元口袋的蓝胖
日期:2026-10-11
标签:Redis, 数据类型, 高并发
Redis知识梳理(1)
写在前面
Redis(REmote DIctionary Server)是目前使用最广泛的内存数据库 。在 Java 后端面试中,Redis 几乎是必考的中间件,出场率比 MySQL 还高。不管你是菜鸟还是资深,都会被问。
为什么 Redis 这么重要?因为现代互联网应用几乎都离不开它------接口性能优化、分布式锁、缓存、排行榜、限流、消息队列......处处都是 Redis 的身影。
一、Redis 是什么?
1.1 官方定义
Redis 是一个开源的、基于内存的、支持多种数据结构的 NoSQL 键值存储数据库。
拆解一下关键词:
| 关键词 | 含义 |
|---|---|
| 开源 | 免费,社区活跃 |
| 基于内存 | 数据默认存储在内存中,读写极快 |
| 多种数据结构 | 不只是 K-V,还支持 String/List/Hash/Set/ZSet 等 |
| NoSQL | 非关系型数据库,和传统关系型数据库(MySQL)不同 |
| 键值存储 | 通过 Key 存取 Value |
1.2 Redis 的历史
Redis 由意大利人 Salvatore Sanfilippo(网名 antirez)在 2009 年开发。最初只是他想解决自己公司网站的实时访问量统计问题,没想到后来成为全球最流行的内存数据库。
2015 年成立 Redis Labs(现名 Redis Inc.),提供商业化的 Redis Enterprise 版本。
1.3 Redis 能做什么?
| 应用场景 | 说明 |
|---|---|
| 缓存 | 最核心场景,减少数据库压力 |
| 分布式锁 | 高并发下保证任务唯一执行 |
| 排行榜 | ZSet 天然适合 |
| 计数器 | 点赞、访问量、库存 |
| 消息队列 | List 发布/订阅 |
| 限流 | 滑动窗口、令牌桶 |
| 会话存储 | 分布式 Session 共享 |
| 地理信息 | GEO 数据类型 |
二、为什么 Redis 快?(面试必问)
这是 Redis 面试第一题,必须背熟。 一般会问"Redis 为什么这么快?"
2.1 六大原因
1. 纯内存操作
这是最核心的原因。Redis 数据存储在内存中,读取内存速度是纳秒级别(~100ns),而磁盘 IO 是毫秒级别(~10ms),相差 10 万倍。
内存访问:约 50-100 ns
SSD 磁盘:约 100-200 μs
机械硬盘:约 5-10 ms
2. 单线程模型,避免上下文切换和锁竞争
Redis 使用单线程处理网络请求。这听起来像缺点,其实是优点:
- 没有线程切换开销
- 不需要加锁(天然线程安全)
- 不存在死锁、锁竞争问题
💡 注意:Redis 6.0 之后引入多线程来处理网络 IO (读socket、写socket),但命令执行仍然是单线程。也就是"网络IO多线程,命令执行单线程"。
3. I/O 多路复用
Redis 利用 Linux 的 epoll 机制,一个线程同时监控多个 socket 连接,哪个 socket 有数据就处理哪个,不用像传统 BIO 那样一个连接一个线程。
传统 BIO: 一个连接 → 一个线程(连接多了就耗尽资源)
Redis: 一个线程 → 监控所有连接(epoll 多路复用)
4. 高效的数据结构
Redis 每种数据类型底层都用了精心设计的数据结构(后面会细讲),比如:
- SDS(简单动态字符串)代替 C 字符串,避免缓冲区溢出
- 跳表(SkipList)实现有序集合,查找效率 O(log n)
- 压缩列表(ziplist)和快速列表(quicklist)节省内存
5. 自己实现了事件模型
Redis 自己实现了基于事件驱动的 Reactor 模型,所有命令在事件循环中串行处理,避免了多线程的复杂性。
6. 单次命令操作是原子性的
因为单线程,单个命令的执行是原子的,不会被打断。这也是 Redis 能实现分布式锁的基础之一。
2.2 为什么 Redis 6.0 要引入多线程?
单线程也有瓶颈------网络 IO 处理慢(要频繁读写 socket,涉及系统调用和上下文切换)。
Redis 6.0 的做法:
- 读取请求、写回响应用多线程(IO 线程池,默认关闭,可配置)
- 命令执行仍然是单线程
这样既享受了多线程处理 IO 的吞吐提升,又保持了命令执行的原子性和简单性。
🎯 面试高频:为什么 Redis 用单线程还这么快?
核心四句话:纯内存、单线程无锁开销、IO多路复用(epoll)、数据结构高效。
三、官方性能基准数据
Redis 官方给出的基准(单节点,2.6GHz CPU):
- 读性能:约 100,000 次/秒(QPS)
- 写性能:约 80,000 次/秒
- 延迟:P99 在 1ms 以内
这是单节点的超高性能,也是 Redis 能成为缓存之王的原因。
四、Redis 与数据库:多服务共享(为什么需要 Redis)
4.1 没有 Redis 的痛点
假设一个电商系统,只有 MySQL,没有缓存:
用户 A 请求商品详情 → 每次都查 MySQL ← 用户 B 也请求
用户 C 查看库存 → MySQL ← 用户 D 也查看
当用户量上来后:
- 数据库压力过大:所有请求直接打到 MySQL,连接耗尽、慢查询、卡死
- 响应慢:磁盘 IO 慢,每次都要查数据库
- 无法支撑高并发:MySQL 一般能支撑几千 QPS,但大促时可能几十万 QPS
4.2 Redis 作为"多服务共享缓存"
引入 Redis 后,多个服务共享同一份内存缓存:
┌─────────────┐
服务A ──────┐ │ Redis │
服务B ──────┼────▶│ (共享缓存) │
服务C ──────┘ │ 内存,超快 │
└──────┬──────┘
│ 未命中时才查
▼
MySQL
多服务共享的意义:
- 多个微服务共享一份缓存:如果两个服务都要读用户信息,不需要各查各的数据库,一起从 Redis 读,数据库只被弱访问
- 热点数据只加载一次:高并发下,热点数据只需从 MySQL 读一次进缓存,之后全部走内存
- 跨服务一致:A 服务更新了数据写 Redis,B 服务能立刻读到最新值
4.3 经典的 Cache-Aside 缓存模式
这是最常用的缓存读写模式(旁路缓存):
读流程(读缓存):
1. 请求来了,先查 Redis
2. 命中 → 直接返回(快)
3. 未命中 → 查 MySQL
4. 把结果写入 Redis
5. 返回给客户端
写流程(更新缓存):
1. 更新数据库 MySQL
2. 删除 Redis 中对应的缓存
(下次读取时再重建缓存)
⚠️ 面试常问:为什么不直接更新 Redis 缓存,而是删除?
因为更新缓存有竞态问题(稍后讲缓存一致性时细说)。业界更多采用"先更新数据库,再删除缓存"策略,配合延迟双删解决并发问题。
4.4 Redis vs MySQL 对比
| 对比维度 | Redis | MySQL |
|---|---|---|
| 存储介质 | 内存(默认) | 磁盘 |
| 读写速度 | 纳秒级 | 毫秒级 |
| 数据容量 | 受内存限制(通常几GB~几十GB) | 大(TB级别) |
| 数据结构 | 丰富(List/Hash/Set等) | 表、行、列(结构化) |
| 事务 | 弱事务(后续讲) | 强事务(ACID) |
| 持久化 | 支持(RDB/AOF) | 天然持久化 |
| 适用场景 | 缓存、实时数据、高并发 | 业务数据存储、复杂查询 |
结论:MySQL 存数据,Redis 提速度。 两者是协作关系,不是替代关系。
五、五种核心数据类型(概览)
Redis 之所以强大,不只是因为它快,更因为它的数据类型丰富。这是 Redis 区别于 Memcached(只能存字符串)的最大优势。
5.1 总览表
| 类型 | 英文名 | 底层结构 | 最大存储 | 典型场景 |
|---|---|---|---|---|
| String | 字符串 | SDS + int | 512MB | 缓存、计数器、分布式锁 |
| Hash | 哈希 | ziplist/hashtable | 2^32-1 字段 | 对象存储、购物车 |
| List | 列表 | quicklist | 2^32-1 元素 | 消息队列、最新列表 |
| Set | 集合 | intset/hashtable | 2^32-1 成员 | 去重、共同好友、抽奖 |
| ZSet | 有序集合 | ziplist/skiplist | 2^32-1 成员 | 排行榜、延时队列 |
5.2 每种类型一句话理解
String → 一个键对应一个字符串值(最简单、最常用)
Hash → 一个键对应一个"类似Java对象"的字段集合
List → 一个有序的字符串列表(可以从两头操作)
Set → 一个无序的不重复集合(天然去重)
ZSet → 一个有序集合,每个元素带一个分数,按分数排序
5.3 通俗类比
| Redis 类型 | 类比 Java | 类比生活 |
|---|---|---|
| String | String | 一条便利贴 |
| Hash | HashMap | 一张填写了多个字段的表格 |
| List | LinkedList | 一条排队购物的队伍 |
| Set | HashSet | 一个装了不同水果的篮子(不能重复) |
| ZSet | TreeSet + 分数 | 一个按分数排名的成绩单 |
5.4 如何选择?
要存简单值/计数/锁 → String
要存对象/多个字段 → Hash
要存有序列表(最新/队列) → List
要去重/集合运算 → Set
要排序/排行榜 → ZSet
六、五种数据类型底层结构简介
6.1 String 底层:SDS
Redis 没有直接用 C 语言的字符串,而是自己实现了一个 SDS(Simple Dynamic String,简单动态字符串):
c
struct sdshdr {
int len; // 已经使用的长度
int alloc; // 分配的总容量
char buf[]; // 实际存储字符串的字节数组
};
为什么不用 C 字符串?
| 问题 | C 字符串 | SDS |
|---|---|---|
| 获取长度 | O(n) 需遍历 | O(1) 直接读 len |
| 缓冲区溢出 | 会溢出 | 自动扩容 |
| 二进制安全 | 遇 \0 截断 |
按 len 存取,安全 |
| 修改字符串 | 需重新分配内存 | 预分配,减少内存分配次数 |
6.2 Hash 底层
小数据量 (元素少且值短)时用 ziplist(压缩列表) ,节省内存;
大数据量 时升级为 hashtable(哈希表)。
6.3 List 底层:quicklist
使用 quicklist(快速列表),本质是多个 ziplist 用双向链表串起来,兼顾了内存紧凑和两端操作高效。
6.4 Set 底层
全整数 且元素少时用 intset(整数集合) ;
否则用 hashtable(value 为 null 的哈希表,利用 key 去重)。
6.5 ZSet 底层
元素少 时用 ziplist ;
元素多 时用 skiplist(跳表)+ hashtable 组合:
- 跳表负责有序遍历和范围查询
- 哈希表负责 O(1) 查分数
💡 深入了解这些底层结构是加分项,后面讲到各类型时再细挖。先有个整体概念。
七、安装 Redis 快速上手
7.1 安装
bash
# Ubuntu / Debian
sudo apt install redis-server
# CentOS
sudo yum install redis
# Docker(推荐,干净)
docker run -d --name redis -p 6379:6379 redis:7
7.2 启动与连接
bash
# 启动服务
redis-server redis.conf
# 连接客户端
redis-cli
redis-cli -h 127.0.0.1 -p 6379
# 验证
127.0.0.1:6379> ping
PONG
7.3 常用全局命令
bash
# 查看所有 Key
KEYS *
# 判断 Key 是否存在
EXISTS key
# 设置过期时间(秒)
EXPIRE key 60
# 删除 Key
DEL key
# 查看数据类型
TYPE key
# 清空当前库
FLUSHDB
# 切换数据库(默认0-15)
SELECT 1
八、Redis 基础配置
conf
# redis.conf 常用配置
# 绑定地址(默认只能本机访问)
bind 0.0.0.0
# 端口
port 6379
# 设置密码
requirepass yourpassword
# 最大内存(达到后触发淘汰策略)
maxmemory 2gb
# 持久化相关(后续章节细讲)
save 900 1
appendonly yes
# 保护模式
protected-mode yes
思维导图速览
Redis 初识 + 五种数据类型
│
├── Redis 是什么
│ ├── 开源 / 内存 / NoSQL / 键值存储
│ └── 场景:缓存/锁/排行榜/计数/队列/限流
│
├── 为什么快(面试必问)
│ ├── 纯内存操作(纳秒级)
│ ├── 单线程无锁竞争
│ ├── IO 多路复用(epoll)
│ ├── 高效数据结构
│ ├── 事件驱动模型
│ └── 命令原子性
│ └── 注:6.0网络IO多线程,命令执行仍单线程
│
├── 多服务共享(缓存作用)
│ ├── 多个微服务共享一份缓存
│ ├── 热点数据只加载一次
│ ├── Cache-Aside 模式(缓存旁路)
│ └── Redis 存速度,MySQL 存数据
│
├── 五种数据类型
│ ├── String(SDS底层)
│ ├── Hash(ziplist/hashtable)
│ ├── List(quicklist)
│ ├── Set(intset/hashtable)
│ └── ZSet(ziplist/skiplist+hashtable)
│
└── 安装与快速上手
├── 安装(apt/yum/docker)
├── redis-cli 连接
├── 全局命令(KEYS/EXISTS/EXPIRE/DEL/TYPE)
└── 基础配置(bind/port/pass/maxmemory)
写在最后
本篇是 Redis 系列的地基。三个核心认知一定要牢牢建立:
- Redis 为什么快------纯内存 + 单线程 + IO多路复用,这四句话随时能被问到。
- Redis 解决什么问题------核心是"多服务共享 + 极速访问",给数据库减负。
- 五种数据类型------每个类型的语义和典型场景要清楚,后面每一篇都会围绕具体类型展开。