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



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

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

相关推荐
IT大白鼠3 天前
Redis 系列 · 第 01 篇——认知入门:Redis 是什么
redis·nosql
彧azz3 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
lbb 小魔仙3 天前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
夕除3 天前
redis--018
redis
两锭子3 天前
Redis基础
redis
烟沙九洲3 天前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索3 天前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞3 天前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路3 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog3 天前
Java 接入Redis: 通用命令与键管理
java·服务器·redis