Redis 列表(List)
Redis 列表是一个双向链表,可以用来存储一系列按顺序排列的元素。列表类型支持从两端插入和删除元素,适用于消息队列、任务调度、排行榜等场景。
场景示例
-
消息队列
- 示例:
LPUSH queue "message1" LPUSH queue "message2" RPOP queue
- 说明:使用
LPUSH
从左侧推入消息,使用RPOP
从右侧弹出消息,实现先进先出(FIFO)的消息队列。
- 示例:
-
任务调度
- 示例:
RPUSH tasks "task1" RPUSH tasks "task2" LPOP tasks
- 说明:使用
RPUSH
从右侧推入任务,使用LPOP
从左侧弹出任务,实现先进先出(FIFO)的任务调度。
- 示例:
-
排行榜
- 示例:
RPUSH leaderboard "user1" RPUSH leaderboard "user2" LRANGE leaderboard 0 -1
- 说明:使用
RPUSH
插入用户,使用LRANGE
获取所有用户,实现简单的排行榜。
- 示例:
-
聊天记录
- 示例:
RPUSH chat:room1 "Hello!" RPUSH chat:room1 "Hi there!" LRANGE chat:room1 0 -1
- 说明:使用
RPUSH
插入聊天消息,使用LRANGE
获取所有聊天记录。
- 示例:
-
定长列表
- 示例:
LPUSH log "error1" LPUSH log "error2" LTRIM log 0 99
- 说明:使用
LPUSH
插入日志信息,使用LTRIM
保持列表长度不超过 100。
- 示例:
-
阻塞队列
- 示例:
BRPOP queue 0
- 说明:使用
BRPOP
命令从右侧弹出元素,如果列表为空则阻塞,直到有新元素被推入。
- 示例:
底层实现
Redis 列表类型的底层实现基于两种数据结构:ziplist
(压缩列表)和 linkedlist
(双向链表)。
-
压缩列表(ziplist)
- 当列表包含的元素数量较少且每个元素的长度较短时,Redis 使用压缩列表存储列表。压缩列表是一种连续内存块,节省空间。
- 结构:
struct { unsigned int zlbytes; // 压缩列表的总字节数 unsigned int zltail; // 到达表尾的偏移量 unsigned int zllen; // 压缩列表包含的节点数量 unsigned char entries[]; // 数据节点 };
- 优点:紧凑内存布局,适合存储小数据量。
-
双向链表(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 列表。下面是一些基本操作示例。
-
连接 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) { // 示例操作 }
-
推入元素
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) } }
-
弹出元素
func example(rdb *redis.Client) { msg, err := rdb.RPop(ctx, "queue").Result() if err != nil { panic(err) } fmt.Println("Popped message:", msg) }
-
获取范围内的元素
func example(rdb *redis.Client) { msgs, err := rdb.LRange(ctx, "queue", 0, -1).Result() if err != nil { panic(err) } fmt.Println("All messages:", msgs) }
-
阻塞弹出元素
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 列表,实现各种常见场景的需求。