Redis红宝书 redis的数据结构

104 阅读33分钟

redis的数据结构

Redis是一个高性能的键值存储数据库,支持多种数据结构。本文将深入讲解Redis的各种数据结构,包括其内存分布、基本命令使用和适合的应用场景,并通过数据模拟演示每个操作的具体效果。

1. Redis字符串 (String)

数据结构

Redis字符串是最基本的数据类型,内部使用SDS(Simple Dynamic String)实现。

graph TD
    A[Redis String] --> B[SDS结构]
    B --> C[len: 字符串长度]
    B --> D[alloc: 分配的空间大小]
    B --> E[flags: 标志位]
    B --> F[buf: 字符数组]
    
    G[内存布局] --> H["len(4字节) | alloc(4字节) | flags(1字节) | buf[]"]    

内存分布

  • len: 记录字符串的实际长度
  • alloc: 记录分配给字符数组的空间大小
  • flags: 标志位,用于标识SDS类型
  • buf: 实际存储字符串内容的字节数组

基本命令使用与数据模拟

初始状态
Redis内存: 空
操作演示

1. SET key "Hello Redis" - 设置字符串键值对,将指定的值存储到键中

执行前: key不存在
执行后: 
  key: "mykey"
  value: SDS {
    len: 11
    alloc: 16
    flags: 1
    buf: "Hello Redis\0"
  }
内存占用: ~27字节

2. GET key - 获取指定key的值 - 获取指定键的字符串值

执行前: key = "Hello Redis"
执行后: 返回 "Hello Redis" (数据不变)

3. APPEND key " World" - 在现有字符串值的末尾追加新内容

执行前: key = "Hello Redis"
执行后:
  key: "mykey"
  value: SDS {
    len: 17
    alloc: 32
    flags: 1
    buf: "Hello Redis World\0"
  }
内存占用: ~41字节

4. STRLEN key - 返回字符串值的长度(字符数)

执行前: key = "Hello Redis World"
执行后: 返回 17 (数据不变)

5. 数值操作演示

SET counter 10 - 设置数值型字符串
执行后:
  counter: SDS {
    len: 2
    alloc: 8
    flags: 1
    buf: "10\0"
  }

INCR counter - 将数值型字符串递增1
执行后:
  counter: SDS {
    len: 2
    alloc: 8
    flags: 1
    buf: "11\0"
  }

INCRBY counter 5 - 将数值型字符串按指定值递增
执行后:
  counter: SDS {
    len: 2
    alloc: 8
    flags: 1
    buf: "16\0"
  }
sequenceDiagram
    participant C as Client
    participant R as Redis
    participant M as Memory
    
    C->>R: SET mykey "Hello"
    R->>M: 分配SDS结构
    M-->>R: 返回内存地址
    R-->>C: OK
    
    C->>R: APPEND mykey " World"
    R->>M: 扩展SDS缓冲区
    M-->>R: 返回新地址
    R-->>C: 11 (新长度)
    
    C->>R: GET mykey
    R->>M: 读取SDS内容
    M-->>R: "Hello World"
    R-->>C: "Hello World"

适合场景

  • 缓存用户信息
  • 计数器(点赞数、访问量)
  • 分布式锁
  • 会话存储

2. Redis列表 (List)

数据结构

Redis列表使用双向链表实现,支持快速的头尾插入和删除操作。

graph LR
    A[List Header] --> B[Node1]
    B --> C[Node2]
    C --> D[Node3]
    D --> E[Node4]
    
    B -.-> A
    C -.-> B
    D -.-> C
    E -.-> D
    
    F[Node结构] --> G[prev指针]
    F --> H[next指针]
    F --> I[value值]

内存分布

  • listNode: 链表节点,包含前驱指针、后继指针和值
  • list: 链表结构,包含头节点、尾节点、长度等信息
  • 每个节点独立分配内存,通过指针连接

基本命令使用与数据模拟

初始状态
Redis内存: 空
List结构: NULL
操作演示

1. LPUSH mylist "world" - 从列表左端(头部)插入元素

执行前: mylist不存在
执行后:
  mylist: List {
    head: Node1
    tail: Node1
    len: 1
  }
  Node1: {
    prev: NULL
    next: NULL
    value: "world"
  }
结构: ["world"]

2. LPUSH mylist "hello" - 从列表左端(头部)插入元素

执行前: ["world"]
执行后:
  mylist: List {
    head: Node2
    tail: Node1
    len: 2
  }
  Node2: {
    prev: NULL
    next: Node1
    value: "hello"
  }
  Node1: {
    prev: Node2
    next: NULL
    value: "world"
  }
结构: ["hello", "world"]

3. RPUSH mylist "!" - 从列表右端(尾部)插入元素

执行前: ["hello", "world"]
执行后:
  mylist: List {
    head: Node2
    tail: Node3
    len: 3
  }
  Node3: {
    prev: Node1
    next: NULL
    value: "!"
  }
结构: ["hello", "world", "!"]

4. LRANGE mylist 0 -1 - 获取列表指定范围内的元素

执行前: ["hello", "world", "!"]
执行后: 返回 ["hello", "world", "!"] (数据不变)

5. LPOP mylist - 从列表左端(头部)弹出并返回元素

执行前: ["hello", "world", "!"]
执行后:
  返回: "hello"
  mylist: List {
    head: Node1
    tail: Node3
    len: 2
  }
  释放Node2内存
结构: ["world", "!"]

6. RPOP mylist - 从列表右端(尾部)弹出并返回元素

执行前: ["world", "!"]
执行后:
  返回: "!"
  mylist: List {
    head: Node1
    tail: Node1
    len: 1
  }
  释放Node3内存
结构: ["world"]

适合场景

  • 消息队列
  • 最新消息列表
  • 用户操作历史
  • 实现栈和队列

3. Redis哈希表 (Hash)

数据结构

Redis哈希表使用字典(dict)实现,采用链地址法解决哈希冲突。

graph TD
    A[Hash Table] --> B[dictht 0]
    A --> C[dictht 1]
    
    B --> D["table[0]"]
    B --> E["table[1]"]
    B --> F["table[2]"]
    B --> G["table[...]"]
    
    D --> H[dictEntry]
    H --> I[key]
    H --> J[value]
    H --> K[next]
    
    L[渐进式rehash] --> M["正常时使用dictht[0]"]
    L --> N["rehash时同时使用两个表"]

基本命令使用与数据模拟

初始状态
Redis内存: 空
Hash结构: NULL
操作演示

1. HSET user:1 name "张三" - 设置哈希表中指定字段的值

执行前: user:1不存在
执行后:
  user:1: dict {
    dictht[0]: {
      table: [NULL, NULL, Entry1, NULL, ...]
      size: 4
      used: 1
    }
  }
  Entry1: {
    key: "name"
    value: "张三"
    next: NULL
  }
哈希表: {"name": "张三"}

2. HSET user:1 age 25 - 设置哈希表中指定字段的值

执行前: {"name": "张三"}
执行后:
  user:1: dict {
    dictht[0]: {
      table: [NULL, Entry2, Entry1, NULL, ...]
      size: 4
      used: 2
    }
  }
  Entry2: {
    key: "age"
    value: "25"
    next: NULL
  }
哈希表: {"name": "张三", "age": "25"}

3. HSET user:1 city "北京" - 设置哈希表中指定字段的值

执行前: {"name": "张三", "age": "25"}
执行后:
  user:1: dict {
    dictht[0]: {
      table: [NULL, Entry2, Entry1, Entry3, ...]
      size: 4
      used: 3
    }
  }
  Entry3: {
    key: "city"
    value: "北京"
    next: NULL
  }
