redis从零单排(二)

0 阅读16分钟

风度翩翩 image.png

image.png

image.png


想象你是 Redis 图书馆的管理员。每天你要处理: 整理书架(键操作)登记书籍(String)管理读者档案(Hash)排队借书(List)分类标签(Set)排行榜(ZSet)处理事务(Transaction)广播通知(Pub/Sub)执行脚本(Lua)统计访客(HyperLogLog)定位书架(GEO)连接管理(Connection)系统维护(Server)


第一章:键(Key)通用命令 —— 图书馆的"索引系统"

graph TD
    A[键命令] --> B[删除 DEL]
    A --> C[存在检查 EXISTS]
    A --> D[过期设置 EXPIRE/TTL/PERSIST]
    A --> E[类型查询 TYPE]
    A --> F[键遍历 KEYS/RANDOMKEY]
    A --> G[重命名 RENAME]
    A --> H[序列化 DUMP]
    
    style A fill:#E1F5FE

1.1 DEL —— 删除键

# 语法:DEL key [key ...]
# 删除单个键
DEL name
# 输出:(integer) 1    ← 成功删除1个键

# 删除多个键
DEL name age city
# 输出:(integer) 3    ← 成功删除3个键

# 删除不存在的键
DEL not_exist_key
# 输出:(integer) 0    ← 键不存在,删除0个

流程图:

graph LR
    A[DEL name] --> B{键是否存在?}
    B -->|存在| C[删除键<br/>返回 1]
    B -->|不存在| D[直接返回<br/>返回 0]
    
    style C fill:#90EE90
    style D fill:#FFB6C1

1.2 EXISTS —— 检查键是否存在

# 语法:EXISTS key [key ...]
SET name "zhangsan"
EXISTS name
# 输出:(integer) 1    ← 存在

EXISTS not_exist
# 输出:(integer) 0    ← 不存在

# 检查多个
SET age 20
EXISTS name age not_exist
# 输出:(integer) 2    ← 2个存在

1.3 EXPIRE / TTL / PERSIST —— 过期时间管理

# 设置过期时间(秒)
SET session_token "abc123"
EXPIRE session_token 60
# 输出:(integer) 1    ← 设置成功

# 查看剩余时间
TTL session_token
# 输出:(integer) 58   ← 还剩58秒

TTL not_exist_key
# 输出:(integer) -2  ← 键不存在

SET permanent_key "value"
TTL permanent_key
# 输出:(integer) -1  ← 永不过期

# 移除过期时间(变为永久)
PERSIST session_token
# 输出:(integer) 1    ← 成功移除
TTL session_token
# 输出:(integer) -1  ← 现在永不过期了

过期时间管理流程:

graph TD
    A[设置键] --> B{需要过期?}
    B -->|是| C[EXPIRE key seconds]
    C --> D[TTL 查看剩余时间]
    D --> E{时间到?}
    E -->|是| F[自动删除]
    E -->|否| D
    B -->|否| G[永久存储<br/>TTL = -1]
    C --> H[PERSIST<br/>取消过期]
    H --> G
    
    style F fill:#FFB6C1
    style G fill:#90EE90

1.4 TYPE —— 查看键的数据类型

SET name "zhangsan"
TYPE name
# 输出:string

LPUSH list1 a b c
TYPE list1
# 输出:list

HSET user:1 name "zhangsan"
TYPE user:1
# 输出:hash

SADD set1 1 2 3
TYPE set1
# 输出:set

ZADD zset1 10 a 20 b
TYPE zset1
# 输出:zset

1.5 KEYS —— 模式匹配查找键(⚠️ 生产环境慎用)

# 语法:KEYS pattern
SET user:1:name "zhangsan"
SET user:1:age 20
SET user:2:name "lisi"
SET article:1:title "Redis入门"

# 查找所有 user 开头的键
KEYS user:*
# 输出:
# 1) "user:1:name"
# 2) "user:1:age"
# 3) "user:2:name"

# 查找所有以 name 结尾的键
KEYS *name
# 输出:
# 1) "user:1:name"
# 2) "user:2:name"

# 查找单个字符
SET a 1
SET b 2
KEYS ?
# 输出:
# 1) "a"
# 2) "b"

⚠️ 警告: KEYS 会遍历整个数据库,数据量大时阻塞其他命令!

替代方案:

# 使用 SCAN 渐进式遍历(Redis 2.8+)
SCAN 0 MATCH user:* COUNT 100
# 输出:
# 1) "17"                    ← 下次游标
# 2) 1) "user:1:name"
#    2) "user:1:age"
#    3) "user:2:name"

SCAN 17 MATCH user:* COUNT 100
# 当返回游标为 0 时,遍历完成

1.6 RANDOMKEY —— 随机返回一个键

# 假设数据库中有:name, age, city
RANDOMKEY
# 输出:"city"   ← 随机返回一个键

