Redis 从入门到精通(一):重新认识 Redis ------ 不只是缓存
一、开篇:Redis 到底是什么?
网上关于 Redis 的介绍铺天盖地,但大多数文章只会告诉你:"Redis 是一个基于内存的高性能键值数据库"。这句话没错,但远远不够。
如果你去翻 Redis 官方文档,第一句话是这样写的:
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, message broker, and streaming engine.
注意这个关键词:data structure store(数据结构存储)。它不是简单的 key-value 存储,而是在内存中维护了丰富的数据结构,并且直接操作这些数据结构。
说得更直白一点:Redis = 内存存储 + 数据结构 + 网络服务。
它的核心设计哲学可以归纳为三点:
| 设计原则 | 说明 |
|---|---|
| All in Memory | 所有数据都在内存中,读写延迟微秒级 |
| Data Structure Oriented | 不是存字节流,而是直接操作 List、Hash、Set 等结构 |
| Single Threaded (核心) | 命令执行是单线程的,天然原子性,无锁竞争 |
Redis 的典型使用场景
在实际业务中,Redis 能做的事情远比"缓存"多得多:
#mermaid-svg-ZDvjhmsqySdXzAS6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZDvjhmsqySdXzAS6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZDvjhmsqySdXzAS6 .error-icon{fill:#552222;}#mermaid-svg-ZDvjhmsqySdXzAS6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZDvjhmsqySdXzAS6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZDvjhmsqySdXzAS6 .marker.cross{stroke:#333333;}#mermaid-svg-ZDvjhmsqySdXzAS6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZDvjhmsqySdXzAS6 p{margin:0;}#mermaid-svg-ZDvjhmsqySdXzAS6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZDvjhmsqySdXzAS6 .cluster-label text{fill:#333;}#mermaid-svg-ZDvjhmsqySdXzAS6 .cluster-label span{color:#333;}#mermaid-svg-ZDvjhmsqySdXzAS6 .cluster-label span p{background-color:transparent;}#mermaid-svg-ZDvjhmsqySdXzAS6 .label text,#mermaid-svg-ZDvjhmsqySdXzAS6 span{fill:#333;color:#333;}#mermaid-svg-ZDvjhmsqySdXzAS6 .node rect,#mermaid-svg-ZDvjhmsqySdXzAS6 .node circle,#mermaid-svg-ZDvjhmsqySdXzAS6 .node ellipse,#mermaid-svg-ZDvjhmsqySdXzAS6 .node polygon,#mermaid-svg-ZDvjhmsqySdXzAS6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZDvjhmsqySdXzAS6 .rough-node .label text,#mermaid-svg-ZDvjhmsqySdXzAS6 .node .label text,#mermaid-svg-ZDvjhmsqySdXzAS6 .image-shape .label,#mermaid-svg-ZDvjhmsqySdXzAS6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZDvjhmsqySdXzAS6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZDvjhmsqySdXzAS6 .rough-node .label,#mermaid-svg-ZDvjhmsqySdXzAS6 .node .label,#mermaid-svg-ZDvjhmsqySdXzAS6 .image-shape .label,#mermaid-svg-ZDvjhmsqySdXzAS6 .icon-shape .label{text-align:center;}#mermaid-svg-ZDvjhmsqySdXzAS6 .node.clickable{cursor:pointer;}#mermaid-svg-ZDvjhmsqySdXzAS6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZDvjhmsqySdXzAS6 .arrowheadPath{fill:#333333;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZDvjhmsqySdXzAS6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZDvjhmsqySdXzAS6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZDvjhmsqySdXzAS6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZDvjhmsqySdXzAS6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZDvjhmsqySdXzAS6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZDvjhmsqySdXzAS6 .cluster text{fill:#333;}#mermaid-svg-ZDvjhmsqySdXzAS6 .cluster span{color:#333;}#mermaid-svg-ZDvjhmsqySdXzAS6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZDvjhmsqySdXzAS6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZDvjhmsqySdXzAS6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZDvjhmsqySdXzAS6 .icon-shape,#mermaid-svg-ZDvjhmsqySdXzAS6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZDvjhmsqySdXzAS6 .icon-shape p,#mermaid-svg-ZDvjhmsqySdXzAS6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZDvjhmsqySdXzAS6 .icon-shape .label rect,#mermaid-svg-ZDvjhmsqySdXzAS6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZDvjhmsqySdXzAS6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZDvjhmsqySdXzAS6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZDvjhmsqySdXzAS6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🔴 Redis 场景
缓存层
Cache-Aside / 热点数据
数据结构服务
排行榜 / 计数器 / 分布式锁 / Session
中间件能力
Pub/Sub / Stream 消息
- 缓存:最经典的用法,Cache-Aside 模式,减轻数据库压力
- 分布式锁 :基于
SET NX+ Lua 脚本,实现跨进程互斥 - 计数器 :文章的阅读量、点赞数,
INCR一条命令搞定 - 排行榜:Sorted Set 实现实时排行,时间复杂度 O(log N)
- Session 共享:分布式系统下统一管理用户会话
- 消息队列:Stream 类型支持消费者组、ACK 确认,已具备基础 MQ 能力
- 限流器:令牌桶、滑动窗口,都可以用 Redis 实现
- 地理位置:GEO 类型支持附近的人、门店距离计算
二、五大数据类型:不只是 get/set 那么简单
Redis 原生提供了五种核心数据类型。下面我不仅讲用法,更会深入底层数据结构的选择机制------这对后续理解性能优化至关重要。
2.1 String ------ 最基础但不简单
String 是 Redis 中最基本的类型,一个 key 对应一个 value。但一个 String 类型的 value 最大可以存 512MB。
常用命令:
bash
# 基本读写
SET key value [EX seconds] [PX milliseconds] [NX|XX]
GET key
# 数值操作(value 必须能解析为数字)
INCR key # 原子自增 1
INCRBY key 10 # 原子自增指定值
DECR key # 原子自减
# 字符串操作
APPEND key value # 末尾追加
STRLEN key # 字符串长度
GETRANGE key 0 4 # 截取子串
# 批量操作
MSET k1 v1 k2 v2 # 批量设置
MGET k1 k2 # 批量获取
# 条件写入 ------ 这是分布式锁的基石
SET key value NX EX 10 # key 不存在才设置,并设 10 秒过期
SET key value XX # key 已存在才设置
底层编码:String 类型有三种内部编码:
| 编码 | 条件 | 说明 |
|---|---|---|
int |
value 是整数且不超过 long 范围 | 直接存为 long,8 字节,最省内存 |
embstr |
字符串长度 ≤ 44 字节 | 一次内存分配,对象头和 SDS 连续存储 |
raw |
字符串长度 > 44 字节 | 两次内存分配,对象头和 SDS 分开 |
这个 44 字节的阈值不是拍脑袋定的------Redis 使用 jemalloc 分配器,64 字节是一个分配单元。去掉 RedisObject 头(16 字节)和 SDS 头(3 字节),剩下 44 字节正好卡在一个分配单元内,避免了内存碎片。
实战场景:
bash
# 分布式锁的核心命令
SET lock:order:1001 uuid_value NX EX 30
# 简单限流计数器
INCR rate_limit:user:123
EXPIRE rate_limit:user:123 60
# 缓存序列化对象(注意选择合适的序列化方式)
SET user:1001 '{"name":"张三","age":28}' EX 3600
2.2 Hash ------ 对象的天然载体
Hash 就是一个 String 类型的 field-value 映射表,特别适合存储对象。
常用命令:
bash
# 基本操作
HSET user:1001 name "张三" age 28 city "北京"
HGET user:1001 name # 获取单个字段
HGETALL user:1001 # 获取全部字段(慎用,field 多时会阻塞)
HMGET user:1001 name age # 批量获取指定字段
# 数值操作
HINCRBY user:1001 age 1 # 字段原子自增
# 字段管理
HEXISTS user:1001 name # 判断字段是否存在
HDEL user:1001 city # 删除字段
HKEYS user:1001 # 获取所有 field
HVALS user:1001 # 获取所有 value
HLEN user:1001 # 字段数量
底层编码:Hash 有两种内部编码:
| 编码 | 条件 | 数据结构 |
|---|---|---|
listpack(Redis 7.0+)/ ziplist(旧版) |
字段少且值短 | 紧凑连续数组,省内存但 O(n) 操作 |
hashtable |
字段多或值长 | 标准哈希表,O(1) 操作但内存开销大 |
切换阈值由 hash-max-listpack-entries(默认 512)和 hash-max-listpack-value(默认 64)控制。
这是经典的空间换时间 vs 时间换空间的权衡------小对象用紧凑结构省内存,大对象用哈希表保证操作效率。
实战场景:
bash
# 用户信息存储
HSET user:1001 name "张三" email "zhangsan@example.com" level 5
# 购物车
HSET cart:user:1001 sku:001 2
HSET cart:user:1001 sku:002 1
HINCRBY cart:user:1001 sku:001 1 # 商品数量+1
# 对象局部更新(相比于 String 存 JSON,Hash 可以只更新一个字段,节省网络带宽)
HINCRBY user:1001 login_count 1 # 只更新登录次数,不影响其他字段
2.3 List ------ 有序可重复的双端队列
List 是一个双向链表,支持从两端压入或弹出元素。注意:Redis 的 List 底层不是纯链表,而是 quicklist。
常用命令:
bash
# 两端操作
LPUSH list key1 key2 key3 # 左侧压入
RPUSH list key4 key5 # 右侧压入
LPOP list # 左侧弹出
RPOP list # 右侧弹出
LRANGE list 0 -1 # 查看全部(O(n),慎用)
# 阻塞操作 ------ 消息队列的基础
BLPOP queue 30 # 阻塞等待 30 秒,有元素才弹出
BRPOP queue 30
# 其他操作
LLEN list # 长度
LINDEX list 0 # 按索引获取元素(O(n))
LREM list 2 "value" # 删除指定元素
LTRIM list 0 99 # 只保留 [0, 99] 范围的元素
底层编码 :Redis 3.2 之后统一使用 quicklist。quicklist 是一个双向链表,但每个节点是一个 listpack(或旧版的 ziplist),这样既保留了链表的灵活插入删除,又通过紧凑存储减少了内存碎片和指针开销。
#mermaid-svg-1jP8YPFCbQU6hnp6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1jP8YPFCbQU6hnp6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1jP8YPFCbQU6hnp6 .error-icon{fill:#552222;}#mermaid-svg-1jP8YPFCbQU6hnp6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1jP8YPFCbQU6hnp6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1jP8YPFCbQU6hnp6 .marker.cross{stroke:#333333;}#mermaid-svg-1jP8YPFCbQU6hnp6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1jP8YPFCbQU6hnp6 p{margin:0;}#mermaid-svg-1jP8YPFCbQU6hnp6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1jP8YPFCbQU6hnp6 .cluster-label text{fill:#333;}#mermaid-svg-1jP8YPFCbQU6hnp6 .cluster-label span{color:#333;}#mermaid-svg-1jP8YPFCbQU6hnp6 .cluster-label span p{background-color:transparent;}#mermaid-svg-1jP8YPFCbQU6hnp6 .label text,#mermaid-svg-1jP8YPFCbQU6hnp6 span{fill:#333;color:#333;}#mermaid-svg-1jP8YPFCbQU6hnp6 .node rect,#mermaid-svg-1jP8YPFCbQU6hnp6 .node circle,#mermaid-svg-1jP8YPFCbQU6hnp6 .node ellipse,#mermaid-svg-1jP8YPFCbQU6hnp6 .node polygon,#mermaid-svg-1jP8YPFCbQU6hnp6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1jP8YPFCbQU6hnp6 .rough-node .label text,#mermaid-svg-1jP8YPFCbQU6hnp6 .node .label text,#mermaid-svg-1jP8YPFCbQU6hnp6 .image-shape .label,#mermaid-svg-1jP8YPFCbQU6hnp6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-1jP8YPFCbQU6hnp6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1jP8YPFCbQU6hnp6 .rough-node .label,#mermaid-svg-1jP8YPFCbQU6hnp6 .node .label,#mermaid-svg-1jP8YPFCbQU6hnp6 .image-shape .label,#mermaid-svg-1jP8YPFCbQU6hnp6 .icon-shape .label{text-align:center;}#mermaid-svg-1jP8YPFCbQU6hnp6 .node.clickable{cursor:pointer;}#mermaid-svg-1jP8YPFCbQU6hnp6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1jP8YPFCbQU6hnp6 .arrowheadPath{fill:#333333;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1jP8YPFCbQU6hnp6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1jP8YPFCbQU6hnp6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1jP8YPFCbQU6hnp6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1jP8YPFCbQU6hnp6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1jP8YPFCbQU6hnp6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1jP8YPFCbQU6hnp6 .cluster text{fill:#333;}#mermaid-svg-1jP8YPFCbQU6hnp6 .cluster span{color:#333;}#mermaid-svg-1jP8YPFCbQU6hnp6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1jP8YPFCbQU6hnp6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1jP8YPFCbQU6hnp6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-1jP8YPFCbQU6hnp6 .icon-shape,#mermaid-svg-1jP8YPFCbQU6hnp6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1jP8YPFCbQU6hnp6 .icon-shape p,#mermaid-svg-1jP8YPFCbQU6hnp6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1jP8YPFCbQU6hnp6 .icon-shape .label rect,#mermaid-svg-1jP8YPFCbQU6hnp6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1jP8YPFCbQU6hnp6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1jP8YPFCbQU6hnp6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1jP8YPFCbQU6hnp6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} head
listpack 节点1
msg1 / msg2 / msg3
listpack 节点2
msg4 / msg5 / msg6
listpack 节点3
msg7 / msg8 / msg9
tail
这种设计巧妙地平衡了:
- 链表的优势:头尾插入删除 O(1),支持长列表
- 紧凑存储的优势:节点内连续内存,缓存友好,减少内存开销
实战场景:
bash
# 消息队列(生产者-消费者)
RPUSH task_queue "send_email:1001"
RPUSH task_queue "generate_report:2001"
BLPOP task_queue 0 # 无限等待
# 最新消息列表(用 LTRIM 控制长度)
LPUSH news_feed "news_1"
LPUSH news_feed "news_2"
LTRIM news_feed 0 49 # 只保留最新 50 条
# 时间线 / 动态流
LPUSH user:timeline:1001 "post_1001" "post_1002"
LRANGE user:timeline:1001 0 19 # 分页取最近 20 条
2.4 Set ------ 无序去重的集合
Set 是无序的、元素不重复的集合。基于哈希表实现,增删查都是 O(1)。它还提供了强大的集合运算能力(交集、并集、差集)。
常用命令:
bash
# 基本操作
SADD tags:article:1001 redis java spring
SREM tags:article:1001 spring # 移除元素
SMEMBERS tags:article:1001 # 获取全部成员
SISMEMBER tags:article:1001 redis # 判断是否存在
SCARD tags:article:1001 # 成员数量
SRANDMEMBER tags:article:1001 2 # 随机获取 2 个成员
SPOP tags:article:1001 # 随机弹出一个成员
# 集合运算
SINTER set1 set2 # 交集 ------ 共同好友
SUNION set1 set2 # 并集 ------ 全部好友
SDIFF set1 set2 # 差集 ------ 可能认识的人(set1 有 set2 没有)
SINTERSTORE dest set1 set2 # 将交集结果存到 dest
底层编码:
| 编码 | 条件 | 说明 |
|---|---|---|
intset |
元素全是整数且数量少 | 有序整数数组,二分查找,极省内存 |
listpack / hashtable |
其他情况 | 类似 Hash 的编码策略 |
实战场景:
bash
# 用户标签
SADD user:tags:1001 "技术" "音乐" "摄影"
SADD user:tags:1002 "技术" "运动" "旅行"
SINTER user:tags:1001 user:tags:1002 # 共同标签 → ["技术"]
# 抽奖系统
SADD lottery:pool user:1 user:2 user:3 ... # 参与用户
SPOP lottery:pool 3 # 随机抽取 3 个中奖者(不重复)
# 点赞(去重保证一人只能点一次)
SADD like:article:1001 user:1005
SCARD like:article:1001 # 总点赞数
SISMEMBER like:article:1001 user:1005 # 当前用户是否点过赞
# 共同关注
SINTER following:user:1001 following:user:1002
2.5 Sorted Set ------ 带权重的有序集合
Sorted Set 是 Redis 中最强大的数据结构之一。每个元素关联一个 score(分数),元素按 score 排序。元素的唯一性由 member 保证,排序由 score 决定,score 相同时按 member 字典序排列。
常用命令:
bash
# 基本操作
ZADD leaderboard 100 "alice" 95 "bob" 88 "charlie"
ZREM leaderboard "charlie"
ZSCORE leaderboard "alice" # 获取 alice 的分数
ZRANK leaderboard "alice" # 获取 alice 的排名(升序,从 0 开始)
ZREVRANK leaderboard "alice" # 降序排名
# 范围查询(这才是 Sorted Set 的杀手锏)
ZRANGE leaderboard 0 9 # 前 10 名(按 score 升序)
ZREVRANGE leaderboard 0 9 # 前 10 名(按 score 降序)
ZRANGE leaderboard 0 9 WITHSCORES # 带分数返回
ZRANGEBYSCORE leaderboard 80 100 # 按分数范围查询
ZRANGEBYSCORE leaderboard 80 100 WITHSCORES LIMIT 0 10
# 聚合操作
ZINCRBY leaderboard 5 "alice" # alice 分数 +5
ZCARD leaderboard # 成员总数
ZCOUNT leaderboard 80 100 # 分数在 [80, 100] 区间的数量
ZREM leaderboard "alice" # 删除成员
# 集合运算
ZINTERSTORE dest 2 set1 set2 WEIGHTS 1 2 # 加权交集
ZUNIONSTORE dest 2 set1 set2 # 并集
底层编码:Sorted Set 同时使用了两种数据结构:
#mermaid-svg-jbRNTC1iHYGBZGGc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jbRNTC1iHYGBZGGc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jbRNTC1iHYGBZGGc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jbRNTC1iHYGBZGGc .error-icon{fill:#552222;}#mermaid-svg-jbRNTC1iHYGBZGGc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jbRNTC1iHYGBZGGc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jbRNTC1iHYGBZGGc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jbRNTC1iHYGBZGGc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jbRNTC1iHYGBZGGc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jbRNTC1iHYGBZGGc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jbRNTC1iHYGBZGGc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jbRNTC1iHYGBZGGc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jbRNTC1iHYGBZGGc .marker.cross{stroke:#333333;}#mermaid-svg-jbRNTC1iHYGBZGGc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jbRNTC1iHYGBZGGc p{margin:0;}#mermaid-svg-jbRNTC1iHYGBZGGc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jbRNTC1iHYGBZGGc .cluster-label text{fill:#333;}#mermaid-svg-jbRNTC1iHYGBZGGc .cluster-label span{color:#333;}#mermaid-svg-jbRNTC1iHYGBZGGc .cluster-label span p{background-color:transparent;}#mermaid-svg-jbRNTC1iHYGBZGGc .label text,#mermaid-svg-jbRNTC1iHYGBZGGc span{fill:#333;color:#333;}#mermaid-svg-jbRNTC1iHYGBZGGc .node rect,#mermaid-svg-jbRNTC1iHYGBZGGc .node circle,#mermaid-svg-jbRNTC1iHYGBZGGc .node ellipse,#mermaid-svg-jbRNTC1iHYGBZGGc .node polygon,#mermaid-svg-jbRNTC1iHYGBZGGc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jbRNTC1iHYGBZGGc .rough-node .label text,#mermaid-svg-jbRNTC1iHYGBZGGc .node .label text,#mermaid-svg-jbRNTC1iHYGBZGGc .image-shape .label,#mermaid-svg-jbRNTC1iHYGBZGGc .icon-shape .label{text-anchor:middle;}#mermaid-svg-jbRNTC1iHYGBZGGc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jbRNTC1iHYGBZGGc .rough-node .label,#mermaid-svg-jbRNTC1iHYGBZGGc .node .label,#mermaid-svg-jbRNTC1iHYGBZGGc .image-shape .label,#mermaid-svg-jbRNTC1iHYGBZGGc .icon-shape .label{text-align:center;}#mermaid-svg-jbRNTC1iHYGBZGGc .node.clickable{cursor:pointer;}#mermaid-svg-jbRNTC1iHYGBZGGc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jbRNTC1iHYGBZGGc .arrowheadPath{fill:#333333;}#mermaid-svg-jbRNTC1iHYGBZGGc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jbRNTC1iHYGBZGGc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jbRNTC1iHYGBZGGc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jbRNTC1iHYGBZGGc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jbRNTC1iHYGBZGGc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jbRNTC1iHYGBZGGc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jbRNTC1iHYGBZGGc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jbRNTC1iHYGBZGGc .cluster text{fill:#333;}#mermaid-svg-jbRNTC1iHYGBZGGc .cluster span{color:#333;}#mermaid-svg-jbRNTC1iHYGBZGGc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jbRNTC1iHYGBZGGc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jbRNTC1iHYGBZGGc rect.text{fill:none;stroke-width:0;}#mermaid-svg-jbRNTC1iHYGBZGGc .icon-shape,#mermaid-svg-jbRNTC1iHYGBZGGc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jbRNTC1iHYGBZGGc .icon-shape p,#mermaid-svg-jbRNTC1iHYGBZGGc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jbRNTC1iHYGBZGGc .icon-shape .label rect,#mermaid-svg-jbRNTC1iHYGBZGGc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jbRNTC1iHYGBZGGc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jbRNTC1iHYGBZGGc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jbRNTC1iHYGBZGGc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} skiplist(跳表)
score 排序
范围查询 O(log N)
排名查询 O(log N)
dict(哈希表)
member → score
单点查询 O(1)
同一个元素
同时存在于
两个结构中
- dict:通过 member 快速找到 score,O(1)
- skiplist:通过 score 进行范围查询和排名,O(log N)
关于跳表(Skip List)的详细原理,将在第二篇「深入数据结构」中展开,这里先记住结论:它是一种通过多层索引实现 O(log N) 查找的概率性数据结构,比红黑树更容易实现,也更适合范围查询。
实战场景:
bash
# 排行榜(实时更新,海量数据下仍能毫秒级响应)
ZADD game:rank:global 99999 "player:A"
ZINCRBY game:rank:global 500 "player:A"
ZREVRANGE game:rank:global 0 9 WITHSCORES # Top 10
# 延迟队列
ZADD delay_queue 1686120000 "order:timeout:1001" # score = 过期时间戳
ZRANGEBYSCORE delay_queue 0 1686120000 LIMIT 0 10 # 取出已到期的任务
# 带权重的标签
ZADD article:tags:1001 10 "redis" 8 "database" 5 "cache"
# 时间段限制(滑动窗口限流器的基础)
ZADD sliding_window 1686120001 "req:1"
ZREMRANGEBYSCORE sliding_window 0 1686119400 # 删除 60 秒之前的记录
ZCARD sliding_window # 当前窗口内的请求数
三、通用命令:那些你必须知道的全局操作
以下命令不区分数据类型,是操作 Redis 时必须掌握的基础能力。
3.1 Keys 相关
bash
# ⚠️ 生产环境慎用!会阻塞 Redis
KEYS pattern # 遍历所有 key,如 KEYS user:*
# ✅ 生产环境使用 SCAN(游标迭代,非阻塞)
SCAN 0 MATCH user:* COUNT 100 # 返回 (游标, [匹配的key列表])
# 下次迭代用返回的游标继续
SCAN 17 MATCH user:* COUNT 100
# 其他
EXISTS key # key 是否存在
TYPE key # key 的类型
DEL key1 key2 # 删除 key(阻塞删除)
UNLINK key1 key2 # 异步删除(Redis 4.0+,非阻塞,推荐)
RENAME old new # 重命名
SCAN 的注意事项:
- SCAN 不保证每次返回的数量严格等于 COUNT,COUNT 只是一个建议值
- SCAN 可能返回重复元素,业务层需要去重
- 在迭代过程中,key 的增删可能会影响结果------新增的 key 可能被返回也可能不会,已删除的 key 同理
3.2 过期与 TTL
bash
EXPIRE key 60 # 60 秒后过期
PEXPIRE key 60000 # 60000 毫秒后过期
EXPIREAT key 1686120000 # 指定 Unix 时间戳过期
TTL key # 查看剩余过期时间(秒),-1 永不过期,-2 已过期
PERSIST key # 移除过期时间,变为永久 key
过期键的删除策略是面试高频考点,这里展开讲一下:
| 策略 | 说明 | 优缺点 |
|---|---|---|
| 惰性删除 | 访问 key 时检查是否过期,过期则删除 | 对 CPU 友好,但可能堆积大量过期 key 不释放内存 |
| 定期删除 | 每隔一段时间随机抽取一批 key 检查并删除过期 key | 折中方案,平衡 CPU 和内存 |
| 定时删除 | 每个 key 创建定时器,到期立即删除 | 对 CPU 最不友好,Redis 未采用 |
Redis 实际采用:惰性删除 + 定期删除,两者配合使用。
3.3 数据库管理
bash
SELECT 0 # 切换数据库(默认 0-15,共 16 个)
DBSIZE # 当前数据库 key 数量
FLUSHDB # 清空当前数据库(⚠️ 危险操作)
FLUSHALL # 清空所有数据库(⚠️ 危险操作)
生产环境建议 :不要使用多数据库(SELECT),而是通过 key 前缀做命名空间隔离,如
user:cache:1001、order:cache:2001。原因很简单:Redis Cluster 只支持 db0,多数据库在集群模式下不可用。
四、Redis vs Memcached:为什么选 Redis?
这可能是每个后端开发者都绕不开的问题。我从 6 个维度做了对比:
| 维度 | Redis | Memcached |
|---|---|---|
| 数据结构 | String、Hash、List、Set、Sorted Set、Stream、GEO 等 | 仅 String(二进制安全的字符串) |
| 持久化 | RDB + AOF + 混合持久化 | 不支持,重启数据全丢 |
| 集群 | 原生 Cluster(去中心化) | 客户端一致性哈希(无官方集群方案) |
| 线程模型 | 单线程执行命令(6.0+ 引入多线程 I/O) | 多线程 |
| 内存管理 | jemalloc,支持内存回收、惰性删除 | slab allocation,易产生碎片,不支持回收 |
| Value 上限 | 512MB | 1MB |
| 过期策略 | 惰性删除 + 定期删除 | 惰性删除 + LRU 淘汰(到达内存上限时) |
| Lua 脚本 | 支持,原子性执行 | 不支持 |
| 发布订阅 | 原生 Pub/Sub + Stream | 不支持 |
| 事务 | MULTI/EXEC(弱事务,不支持回滚) | 不支持 |
选型建议
选 Redis 的场景(覆盖 90% 的需求):
- 需要复杂数据结构(排行榜、社交关系、实时计数)
- 需要持久化(缓存数据不能丢)
- 需要高可用(Sentinel / Cluster)
- 需要消息队列、分布式锁等高级特性
- 数据量较大(单个 value 可能超过 1MB)
选 Memcached 的场景(现在越来越少了):
- 纯 KV 缓存,对数据结构零需求
- 多线程环境下需要利用多核 CPU(Redis 6.0 之前这条是硬差距)
- 对内存利用率极度敏感(slab 在某些场景下比 jemalloc 更可控)
- 团队有多年的 Memcached 运维经验,迁移成本过高
一句话总结:新项目无脑选 Redis,老项目评估迁移成本再决定。
欢迎关注,不错过后续更新。
如有疑问或建议,欢迎在评论区交流。