哈希表: {"name": "张三", "age": "25", "city": "北京"}

4. HGET user:1 name - 获取哈希表中指定字段的值

执行前: {"name": "张三", "age": "25", "city": "北京"}
执行后: 返回 "张三" (数据不变)
查找过程:
  1. 计算"name"的哈希值
  2. 定位到table[2]
  3. 返回Entry1.value

5. HGETALL user:1 - 获取哈希表中所有字段和值

执行前: {"name": "张三", "age": "25", "city": "北京"}
执行后: 返回 ["name", "张三", "age", "25", "city", "北京"]
遍历过程:
  1. 遍历table数组
  2. 对每个非空Entry输出key-value

6. HDEL user:1 age - 删除哈希表中指定的字段

执行前: {"name": "张三", "age": "25", "city": "北京"}
执行后:
  user:1: dict {
    dictht[0]: {
      table: [NULL, NULL, Entry1, Entry3, ...]
      size: 4
      used: 2
    }
  }
  释放Entry2内存
哈希表: {"name": "张三", "city": "北京"}

适合场景

  • 存储对象信息
  • 用户属性管理
  • 购物车
  • 配置信息存储

4. Redis集合 (Set)

数据结构

Redis集合使用哈希表实现,确保元素的唯一性。

graph TD
    A[Set] --> B[dict字典]
    B --> C["哈希表存储元素"]
    C --> D["key: 集合元素"]
    C --> E["value: NULL"]
    
    F[集合操作] --> G[并集 UNION]
    F --> H[交集 INTER]
    F --> I[差集 DIFF]
    
    G --> J["Set A ∪ Set B"]
    H --> K["Set A ∩ Set B"]
    I --> L["Set A - Set B"]

基本命令使用与数据模拟

初始状态
Redis内存: 空
Set结构: NULL
操作演示

1. SADD myset "apple" - 向集合中添加一个或多个元素

执行前: myset不存在
执行后:
  myset: dict {
    dictht[0]: {
      table: [NULL, Entry1, NULL, NULL, ...]
      size: 4
      used: 1
    }
  }
  Entry1: {
    key: "apple"
    value: NULL
    next: NULL
  }
集合: {"apple"}

2. SADD myset "banana" - 向集合中添加一个或多个元素

执行前: {"apple"}
执行后:
  myset: dict {
    dictht[0]: {
      table: [NULL, Entry1, Entry2, NULL, ...]
      size: 4
      used: 2
    }
  }
  Entry2: {
    key: "banana"
    value: NULL
    next: NULL
  }
集合: {"apple", "banana"}

3. SADD myset "orange" - 向集合中添加一个或多个元素

执行前: {"apple", "banana"}
执行后:
  myset: dict {
    dictht[0]: {
      table: [Entry3, Entry1, Entry2, NULL, ...]
      size: 4
      used: 3
    }
  }
  Entry3: {
    key: "orange"
    value: NULL
    next: NULL
  }
集合: {"apple", "banana", "orange"}

4. SADD myset "apple" - 向集合中添加一个或多个元素(重复元素会被忽略)

执行前: {"apple", "banana", "orange"}
执行后: 返回 0 (元素已存在,集合不变)
集合: {"apple", "banana", "orange"}

5. SMEMBERS myset - 返回集合中的所有成员

执行前: {"apple", "banana", "orange"}
执行后: 返回 ["apple", "banana", "orange"] (顺序可能不同)

6. SISMEMBER myset "apple" - 判断元素是否是集合的成员

执行前: {"apple", "banana", "orange"}
执行后: 返回 1 (存在)
查找过程:
  1. 计算"apple"的哈希值
  2. 在哈希表中查找
  3. 找到则返回1

7. SREM myset "banana" - 从集合中移除一个或多个元素

执行前: {"apple", "banana", "orange"}
执行后:
  myset: dict {
    dictht[0]: {
      table: [Entry3, Entry1, NULL, NULL, ...]
      size: 4
      used: 2
    }
  }
  释放Entry2内存
集合: {"apple", "orange"}

8. 集合运算演示

SADD set1 "a" "b" "c" - 创建集合set1并添加元素
set1: {"a", "b", "c"}

SADD set2 "b" "c" "d" - 创建集合set2并添加元素
set2: {"b", "c", "d"}

SUNION set1 set2 - 返回两个集合的并集
结果: {"a", "b", "c", "d"}
算法: 遍历两个集合,合并所有元素

SINTER set1 set2 - 返回两个集合的交集
结果: {"b", "c"}
算法: 遍历较小集合,检查元素是否在另一个集合中

SDIFF set1 set2 - 返回第一个集合与其他集合的差集
结果: {"a"}
算法: 遍历set1,排除在set2中的元素
flowchart LR
    A["空集合"] --> B["SADD apple"]
    B --> C["{apple}"]
    C --> D["SADD banana"]
    D --> E["{apple, banana}"]
    E --> F["SADD orange"]
    F --> G["{apple, banana, orange}"]
    G --> H["SREM banana"]
    H --> I["{apple, orange}"]
    
    J["set1: {a,b,c}"] --> K["SUNION"]
    L["set2: {b,c,d}"] --> K
    K --> M["{a,b,c,d}"]
    
    J --> N["SINTER"]
    L --> N
    N --> O["{b,c}"]

适合场景

  • 标签系统
  • 好友关系
  • 去重操作
  • 共同关注/粉丝

5. Redis有序集合 (ZSet)

数据结构

Redis有序集合同时使用哈希表和跳跃表实现,既保证元素唯一性又维护有序性。

11.png

基本命令使用与数据模拟

初始状态
Redis内存: 空
ZSet结构: NULL
操作演示

1. ZADD leaderboard 100 "player1" - 向有序集合添加一个或多个成员,或者更新已存在成员的分数

执行前: leaderboard不存在
执行后:
  leaderboard: {
    dict: {
      "player1" -> 100.0
    }
    skiplist: {
      level: 1
      length: 1
      header -> Node1 -> NULL
    }
  }
  Node1: {
    member: "player1"
    score: 100.0
    level[0]: {forward: NULL, span: 0}
  }
有序集合: [("player1", 100)]

2. ZADD leaderboard 200 "player2" - 向有序集合添加一个或多个成员,或者更新已存在成员的分数

执行前: [("player1", 100)]
执行后:
  leaderboard: {
    dict: {
      "player1" -> 100.0
      "player2" -> 200.0
    }
    skiplist: {
      level: 2
      length: 2
      header -> Node1 -> Node2 -> NULL
    }
  }
  Node2: {
    member: "player2"
    score: 200.0
    level[0]: {forward: NULL, span: 0}
    level[1]: {forward: NULL, span: 0}
  }
有序集合: [("player1", 100), ("player2", 200)]

3. ZADD leaderboard 150 "player3" - 向有序集合添加一个或多个成员,或者更新已存在成员的分数

执行前: [("player1", 100), ("player2", 200)]
执行后:
  插入位置: player1和player2之间
  leaderboard: {
    dict: {
      "player1" -> 100.0
      "player2" -> 200.0
      "player3" -> 150.0
    }
    skiplist: {
      level: 2
      length: 3
      header -> Node1 -> Node3 -> Node2 -> NULL
    }
  }
有序集合: [("player1", 100), ("player3", 150), ("player2", 200)]

4. ZRANGE leaderboard 0 -1 WITHSCORES - 通过索引区间返回有序集合指定区间内的成员

执行前: [("player1", 100), ("player3", 150), ("player2", 200)]
执行后: 返回 ["player1", "100", "player3", "150", "player2", "200"]
遍历过程:
  1. 从跳跃表头节点开始
  2. 按level[0]链表顺序遍历
  3. 输出memberscore

