列表

Redis 的列表是一种线性的有序结构,可以按照元素被推入列表中的顺序来存储元素。这些元素既可以是文字数据,又可以是二进制数据,并且列表中的元素可以重复出现。

基本命令

LPUSH

将一个或多个元素依次推入列表左端,返回当前列表包含的元素数量。

当列表不存在,会创建一个新列表。

1
2
3
4
5
-- 返回 1。hanzhang
LPUSH name "hanzhang"

-- 返回 3。hanhan hanzuo hanzhang
LPUSH name "hanzuo" "hanhan"

时间复杂度:O(n)

RPUSH

将一个或多个元素依次推入列表右端,返回当前列表包含的元素数量。

当列表不存在,会创建一个新列表。

1
2
3
4
-- 返回 4.hanhan hanzuo hanzhang hanyang
RPUSH name "hanyang"
-- 返回 6.hanhan hanzuo hanzhang hanyang hantang hanshen
RPUSH name "hantang" "hanshen"

时间复杂度:O(n)

LPUSHX

当列表存在的情况下,将一个或多个元素依次推入列表左端,返回当前列表包含的元素数量。

当列表不存在的情况下,不会创建新列表,并返回 0.

时间复杂度:O(n)

RPUSHX

当列表存在的情况下,将一个或多个元素依次推入列表右端,返回当前列表包含的元素数量。

当列表不存在的情况下,不会创建新列表,并返回 0.

时间复杂度:O(n)

LPOP

移除左边第一个元素,并返回。

列表不存在,则返回 nil。

1
2
3
4
5
-- hanhan
LPOP name

-- hanzuo
LPOP name

时间复杂度:O(1)

RPOP

移除右边第一个元素,并返回。

列表不存在,则返回 nil。

1
2
3
4
5
-- hanshen
RPOP name

-- hantang
RPOP name

时间复杂度:O(1)

RPOPLPUSH

移除右边第一个元素,并从左边推入 target 列表。

源列表不存在,则返回 nil。

1
2
3
4
5
6
7
-- 同源。hanyang hanzhang
RPOPLPUSH name name

-- 不同源
-- name hanyang
-- new::name hanzhang
RPOPLPUSH name new::name

时间复杂度:O(1)

LLEN

获取列表长度。

不存在则返回 0。

1
2
-- 返回 school 的长度
LLEN school

时间复杂度:O(1)

LINDEX

返回列表指定下标的元素。

  • 正索引从左端 0 开始;
  • 负索引从右端 -1 开始;

列表不存在或下标不合法则返回 nil.

1
2
3
4
5
6
7
8
9
10
11
12
-- hanzhang hantang hanshen
RPUSH name "hanzhang" "hantang" "hanshen"

-- nil
LINDEX name 100
-- nil
LINDEX nama 0

-- hantang
LINDEX name 1
-- hanzhang
LINDEX name -3

时间复杂度:O(n)

LRANGE

获取列表指定闭区间范围的所有元素。

  1. 起止索引都超过,返回 nil;
  2. 其中一个超过,则修正为实际范围(左端为 0,右端为 -1);
1
2
3
4
-- 列表的所有元素
LRANGE name 0 -1
-- 列表 0-1 之间的元素
LRANGE name 0 1

时间复杂度:O(n)

LSET

设置列表指定下标的元素。

下标不合法会抛出错误。

1
2
3
4
-- 设置 ok
LSET name 0 "hanhan"
-- error
LSET name 110 "hanhan"

时间复杂度:O(n)

LINSERT

将新元素插入到指定列表元素的前或后。

指定元素不存在,返回 -1。

只操作满足条件的第一个指定元素。

1
2
3
4
5
6
7
8
-- 将 hanzhang 插入到 hanhan 前面
LINSERT name BEFORE "hanhan" "hanzhang"

-- 将 hanzhang 插入到 hanhan 后面
LINSERT name AFTER "hantang" "hanzhang"

-- hhah 不存在,返回 -1
LINSERT name BEFORE "hhah" "hanyang"

时间复杂度:O(n)

LTRIM

保留指定下标闭区间元素。

正负索引都可。

1
2
3
4
5
-- 保留 0-3 闭区间元素
LTRIM name 0 3

-- 保留 -3 - -1 闭区间元素
LTRIM name -3 -1

时间复杂度:O(n)

LREM

移除列表元素,返回移除的个数。

1
2
3
4
5
6
7
8
RPUSH name "hanzhang" "hantang" "hanshen" "hanzhang" "hantang" "hanshen"

-- 从左边移除 2 个 hanzhang: hantang hanshen hantang hanshen
LREM name 2 "hanzhang"
-- 从右边移除 2 个 hantang: hanshen hanshen
LREM name -2 "hanzhang"
-- 移除所有 hanshen
LREM name 0 "hanshen"

时间复杂度:O(n)

BLPOP

带有阻塞的左端弹出操作,单位是 s。

timeout 为 0 时,表示一直等待,知道有消息。

1
2
-- 左端弹出 school 元素,阻塞最多 10s
BLPOP school 10

同时很多线程阻塞时,先阻塞,先服务。

时间复杂度:O(n)

BRPOP

作用同 BLPOP,只是从右端弹出。

BRPOPLPUSH

作用同 RPOPLPUSH,只是带有阻塞功能。

时间复杂度:O(1)

示例

使用 BLPOP 实现异步处理消息

Producer 只需要将消息 PUSH 到 redis 中,然后处理自己的业务。而 redis 中监听该消息的程序 Consumer 会后续处理,实现了消息生产者与消费者的解耦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Producer
func MsgProducer(key, value string) (int64, error) {
rdb := redis.NewClient(&redis.Options{
Addr: "43.142.129.110:6379",
Password: "", // no password set
DB: 0, // use default DB
})

return rdb.RPush(context.Background(), key, value).Result()
}

// Consumer
func MsgConsumer(key string) ([]string, error) {
rdb := redis.NewClient(&redis.Options{
Addr: "43.142.129.110:6379",
Password: "", // no password set
DB: 0, // use default DB
})
return rdb.BLPop(context.Background(), 0, key).Result()
}

// Test
func TestMsgProducer(t *testing.T) {
result, err := MsgProducer("name", "zhanghan")
assert.Nil(t, err)
assert.Equal(t, 1, int(result))
}

func TestMsgConsumer(t *testing.T) {
result, err := MsgConsumer("name")
assert.Nil(t, err)
fmt.Println(result)
}

异步阻塞方式,使得 Consumer 程序更为简单,不需要 while true 进行处理,减少了 CPU 消耗;

总结

  • 列表是线性有序结构,元素可重复;
  • POP 和 PUSH 都可以进行双端操作,LINSERT 可以插入元素;
  • LREM 可以删除元素,LTRIM 对列表进行截取;
  • LRANGE 会对下标进行修正,左端为 0,右端为 -1;
  • BLPOP、BRPOP 和 BRPOPLPUSH 当列表为空时,带有阻塞;