# 空数据库
FLUSHDB
RANDOMKEY
# 输出:(nil)    ← 空库返回 nil

1.7 RENAME / RENAMENX —— 重命名键

SET name "zhangsan"

# 重命名(覆盖目标键)
RENAME name username
# 输出:OK
GET username
# 输出:"zhangsan"

# 重命名(目标键已存在则失败)
SET old_key "value1"
SET new_key "value2"
RENAMENX old_key new_key
# 输出:(integer) 0    ← 失败,new_key 已存在

DEL new_key
RENAMENX old_key new_key
# 输出:(integer) 1    ← 成功

1.8 DUMP —— 序列化键值

SET name "zhangsan"
DUMP name
# 输出:"\x00\bzhangsan\t\x00\xb4\x8e\x8e\xa3\x8b\xe9\x8c"  ← 二进制序列化数据

# 配合 RESTORE 可迁移数据
RESTORE name_copy 0 "\x00\bzhangsan\t\x00\xb4\x8e\x8e\xa3\x8b\xe9\x8c"
# 输出:OK
GET name_copy
# 输出:"zhangsan"

第二章:字符串(String)— 最基础的"便签纸"

graph TD
    A[String命令] --> B[基础读写 SET/GET]
    A --> C[原子计数 INCR/DECR]
    A --> D[批量操作 MSET/MGET]
    A --> E[过期设置 SETEX/SETNX]
    A --> F[追加修改 APPEND]
    A --> G[获取设置 GETSET]
    A --> H[字符串长度 STRLEN]
    
    style A fill:#E8F5E9

2.1 SET / GET —— 基础读写

# 基础设置
SET name "zhangsan"
# 输出:OK

# 获取
GET name
# 输出:"zhangsan"

# 设置带过期时间
SETEX code 60 1234
# 输出:OK
TTL code
# 输出:(integer) 58

# 仅当不存在时才设置(分布式锁基础)
SETNX lock:resource "owner"
# 输出:(integer) 1    ← 设置成功

SETNX lock:resource "other"
# 输出:(integer) 0    ← 已存在,设置失败

2.2 MSET / MGET —— 批量操作(减少网络往返)

# 批量设置
MSET name "zhangsan" age 20 city "Beijing"
# 输出:OK

# 批量获取
MGET name age city
# 输出:
# 1) "zhangsan"
# 2) "20"
# 3) "Beijing"

# 批量设置(仅当都不存在)
MSETNX name "lisi" gender "male"
# 输出:(integer) 0    ← 失败,因为 name 已存在
# MSETNX 是原子操作,任一键存在则全部失败

批量 vs 单条性能对比:

image.png

2.3 INCR / DECR / INCRBY / DECRBY —— 原子计数器

# 初始化计数器
SET count 0

# 原子递增
INCR count
# 输出:(integer) 1

INCR count
# 输出:(integer) 2

# 指定增量
INCRBY count 5
# 输出:(integer) 7

# 原子递减
DECR count
# 输出:(integer) 6

DECRBY count 3
# 输出:(integer) 3

# 应用场景:文章阅读量
INCR view:article:1001
# 输出:(integer) 1
INCR view:article:1001
# 输出:(integer) 2

原子性保证:

sequenceDiagram
    participant ClientA
    participant Redis
    participant ClientB
    
    ClientA->>Redis: GET count → 0
    ClientB->>Redis: GET count → 0
    ClientA->>Redis: INCR count → 1
    ClientB->>Redis: INCR count → 2
    
    Note over Redis: INCR 是原子操作<br/>无需 WATCH 事务<br/>天然线程安全

2.4 APPEND —— 追加内容

SET name "zhang"
APPEND name "san"
# 输出:(integer) 8    ← 返回最终长度
GET name
# 输出:"zhangsan"

# 追加数字(注意是字符串拼接)
APPEND name "666"
# 输出:(integer) 11
GET name
# 输出:"zhangsan666"

2.5 STRLEN —— 获取字符串长度

SET name "zhangsan"
STRLEN name
# 输出:(integer) 8

STRLEN not_exist
# 输出:(integer) 0    ← 不存在的键长度为0

2.6 GETSET —— 获取旧值并设置新值

SET counter 100
GETSET counter 200
# 输出:"100"          ← 返回旧值
GET counter
# 输出:"200"          ← 新值已设置

# 应用场景:重置计数器时获取上次值
GETSET view:today 0
# 返回昨天的阅读量,同时重置为0

第三章:哈希(Hash)— 存储对象的"档案柜"

graph TD
    A[Hash命令] --> B[单字段 HSET/HGET]
    A --> C[多字段 HMSET/HMGET]
    A --> D[获取全部 HGETALL/HKEYS/HVALS]
    A --> E[删除字段 HDEL]
    A --> F[字段计数 HLEN]
    A --> G[字段存在 HEXISTS]
    A --> H[原子递增 HINCRBY]
    
    style A fill:#FFF3E0