5. ZREVRANGE leaderboard 0 2 - 返回有序集合中指定区间内的成员,通过索引,分数从高到低

执行前: [("player1", 100), ("player3", 150), ("player2", 200)]
执行后: 返回 ["player2", "player3", "player1"]
遍历过程:
  1. 从跳跃表尾节点开始
  2. 按backward指针逆序遍历
  3. 取前3个元素

6. ZSCORE leaderboard "player1" - 返回有序集合中成员的分数值

执行前: [("player1", 100), ("player3", 150), ("player2", 200)]
执行后: 返回 "100"
查找过程:
  1. 在dict中查找"player1"
  2. 直接返回对应的score值
  时间复杂度: O(1)

7. ZRANK leaderboard "player3" - 返回有序集合中指定成员的排名

执行前: [("player1", 100), ("player3", 150), ("player2", 200)]
执行后: 返回 1 (排名从0开始)
查找过程:
  1. 在跳跃表中查找"player3"
  2. 计算前面节点的span总和
  3. 返回排名位置

8. ZRANGEBYSCORE leaderboard 100 200 - 通过分数返回有序集合指定区间内的成员

执行前: [("player1", 100), ("player3", 150), ("player2", 200)]
执行后: 返回 ["player1", "player3", "player2"]
查找过程:
  1. 在跳跃表中定位score>=100的第一个节点
  2. 顺序遍历直到score>200
  3. 返回范围内的所有member
sequenceDiagram
    participant C as Client
    participant Z as ZSet
    participant D as Dict
    participant S as SkipList
    
    C->>Z: ZADD key 100 "player1"
    Z->>D: 添加 "player1" -> 100
    Z->>S: 插入节点("player1", 100)
    S-->>Z: 插入完成
    Z-->>C: 1 (新增元素数)
    
    C->>Z: ZADD key 150 "player3"
    Z->>D: 添加 "player3" -> 150
    Z->>S: 插入节点("player3", 150)
    Note over S: 维护有序性,插入到正确位置
    S-->>Z: 插入完成
    Z-->>C: 1
    
    C->>Z: ZSCORE key "player1"
    Z->>D: 查找 "player1"
    D-->>Z: 返回 100
    Z-->>C: "100"

适合场景

  • 排行榜
  • 优先级队列
  • 延时队列
  • 范围查询

6. Redis地理空间 (GEO)

数据结构

Redis GEO基于有序集合实现,使用GeoHash算法将二维坐标转换为一维分数。

graph TD
    A[GEO] --> B[ZSet有序集合]
    B --> C["member: 地点名称"]
    B --> D["score: GeoHash值"]
    
    E[GeoHash编码] --> F["经纬度 (116.4, 39.9)"]
    F --> G["二进制编码"]
    G --> H["Base32编码"]
    H --> I["最终GeoHash: wx4g0ec1"]
    
    J[空间查询] --> K["GEORADIUS: 半径查询"]
    J --> L["GEODIST: 距离计算"]
    J --> M["GEOPOS: 坐标获取"]

基本命令使用与数据模拟

初始状态
Redis内存: 空
GEO结构: NULL (底层为ZSet)
操作演示

1. GEOADD cities 116.4074 39.9042 "北京" - 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中

执行前: cities不存在
执行后:
  GeoHash计算:
    经度: 116.4074 -> 二进制编码
    纬度: 39.9042 -> 二进制编码
    交错编码 -> GeoHash: 4069885555089301
  
  cities: ZSet {
    dict: {"北京" -> 4069885555089301}
    skiplist: Node1("北京", 4069885555089301)
  }
地理集合: [("北京", 116.4074, 39.9042)]

2. GEOADD cities 121.4737 31.2304 "上海" - 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中

执行前: [("北京", 116.4074, 39.9042)]
GeoHash计算:
  上海坐标 -> GeoHash: 4054803516645980
执行后:
  cities: ZSet {
    dict: {
      "北京" -> 4069885555089301
      "上海" -> 4054803516645980
    }
    skiplist: 按GeoHash值排序
  }
地理集合: [("上海", 121.4737, 31.2304), ("北京", 116.4074, 39.9042)]

3. GEOADD cities 113.2644 23.1291 "广州" - 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中

执行前: [("上海", 121.4737, 31.2304), ("北京", 116.4074, 39.9042)]
GeoHash计算:
  广州坐标 -> GeoHash: 4046432192579584
执行后:
地理集合: [("广州", 113.2644, 23.1291), ("上海", 121.4737, 31.2304), ("北京", 116.4074, 39.9042)]

4. GEOPOS cities "北京" - 从key里返回所有给定位置元素的位置(经度和纬度)

执行前: 地理集合包含北京
执行后: 返回 [116.40740036964416, 39.90419993200187]
解码过程:
  1. 从dict获取"北京"的GeoHash值
  2. 解码GeoHash为经纬度
  3. 返回坐标(可能有精度损失)

5. GEODIST cities "北京" "上海" km - 返回两个给定位置之间的距离

执行前: 北京和上海都存在
执行后: 返回 "1067.3788"
计算过程:
  1. 获取两个城市的坐标
  2. 使用Haversine公式计算球面距离
  3. 转换为指定单位(km)

6. GEORADIUS cities 116.4 39.9 100 km WITHDIST - 以给定的经纬度为中心,找出某一半径内的元素

执行前: 查询点(116.4, 39.9),半径100km
执行后: 返回 [["北京", "0.0668"]]
查询过程:
  1. 计算查询点的GeoHash值
  2. 确定GeoHash范围
  3. 在ZSet中查找范围内的元素
  4. 精确计算距离并过滤
  5. 返回符合条件的地点

7. GEOHASH cities "北京" - 返回一个或多个位置元素的GeoHash表示

执行前: 北京存在于集合中
执行后: 返回 "wx4g0ec19tz00"
过程:
  1. 获取"北京"的GeoHash数值
  2. 转换为Base32字符串
  3. 返回GeoHash字符串
flowchart TD
    A["坐标(116.4, 39.9)"] --> B["经度二进制编码"]
    A --> C["纬度二进制编码"]
    B --> D["交错编码"]
    C --> D
    D --> E["GeoHash数值"]
    E --> F["存储到ZSet"]
    
    G["半径查询"] --> H["计算查询范围"]
    H --> I["GeoHash范围查找"]
    I --> J["精确距离计算"]
    J --> K["返回结果"]
    
    L["距离计算"] --> M["获取两点坐标"]
    M --> N["Haversine公式"]
    N --> O["球面距离"]

适合场景

  • 位置服务
  • 附近的人/店铺
  • 地理围栏
  • 路径规划

7. Redis基数统计 (HyperLogLog)

数据结构

HyperLogLog使用概率算法进行基数估算,占用固定的12KB内存。

graph TD
    A[HyperLogLog基数统计] --> B["固定12KB内存"]
    A --> C["16384个桶(2^14)"]
    A --> D["每桶6位存储"]
    
    subgraph "算法原理"
        E["输入元素"] --> F["64位哈希值"]
        F --> G["前14位: 桶索引<br/>(0-16383)"]
        F --> H["后50位: 计算前导零"]
        
        G --> I["定位桶位置"]
        H --> J["统计连续0的个数"]
        
        I --> K["更新桶值"]
        J --> K
        K --> L["取最大值存储"]
    end
    
    subgraph "基数估算公式"
        M["所有桶值"] --> N["调和平均数计算"]
        N --> O["α × m² / Σ(2^(-M[j]))"]
        O --> P["小范围修正"]
        O --> Q["大范围修正"]
        P --> R["最终基数估计"]
        Q --> R
    end
    
    subgraph "实际示例"
        S["hash('user1')<br/>= 0x8A7B2C3D..."]
        S --> T["前14位: 0x22EC<br/>桶索引: 8940"]
        S --> U["后50位前导零: 3个"]
        T --> V["registers[8940] = max(old, 3)"]
        U --> V
    end

