Redis 从入门到精通(一):重新认识 Redis —— 不只是缓存

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:1001order: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,老项目评估迁移成本再决定。



欢迎关注,不错过后续更新。

如有疑问或建议,欢迎在评论区交流。

相关推荐
IT策士2 小时前
Redis 从入门到精通:事务与 Lua 脚本
redis·junit·lua
胡小禾7 小时前
Redis哨兵模式下主从同步的偏差
数据库·redis·缓存
zzqssliu7 小时前
Taocarts接口限流实操:基于Redis实现API防刷与流量管控
数据库·redis·缓存
啦啦啦啦啦zzzz7 小时前
redis的持久化操作和主从复制与集群的关系及其应用
数据库·redis
IT策士7 小时前
Redis 从入门到精通:分片之道 —— Redis Cluster
数据库·redis·缓存
AOwhisky8 小时前
学习自测与解析:Redis系列第一期与第二期核心知识点详解
运维·数据库·redis·学习·云计算
Java爱好狂.8 小时前
阿里1658页2026最新Java面试题总结(含答案)
数据库·redis·程序员·java面试·java面试题·java编程·java八股文
布朗克1689 小时前
40 Redis与微服务入门
java·数据库·redis·微服务
北极星日淘9 小时前
Python爬虫断点续爬实战|基于Redis实现日淘商品增量抓取(解决重启全量重爬问题)
redis·爬虫·python
IT策士9 小时前
Redis 从入门到精通:Redis Sentinel 哨兵
数据库·redis·sentinel