3.1 HSET / HGET —— 单字段操作

# 设置用户档案
HSET user:1 id 1
# 输出:(integer) 1    ← 新建字段

HSET user:1 name "zhangsan"
# 输出:(integer) 1

HGET user:1 id
# 输出:"1"

HGET user:1 name
# 输出:"zhangsan"

# 获取不存在的字段
HGET user:1 age
# 输出:(nil)

3.2 HMSET / HMGET —— 批量字段操作

# 批量设置(Redis 4.0+ 中 HSET 也可批量)
HMSET user:2 name "lisi" age 25 city "Shanghai"
# 输出:OK

HMGET user:2 name age city
# 输出:
# 1) "lisi"
# 2) "25"
# 3) "Shanghai"

# 对比 String 存储 JSON
SET user:2_json '{"name":"lisi","age":25,"city":"Shanghai"}'
# 修改年龄需要:GET → 解析JSON → 修改 → 序列化 → SET(5步!)
# Hash 只需:HSET user:2 age 26(1步!)

3.3 HGETALL / HKEYS / HVALS —— 获取全部信息

HGETALL user:2
# 输出:
# 1) "name"
# 2) "lisi"
# 3) "age"
# 4) "25"
# 5) "city"
# 6) "Shanghai"

HKEYS user:2
# 输出:
# 1) "name"
# 2) "age"
# 3) "city"

HVALS user:2
# 输出:
# 1) "lisi"
# 2) "25"
# 3) "Shanghai"

3.4 HDEL / HLEN / HEXISTS —— 删除、计数、检查

# 删除字段
HDEL user:2 city
# 输出:(integer) 1    ← 删除了1个字段

# 字段数量
HLEN user:2
# 输出:(integer) 2    ← 还剩 name, age

# 检查字段存在
HEXISTS user:2 name
# 输出:(integer) 1

HEXISTS user:2 city
# 输出:(integer) 0    ← 已删除

3.5 HINCRBY —— 字段原子递增

HSET user:1 age 20
HINCRBY user:1 age 1
# 输出:(integer) 21

HINCRBY user:1 visit_count 1
# 输出:(integer) 1    ← 字段不存在,从0开始递增

第四章:列表(List)— 排队系统的"魔法通道"

graph TD
    A[List命令] --> B[左侧操作 LPUSH/LPOP]
    A --> C[右侧操作 RPUSH/RPOP]
    A --> D[范围查询 LRANGE]
    A --> E[长度查询 LLEN]
    A --> F[插入修改 LINSERT/LSET]
    A --> G[阻塞弹出 BLPOP/BRPOP]
    A --> H[转移元素 RPOPLPUSH]
    A --> I[修剪列表 LTRIM]
    
    style A fill:#F3E5F5

4.1 LPUSH / RPUSH —— 入队操作

# 左侧入队(栈顶/队列头部)
LPUSH list1 a b c
# 输出:(integer) 3    ← 列表长度
# 列表状态:[c, b, a]  ← c 在最左边(头部)

# 右侧入队(队列尾部)
RPUSH list1 x y
# 输出:(integer) 5
# 列表状态:[c, b, a, x, y]

4.2 LPOP / RPOP —— 出队操作

# 左侧弹出
LPOP list1
# 输出:"c"            ← 弹出最左边的元素
# 列表状态:[b, a, x, y]

# 右侧弹出
RPOP list1
# 输出:"y"            ← 弹出最右边的元素
# 列表状态:[b, a, x]

队列 vs 栈:

image.png

4.3 LRANGE —— 范围查询(分页核心)

# 重建列表
RPUSH list2 a b c d e f g

# 获取全部(0 到 -1 表示从头到尾)
LRANGE list2 0 -1
# 输出:
# 1) "a"
# 2) "b"
# 3) "c"
# 4) "d"
# 5) "e"
# 6) "f"
# 7) "g"

# 获取前3个(分页第一页)
LRANGE list2 0 2
# 输出:
# 1) "a"
# 2) "b"
# 3) "c"

# 获取第4-6个(分页第二页)
LRANGE list2 3 5
# 输出:
# 1) "d"
# 2) "e"
# 3) "f"

4.4 LLEN / LINSERT / LSET / LTRIM

# 列表长度
LLEN list2
# 输出:(integer) 7

# 在元素前/后插入
LINSERT list2 BEFORE c "insert_before_c"
# 输出:(integer) 8    ← 新长度
LRANGE list2 0 -1
# 输出:[a, b, insert_before_c, c, d, e, f, g]

LINSERT list2 AFTER c "insert_after_c"
# 输出:(integer) 9

# 修改指定索引
LSET list2 0 "new_a"
# 输出:OK