基本命令使用与数据模拟

初始状态
Redis内存: 空
HyperLogLog结构: NULL
操作演示

1. PFADD unique_visitors "user1" - 添加指定元素到HyperLogLog中

执行前: unique_visitors不存在
执行后:
  创建HyperLogLog结构:
  unique_visitors: {
    registers: [0, 0, 0, ..., 0] // 16384个桶,初始值为0
  }
  
  处理"user1":
  1. hash("user1") = 0x8a7b2c3d4e5f6789
  2. 前14位: 0x22ec (确定桶8940)
  3. 后续位前导零: 34. registers[8940] = max(0, 3) = 3
  
  返回: 1 (估算基数变化)

2. PFADD unique_visitors "user2" - 添加指定元素到HyperLogLog中

执行前: registers[8940] = 3, 其他为0
执行后:
  处理"user2":
  1. hash("user2") = 0x1b2c3d4e5f6789ab
  2. 前14位: 0x06cb (确定桶1739)
  3. 后续位前导零: 24. registers[1739] = max(0, 2) = 2
  
  返回: 1 (估算基数变化)

3. PFADD unique_visitors "user3" - 添加指定元素到HyperLogLog中

执行前: registers[8940] = 3, registers[1739] = 2
执行后:
  处理"user3":
  1. hash("user3") = 0x3c4d5e6f7a8b9cde
  2. 前14位: 0x0f13 (确定桶3859)
  3. 后续位前导零: 14. registers[3859] = max(0, 1) = 1
  
  返回: 1 (估算基数变化)

4. PFADD unique_visitors "user1" - 添加指定元素到HyperLogLog中(重复元素)

执行前: 已有user1的记录
执行后:
  处理"user1":
  1. hash("user1") = 0x8a7b2c3d4e5f6789 (相同)
  2. 前14位: 0x22ec (桶8940)
  3. 后续位前导零: 34. registers[8940] = max(3, 3) = 3 (无变化)
  
  返回: 0 (估算基数无变化)

5. PFCOUNT unique_visitors - 返回给定HyperLogLog的基数估算值

执行前: registers中有3个非零值
执行后: 返回估算基数
计算过程:
  1. 统计非零桶数: 3
  2. 计算调和平均数:
     raw_estimate = α * m² / Σ(2^(-registers[i]))
     其中 α ≈ 0.7213, m = 16384
  3. 应用偏差修正
  4. 最终估算: 约3

6. 合并操作演示

PFADD hll1 "a" "b" "c" - 创建第一个HyperLogLog并添加元素
hll1状态:
  registers[hash("a")的桶] = 前导零数
  registers[hash("b")的桶] = 前导零数
  registers[hash("c")的桶] = 前导零数

PFADD hll2 "c" "d" "e" - 创建第二个HyperLogLog并添加元素
hll2状态:
  registers[hash("c")的桶] = 前导零数 (可能与hll1不同桶)
  registers[hash("d")的桶] = 前导零数
  registers[hash("e")的桶] = 前导零数

PFMERGE result hll1 hll2 - 将多个HyperLogLog合并为一个HyperLogLog
result状态:
  对每个桶i: result.registers[i] = max(hll1.registers[i], hll2.registers[i])
  
PFCOUNT result - 返回合并后HyperLogLog的基数估算值
返回: 合并后的基数估算 (约5)
sequenceDiagram
    participant U as User
    participant H as HyperLogLog
    participant B as Buckets
    
    U->>H: PFADD key "user1"
    H->>H: hash("user1")
    H->>B: 更新bucket[8940] = 3
    H-->>U: 1
    
    U->>H: PFADD key "user2"
    H->>H: hash("user2")
    H->>B: 更新bucket[1739] = 2
    H-->>U: 1
    
    U->>H: PFADD key "user1"
    H->>H: hash("user1")
    H->>B: bucket[8940] 无变化
    H-->>U: 0
    
    U->>H: PFCOUNT key
    H->>B: 读取所有buckets
    H->>H: 计算调和平均数
    H-->>U: 估算基数

适合场景

  • 网站UV统计
  • 去重计数
  • 大数据基数估算
  • 实时统计分析

8. Redis位图 (Bitmap)

数据结构

Bitmap基于字符串实现,每个位可以存储0或1,支持位操作。

graph TD
    A[Bitmap] --> B["String字符串"]
    B --> C["字节数组"]
    C --> D["位操作"]
    
    E["位图示例"] --> F["用户签到: 0110101"]
    F --> G["第0天: 0 (未签到)"]
    F --> H["第1天: 1 (已签到)"]
    F --> I["第2天: 1 (已签到)"]
    F --> J["第3天: 0 (未签到)"]
    
    K[位运算] --> L["AND: 交集"]
    K --> M["OR: 并集"]
    K --> N["XOR: 异或"]
    K --> O["NOT: 取反"]

基本命令使用与数据模拟

初始状态
Redis内存: 空
Bitmap结构: NULL
操作演示

1. SETBIT user:sign:2024 0 1 - 对key所储存的字符串值,设置或清除指定偏移量上的位(bit)

执行前: user:sign:2024不存在
执行后:
  创建字符串结构:
  user:sign:2024: SDS {
    len: 1
    alloc: 1
    buf: [0x80] // 二进制: 10000000
  }
  位图状态: [1,0,0,0,0,0,0,0]
  第0天: 已签到

2. SETBIT user:sign:2024 1 0 - 对key所储存的字符串值,设置或清除指定偏移量上的位(bit)

执行前: buf = [0x80] // 10000000
执行后:
  buf = [0x80] // 10000000 (第1位本来就是0)
  位图状态: [1,0,0,0,0,0,0,0]
  第1天: 未签到

3. SETBIT user:sign:2024 2 1 - 对key所储存的字符串值,设置或清除指定偏移量上的位(bit)

执行前: buf = [0x80] // 10000000
执行后:
  buf = [0xA0] // 10100000
  位图状态: [1,0,1,0,0,0,0,0]
  第2天: 已签到

4. SETBIT user:sign:2024 8 1 - 对key所储存的字符串值,设置或清除指定偏移量上的位(bit)

执行前: buf = [0xA0], len = 1
执行后:
  扩展字符串:
  user:sign:2024: SDS {
    len: 2
    alloc: 2
    buf: [0xA0, 0x80] // 10100000 10000000
  }
  位图状态: [1,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0]
  第8天: 已签到

5. GETBIT user:sign:2024 0 - 对key所储存的字符串值,获取指定偏移量上的位(bit)

执行前: buf = [0xA0, 0x80]
执行后: 返回 1
查找过程:
  1. 计算字节位置: 0 / 8 = 0
  2. 计算位位置: 0 % 8 = 0
  3. 检查buf[0]的第0位: (0xA0 >> 7) & 1 = 1

6. GETBIT user:sign:2024 1 - 对key所储存的字符串值,获取指定偏移量上的位(bit)

执行前: buf = [0xA0, 0x80]
执行后: 返回 0
查找过程:
  1. 计算字节位置: 1 / 8 = 0
  2. 计算位位置: 1 % 8 = 1
  3. 检查buf[0]的第1位: (0xA0 >> 6) & 1 = 0

