《什么是Redis?》
前言:
在高并发、大数据场景下,传统关系型数据库逐渐暴露出性能瓶颈,而 Redis 作为一款高性能的键值对数据库,凭借其内存存储、多数据类型支持、持久化等特性,成为解决高并发问题的核心技术之一。本文从基础概念入手,逐步深入到核心操作、持久化机制、删除策略,并结合企业级场景给出解决方案,适合 Redis 初学者参考。
一、Redis简介与NoSQL背景
1.传统关系型数据库的瓶颈
在学习 Redis 之前,我们首先要理解 “为什么需要 Redis”—— 这需要从传统数据库的痛点和 NoSQL 的优势说起。以 12306、淘宝等高频崩溃场景为例,海量用户并发访问时,关系型数据库(如 MySQL、Oracle)会面临两大核心问题:
- 性能瓶颈:数据存储在磁盘中,磁盘 IO 速度远低于内存,高并发下查询延迟显著;
- 扩展瓶颈:数据间存在复杂关联(如多表 Join),难以横向扩展为大规模集群。
解决方案的核心思路很明确:用内存存储降低 IO 次数,用 “去关系化” 简化数据结构—— 这正是 NoSQL 数据库的设计理念。
2.NoSQL 与 Redis 定位
NoSQL(Not Only SQL)并非 “否定 SQL”,而是对传统关系型数据库的补充,其核心优势包括开源免费、内存存储、灵活的数据格式(Key-Value、文档等)、易扩展。
主流 NoSQL 产品分为四类,Redis 属于键值(Key-Value)存储数据库,专注于 “高性能缓存与数据存储”,典型应用场景包括内容缓存、热点数据存储、计数器等。
NoSQL 类型 | 代表产品 | 核心场景 | 优势 |
---|---|---|---|
键值存储 | Redis、Memcached | 缓存、计数器 | 查询速度快 |
列存储 | HBase、Cassandra | 分布式文件系统 | 可扩展性强 |
文档存储 | MongoDB | Web 应用(结构化 Value) | 数据结构灵活 |
图形存储 | Neo4j | 社交网络(图结构数据) | 支持图算法 |
3.Redis 核心特性
Redis(Remote Dictionary Server)是用 C 语言开发的开源键值数据库,其核心特性决定了它的 “高性能” 基因:
- 无数据关联:数据间无复杂关系,仅以 Key-Value 形式存储,简化操作;
- 单线程模型:避免多线程上下文切换开销,单核即可发挥高性能(官方测试:读 11 万次 / 秒,写 8.1 万次 / 秒);
- 多数据类型:支持 String、Hash、List、Set、Sorted_Set 5 种常用类型;
- 持久化支持:通过 RDB、AOF 机制将内存数据写入磁盘,避免数据丢失;
- 跨平台:支持 Linux(企业级首选)和 Windows(学习用)。
二、Redis常见的 5 种数据类型与命令
Redis 的所有数据都以 “Key-Value” 形式存储,Key 永远是字符串,数据类型差异体现在 Value 上。以下是 5 种常用类型的核心命令与应用场景。
1. String:最简单的键值存储
String 是 Redis 最基础的类型,Value 可存储字符串或整数,适合存储单个数据(如计数器、用户 Token)。
命令 | 作用 | 示例 |
---|---|---|
set key value | 添加 / 修改数据 | set name "zhangsan" |
get key | 获取数据(不存在返回 nil) | get name → "zhangsan" |
del key | 删除数据 | del name |
mset key1 v1 key2 v2 | 批量添加 / 修改 | mset age 20 gender "male" |
incr key | 整数 Value 自增 1(常用于计数器) | incr view_count |
setex key sec value | 设置数据有效期(秒) | setex code 60 "123456" |
应用场景:
- 商品库存计数(
incr/decr
); - 短信验证码(
setex
设置 60 秒有效期); - 用户 ID 与昵称映射(
set user:100:name "lisi"
)。
2. Hash:对象型数据存储
Hash 适合存储结构化对象(如用户信息、商品详情),Value 是 “字段 - 值”(Field-Value)的映射,可灵活修改对象的单个字段。
命令 | 作用 | 示例 |
---|---|---|
hset key field value | 添加 / 修改对象字段 | hset user:100 age 25 |
hget key field | 获取对象字段 | hget user:100 age → 25 |
hgetall key | 获取对象所有字段与值 | hgetall user:100 |
hdel key field | 删除对象字段 | hdel user:100 age |
hlen key | 获取对象字段数量 | hlen user:100 → 2 |
应用场景:
- 电商购物车(Key 为用户 ID,Field 为商品 ID,Value 为购买数量);
- 用户信息存储(避免 String 存储 JSON 的 “全量修改” 开销)。
3. List:有序列表(双向链表)
List 是有序的字符串集合,支持从两端添加 / 删除数据,适合实现队列、栈、消息列表。
命令 | 作用 | 示例 |
---|---|---|
lpush key v1 v2 | 从左侧添加数据(栈:先进后出) | lpush list1 a b → [b,a] |
rpush key v1 v2 | 从右侧添加数据(队列:先进先出) | rpush list1 c → [b,a,c] |
lpop key | 从左侧删除并返回数据 | lpop list1 → b |
rpop key | 从右侧删除并返回数据 | rpop list1 → c |
lrange key start stop | 获取指定范围数据(-1 表示末尾) | lrange list1 0 -1 → [a] |
应用场景:
- 消息队列(
rpush
生产消息,lpop
消费消息); - 朋友圈时间线(
lpush
新增动态,lrange
分页查询)。
4. Set:无序无重复集合
Set 是无序的字符串集合,自动去重,支持交集、并集、差集运算,适合存储 “不重复” 的数据集。
命令 | 作用 | 示例 |
---|---|---|
sadd key member | 添加数据(重复数据自动忽略) | sadd tag "java" "python" |
smembers key | 获取所有数据 | smembers tag → ["java","python"] |
srem key member | 删除数据 | srem tag "python" |
sinter key1 key2 | 求两个集合的交集(共同元素) | sinter set1 set2 |
srandmember key n | 随机获取 n 个数据(不删除) | srandmember tag 1 → "java" |
应用场景:
- 用户标签(一个用户的多个标签去重);
- 好友推荐(求两个用户的共同好友:
sinter user1:friends user2:friends
); - 抽奖(
spop key
随机删除并返回数据,确保不重复中奖)。
5. Sorted_Set:有序无重复集合
Sorted_Set(有序集合)在 Set 基础上增加了 “分数(Score)” 字段,按 Score 排序,适合需要排序的场景(如排行榜)。
命令 | 作用 | 示例 |
---|---|---|
zadd key score member | 添加数据(按 Score 排序) | zadd rank 95 "zhangsan" 88 "lisi" |
zrange key start stop | 按 Score 升序获取数据 | zrange rank 0 -1 → ["lisi","zhangsan"] |
zrevrange key start stop | 按 Score 降序获取数据 | zrevrange rank 0 -1 → ["zhangsan","lisi"] |
zrem key member | 删除数据 | zrem rank "lisi" |
zcard key | 获取数据总数 | zcard rank → 1 |
应用场景:
- 学生成绩排行榜(Score 为分数,降序展示);
- 热门商品排序(Score 为销量,实时更新)。
三、Redis 持久化:避免内存数据丢失
Redis 是内存数据库,一旦服务器宕机,内存数据会丢失。持久化机制通过将数据写入磁盘,确保重启后可恢复数据,核心有两种方式:RDB 和 AOF。
1. RDB:快照式持久化
RDB(Redis Database)在指定时间间隔内,将内存中的 “数据快照” 写入磁盘(生成dump.rdb
文件),恢复时直接加载快照到内存。
1. 触发方式
- 手动触发:
save
:阻塞 Redis 服务器,直到 RDB 完成(不推荐生产环境);bgsave
:创建子进程执行 RDB,主进程继续处理请求(推荐)。
- 自动触发:在
redis.conf
中配置 “时间 - key 变化量” 规则,例如:save 900 1 # 900秒内至少1个key变化,触发RDB save 300 10 # 300秒内至少10个key变化,触发RDB save 60 10000# 60秒内至少10000个key变化,触发RDB
2. RDB 优缺点
- 优点:文件体积小、恢复速度快(适合全量备份);
- 缺点:无法实时持久化,宕机可能丢失 “最后一次快照到宕机” 的数据;Fork 子进程时,内存会临时膨胀(需预留足够内存)。
2. AOF:日志式持久化
AOF(Append Only File)以 “日志” 形式记录每一条写命令(如set
、hset
),重启时重新执行日志中的命令恢复数据,解决了 RDB 的实时性问题。
1. 核心配置
在redis.conf
中开启 AOF:
appendonly yes # 开启AOF(默认关闭) appendfilename "appendonly.aof" # AOF文件名 appendfsync everysec # 写策略:每秒同步一次(平衡性能与安全性)
AOF 写策略有三种选择:
always
:每次写命令都同步到磁盘(数据零丢失,性能最低);everysec
:每秒同步一次(默认,丢失最多 1 秒数据);no
:由操作系统决定同步时机(性能最高,数据丢失风险高)。
2. AOF 重写
AOF 文件会随命令增加而变大(如多次set name
),重写机制通过 “合并重复命令” 压缩文件(如将set name a
、set name b
合并为set name b
)。
- 手动重写:执行
bgrewriteaof
; - 自动重写:配置触发条件(文件大小达上次重写的 2 倍且≥64MB):
auto-aof-rewrite-percentage 100 # 增长率100% auto-aof-rewrite-min-size 64mb # 最小文件大小64MB
3. AOF 优缺点
- 优点:实时性高(丢失数据少)、日志文件可读性强;
- 缺点:文件体积大、恢复速度慢(需重新执行所有命令)。
3. RDB 与 AOF 对比
维度 | RDB | AOF |
---|---|---|
存储内容 | 数据快照(二进制) | 写命令日志(文本) |
文件体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 可能丢失大量数据 | 最多丢失 1 秒数据 |
资源消耗 | 高(Fork 子进程) | 低 |
启动优先级 | 低(AOF 开启时优先加载 AOF) | 高 |
四、Redis 删除策略:平衡内存与 CPU
Redis 中存在大量 “过期数据”(如setex
设置的验证码),若不及时清理,会导致内存溢出。删除策略的核心是 “在内存占用与 CPU 消耗间找平衡”,分为两类:基于过期时间的删除策略、基于内存淘汰的删除策略。
1. 基于过期时间的删除策略
Redis 通过TTL key
查看数据有效期(XX:有效,-1:永久,-2:已过期 / 删除),过期数据的删除有三种策略:
策略 | 原理 | 优点 | 缺点 |
---|---|---|---|
定时删除 | 为过期 key 创建定时器,到期立即删除 | 内存占用少 | CPU 压力大(高并发下阻塞) |
惰性删除 | 过期后不主动删除,访问时才检查并删除 | CPU 消耗少 | 内存泄漏风险(长期占用内存) |
定期删除 | 每秒 10 次随机抽查过期 key,按比例删除 | 平衡内存与 CPU | 可能存在少量过期数据残留 |
Redis 默认采用 “定期删除 + 惰性删除”:定期抽查减少 CPU 消耗,惰性删除避免内存泄漏。
2. 基于内存淘汰的删除策略
当 Redis 内存达到maxmemory
限制时,会触发 “内存淘汰”,删除部分数据腾出空间。核心配置:
maxmemory 1024mb # 最大内存(生产环境建议设为物理内存的50%-70%) maxmemory-policy volatile-lru # 淘汰策略
常用淘汰策略(8 种):
- volatile-lru:删除 “过期数据” 中最近最少使用的;
- allkeys-lru:删除 “所有数据” 中最近最少使用的(内存不足时推荐);
- volatile-random:随机删除过期数据;
- allkeys-random:随机删除所有数据;
- volatile-ttl:删除过期数据中剩余时间最短的;
- no-enviction:禁止删除数据,写操作报错(默认,不推荐生产环境)。
推荐策略:allkeys-lru
(优先删除长期未使用的数据,最大化内存利用率)。
五、企业级解决方案:解决 Redis 高频问题
1. 缓存预热:避免启动后数据库压力激增
问题:Redis 服务器重启后,缓存为空,所有请求直接打向数据库,导致数据库崩溃。
解决方案:启动前提前加载 “热点数据” 到缓存:
- 日常统计热点数据(如访问量 Top1000 的商品);
- 编写脚本(如 Python、Shell),在 Redis 启动后自动执行
set
/hset
命令加载数据; - 结合 CDN(内容分发网络),将静态资源(图片、HTML)缓存到边缘节点,减少 Redis 请求。
2. 缓存雪崩:避免大量 key 同时过期
问题:同一时段大量 key 过期或 Redis 集群宕机,导致请求全部流向数据库。
解决方案:
- key 过期时间加随机值:避免集中过期,例如
setex key (3600+random(100)) value
; - Redis 集群化:部署主从复制 + 哨兵模式,避免单点故障(主节点宕机后从节点自动切换为主);
- 降级限流:高并发时,对非核心接口(如商品评论)返回 “服务繁忙”,减少数据库压力;
- 多级缓存:增加本地缓存(如 Java 的 Caffeine),减少 Redis 请求。
3. 缓存击穿:避免热点 key 过期导致数据库压力
问题:某个 “高并发热点 key”(如秒杀商品)突然过期,大量请求同时查询该 key,均未命中缓存,直接打向数据库。
解决方案:两种主流方案,各有优劣:
方案 | 原理 | 优点 | 缺点 |
---|---|---|---|
互斥锁(Redis 分布式锁) | 第一个请求未命中缓存时,获取锁后查询数据库并更新缓存,其他请求等待锁释放后从缓存获取数据 | 数据一致性高、无额外内存消耗 | 线程等待,性能下降;有死锁风险 |
逻辑过期 | key 不设置实际过期时间,Value 中包含 “逻辑过期时间”,请求时检查逻辑时间,过期则异步更新缓存 | 线程无需等待,性能高 | 数据可能短期不一致;需额外存储过期时间 |
示例(互斥锁):
- 线程 1 查询缓存未命中,执行
setnx lock:key 1
获取锁(成功则返回 1); - 线程 1 查询数据库,更新缓存,执行
del lock:key
释放锁; - 线程 2 查询缓存未命中,执行
setnx lock:key 1
失败,休眠 100ms 后重试,直到从缓存获取数据。
4. 缓存穿透:避免 “不存在的数据” 直击数据库
问题:客户端请求 “缓存和数据库中都不存在的数据”,缓存永远未命中,所有请求流向数据库。
解决方案:
- 缓存空对象:数据库查询为空时,仍将 “key - 空值” 存入缓存(设置短期 TTL,如 5 分钟),避免重复查询;
- 优点:实现简单;
- 缺点:额外占用内存,可能存在短期数据不一致。
- 布隆过滤器:在 Redis 前加一层布隆过滤器,判断数据是否存在(基于哈希思想):
- 原理:将所有存在的 key 通过多个哈希函数映射到二进制数组,请求时先查布隆过滤器,不存在则直接返回;
- 优点:内存占用少;
- 缺点:存在误判(哈希冲突),需定期更新过滤器。
六、总结
Redis 作为高性能的键值数据库,是解决高并发问题的核心技术之一。Redis 的学习重点在于 “理解原理 + 结合场景”,只有掌握其底层逻辑(如单线程模型、持久化机制),才能在实际业务中灵活运用,真正发挥其高性能优势。