# 修剪列表(只保留指定范围)
LTRIM list2 0 4
# 输出:OK
LRANGE list2 0 -1
# 输出:[new_a, b, insert_before_c, c, insert_after_c]

4.5 BLPOP / BRPOP —— 阻塞弹出(消息队列核心)

# 客户端A执行(列表为空,阻塞等待)
BLPOP queue:orders 30
# 阻塞中... 等待30秒

# 客户端B执行
LPUSH queue:orders "order_001"

# 客户端A立即收到:
# 1) "queue:orders"
# 2) "order_001"

消息队列流程:

sequenceDiagram
    participant Producer
    participant Redis
    participant Consumer
    
    Consumer->>Redis: BLPOP queue 30
    Redis-->>Consumer: 阻塞等待...
    
    Producer->>Redis: LPUSH queue "msg1"
    Redis-->>Consumer: 返回 "msg1"
    
    Consumer->>Consumer: 处理消息...
    
    Note over Redis: 多个消费者时<br/>轮询分发

4.6 RPOPLPUSH —— 安全队列模式

# 原子操作:从 source 弹出,压入 destination
RPOPLPUSH queue:processing queue:backup
# 输出:"order_001"    ← 弹出的元素

# 应用场景:处理中的任务备份
# 1. 从待处理队列取出
# 2. 放入处理中队列
# 3. 处理完成后删除
# 如果处理失败,可从 backup 恢复

第五章:集合(Set)— 标签与关系的"社交网络"

graph TD
    A[Set命令] --> B[添加删除 SADD/SREM]
    A --> C[成员查询 SMEMBERS/SISMEMBER]
    A --> D[集合运算 SINTER/SUNION/SDIFF]
    A --> E[随机操作 SRANDMEMBER/SPOP]
    A --> F[集合信息 SCARD]
    
    style A fill:#FFEBEE

5.1 SADD / SREM / SMEMBERS —— 基础操作

# 添加标签
SADD tags:article:1001 redis database cache
# 输出:(integer) 3    ← 添加了3个新成员

# 重复添加
SADD tags:article:1001 redis
# 输出:(integer) 0    ← redis 已存在,未添加

# 查看所有标签
SMEMBERS tags:article:1001
# 输出:
# 1) "cache"
# 2) "database"
# 3) "redis"

# 删除标签
SREM tags:article:1001 cache
# 输出:(integer) 1

5.2 SISMEMBER / SCARD —— 查询与计数

# 判断成员是否存在
SISMEMBER tags:article:1001 redis
# 输出:(integer) 1

SISMEMBER tags:article:1001 java
# 输出:(integer) 0

# 集合大小
SCARD tags:article:1001
# 输出:(integer) 2

5.3 SINTER / SUNION / SDIFF —— 集合运算(社交关系核心)

# 用户A的好友
SADD friends:user:A B C D E
# 输出:(integer) 5

# 用户B的好友
SADD friends:user:B C D F G
# 输出:(integer) 5

# 共同好友(交集)
SINTER friends:user:A friends:user:B
# 输出:
# 1) "C"
# 2) "D"

# 所有好友(并集)
SUNION friends:user:A friends:user:B
# 输出:
# 1) "B"
# 2) "C"
# 3) "D"
# 4) "E"
# 5) "F"
# 6) "G"

# A有但B没有的好友(差集)
SDIFF friends:user:A friends:user:B
# 输出:
# 1) "B"
# 2) "E"

社交关系图:

graph LR
    subgraph "用户A的好友"
        A1[B]
        A2[C]
        A3[D]
        A4[E]
    end
    
    subgraph "用户B的好友"
        B1[C]
        B2[D]
        B3[F]
        B4[G]
    end
    
    A2 --- B1
    A3 --- B2
    
    style A2 fill:#90EE90
    style A3 fill:#90EE90
    style B1 fill:#90EE90
    style B2 fill:#90EE90

5.4 SRANDMEMBER / SPOP —— 随机操作(抽奖场景)

# 抽奖参与者
SADD lottery:participants user1 user2 user3 user4 user5

# 随机查看3个(不删除)
SRANDMEMBER lottery:participants 3
# 输出:
# 1) "user2"
# 2) "user5"
# 3) "user3"

# 随机抽取3个(删除,不能重复中奖)
SPOP lottery:participants 3
# 输出:
# 1) "user1"
# 2) "user4"
# 3) "user5"

# 剩余参与者
SMEMBERS lottery:participants
# 输出:
# 1) "user2"
# 2) "user3"

第六章:有序集合(Sorted Set / ZSet)— 排行榜的"魔法榜"

graph TD
    A[ZSet命令] --> B[添加 ZADD]
    A --> C[范围查询 ZRANGE/ZREVRANGE]
    A --> D[分数查询 ZSCORE/ZRANK]
    A --> E[分数递增 ZINCRBY]
    A --> F[删除 ZREM]
    A --> G[范围计数 ZCOUNT]
    A --> H[分数范围 ZRANGEBYSCORE]
    
    style A fill:#E8EAF6