7. BITCOUNT user:sign:2024 - 计算给定字符串中,被设置为1的比特位的数量

执行前: buf = [0xA0, 0x80] // 10100000 10000000
执行后: 返回 3
计算过程:
  1. 遍历每个字节
  2. 使用位计数算法统计1的个数
  3. buf[0] = 0xA0 有2个1
  4. buf[1] = 0x80 有1个1
  5. 总计: 3个1

8. 位运算演示

SETBIT bitmap1 0 1 - 设置bitmap1的第0位为1
SETBIT bitmap1 1 0 - 设置bitmap1的第1位为0
SETBIT bitmap1 2 1 - 设置bitmap1的第2位为1
bitmap1: buf = [0xA0] // 10100000

SETBIT bitmap2 0 0 - 设置bitmap2的第0位为0
SETBIT bitmap2 1 1 - 设置bitmap2的第1位为1
SETBIT bitmap2 2 1 - 设置bitmap2的第2位为1
bitmap2: buf = [0x60] // 01100000

BITOP AND result bitmap1 bitmap2 - 对一个或多个保存二进制位的字符串key进行位元操作
计算过程:
  result.buf[0] = bitmap1.buf[0] & bitmap2.buf[0]
                = 0xA0 & 0x60
                = 0x20 // 00100000
result位图: [0,0,1,0,0,0,0,0]

BITOP OR result bitmap1 bitmap2 - 对一个或多个保存二进制位的字符串key进行位元操作
计算过程:
  result.buf[0] = bitmap1.buf[0] | bitmap2.buf[0]
                = 0xA0 | 0x60
                = 0xE0 // 11100000
result位图: [1,1,1,0,0,0,0,0]

BITOP XOR result bitmap1 bitmap2 - 对一个或多个保存二进制位的字符串key进行位元操作
计算过程:
  result.buf[0] = bitmap1.buf[0] ^ bitmap2.buf[0]
                = 0xA0 ^ 0x60
                = 0xC0 // 11000000
result位图: [1,1,0,0,0,0,0,0]
flowchart TD
    A["SETBIT key 0 1"] --> B["创建字符串"]
    B --> C["buf[0] = 0x80"]
    C --> D["SETBIT key 2 1"]
    D --> E["buf[0] = 0xA0"]
    E --> F["SETBIT key 8 1"]
    F --> G["扩展到2字节"]
    G --> H["buf = [0xA0, 0x80]"]
    
    I["bitmap1: 0xA0"] --> J["AND运算"]
    K["bitmap2: 0x60"] --> J
    J --> L["result: 0x20"]
    
    I --> M["OR运算"]
    K --> M
    M --> N["result: 0xE0"]
    
    I --> O["XOR运算"]
    K --> O
    O --> P["result: 0xC0"]

适合场景

  • 用户签到统计
  • 在线用户统计
  • 权限管理
  • 布隆过滤器

9. Redis位域 (Bitfield)

数据结构

Bitfield允许在位图中操作任意位宽的整数,支持有符号和无符号整数。

graph TD
    A[Bitfield] --> B["位图基础"]
    B --> C["任意位宽整数"]
    C --> D["有符号整数"]
    C --> E["无符号整数"]
    
    F["位域操作"] --> G["GET: 获取位域"]
    F --> H["SET: 设置位域"]
    F --> I["INCRBY: 增加位域"]
    
    J["溢出处理"] --> K["WRAP: 回绕"]
    J --> L["SAT: 饱和"]
    J --> M["FAIL: 失败"]

基本命令使用与数据模拟

初始状态
Redis内存: 空
Bitfield结构: NULL (基于String)
操作演示

1. BITFIELD mykey SET u8 0 255 - 设置指定位域的值

执行前: mykey不存在
执行后:
  创建字符串:
  mykey: SDS {
    len: 1
    alloc: 1
    buf: [0xFF] // 255的二进制: 11111111
  }
  位域布局: [11111111] (位置0-7: 255)
  返回: [0] (原值)

2. BITFIELD mykey SET i8 8 -128 - 设置指定位域的值

执行前: buf = [0xFF]
执行后:
  扩展字符串:
  mykey: SDS {
    len: 2
    alloc: 2
    buf: [0xFF, 0x80] // 255, -128的二进制表示
  }
  位域布局: [11111111][10000000]
  位置0-7: 255 (u8)
  位置8-15: -128 (i8)
  返回: [0] (原值)

3. BITFIELD mykey GET u8 0 - 获取指定位域的值

执行前: buf = [0xFF, 0x80]
执行后: 返回 [255]
读取过程:
  1. 从位置0开始读取8位
  2. 读取buf[0] = 0xFF
  3. 解释为无符号8位整数: 255

4. BITFIELD mykey GET i8 8 - 获取指定位域的值

执行前: buf = [0xFF, 0x80]
执行后: 返回 [-128]
读取过程:
  1. 从位置8开始读取8位
  2. 读取buf[1] = 0x80
  3. 解释为有符号8位整数: -128

5. BITFIELD mykey INCRBY u8 0 1 - 对指定位域进行增量操作

执行前: buf = [0xFF, 0x80], 位置0值为255
执行后:
  计算: 255 + 1 = 256
  溢出处理(默认WRAP): 256 % 256 = 0
  buf = [0x00, 0x80]
  返回: [0] (新值)

6. BITFIELD mykey INCRBY i8 8 1 - 对指定位域进行增量操作

执行前: buf = [0x00, 0x80], 位置8值为-128
执行后:
  计算: -128 + 1 = -127
  buf = [0x00, 0x81]
  返回: [-127] (新值)

7. 溢出控制演示

BITFIELD mykey SET u8 16 200 - 设置位置16的8位无符号整数为200
执行后: buf = [0x00, 0x81, 0xC8]
位域布局: [00000000][10000001][11001000]

BITFIELD mykey OVERFLOW SAT INCRBY u8 16 100 - 设置溢出策略为饱和模式并增加100
执行前: 位置16值为200
计算: 200 + 100 = 300
溢出处理(SAT): min(300, 255) = 255
执行后: buf = [0x00, 0x81, 0xFF]
返回: [255]

BITFIELD mykey OVERFLOW FAIL INCRBY u8 16 1 - 设置溢出策略为失败模式并增加1
执行前: 位置16值为255
计算: 255 + 1 = 256 (溢出)
溢出处理(FAIL): 返回NULL,不修改
执行后: buf不变
返回: [null]

BITFIELD mykey OVERFLOW WRAP INCRBY u8 16 1 - 设置溢出策略为回绕模式并增加1
执行前: 位置16值为255
计算: 255 + 1 = 256
溢出处理(WRAP): 256 % 256 = 0
执行后: buf = [0x00, 0x81, 0x00]
返回: [0]

8. 复杂位域操作

BITFIELD counters SET u4 0 15 SET u4 4 10 SET u4 8 5
执行后:
  buf = [0xFA, 0x50] // 11111010 01010000
  位域布局:
    位置0-3: 15 (1111)
    位置4-7: 10 (1010)
    位置8-11: 5 (0101)
    位置12-15: 0 (0000)

BITFIELD counters GET u4 0 GET u4 4 GET u4 8
返回: [15, 10, 5]

BITFIELD counters INCRBY u4 0 1 INCRBY u4 4 1 INCRBY u4 8 1
计算:
  位置0: 15 + 1 = 16, 溢出回绕为0
  位置4: 10 + 1 = 11
  位置8: 5 + 1 = 6
