Redis 列表(List)

Redis 列表是一个双向链表,可以用来存储一系列按顺序排列的元素。列表类型支持从两端插入和删除元素,适用于消息队列、任务调度、排行榜等场景。

场景示例

  1. 消息队列

    • 示例:
      LPUSH queue "message1"
      LPUSH queue "message2"
      RPOP queue
      
    • 说明:使用 LPUSH 从左侧推入消息,使用 RPOP 从右侧弹出消息,实现先进先出(FIFO)的消息队列。
  2. 任务调度

    • 示例:
      RPUSH tasks "task1"
      RPUSH tasks "task2"
      LPOP tasks
      
    • 说明:使用 RPUSH 从右侧推入任务,使用 LPOP 从左侧弹出任务,实现先进先出(FIFO)的任务调度。
  3. 排行榜

    • 示例:
      RPUSH leaderboard "user1"
      RPUSH leaderboard "user2"
      LRANGE leaderboard 0 -1
      
    • 说明:使用 RPUSH 插入用户,使用 LRANGE 获取所有用户,实现简单的排行榜。
  4. 聊天记录

    • 示例:
      RPUSH chat:room1 "Hello!"
      RPUSH chat:room1 "Hi there!"
      LRANGE chat:room1 0 -1
      
    • 说明:使用 RPUSH 插入聊天消息,使用 LRANGE 获取所有聊天记录。
  5. 定长列表

    • 示例:
      LPUSH log "error1"
      LPUSH log "error2"
      LTRIM log 0 99
      
    • 说明:使用 LPUSH 插入日志信息,使用 LTRIM 保持列表长度不超过 100。
  6. 阻塞队列

    • 示例:
      BRPOP queue 0
      
    • 说明:使用 BRPOP 命令从右侧弹出元素,如果列表为空则阻塞,直到有新元素被推入。

底层实现

Redis 列表类型的底层实现基于两种数据结构:ziplist(压缩列表)和 linkedlist(双向链表)。

  1. 压缩列表(ziplist)

    • 当列表包含的元素数量较少且每个元素的长度较短时,Redis 使用压缩列表存储列表。压缩列表是一种连续内存块,节省空间。
    • 结构:
      struct {
          unsigned int zlbytes;     // 压缩列表的总字节数
          unsigned int zltail;      // 到达表尾的偏移量
          unsigned int zllen;       // 压缩列表包含的节点数量
          unsigned char entries[];  // 数据节点
      };
      
    • 优点:紧凑内存布局,适合存储小数据量。
  2. 双向链表(linkedlist)

    • 当列表包含的元素数量较多或某些元素的长度较长时,Redis 使用双向链表存储列表。双向链表提供高效的插入和删除操作。
    • 结构:
      typedef struct listNode {
          struct listNode *prev;    // 前置节点指针
          struct listNode *next;    // 后置节点指针
          void *value;              // 节点值
      } listNode;
      
      typedef struct list {
          listNode *head;           // 表头节点指针
          listNode *tail;           // 表尾节点指针
          unsigned long len;        // 节点数量
          // ...
      } list;
      
    • 优点:高效处理大数据量和长字符串。

Redis 通过内存占用和访问速度的权衡,选择最合适的数据结构来存储列表数据。开始时,Redis 使用压缩列表存储小型列表,当列表增长到一定规模时,自动转换为双向链表,从而兼顾空间效率和性能。

Go 中使用 Redis 列表

在 Go 语言中,可以使用 go-redis 库来操作 Redis 列表。下面是一些基本操作示例。

  1. 连接 Redis

    package main
    
    import (
        "github.com/go-redis/redis/v8"
        "context"
        "fmt"
    )
    
    var ctx = context.Background()
    
    func main() {
        rdb := redis.NewClient(&redis.Options{
            Addr:     "localhost:6379",
            Password: "", // no password set
            DB:       0,  // use default DB
        })
    
        // 示例代码调用
        example(rdb)
    }
    
    func example(rdb *redis.Client) {
        // 示例操作
    }
    
  2. 推入元素

    func example(rdb *redis.Client) {
        err := rdb.LPush(ctx, "queue", "message1").Err()
        if err != nil {
            panic(err)
        }
        err = rdb.RPush(ctx, "queue", "message2").Err()
        if err != nil {
            panic(err)
        }
    }
    
  3. 弹出元素

    func example(rdb *redis.Client) {
        msg, err := rdb.RPop(ctx, "queue").Result()
        if err != nil {
            panic(err)
        }
        fmt.Println("Popped message:", msg)
    }
    
  4. 获取范围内的元素

    func example(rdb *redis.Client) {
        msgs, err := rdb.LRange(ctx, "queue", 0, -1).Result()
        if err != nil {
            panic(err)
        }
        fmt.Println("All messages:", msgs)
    }
    
  5. 阻塞弹出元素

    func example(rdb *redis.Client) {
        msg, err := rdb.BRPop(ctx, 0, "queue").Result()
        if err != nil {
            panic(err)
        }
        fmt.Println("Popped message:", msg)
    }
    

通过这些示例,可以在 Go 语言中方便地操作 Redis 列表,实现各种常见场景的需求。