6.1 ZADD —— 添加带分数的成员

# 添加游戏排行榜
ZADD leaderboard 100 "player:张三" 85 "player:李四" 120 "player:王五" 95 "player:赵六"
# 输出:(integer) 4    ← 添加了4个成员

# 更新分数
ZADD leaderboard 130 "player:张三"
# 输出:(integer) 0    ← 0表示更新已有成员

6.2 ZRANGE / ZREVRANGE —— 范围查询(排行榜展示)

# 按分数从低到高(正序)
ZRANGE leaderboard 0 -1 WITHSCORES
# 输出:
#  1) "player:李四"
#  2) "85"
#  3) "player:赵六"
#  4) "95"
#  5) "player:王五"
#  6) "120"
#  7) "player:张三"
#  8) "130"

# 按分数从高到低(倒序,排行榜常用)
ZREVRANGE leaderboard 0 2 WITHSCORES
# 输出前三名:
#  1) "player:张三"
#  2) "130"
#  3) "player:王五"
#  4) "120"
#  5) "player:赵六"
#  6) "95"

6.3 ZSCORE / ZRANK / ZREVRANK —— 查询个人信息

# 查询分数
ZSCORE leaderboard "player:张三"
# 输出:"130"

# 正序排名(从0开始)
ZRANK leaderboard "player:张三"
# 输出:(integer) 3    ← 第4名(正序)

# 倒序排名(实际排行榜名次)
ZREVRANK leaderboard "player:张三"
# 输出:(integer) 0    ← 第1名!

6.4 ZINCRBY —— 分数递增(实时更新)

# 张三又赢了比赛,加20分
ZINCRBY leaderboard 20 "player:张三"
# 输出:"150"           ← 新分数

# 查看更新后的排行榜
ZREVRANGE leaderboard 0 0 WITHSCORES
# 输出:
# 1) "player:张三"
# 2) "150"

6.5 ZCOUNT / ZRANGEBYSCORE —— 范围查询

# 统计分数在90-120之间的人数
ZCOUNT leaderboard 90 120
# 输出:(integer) 2    ← 赵六(95)和王五(120)

# 获取分数范围内的成员
ZRANGEBYSCORE leaderboard 90 120 WITHSCORES
# 输出:
# 1) "player:赵六"
# 2) "95"
# 3) "player:王五"
# 4) "120"

6.6 ZREM —— 删除成员

# 李四退出游戏
ZREM leaderboard "player:李四"
# 输出:(integer) 1

# 查看更新后的排行榜
ZREVRANGE leaderboard 0 -1 WITHSCORES
# 输出:
# 1) "player:张三"
# 2) "150"
# 3) "player:王五"
# 4) "120"
# 5) "player:赵六"
# 6) "95"

第七章:事务(Transaction)— 批量执行的"原子操作"

graph TD
    A[事务命令] --> B[开启 MULTI]
    A --> C[执行 EXEC]
    A --> D[取消 DISCARD]
    A --> E[乐观锁 WATCH]
    A --> F[取消监控 UNWATCH]
    
    style A fill:#FFF8E1

7.1 MULTI / EXEC —— 事务执行

# 开启事务
MULTI
# 输出:OK

# 命令入队(不会立即执行)
SET balance:A 100
SET balance:B 200
DECR balance:A 50
INCR balance:B 50

# 执行事务
EXEC
# 输出:
# 1) OK
# 2) OK
# 3) (integer) 50
# 4) (integer) 250

# 验证结果
GET balance:A
# 输出:"50"
GET balance:B
# 输出:"250"

事务执行流程:

sequenceDiagram
    participant Client
    participant Redis
    
    Client->>Redis: MULTI
    Redis-->>Client: OK
    
    Client->>Redis: SET a 1
    Redis-->>Client: QUEUED      ← 入队,不执行
    
    Client->>Redis: SET b 2
    Redis-->>Client: QUEUED
    
    Client->>Redis: EXEC
    Redis-->>Client: [OK, OK]    ← 原子执行所有命令
    
    Note over Redis: 事务中的命令<br/>要么全部执行<br/>要么全部不执行<br/>(但不支持回滚!)

7.2 DISCARD —— 取消事务

MULTI
SET temp:key "value"
INCR temp:counter
DISCARD
# 输出:OK

GET temp:key
# 输出:(nil)            ← 事务已取消,未执行

7.3 WATCH —— 乐观锁(并发控制核心)

# 监控余额
WATCH balance:A

# 获取当前值
GET balance:A
# 输出:"50"

# 开启事务
MULTI
DECR balance:A 30
# 假设此时另一个客户端修改了 balance:A
# EXEC 会失败!