执行后: buf = [0x0B, 0x60] // 00001011 01100000
返回: [0, 11, 6]
sequenceDiagram
    participant C as Client
    participant B as Bitfield
    participant M as Memory
    
    C->>B: SET u8 0 255
    B->>M: 写入位置0-7: 11111111
    M-->>B: 完成
    B-->>C: [0]
    
    C->>B: SET i8 8 -128
    B->>M: 写入位置8-15: 10000000
    M-->>B: 完成
    B-->>C: [0]
    
    C->>B: INCRBY u8 0 1
    B->>M: 读取位置0-7: 255
    B->>B: 计算255+1=256, 溢出处理
    B->>M: 写入位置0-7: 00000000
    M-->>B: 完成
    B-->>C: [0]

适合场景

  • 计数器数组
  • 统计信息存储
  • 紧凑的数值存储
  • 位级别的精确控制

10. Redis流 (Stream)

数据结构

Redis Stream是一个日志型数据结构,支持消息的持久化和消费者组。

graph TD
    A[Stream] --> B["Radix Tree"]
    B --> C["消息节点"]
    C --> D["消息ID"]
    C --> E["字段值对"]
    
    F[消费者组] --> G["Consumer Group"]
    G --> H["Consumer 1"]
    G --> I["Consumer 2"]
    G --> J["Consumer 3"]
    
    K["消息流转"] --> L["生产者 XADD"]
    L --> M["Stream存储"]
    M --> N["消费者 XREAD"]
    N --> O["确认 XACK"]

基本命令使用与数据模拟

初始状态
Redis内存: 空
Stream结构: NULL
操作演示

1. XADD mystream * name "张三" age 25 - 向流中添加消息

执行前: mystream不存在
执行后:
  创建Stream结构:
  mystream: {
    radix_tree: {
      length: 1
      entries: [
        {
          id: "1640995200000-0"
          fields: {"name": "张三", "age": "25"}
        }
      ]
    }
    last_id: "1640995200000-0"
    length: 1
  }
  返回: "1640995200000-0"

2. XADD mystream * name "李四" age 30 - 向流中添加消息

执行前: length = 1, last_id = "1640995200000-0"
执行后:
  mystream: {
    radix_tree: {
      length: 2
      entries: [
        {
          id: "1640995200000-0"
          fields: {"name": "张三", "age": "25"}
        },
        {
          id: "1640995200001-0"
          fields: {"name": "李四", "age": "30"}
        }
      ]
    }
    last_id: "1640995200001-0"
     length: 2
   }
   返回: "1640995200001-0"

3. XREAD STREAMS mystream 0 - 从一个或多个流中读取消息

执行前: Stream包含2条消息
执行后: 返回所有消息
[  [    "mystream",    [      ["1640995200000-0", ["name", "张三", "age", "25"]],
      ["1640995200001-0", ["name", "李四", "age", "30"]]
    ]
  ]
]

4. XGROUP CREATE mystream mygroup 0 - 创建消费者组

执行前: Stream存在,消费者组不存在
执行后:
  mystream: {
    radix_tree: {...} // 消息数据不变
    consumer_groups: {
      "mygroup": {
        last_delivered_id: "0-0"
        consumers: {}
        pending: {}
      }
    }
  }
  返回: OK

5. XREADGROUP GROUP mygroup consumer1 STREAMS mystream > - 从消费者组中读取消息

执行前: 消费者组存在,consumer1首次消费
执行后:
  消费者组状态:
  "mygroup": {
    last_delivered_id: "1640995200001-0"
    consumers: {
      "consumer1": {
        pending: {
          "1640995200000-0": timestamp,
          "1640995200001-0": timestamp
        }
      }
    }
    pending: {
      "1640995200000-0": "consumer1",
      "1640995200001-0": "consumer1"
    }
  }
  返回: 所有未消费的消息

6. XACK mystream mygroup 1640995200000-0 - 确认消息已被消费者组中的消费者处理

执行前: 消息在pending列表中
执行后:
  消费者组状态:
  "mygroup": {
    last_delivered_id: "1640995200001-0"
    consumers: {
      "consumer1": {
        pending: {
          "1640995200001-0": timestamp
        }
      }
    }
    pending: {
      "1640995200001-0": "consumer1"
    }
  }
  返回: 1 (确认的消息数)

7. XINFO - 以一种易于理解和阅读的格式,返回关于Redis服务器的各种信息和统计数值 STREAM mystream - 返回流的详细信息

执行前: Stream包含完整信息
执行后: 返回Stream详细信息
[
  "length", 2,
  "radix-tree-keys", 1,
  "radix-tree-nodes", 2,
  "last-generated-id", "1640995200001-0",
  "groups", 1,
  "first-entry", ["1640995200000-0", ["name", "张三", "age", "25"]],
  "last-entry", ["1640995200001-0", ["name", "李四", "age", "30"]]
]

8. 消息修剪演示

XTRIM mystream MAXLEN 1 - 修剪流,限制流的长度
执行前: Stream包含2条消息
执行后:
  mystream: {
    radix_tree: {
      length: 1
      entries: [
        {
          id: "1640995200001-0"
          fields: {"name": "李四", "age": "30"}
        }
      ]
    }
    last_id: "1640995200001-0"
    length: 1
  }
  删除最旧的消息,保留最新的1条
  返回: 1 (删除的消息数)
sequenceDiagram
    participant P as Producer
    participant S as Stream
    participant G as ConsumerGroup
    participant C as Consumer
    
    P->>S: XADD * name "张三" age 25
    S-->>P: "1640995200000-0"
    
    P->>S: XADD * name "李四" age 30
    S-->>P: "1640995200001-0"
    
    Note over G: XGROUP CREATE mystream mygroup 0
    
    C->>G: XREADGROUP GROUP mygroup consumer1 STREAMS mystream >
    G->>S: 读取未消费消息
    S-->>G: 返回消息列表
    G->>G: 添加到pending列表
    G-->>C: 返回消息
    
    C->>G: XACK mystream mygroup 1640995200000-0
    G->>G: 从pending列表移除
    G-->>C: 1
flowchart TD
    A["空Stream"] --> B["XADD消息1"]
    B --> C["Stream: [消息1]"]
    C --> D["XADD消息2"]
    D --> E["Stream: [消息1, 消息2]"]
    E --> F["创建消费者组"]
    F --> G["消费者读取"]
    G --> H["消息进入pending"]
    H --> I["消费者确认"]
    I --> J["消息从pending移除"]
    
    K["消息修剪"] --> L["保留最新N条"]
    L --> M["删除旧消息"]

适合场景

  • 消息队列
  • 事件溯源
  • 日志收集
  • 实时数据流处理

总结

Redis提供了丰富的数据结构,每种数据结构都有其特定的应用场景和内部实现机制:

graph LR
    A[Redis数据结构] --> B[String]
    A --> C[List]
    A --> D[Hash]
    A --> E[Set]
    A --> F[ZSet]
    A --> G[GEO]
    A --> H[HyperLogLog]
    A --> I[Bitmap]
    A --> J[Bitfield]
    A --> K[Stream]
    
    B --> B1["SDS结构<br/>缓存/计数"]
    C --> C1["双向链表<br/>队列/栈"]
    D --> D1["哈希表<br/>对象存储"]
    E --> E1["哈希表<br/>去重/集合运算"]
    F --> F1["跳跃表+哈希表<br/>排行榜"]
    G --> G1["ZSet+GeoHash<br/>地理位置"]
    H --> H1["概率算法<br/>基数统计"]
    I --> I1["字符串+位操作<br/>位图统计"]
    J --> J1["字符串+位域<br/>紧凑存储"]
    K --> K1["基数树<br/>消息流"]

