1. Redis 是什么?
Redis (Remote Dictionary Server) 是一个开源的、基于内存的 键值对 (Key-Value) 数据库。
打个比方:
- 传统数据库 (MySQL/SQL Server) 就像是 图书馆的仓库,存储在硬盘上,容量大但读取慢。
- Redis 就像是 图书馆的阅览桌,存储在内存中,空间有限但拿取数据极其快(因为不需要等待磁盘转动)。
核心特点:
- 速度极快:基于内存操作,读写速度可达 10万次/秒。
- 支持多种数据结构:不仅仅存字符串,还能存列表、集合等。
- 持久化:虽然是内存数据库,但它能把数据定期存到硬盘,防止断电丢失。
2. Redis 有什么作用?
- 缓存 (Cache):最主要的作用。将热点数据放在 Redis 中,减少数据库查询压力,提升网页响应速度。
- 会话存储 (Session):在分布式系统中存储用户的登录状态。
- 排行榜:利用自带的排序功能实现游戏或电商排行榜。
- 计数器:视频播放量、点赞数(因为 Redis 的自增操作是原子性的,不会出错)。
- 分布式锁:在多台服务器之间协调任务,防止冲突。
3. 五大数据类型及适用场景
Redis 最经典的五种数据类型如下。使用 C# (配合 StackExchange.Redis 库) 进行简单演示。
假设我们已经创建了连接对象 IDatabase db:
(1) String (字符串)
最基本类型,Key 对应一个 Value(可以是文本、数字、甚至序列化后的 JSON 对象)。
-
场景:缓存用户信息(JSON)、验证码、网页点击量计数。
-
C# 示例:
// 1. 简单的键值存储 db.StringSet("username:1001", "zhangsan"); string name = db.StringGet("username:1001"); // 2. 计数器 (点赞数) db.StringIncrement("article:likes:55"); // 每次调用加1
(2) Hash (哈希)
类似于 C# 中的 Dictionary<string, string>,一个 Key 里面存着多个字段和值。
-
场景:存储对象,例如“用户资料”(修改名字时不需要取出整个对象,只需修改 Name 字段)。
-
C# 示例:
// 存储用户ID为1的资料 db.HashSet("user:1", new HashEntry[] { new HashEntry("name", "李四"), new HashEntry("age", 25) }); // 只获取年龄 var age = db.HashGet("user:1", "age");
(3) List (列表)
一个有序的字符串链表,可以从头或尾添加元素。
-
场景:消息队列、最新消息流(朋友圈时间线)、待办事项。
-
C# 示例:
// 左侧推入消息 (由新到旧) db.ListLeftPush("msg_queue", "消息A"); db.ListLeftPush("msg_queue", "消息B"); // 右侧弹出处理 var msg = db.ListRightPop("msg_queue"); // 取出 "消息A"
(4) Set (集合)
无序的字符串集合,自动去重(添加重复元素无效)。
-
场景:标签系统(Tag)、抽奖(随机抽取)、计算共同好友(利用交集功能)。
-
C# 示例:
// 添加标签,自动去重 db.SetAdd("user:1:tags", "sport"); db.SetAdd("user:1:tags", "music"); db.SetAdd("user:1:tags", "sport"); // 不会重复添加 // 判断是否包含 bool isMusicFan = db.SetContains("user:1:tags", "music");
(5) Sorted Set / ZSet (有序集合)
类似 Set,但每个元素关联一个 分数 (Score),根据分数自动排序。
-
场景:排行榜(按分数排序)、带权重的任务队列。
-
C# 示例:
// 记录游戏分数 db.SortedSetAdd("game:rank", "玩家A", 100); db.SortedSetAdd("game:rank", "玩家B", 500); db.SortedSetAdd("game:rank", "玩家C", 300); // 获取前三名 (Redis中通常是按分数低到高,高到低用Descending) var topPlayers = db.SortedSetRangeByRank("game:rank", 0, 2, Order.Descending);
4. 缓存三大经典问题
在面试和实战中,这三个问题非常关键。
(1) 缓存穿透 (Cache Penetration)
- 是什么:用户查询一个数据,Redis 里没有,数据库里也没有。
- 比如黑客疯狂请求 ID 为 -1 的用户。请求会直接穿过 Redis 打到数据库,导致数据库崩溃。
- 怎么办:
- 缓存空对象:如果数据库没查到,也在 Redis 存一个
null,并设置较短的过期时间。 - 布隆过滤器 (Bloom Filter):一种特殊算法,在请求到达 Redis 前先判断该 ID 是否可能存在。
- 缓存空对象:如果数据库没查到,也在 Redis 存一个
(2) 缓存击穿 (Cache Breakdown)
- 是什么:一个非常热点的 Key(比如微博热搜第一名)突然过期了。
- 在过期的那一瞬间,成千上万的请求同时发现缓存没了,全部涌向数据库。
- 怎么办:
- 互斥锁 (Mutex):发现缓存没了,只允许一个线程去查数据库,其他线程等待。
- 逻辑过期:数据本身不设置过期时间,而是在 Value 里存一个时间戳,代码检测到“过期”后异步更新。
(3) 缓存雪崩 (Cache Avalanche)
- 是什么:大量的 Key 在同一时间集体过期,或者 Redis 服务器直接宕机。
- 所有请求全部砸向数据库。
- 怎么办:
- 随机过期时间:存数据时,过期时间设为
1小时 + 随机几分钟,避免集体过期。 - 高可用集群:使用 Redis Sentinel 或 Cluster 模式,一台挂了另一台顶上。
- 随机过期时间:存数据时,过期时间设为
5. 触发器/钩子 (Triggers / Hooks)
新手需要注意:Redis 主要是数据存储,不像 SQL Server 或 Oracle 那样有完善的“触发器”功能(即数据变动自动触发代码)。
但在 Redis 中,我们通常通过以下方式实现类似“钩子”的效果:
(1) 键空间通知 (Keyspace Notifications)
这是 Redis 最接近“触发器”的功能。
- 原理:Redis 开启此功能后,当发生命令(如
DEL,EXPIRE,SET)时,Redis 会自动发布一条消息。 - 场景:订单超时自动取消。
-
做法:监听 Key 的
Expired事件。当 Key 过期消失时,你的 C# 代码会收到通知,然后去数据库取消订单。 -
C# 概念代码:
var sub = redis.GetSubscriber(); // 订阅过期事件 (需在 redis.conf 中开启 notify-keyspace-events Ex) sub.Subscribe("__keyevent@0__:expired", (channel, key) => { Console.WriteLine($"Key {key} 刚刚过期了!"); // 执行业务逻辑... });
-
(2) Redis Gears (高级模块)
简单来说,Redis Gears 是 Redis 的一个“大脑”插件。它是一个 动态编程框架/引擎。
- 以前的 Redis:是一个单纯的仓库。你(C# 代码)发指令说“存这个”、“取那个”,Redis 只是照做。
- 装了 Gears 的 Redis:变成了一个智能仓库。你不仅可以存数据,还可以把一段代码(脚本)上传给 Redis,让 Redis 自己在内部运行这段代码。
核心亮点:它支持 Python!你可以写一段 Python 脚本,让 Redis 服务器直接执行,而不需要把数据拉回到你的 C# 客户端处理。
它比 Lua 脚本强在哪里?
- Lua 脚本:像是一个 简单的计算器。适合做一些原子性的、短平快的小操作(比如“如果库存够就减1,不够就报错”)。
- Redis Gears:像是一个 全功能的机器人。
- 它支持 跨分片(Cluster)操作:可以在集群的多个节点间处理数据。
- 它可以 长期运行:不是运行完就结束,而是可以一直监听。
- 它支持 非阻塞:可以在后台慢慢跑,不卡住主线程。
核心应用场景:
A. “写后同步” (Write-Behind / Write-Through) —— 自动存入数据库
- 传统做法:你的 C# 代码先写 Redis,再写 MySQL。如果中间断网了,数据就不一致了。
- Gears 做法:你的 C# 代码 只管写 Redis。Redis Gears 配置了一个监听器,一旦发现有新数据写入,它 自动 在后台把数据同步写入 MySQL。
- 好处:你的 C# 代码变得极简,而且读写速度飞快(因为只跟内存打交道)。
B. 超级触发器
- 场景:你要做一个“实时反欺诈”系统。
- Gears 做法:每当有新的一笔交易(Key)写入,Gears 脚本自动抓取过去 10 分钟该用户的交易记录,分析是否有风险。如果风险高,直接在 Redis 内部就把账号封锁,整个过程毫秒级完成,不需要你的 C# 后端介入。
6. Redis 持久化:RDB vs AOF
6.1 RDB (Redis DataBase) - 快照模式
是什么?
RDB 就像是给你的作业本 “拍照”。
它会在指定的时间间隔内,把内存里所有的数据生成一个文件(通常叫 dump.rdb)存在硬盘上。
工作原理:
Redis 会在后台启动一个子进程,把当前那一瞬间的所有数据“复制”一份写入硬盘。
- 比如:你设置“每隔 1 小时保存一次”。
- 在 8:00、9:00、10:00 时,Redis 都会生成一个当时的数据快照文件。
优缺点:
- 优点(恢复快):
- 文件紧凑,体积小(因为是二进制压缩文件)。
- 恢复速度极快,适合大规模的数据恢复(直接把文件读进内存就行)。
- 缺点(容易丢数据):
- 安全性较低。如果你在 9:59 停电了,而上次保存是 9:00,那你这 59分钟 的数据就全没了!
6.2 AOF (Append Only File) - 日志模式
是什么?
AOF 就像是 “记流水账”。
它不会存数据的最终状态,而是把你执行的每一条 写命令(如 set key value、del key)都记录到一个文件里(通常叫 appendonly.aof)。
工作原理:
每当你向 Redis 写入数据,Redis 就会把这条命令追加到 AOF 文件的末尾。
- 恢复数据时,Redis 就像“重放录像”一样,把 AOF 文件里的命令从头到尾再执行一遍。
优缺点:
- 优点(数据安全):
- 安全性高。你可以设置“每秒钟保存一次”,就算断电,最多也只丢失这一秒的数据。
- 缺点(文件大、恢复慢):
- 文件体积通常比 RDB 大很多(因为它记录的是流水账)。
- 恢复速度慢。因为要重新执行一遍所有命令,如果数据量大,启动会比较久。
6.3 应该怎么选?
作为新手,你可能会问:“我该用哪一个?”
答案是:通常混合使用。
在 Redis 4.0 之后,官方推荐 混合持久化(默认开启)。
- 平时:Redis 主要利用 AOF 来保证数据不丢失。
- 重启/重写时:Redis 会把 AOF 文件里的一部分内容转换成 RDB 格式(为了瘦身和快速加载),新的增量数据继续用 AOF 记录。
建议配置:
- 如果你的数据只是 纯缓存(丢了也没事,数据库里还有),甚至可以把持久化 全部关闭,这样 Redis 性能最高。
- 如果是生产环境,一般 同时开启 RDB 和 AOF。
- 用 RDB 做定期的冷备份(比如每天凌晨备份一次发给其他服务器)。
- 用 AOF 保证服务器宕机后的数据实时恢复。
6.4 简单配置示例 (redis.conf)
虽然不需要你死记硬背,但了解一下配置文件长什么样很有帮助:
RDB 配置:
# 格式:save <秒数> <变化次数>
save 900 1 # 900秒内如果有1个键被修改,就保存一次
save 300 10 # 300秒内如果有10个键被修改,就保存一次
AOF 配置:
appendonly yes # 开启 AOF 功能
appendfsync everysec # 每秒钟刷盘一次 (折中方案,推荐)
# appendfsync always # 每次写入都刷盘 (最安全,但慢)
# appendfsync no # 看操作系统心情刷盘 (最快,但不安全)