EXEC
# 输出:(nil)            ← 监控的键被修改,事务取消!

# 需要重新获取最新值,重试
UNWATCH
WATCH balance:A
GET balance:A
# 输出:"100"            ← 新值
MULTI
DECR balance:A 30
EXEC
# 输出:(integer) 70

乐观锁 vs 悲观锁:

graph LR
    subgraph "悲观锁"
        A1[获取锁] --> B1[执行业务]
        B1 --> C1[释放锁]
        C1 --> D1[串行执行<br/>安全但慢]
    end
    
    subgraph "乐观锁 WATCH"
        A2[读取数据] --> B2[执行业务]
        B2 --> C2{数据是否变化?}
        C2 -->|否| D2[提交成功]
        C2 -->|是| E2[重试]
        D2 --> F2[并发执行<br/>快但可能重试]
    end
    
    style D1 fill:#FFB6C1
    style F2 fill:#90EE90

第八章:发布订阅(Pub/Sub)— 广播消息的"大喇叭"

graph TD
    A[Pub/Sub命令] --> B[发布 PUBLISH]
    A --> C[订阅 SUBSCRIBE]
    A --> D[模式订阅 PSUBSCRIBE]
    
    style A fill:#E0F2F1

8.1 PUBLISH / SUBSCRIBE —— 基础发布订阅

# 客户端A:订阅频道(阻塞等待消息)
SUBSCRIBE chat:room:1
# 输出:
# 1) "subscribe"
# 2) "chat:room:1"
# 3) (integer) 1        ← 当前订阅数

# 客户端B:发布消息
PUBLISH chat:room:1 "Hello everyone!"
# 输出:(integer) 1     ← 1个客户端收到

# 客户端A收到:
# 1) "message"
# 2) "chat:room:1"
# 3) "Hello everyone!"

发布订阅架构:

graph TD
    P1[发布者A] -->|PUBLISH| Channel[频道: chat:room:1]
    P2[发布者B] -->|PUBLISH| Channel
    
    Channel -->|消息推送| S1[订阅者1]
    Channel -->|消息推送| S2[订阅者2]
    Channel -->|消息推送| S3[订阅者3]
    
    style Channel fill:#90EE90

8.2 PSUBSCRIBE —— 模式订阅(通配符)

# 订阅所有以 chat: 开头的频道
PSUBSCRIBE chat:*
# 输出:
# 1) "psubscribe"
# 2) "chat:*"
# 3) (integer) 1

# 发布到任意匹配频道
PUBLISH chat:room:2 "Room 2 msg"
# 客户端收到:
# 1) "pmessage"
# 2) "chat:*"           ← 匹配的模式
# 3) "chat:room:2"      ← 实际频道
# 4) "Room 2 msg"       ← 消息内容

第九章:脚本(Lua Script)— 自定义逻辑的"编程接口"

graph TD
    A[Lua脚本命令] --> B[执行 EVAL]
    A --> C[加载 SCRIPT LOAD]
    A --> D[执行缓存 SCRIPT EXISTS/EVALSHA]
    A --> E[清空 SCRIPT FLUSH]
    
    style A fill:#FCE4EC

9.1 EVAL —— 直接执行 Lua 脚本

# 语法:EVAL script numkeys key [key ...] arg [arg ...]
# numkeys: 后面有多少个键名参数

# 示例:原子性检查并设置
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('set', KEYS[1], ARGV[2]) else return nil end" 1 mykey oldvalue newvalue
# 如果 mykey 的值等于 oldvalue,则设置为 newvalue

# 示例:返回键名+参数拼接
EVAL "return KEYS[1]..ARGV[1]" 1 mykey "suffix"
# 输出:"mykeysuffix"

# 示例:计算
EVAL "return tonumber(ARGV[1]) + tonumber(ARGV[2])" 0 5 10
# 输出:(integer) 15    ← 0表示没有KEYS参数

9.2 SCRIPT LOAD / EVALSHA —— 缓存脚本(性能优化)

# 加载脚本,返回 SHA1 摘要
SCRIPT LOAD "return redis.call('get', KEYS[1])"
# 输出:"a5e9f28f7d8c3b2a1e4d5f6c7b8a9d0e1f2a3b4c"  ← SHA1

# 用 SHA1 执行(无需传输完整脚本,节省带宽)
EVALSHA a5e9f28f7d8c3b2a1e4d5f6c7b8a9d0e1f2a3b4c 1 mykey

# 检查脚本是否已缓存
SCRIPT EXISTS a5e9f28f7d8c3b2a1e4d5f6c7b8a9d0e1f2a3b4c
# 输出:
# 1) (integer) 1          ← 1表示存在

# 清空所有缓存脚本
SCRIPT FLUSH
# 输出:OK

第十章:HyperLogLog —— 基数统计的"魔法计数器"