选择建议

  1. 简单键值存储: 使用String
  2. 队列/栈操作: 使用List
  3. 对象属性存储: 使用Hash
  4. 去重/集合运算: 使用Set
  5. 排序/排行榜: 使用ZSet
  6. 地理位置服务: 使用GEO
  7. 大数据去重计数: 使用HyperLogLog
  8. 位级别统计: 使用Bitmap
  9. 紧凑数值存储: 使用Bitfield
  10. 消息队列/事件流: 使用Stream

11. Redis其他常用指令

除了各种数据结构的专用命令外,Redis还提供了许多通用的管理和操作命令。

11.1 键管理命令

基本键操作
graph TD
    A[键管理命令] --> B[EXISTS]
    A --> C[TYPE]
    A --> D[DEL]
    A --> E[EXPIRE]
    A --> F[TTL]
    A --> G[KEYS]
    A --> H[SCAN]
    
    B --> B1["检查键是否存在"]
    C --> C1["获取键的数据类型"]
    D --> D1["删除一个或多个键"]
    E --> E1["设置键的过期时间"]
    F --> F1["查看键的剩余生存时间"]
    G --> G1["查找匹配模式的键"]
    H --> H1["迭代数据库中的键"]
操作演示

1. 键存在性检查

初始状态: Redis中有键 "user:1", "user:2"

EXISTS user:1 - 检查给定key是否存在
返回: 1 (存在)

EXISTS user:3 - 检查给定key是否存在
返回: 0 (不存在)

EXISTS user:1 user:2 user:3 - 检查多个key是否存在
返回: 2 (存在的键数量)

2. 键类型查询

设置不同类型的键:
SET name "张三"          # String类型
LPUSH list "item"       # List类型
HSET hash field value   # Hash类型

TYPE name - 返回key所储存的值的类型
返回: "string"

TYPE list - 返回key所储存的值的类型
返回: "list"

TYPE hash - 返回key所储存的值的类型
返回: "hash"

TYPE nonexistent - 返回key所储存的值的类型
返回: "none"

3. 键删除操作

初始状态: 存在键 key1, key2, key3

DEL key1 - 删除一个或多个key
执行后: key1被删除
返回: 1 (删除的键数量)

DEL key2 key3 key4 - 删除一个或多个key
执行后: key2和key3被删除,key4不存在
返回: 2 (实际删除的键数量)

4. 过期时间管理

SET session:123 "user_data"

EXPIRE session:123 3600 - 为给定key设置过期时间
执行后: 键将在3600秒后过期
返回: 1 (设置成功)

TTL session:123 - 以秒为单位,返回给定key的剩余生存时间
返回: 3599 (剩余秒数,会递减)

等待过期后:
TTL session:123
返回: -2 (键已过期不存在)

TTL persistent_key
返回: -1 (键存在但无过期时间)

11.2 数据库管理命令

flowchart LR
    A[数据库管理] --> B[SELECT]
    A --> C[DBSIZE - 返回当前数据库的key的数量]
    A --> D[FLUSHDB]
    A --> E[FLUSHALL]
    A --> F[MOVE]
    
    B --> B1["切换数据库"]
    C --> C1["查看当前数据库键数量"]
    D --> D1["清空当前数据库"]
    E --> E1["清空所有数据库"]
    F --> F1["移动键到其他数据库"]
操作演示

1. 数据库切换

默认连接到数据库0

SELECT 1 - 切换到指定的数据库
执行后: 切换到数据库1
返回: OK

SET key "value in db1"
SELECT 0
GET key
返回: (nil) - 因为key在数据库1

2. 数据库大小查询

当前数据库包含100个键

DBSIZE
返回: 100

添加新键后:
SET newkey "value" - 设置指定key的值
DBSIZE
返回: 101

3. 键在数据库间移动

当前在数据库0,存在键 "user:1"

MOVE user:1 1 - 将当前数据库的key移动到给定的数据库db当中
执行后: 
  - 数据库0中的user:1被删除
  - 数据库1中创建user:1
返回: 1 (移动成功)

MOVE nonexistent 1 - 将当前数据库的key移动到给定的数据库db当中
返回: 0 (键不存在,移动失败)

11.3 服务器信息命令

graph TD
    A[服务器信息] --> B[INFO]
    A --> C[CONFIG]
    A --> D[CLIENT]
    A --> E[MONITOR]
    A --> F[SLOWLOG]
    
    B --> B1["服务器统计信息"]
    C --> C1["配置参数管理"]
    D --> D1["客户端连接管理"]
    E --> E1["实时监控命令"]
    F --> F1["慢查询日志"]
操作演示

1. 服务器信息查询

INFO
返回服务器详细信息:
# Server
redis_version:7.0.0
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:0
redis_mode:standalone
os:Linux 5.4.0-74-generic x86_64

# Memory
used_memory:1048576
used_memory_human:1.00M
used_memory_rss:2097152

# Stats
total_connections_received:100
total_commands_processed:1000
instantaneous_ops_per_sec:10

2. 配置管理

CONFIG GET maxmemory - 获取运行时配置参数
返回: 
1) "maxmemory"
2) "1073741824"

CONFIG SET maxmemory 2147483648 - 设置运行时配置参数
返回: OK

CONFIG GET save - 获取运行时配置参数
返回:
1) "save"
2) "900 1 300 10 60 10000"

3. 客户端管理

CLIENT LIST - 以人类可读的格式,返回所有连接到服务器的客户端信息和统计数据
返回当前连接的客户端信息:
id=3 addr=127.0.0.1:52555 fd=8 name= age=855 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client

CLIENT SETNAME myclient - 为当前连接分配一个名字
返回: OK

CLIENT GETNAME - 返回当前连接的名字
返回: "myclient"

11.4 事务命令

sequenceDiagram
    participant C as Client
    participant R as Redis
    
    C->>R: MULTI - 标记一个事务块的开始
    R-->>C: OK
    Note over R: 开始事务
    
    C->>R: SET key1 value1
    R-->>C: QUEUED
    
    C->>R: INCR counter
    R-->>C: QUEUED
    
    C->>R: LPUSH list item
    R-->>C: QUEUED
    
    Note over R: 命令入队列
    
    C->>R: EXEC - 执行所有事务块内的命令
    R->>R: 原子执行所有命令
    R-->>C: [OK, 1, 1]
    
    Note over R: 事务完成
操作演示

1. 正常事务执行

MULTI
返回: OK
事务状态: 开始

SET account:1 100 - 设置指定key的值(在事务中)
返回: QUEUED
命令队列: ["SET account:1 100"]

SET account:2 200 - 设置指定key的值(在事务中)
返回: QUEUED
命令队列: ["SET account:1 100", "SET account:2 200"]

EXEC
执行过程:
  1. SET account:1 100 -> OK
  2. SET account:2 200 -> OK
返回: ["OK", "OK"]
事务状态: 完成

2. 事务取消

MULTI
返回: OK

SET key1 value1
返回: QUEUED

SET key2 value2
返回: QUEUED

DISCARD - 取消事务,放弃执行事务块内的所有命令
执行后: 清空命令队列,取消事务
返回: OK
结果: key1和key2都没有被设置

3. 监视键的事务

客户端1:
WATCH balance - 监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断
GET balance
返回: "100"

MULTI
SET balance 90
QUEUED

客户端2 (在客户端1执行EXEC前):
SET balance 50
返回: OK

客户端1:
EXEC
返回: (nil) - 事务被取消,因为balance被其他客户端修改

11.5 发布订阅命令

graph LR
    A[Publisher] -->|PUBLISH| B[Channel]
    B --> C[Subscriber1]
    B --> D[Subscriber2]
    B --> E[Subscriber3]
    
    F[Pattern Subscriber] -->|PSUBSCRIBE| G["news.*"]
    G --> H["news.sports"]
    G --> I["news.tech"]
    G --> J["news.finance"]
