Redis 从入门到数据类型详解(上篇):为什么需要 Redis?NoSQL 认知与 String 实战
摘要:这是系列的上篇,解决"认知问题"。我们从 MySQL 的三大瓶颈讲起,理清 NoSQL 到底是什么、四大分类各自解决什么问题,然后认识 Redis 的核心特性与典型场景,最后落到最基础也最万能的 String 类型 和真正决定项目质量的 Key 层级设计。
适合人群:刚接触 Redis 的后端同学、准备面试的应届生、想把缓存用得再稳一点的老手。
本篇收益:看完你应该能回答前两个问题——为什么要用 Redis?这个 Key 该怎么起名字?
下篇预告:Hash / List / Set / SortedSet 四大类型逐个拆解、类型选型速查、Value 设计避坑、Spring Data Redis 落地代码与线上踩坑清单。
一、为什么要引入 Redis:从 MySQL 的瓶颈说起
刚开始学后端时,我们的世界很简单:所有数据都塞进 MySQL,一个 SELECT 走天下。业务量小的时候这套方案毫无问题,但只要用户量稍微起来一点,问题就会集中爆发。
第一个撞墙的是磁盘 IO。 MySQL 的数据最终落在磁盘上,即使有 Buffer Pool、索引和各种优化,一次稍复杂的查询依然是毫秒级起步;而一次内存访问是纳秒级,中间差了四五个数量级。
第二个是并发。 单机 MySQL 的 QPS 通常在几千量级,遇到秒杀这种瞬时高并发,连接池瞬间被打满,后面的请求全部排队超时。
第三个是数据模型。 建表要先定 Schema,加字段得改表;存个嵌套的 SKU 列表还得拆表 JOIN;字段类型长度都要提前规划死。
于是就出现了这样一条经典的演进路径:
用户请求 → Nginx → Tomcat → 直接查 MySQL
↓ 流量变大
用户请求 → Nginx → Tomcat → Redis 缓存 → 命中则返回
↓ 未命中
MySQL(回写 Redis)
Redis 在这里扮演的角色,本质上就是在数据库前面加一层内存,把读多写少的热点数据挡在内存里,让绝大多数请求根本不落到磁盘上。
但 Redis 的价值远不止"缓存"这一个字。它的能力边界其实要宽得多,这一点我们放到第三节展开。
二、认识 NoSQL
2.1 什么是 NoSQL
NoSQL 全称 Not Only SQL,注意不是 “No SQL”——它不是要干掉关系型数据库,而是说"除了 SQL 之外,你还有别的选择"。
传统关系型数据库(RDBMS)的四大特征:结构化(表结构固定)、关联性(外键、JOIN)、SQL 查询、ACID 事务。NoSQL 恰恰是在这四个维度上做了取舍:
| 维度 | 关系型数据库(RDBMS) | NoSQL |
|---|---|---|
| 数据模型 | 结构化,表 + 行 + 列,Schema 严格 | 非结构化 / 半结构化,Schema 灵活 |
| 数据关联 | 强关联,靠外键、JOIN 维护 | 弱关联,数据冗余存储,避免 JOIN |
| 查询方式 | SQL(声明式,通用) | 各不相同,多为 API / 类 SQL |
| 事务 | 强一致的 ACID | 弱化,BASE(基本可用、软状态、最终一致) |
| 扩展方式 | 垂直扩展为主(加 CPU、内存) | 水平扩展为主(加机器、分片) |
| 典型场景 | 订单、账务、强一致业务 | 缓存、日志、社交、海量数据 |
关键在于最后一行——选型不是谁替代谁,而是各干各的活。涉及钱的、必须强一致的,老老实实上 MySQL;海量、高频、能容忍最终一致的,交给 NoSQL。
2.2 NoSQL 的四大分类
面试里经常问,也确实是理解生态的好抓手:
① 键值型(Key-Value)
最朴素的模型,一个 Key 对应一个 Value,Value 可以是字符串、JSON、甚至二进制。
- 代表:Redis、Memcached、RocksDB
- 特点:极致简单,性能极高,通常放内存
- 场景:缓存、计数器、分布式锁、Session 共享
② 文档型(Document)
Value 是结构化的文档(通常是 JSON/BSON),可以直接按字段查询,Schema 灵活。
- 代表:MongoDB、CouchDB
- 特点:天然支持嵌套结构,适合结构不固定的数据
- 场景:商品详情、文章内容、日志、爬虫数据
③ 列式(Column-Family)
数据按列族组织,同一列的数据连续存储,压缩率高、聚合查询快。
- 代表:HBase、Cassandra、ClickHouse
- 特点:海量写入友好,适合宽表和大数据分析
- 场景:Hadoop 生态、时序数据、离线数仓
④ 图(Graph)
节点 + 边,专门为处理"关系的关系"而生。
- 代表:Neo4j、ArangoDB
- 特点:多跳关系查询性能远超 JOIN
- 场景:社交网络、推荐系统、知识图谱、风控反欺诈
一句话记忆法:Key-Value 查得快,文档型存得活,列式算得爽,图数据库连得深。
2.3 数据库的整体分类
把视角拉高一点,今天的数据存储大致分三代:
- SQL(关系型):MySQL、Oracle、PostgreSQL、SQL Server。强一致、事务完备。
- NoSQL(非关系型):上面那四类。牺牲一部分一致性换取扩展性和性能。
- NewSQL(新关系型):TiDB、OceanBase、CockroachDB。想同时要水平扩展和 ACID——“我全都要”。
实际工程里,一个稍大的系统往往是混合存储:MySQL 存核心交易,Redis 做缓存和计数,Elasticsearch 做搜索,HBase 存日志,MongoDB 存详情。这不是炫技,是各取所长。
三、Redis 到底是什么
3.1 一句话定义
Redis(REmote DIctionary Server)是一个开源的、基于内存的 Key-Value 数据库,支持多种数据结构,常被用作缓存、消息中间件和分布式协调组件。
拆开看几个关键词:
- 基于内存:数据主要放在内存里,读写在微秒级,这是它快的根本原因。
- Key-Value:最外层就是一个巨大的字典,通过 Key 找 Value。
- 多种数据结构:Value 不只是字符串,还能是 Hash、List、Set、SortedSet、Bitmap、HyperLogLog、GEO、Stream。这是 Redis 区别于 Memcached 的核心竞争力。
- 单线程执行命令:命令串行执行,天然没有并发安全问题,单条命令都是原子的。
3.2 核心特性
| 特性 | 说明 |
|---|---|
| 极高性能 | 单机 QPS 可达 10 万级,延迟稳定在亚毫秒 |
| 丰富的数据结构 | 五大基本类型 + 四种扩展类型,直接对应业务模型 |
| 原子性 | 单条命令原子执行,INCR、SETNX 等可放心用于并发场景 |
| 持久化 | RDB 快照 + AOF 日志,宕机后可恢复 |
| 高可用 | 主从复制、Sentinel 哨兵、Cluster 分片 |
| 过期与淘汰 | 支持 TTL,内存不足时按策略淘汰 |
| 发布订阅 / 事务 / Lua | 轻量的消息与原子脚本能力 |
3.3 为什么 Redis 这么快
这是高频面试题,四个角度回答:
- 纯内存操作:避免磁盘 IO,这是量级上的差距。
- 单线程 + 无锁:不用加锁,也就没有锁竞争和上下文切换开销;单线程反而避免了多线程的复杂度。
- IO 多路复用:基于
epoll,一个线程管理上万个连接,谁有数据就处理谁,不会阻塞等待。 - 高效的数据结构:SDS 动态字符串、listpack 紧凑编码、跳表,都是为性能量身定制的。
⚠️ 补充一个容易过时的点:Redis 6.0 引入了多线程 IO,但它多线程的只是"网络读写和协议解析",命令执行依然是单线程。所以"Redis 是单线程的"这句话在 6.0 之后要加限定条件,面试时说出来是加分项。
3.4 典型应用场景
Redis 的用途远比"缓存"两个字丰富:
① 缓存(Cache):最主流用法。商品详情、首页楼层、字典数据,读多写少,T+TTL 过期。
② 分布式锁:SET key value NX EX 30 一条命令搞定加锁和超时,配合 Lua 脚本保证解锁原子性。
③ 计数器 / 限流:INCR 天然原子,做文章阅读量、点赞数、接口访问频次限流;配合 EXPIRE 就是固定窗口限流器。
④ 排行榜:SortedSet 的看家本领,ZINCRBY 加分、ZREVRANGE 取 Top N,时间复杂度 O(log N)。
⑤ Session 共享:集群环境下把登录态放 Redis,任意节点都能校验。
⑥ 消息队列:List 做简单队列(LPUSH + BRPOP),Stream 做支持消费组的高级队列。
⑦ 社交与地理:Set 求交集做共同好友、共同关注;GEO 算距离、找附近的人。
⑧ 签到与 UV 统计:Bitmap 做签到打卡,HyperLogLog 做亿级 UV 去重(误差 0.81%,内存只要 12KB)。
3.5 快速装一个跑起来
方式一:Docker(最推荐)
docker run -d --name redis \
-p 6379:6379 \
-v /data/redis:/data \
redis:7.2 redis-server --appendonly yes
方式二:Linux 源码编译
wget https://download.redis.io/releases/redis-7.2.4.tar.gz
tar -zxvf redis-7.2.4.tar.gz && cd redis-7.2.4
make && make install
redis-server /path/to/redis.conf
连接测试
redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name redis
OK
127.0.0.1:6379> get name
"redis"
💡 Windows 上不建议用那些年久失修的 msi 安装包,直接用 Docker Desktop 或 WSL2 更省心。
四、Redis 的数据类型全景
4.1 五种基本类型与底层编码
先把总览图刻在脑子里:
| 类型 | 结构 | 典型命令 | 底层编码(7.x) | 一句话场景 |
|---|---|---|---|---|
| String | 字符串 | SET GET INCR | int / embstr / raw(SDS) | 缓存、计数、锁 |
| Hash | 键值对集合 | HSET HGET HGETALL | listpack / hashtable | 存对象、购物车 |
| List | 双向链表 | LPUSH RPOP LRANGE | quicklist / listpack | 队列、栈、时间线 |
| Set | 无序去重集合 | SADD SINTER SPOP | intset / listpack / hashtable | 去重、抽奖、共同好友 |
| SortedSet | 带分数的有序集合 | ZADD ZRANGE ZINCRBY | listpack / skiplist | 排行榜、延时队列 |
本篇先把全景摆出来,Hash / List / Set / SortedSet 的详细拆解放在下篇。
4.2 通用命令:所有类型都能用
这些命令不分类型,是对 Key 本身的操作,必须熟:
| 命令 | 作用 | 说明 |
|---|---|---|
KEYS pattern | 查找匹配的 Key | O(N),生产环境禁用! |
SCAN cursor MATCH p* COUNT 100 | 渐进式遍历 | 替代 KEYS 的正确姿势 |
DEL key [key ...] | 删除 Key | 大 Key 用 UNLINK 异步删除 |
EXISTS key | 判断是否存在 | 返回 1 / 0 |
EXPIRE key seconds | 设置过期时间 | 单位秒,PEXPIRE 毫秒 |
TTL key | 查看剩余存活时间 | 详见下方说明 |
PERSIST key | 移除过期时间 | 变回永久 |
TYPE key | 查看值类型 | 返回值可能是 none |
DBSIZE | 当前库 Key 总数 | 比 KEYS * 安全得多 |
SELECT index | 切换数据库 | 默认 0 号库,共 16 个 |
FLUSHDB / FLUSHALL | 清库 | 生产环境慎用,会清数据 |
RENAME key newkey | 重命名 | 目标存在会被覆盖 |
重点说说 TTL 的三种返回值,这是很多人踩过的坑:
- 返回 正整数:还剩多少秒过期。
- 返回 -1:Key 存在,但没有设置过期时间,永久有效。
- 返回 -2:Key 不存在(可能从未创建,也可能已过期被删除了)。
所以判断"这个 Key 还在不在"千万不要用 TTL,用 EXISTS 更明确。而看到 -1 就意味着这个 Key 会一直占着内存——缓存场景下没有 TTL 的 Key,基本等于埋了一颗内存泄漏的雷。
为什么生产禁用 KEYS *? Redis 命令是单线程执行的,KEYS 遍历全库是 O(N) 阻塞操作,几十万 Key 能卡住几秒甚至几十秒,期间所有请求全部超时——这往往是线上"Redis 突然雪崩"的真实原因之一。正确做法是用 SCAN:
五、String 类型
5.1 特性
String 是 Redis 最基础的类型,一个 Key 对应一个 Value,Value 可以是字符串、数字、甚至二进制(图片、序列化后的对象)。单个 Value 最大 512MB——但真存到这么大,你就该反思设计问题了。
String 在底层没有直接用 C 语言字符串,而是自己实现了 SDS(Simple Dynamic String),好处有三:
- O(1) 获取长度(C 字符串要遍历到
\0); - 二进制安全,可以存
\0,因此能存图片、序列化数据; - 空间预分配 + 惰性释放,减少内存重分配次数。
编码方式三种:int(纯数字且不超长)、embstr(≤44 字节,一次分配、内存连续)、raw(>44 字节,SDS)。
5.2 常用命令速查
基础读写
SET key value # 设置
GET key # 获取,不存在返回 nil
MSET k1 v1 k2 v2 # 批量设(比多次 SET 省网络往返)
MGET k1 k2 # 批量取,一次网络 IO
APPEND key value # 追加,返回追加后长度
STRLEN key # 长度
GETRANGE key 0 5 # 截取子串(含头含尾)
SETRANGE key 0 "xx" # 从指定位置覆盖
GETSET key value # 取旧值并设新值
带过期时间的设置(原子)
SET key value EX 10 # 10 秒后过期
SET key value PX 1000 # 1000 毫秒后过期
SETEX key 10 value # 等价写法
SET key value KEEPTTL # 保留原有 TTL
存在性控制(分布式锁的核心)
SET key value NX # 不存在才设置(Not eXists)
SET key value XX # 存在才设置
SET lock:order:1001 uuid NX EX 30 # 加锁 + 超时,一条命令原子完成
这里有个历史上非常经典的问题:早期做分布式锁要写 SETNX + EXPIRE 两条命令,两条命令之间如果服务宕机,锁就永远不会释放。Redis 2.6.12 之后官方提供了 SET ... NX EX 的组合参数,一条命令原子完成,务必用新写法。
数值增减(原子计数器)
SET num 10
INCR num # 11 不存在时按 0 处理,结果 1
INCRBY num 5 # 16
DECR num # 15
DECRBY num 3 # 12
INCRBYFLOAT num 0.5 # 12.5
INCR 是原子操作,多线程并发下不会丢更新,这是它比"查出来 +1 再写回去"强的地方——后者在并发下必然出错。
六、Key 的层级结构:别把 Redis 当垃圾桶
这是很多人写代码时完全忽略、但后期维护成本极高的一点。
6.1 问题从哪来
Redis 的 Key 是扁平的,没有库、表、字段的概念。如果你的代码里这么写:
SET user1 "张三"
SET user1name "张三"
SET user1age 18
很快就会遇到三个麻烦:命名冲突(不同业务可能撞名)、无法归类(想查"所有用户相关 Key"只能 KEYS *user*)、可读性差(三个月后你自己都看不懂)。
6.2 解决方案:冒号分隔的伪层级
Redis 官方推荐用 冒号 : 作为分隔符来模拟层级,格式一般为:
业务模块:数据类别:唯一标识[:字段]
举例:
# 商品
SET item:1001 '{"name":"iPhone"}' # 商品详情
HSET item:stock:1001 total 100 locked 0 # 商品库存
# 用户
SET user:1:name "张三"
SET user:1:follow:targets "2,3,5" # 关注列表
SADD user:1:follow:2 1 # 2 号被 1 号关注
# 订单
SET order:20261028:10001 "..." # 日期作为中间层,方便按天清理
# 登录态
SET login:token:abc123 "userId=1" EX 1800
这样做的好处:
- 有层级感:Redis Desktop Manager、Another Redis Desktop Manager 等可视化工具会自动按冒号折叠成树形目录,一眼看清结构。
- 方便批量管理:
SCAN 0 MATCH item:* COUNT 100可以按模块巡检、统计、清理。 - 天然防冲突:不同业务用不同前缀,同一个 Redis 实例也能共用一个库。
图:可视化工具中
item:*、user:*会自动折叠成两级目录,建议在博客里配一张截图,效果比文字说明好十倍。
6.3 Key 设计规范
| 规范 | 说明 | 反例 / 正例 |
|---|---|---|
| 业务前缀必加 | 防止多业务共用实例时冲突 | name ❌ → user:1001:name ✅ |
| 层级不超过 3~4 层 | 太深可读性反而下降 | a:b:c:d:e:f ❌ |
| 长度控制在 44 字节内 | 官方建议值,Key 本身也占内存 | this_is_a_very_long_key_name_for_user_profile ❌ |
| 禁止中文、空格、特殊符号 | 客户端兼容性、命令行转义麻烦 | 用户:1:姓名 ❌ |
| 统一小写 + 冒号 + 短横线 | 团队统一风格 | User_1_Name ❌ |
| 缓存必须设 TTL | 兜底防止内存无限增长 | 无 EXPIRE ❌ |
| 冷热分离 | 热点 Key 单独处理 | 爆款商品一个 Key 打满单核 ❌ |
| 禁止大 Key | 删除、读取都会阻塞 | 一个 Hash 存 100 万字段 ❌ |
七、上篇小结
到这儿,认知层和最基础的用法就齐了,先记住这五条:
- 为什么要有 Redis:磁盘 IO 和并发能力是关系型数据库的天然瓶颈,在数据库前面加一层内存是性价比最高的解法。
- NoSQL 不是替代 SQL,而是补位。KV 查得快、文档存得活、列式算得爽、图库连得深,按场景选。
- Redis 的核心竞争力不是"快",而是"快 + 数据结构丰富"。纯内存 + 单线程 + epoll + 高效编码,四者叠加才有了 10 万 QPS。
- 通用命令里最该记住的两件事:
TTL返回 -1 是永久、-2 是不存在;生产环境永远别用KEYS *,一律换SCAN。 - Key 不是随便起的字符串:冒号分层、加业务前缀、控制长度、必设 TTL——这是最低成本、收益最高的规范。
如果只能记一句话带走:
Redis 用得好不好,不看你背了多少命令,而看你有没有把数据"设计"成它擅长的形状。
转载自 CSDN-专业IT技术社区