graph TD
    A[HyperLogLog命令] --> B[添加 PFADD]
    A --> C[计数 PFCOUNT]
    A --> D[合并 PFMERGE]
    
    style A fill:#E8F5E9

10.1 PFADD / PFCOUNT —— 添加与计数

# 记录访问用户(自动去重)
PFADD uv:2024:04:20 user1 user2 user3
# 输出:(integer) 1

PFADD uv:2024:04:20 user2 user3 user4
# 输出:(integer) 1    ← user2,user3已存在,只新增user4

# 统计独立访客数(UV)
PFCOUNT uv:2024:04:20
# 输出:(integer) 4    ← user1,user2,user3,user4

# 误差测试:添加10000个元素
for i in {1..10000}; do redis-cli PFADD uv:test "user$i"; done
PFCOUNT uv:test
# 输出:(integer) 9973  ← 误差约0.27%,在0.81%标准内

内存对比:

graph LR
    A[存储1000万UV] --> B[Set方式]
    B --> C[约 400MB<br/>精确但费内存]
    
    A --> D[HyperLogLog]
    D --> E[固定 12KB<br/>误差0.81%]
    
    style C fill:#FFB6C1
    style E fill:#90EE90

10.2 PFMERGE —— 合并统计

# 两天的UV
PFADD uv:day1 user1 user2 user3
PFADD uv:day2 user2 user3 user4 user5

# 合并两天的独立访客
PFMERGE uv:total uv:day1 uv:day2
PFCOUNT uv:total
# 输出:(integer) 5    ← user1,user2,user3,user4,user5

第十一章:地理位置(GEO)— 附近服务的"雷达系统"

graph TD
    A[GEO命令] --> B[添加 GEOADD]
    A --> C[查询位置 GEOPOS]
    A --> D[距离计算 GEODIST]
    A --> E[范围查询 GEORADIUS]
    
    style A fill:#E1F5FE

11.1 GEOADD —— 添加地理位置

# 添加城市坐标(经度, 纬度)
GEOADD cities 116.40 39.90 "beijing"   # 北京
# 输出:(integer) 1

GEOADD cities 121.47 31.23 "shanghai"  # 上海
GEOADD cities 113.26 23.13 "guangzhou" # 广州
GEOADD cities 114.05 22.55 "shenzhen"  # 深圳

11.2 GEOPOS / GEODIST —— 查询与距离

# 获取城市坐标
GEOPOS cities beijing
# 输出:
# 1) 1) "116.39999896287918091"
#    2) "39.90000009167092543"

# 计算两地距离(默认米)
GEODIST cities beijing shanghai
# 输出:"1067372.2781"  ← 约1067公里

# 指定单位
GEODIST cities beijing shanghai km
# 输出:"1067.3723"     ← 公里

11.3 GEORADIUS —— 范围查询(附近的人)

# 查找北京 500km 范围内的城市
GEORADIUS cities 116.40 39.90 500 km WITHDIST WITHCOORD
# 输出:
# 1) 1) "beijing"
#    2) "0.0000"                    ← 距离
#    3) 1) "116.39999896287918091"  ← 经度
#       2) "39.90000009167092543"   ← 纬度

# 查找北京 1500km 范围内的城市,按距离排序,取前3个
GEORADIUS cities 116.40 39.90 1500 km WITHDIST COUNT 3 ASC
# 输出:
# 1) 1) "beijing"
#    2) "0.0000"
# 2) 1) "shanghai"
#    2) "1067.3723"
# 3) 1) "shenzhen"
#    2) "1941.8897"

第十二章:连接(Connection)— 客户端的"握手协议"

graph TD
    A[连接命令] --> B[心跳 PING]
    A --> C[回显 ECHO]
    A --> D[选择库 SELECT]
    A --> E[认证 AUTH]
    A --> F[退出 QUIT]
    
    style A fill:#FFF3E0

12.1 PING / ECHO —— 连接测试

# 测试连接
PING
# 输出:PONG

# 带消息的心跳
PING "hello"
# 输出:"hello"

# 回显测试
ECHO "Hello Redis"
# 输出:"Hello Redis"

12.2 SELECT —— 选择数据库

# Redis 默认有16个数据库(0-15)
SELECT 1
# 输出:OK

SET db1_key "value in db1"
# 输出:OK

SELECT 0
# 输出:OK

GET db1_key
# 输出:(nil)            ← db0 中没有这个键

SELECT 1
GET db1_key
# 输出:"value in db1"   ← 在 db1 中

12.3 AUTH —— 密码认证

# 配置 requirepass 后需要认证
AUTH mypassword
# 输出:OK

# 认证失败
AUTH wrongpassword
# 输出:(error) ERR invalid password

第十三章:服务器(Server)— 系统管理的"控制面板"