操作演示

1. 基本发布订阅

订阅者:
SUBSCRIBE news - 订阅给定的一个或多个频道的信息
返回:
1) "subscribe"
2) "news"
3) (integer) 1

发布者:
PUBLISH news "重要新闻内容" - 将信息发送到指定的频道
返回: 1 (接收消息的订阅者数量)

订阅者接收到:
1) "message"
2) "news"
3) "重要新闻内容"

2. 模式订阅

订阅者:
PSUBSCRIBE news.* - 订阅一个或多个符合给定模式的频道
返回:
1) "psubscribe"
2) "news.*"
3) (integer) 1

发布者:
PUBLISH news.sports "体育新闻" - 将信息发送到指定的频道
PUBLISH news.tech "科技新闻" - 将信息发送到指定的频道
PUBLISH weather.today "天气预报" - 将信息发送到指定的频道

订阅者接收到:
1) "pmessage"
2) "news.*"
3) "news.sports"
4) "体育新闻"

1) "pmessage"
2) "news.*"
3) "news.tech"
4) "科技新闻"

(weather.today不匹配模式,不会收到)

12. 总结

通过本文的详细分析和数据模拟演示,我们深入了解了Redis的核心数据结构和常用命令。Redis作为一个高性能的内存数据库,其精心设计的数据结构为不同的应用场景提供了最优的解决方案。

12.1 数据结构特点总结

核心数据结构概览
数据结构底层实现主要特点时间复杂度典型应用场景
StringSDS动态字符串二进制安全、自动扩容O(1)缓存、计数器、分布式锁
List双向链表快速头尾操作、有序O(1)头尾/O(N)中间消息队列、最新列表
Hash哈希表字段级操作、内存优化O(1)平均对象存储、用户信息
Set哈希表自动去重、集合运算O(1)平均标签系统、好友关系
ZSet跳跃表+哈希表有序性、范围查询O(log N)排行榜、优先队列
GEOZSet+GeoHash地理位置编码O(log N)LBS服务、附近搜索
HyperLogLog概率算法固定内存、基数估算O(1)UV统计、去重计数
Bitmap字符串+位操作极致空间效率O(1)用户签到、布隆过滤器
Bitfield位域操作紧凑数值存储O(1)数值数组、状态标记
StreamRadix Tree消息持久化、消费组O(log N)消息队列、事件流
数据结构选择指南
flowchart TD
    A["数据存储需求"] --> B["简单键值对"]
    A --> C["集合类数据"]
    A --> D["有序数据"]
    A --> E["特殊场景"]
    
    B --> B1["String"]
    B1 --> B2["✓ 缓存数据<br/>✓ 计数器<br/>✓ 分布式锁"]
    
    C --> C1["需要去重?"]
    C1 -->|是| C2["Set"]
    C1 -->|否| C3["需要字段操作?"]
    C3 -->|是| C4["Hash"]
    C3 -->|否| C5["List"]
    
    C2 --> C6["✓ 标签系统<br/>✓ 集合运算"]
    C4 --> C7["✓ 对象存储<br/>✓ 用户信息"]
    C5 --> C8["✓ 消息队列<br/>✓ 时间线"]
    
    D --> D1["ZSet"]
    D1 --> D2["✓ 排行榜<br/>✓ 范围查询<br/>✓ 优先队列"]
    
    E --> E1["地理位置"]
    E --> E2["基数统计"]
    E --> E3["位操作"]
    E --> E4["消息流"]
    
    E1 --> E5["GEO"]
    E2 --> E6["HyperLogLog"]
    E3 --> E7["Bitmap/Bitfield"]
    E4 --> E8["Stream"]

12.2 性能对比分析

数据结构插入复杂度查找复杂度删除复杂度内存效率适用场景
StringO(1)O(1)O(1)简单键值存储
ListO(1)头尾O(N)O(1)头尾队列、栈操作
HashO(1)平均O(1)平均O(1)平均对象属性存储
SetO(1)平均O(1)平均O(1)平均去重、集合运算
ZSetO(log N)O(1)按成员
O(log N)按分数
O(log N)排序、范围查询
GEOO(log N)O(log N)O(log N)地理位置查询
HyperLogLogO(1)O(1)-极高大数据基数统计
BitmapO(1)O(1)O(1)极高位级别统计
BitfieldO(1)O(1)O(1)极高紧凑数值存储
StreamO(1)O(log N)O(1)消息流处理

12.3 选择决策树

flowchart TD
    A["需要存储什么数据?"] --> B["简单值"]
    A --> C["复杂结构"]
    
    B --> D["需要数值操作?"]
    D -->|是| E["String"]
    D -->|否| F["需要位操作?"]
    F -->|是| G["Bitmap/Bitfield"]
    F -->|否| E
    
    C --> H["需要排序?"]
    H -->|是| I["ZSet"]
    H -->|否| J["需要去重?"]
    
    J -->|是| K["Set"]
    J -->|否| L["需要顺序?"]
    
    L -->|是| M["List"]
    L -->|否| N["Hash"]
    
    O["特殊需求"] --> P["地理位置"]
    O --> Q["基数统计"]
    O --> R["消息队列"]
    
    P --> S["GEO"]
    Q --> T["HyperLogLog"]
    R --> U["Stream"]

12.4 最佳实践建议

12.4.1 性能优化
  1. 键命名规范

    • 使用有意义的前缀:user:1001:profile
    • 避免过长的键名
    • 使用一致的命名约定
  2. 内存优化

    • 合理设置过期时间
    • 使用压缩友好的数据结构
    • 定期清理无用数据
  3. 操作优化

    • 批量操作优于单个操作
    • 使用管道减少网络往返
    • 避免大键操作阻塞
12.4.2 数据安全
  1. 持久化策略

    • RDB:适合备份和灾难恢复
    • AOF:适合数据安全要求高的场景
    • 混合持久化:兼顾性能和安全
  2. 高可用部署

    • 主从复制:读写分离
    • 哨兵模式:自动故障转移
    • 集群模式:水平扩展
12.4.3 监控运维
  1. 关键指标监控

    • 内存使用率
    • 命令执行延迟
    • 连接数和QPS
    • 慢查询日志
  2. 容量规划

    • 预估数据增长
    • 监控内存碎片率
    • 合理配置最大内存

12.5 结语

Redis的强大之处在于其丰富的数据结构和高效的实现。每种数据结构都针对特定的使用场景进行了优化,理解其内部机制有助于我们:

  1. 正确选择:根据业务需求选择最适合的数据结构
  2. 性能优化:了解操作复杂度,避免性能陷阱
  3. 内存管理:合理使用内存,提高存储效率
  4. 架构设计:设计可扩展、高性能的系统架构

通过本文的学习,相信你已经掌握了Redis数据结构的核心知识。在实际应用中,建议结合具体的业务场景,灵活运用这些数据结构,充分发挥Redis的性能优势。

graph LR
    A["理论学习"] --> B["实践应用"]
    B --> C["性能调优"]
    C --> D["架构优化"]
    D --> E["业务成功"]
    
    F["持续学习"] --> A
    E --> F
    
    style A fill:#e1f5fe
    style B fill:#f3e5f5
    style C fill:#fff3e0
    style D fill:#e8f5e8
    style E fill:#ffebee
    style F fill:#fce4ec

Redis的世界博大精深,本文只是一个开始。继续探索Redis的高级特性,如Lua脚本、模块系统、集群管理等,将帮助你在数据存储和处理领域达到更高的水平。