Her~senberg头像
# Redis 从入门到数据类型详解(上篇):为什么需要 Redis?封面图

# Redis 从入门到数据类型详解(上篇):为什么需要 Redis?

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 数据库的整体分类

把视角拉高一点,今天的数据存储大致分三代:

  1. SQL(关系型):MySQL、Oracle、PostgreSQL、SQL Server。强一致、事务完备。
  2. NoSQL(非关系型):上面那四类。牺牲一部分一致性换取扩展性和性能。
  3. 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 这么快

这是高频面试题,四个角度回答:

  1. 纯内存操作:避免磁盘 IO,这是量级上的差距。
  2. 单线程 + 无锁:不用加锁,也就没有锁竞争和上下文切换开销;单线程反而避免了多线程的复杂度。
  3. IO 多路复用:基于 epoll,一个线程管理上万个连接,谁有数据就处理谁,不会阻塞等待。
  4. 高效的数据结构: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 INCRint / embstr / raw(SDS)缓存、计数、锁
Hash键值对集合HSET HGET HGETALLlistpack / hashtable存对象、购物车
List双向链表LPUSH RPOP LRANGEquicklist / listpack队列、栈、时间线
Set无序去重集合SADD SINTER SPOPintset / listpack / hashtable去重、抽奖、共同好友
SortedSet带分数的有序集合ZADD ZRANGE ZINCRBYlistpack / skiplist排行榜、延时队列

本篇先把全景摆出来,Hash / List / Set / SortedSet 的详细拆解放在下篇。

4.2 通用命令:所有类型都能用

这些命令不分类型,是对 Key 本身的操作,必须熟:

命令作用说明
KEYS pattern查找匹配的 KeyO(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),好处有三:

  1. O(1) 获取长度(C 字符串要遍历到 \0);
  2. 二进制安全,可以存 \0,因此能存图片、序列化数据;
  3. 空间预分配 + 惰性释放,减少内存重分配次数。

编码方式三种: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

这样做的好处:

  1. 有层级感:Redis Desktop Manager、Another Redis Desktop Manager 等可视化工具会自动按冒号折叠成树形目录,一眼看清结构。
  2. 方便批量管理:SCAN 0 MATCH item:* COUNT 100 可以按模块巡检、统计、清理。
  3. 天然防冲突:不同业务用不同前缀,同一个 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 万字段 ❌

七、上篇小结

到这儿,认知层和最基础的用法就齐了,先记住这五条:

  1. 为什么要有 Redis:磁盘 IO 和并发能力是关系型数据库的天然瓶颈,在数据库前面加一层内存是性价比最高的解法。
  2. NoSQL 不是替代 SQL,而是补位。KV 查得快、文档存得活、列式算得爽、图库连得深,按场景选。
  3. Redis 的核心竞争力不是"快",而是"快 + 数据结构丰富"。纯内存 + 单线程 + epoll + 高效编码,四者叠加才有了 10 万 QPS。
  4. 通用命令里最该记住的两件事:TTL 返回 -1 是永久、-2 是不存在;生产环境永远别用 KEYS *,一律换 SCAN。
  5. Key 不是随便起的字符串:冒号分层、加业务前缀、控制长度、必设 TTL——这是最低成本、收益最高的规范。

如果只能记一句话带走:

Redis 用得好不好,不看你背了多少命令,而看你有没有把数据"设计"成它擅长的形状。


转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/Zyzui/article/details/169349900

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--