graph TD
    A[服务器命令] --> B[信息查询 INFO]
    A --> C[配置管理 CONFIG GET/SET]
    A --> D[持久化 BGSAVE/BGREWRITEAOF]
    A --> E[数据清空 FLUSHDB/FLUSHALL]
    A --> F[客户端管理 CLIENT LIST]
    A --> G[时间查询 TIME]
    A --> H[关闭服务 SHUTDOWN]
    
    style A fill:#FFEBEE

13.1 INFO —— 服务器信息大全

# 查看全部信息
INFO
# 输出大量信息,包括:
# Server: Redis版本、运行模式
# Clients: 连接客户端数
# Memory: 内存使用
# Persistence: 持久化状态
# Stats: 统计信息
# Replication: 主从信息
# CPU: CPU使用
# Keyspace: 数据库键数量

# 查看指定部分
INFO memory
# 输出:
# # Memory
# used_memory:1048576
# used_memory_human:1.00M
# used_memory_rss:2048000
# ...

INFO keyspace
# 输出:
# # Keyspace
# db0:keys=10,expires=2,avg_ttl=9865
# db1:keys=5,expires=0,avg_ttl=0

13.2 CONFIG GET / SET —— 配置管理

# 查看配置
CONFIG GET maxmemory
# 输出:
# 1) "maxmemory"
# 2) "0"                  ← 0表示无限制

CONFIG GET maxmemory-policy
# 输出:
# 1) "maxmemory-policy"
# 2) "noeviction"

# 动态修改配置(无需重启)
CONFIG SET maxmemory 1G
# 输出:OK

CONFIG SET maxmemory-policy allkeys-lru
# 输出:OK

# 查看所有配置
CONFIG GET *

13.3 BGSAVE / BGREWRITEAOF —— 持久化控制

# 后台保存 RDB
BGSAVE
# 输出:Background saving started

# 查看最后保存时间
LASTSAVE
# 输出:(integer) 1716192000  ← Unix时间戳

# 重写 AOF 文件(压缩)
BGREWRITEAOF
# 输出:Background append only file rewriting started

13.4 FLUSHDB / FLUSHALL —— 数据清空(⚠️ 危险!)

# 清空当前数据库
SELECT 1
FLUSHDB
# 输出:OK

# 清空所有数据库
FLUSHALL
# 输出:OK

# 异步清空(不阻塞,4.0+)
FLUSHDB ASYNC
FLUSHALL ASYNC

13.5 CLIENT LIST / TIME / SHUTDOWN

# 查看连接客户端
CLIENT LIST
# 输出:
# id=3 addr=127.0.0.1:54321 fd=8 name= age=10 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client
# id=4 addr=127.0.0.1:54322 fd=9 name= age=5 idle=3 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get

# 当前时间戳
TIME
# 输出:
# 1) (integer) 1716192000  ← 秒
# 2) (integer) 123456      ← 微秒

# 关闭服务器(保存数据后退出)
SHUTDOWN
# 无输出,服务器关闭

# 不保存直接关闭
SHUTDOWN NOSAVE

命令总览图

mindmap
  root((Redis<br/>命令大全))
    键操作
      DEL EXISTS
      EXPIRE TTL PERSIST
      TYPE KEYS
      RENAME RANDOMKEY
      DUMP
    String
      SET GET SETNX SETEX
      MSET MGET
      INCR DECR INCRBY
      APPEND STRLEN
      GETSET
    Hash
      HSET HGET HMSET HMGET
      HGETALL HKEYS HVALS
      HDEL HLEN HEXISTS
      HINCRBY
    List
      LPUSH RPUSH LPOP RPOP
      LRANGE LLEN
      LINSERT LSET LTRIM
      BLPOP BRPOP
      RPOPLPUSH
    Set
      SADD SREM SMEMBERS
      SISMEMBER SCARD
      SINTER SUNION SDIFF
      SRANDMEMBER SPOP
    ZSet
      ZADD ZRANGE ZREVRANGE
      ZSCORE ZRANK ZREVRANK
      ZINCRBY ZREM
      ZCOUNT ZRANGEBYSCORE
    事务
      MULTI EXEC DISCARD
      WATCH UNWATCH
    Pub/Sub
      PUBLISH SUBSCRIBE
      PSUBSCRIBE
    Lua脚本
      EVAL SCRIPT LOAD
      EVALSHA SCRIPT EXISTS
      SCRIPT FLUSH
    HyperLogLog
      PFADD PFCOUNT PFMERGE
    GEO
      GEOADD GEOPOS
      GEODIST GEORADIUS
    连接
      PING ECHO
      SELECT AUTH QUIT
    服务器
      INFO CONFIG
      BGSAVE BGREWRITEAOF
      FLUSHDB FLUSHALL
      CLIENT LIST TIME
      SHUTDOWN

每个命令都建议亲手在 redis-cli 中执行一遍,配合 MONITOR 命令观察内部执行过程,才能真正掌握!