0%

Redis 五种数据类型

前言

本文介绍 Redis 的五种数据类型,并结合 redis-cli 交互式命令行,以及 Node.js 应用演示如何与 Redis 的五种数据类型进行交互,在文章最后还有关于过期时间设置的介绍。相关代码已上传至 GitHub

  • Node.js 应用使用 Koa2 框架,与 Redis 的操作都使用 Restful API 的形式展现,使用 Postman 测试接口;
  • 使用的依赖为ioredis,执行 npm i ioredis 安装依赖。之前有尝试 noderedis,但是在服务器上部署运行有连接不上 Redis 的 Bug 出现。

数据类型

Redis 通常被称为数据结构服务器,因为它提供了五种数据类型用于数据存储,包括 String 字符串、Hash 哈希、List 列表、Set 集合、Zset 有序集合

String

Redis String 是 Redis 最基本的类型,用于保存字符串类型的 Key-Value 键值对。

1
2
3
4
5
6
127.0.0.1:6379> SET string:s1 hello
OK
127.0.0.1:6379> GET string:s1
"hello"
127.0.0.1:6379> DEL string:s1
(integer) 1

Node.js 应用中的 contollers/string.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const router = require('koa-router')()
const redis = require('../config/redis')

const setString = async (ctx) => {
ctx.body = await redis.set('string:s1', 'hello world')
}

const getString = async (ctx) => {
ctx.body = await redis.get('string:s1')
}

const delString = async (ctx) => {
ctx.body = await redis.del('string:s1')
}

router.post('/string', setString)
router.get('/string', getString)
router.delete('/string', delString)

module.exports = router

Hash

Redis Hash 是一个 String 类型的 Field-Value 映射表,类似于 Java 的 HashMap,特别适合用于存储对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6379> HMSET hash:hash1 name "jack" age 10 score 90
OK
127.0.0.1:6379> HGET hash:hash1 name
"jack"
127.0.0.1:6379> HGETALL hash:hash1
1) "name"
2) "jack"
3) "age"
4) "10"
5) "score"
6) "90"
127.0.0.1:6379> HDEL hash:hash1 name age
(integer) 2
127.0.0.1:6379> HGETALL hash:hash1
1) "score"
2) "90"
127.0.0.1:6379> DEL hash:hash1
(integer) 1

需要特别注意的是,HGETALL key命令用于获取键的映射列表,HGETALL key field用于获取某个字段的值,HDEL key field用于删除键的某个字段,DEL key才是删除整个键。

contollers/hash.js 文件:

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
const setHash = async (ctx) => {
const student = {
name: 'Jack',
age: 18,
score: 90
}
ctx.body = await redis.hmset('hash:hash1', student)
}

const getHashAll = async (ctx) => {
ctx.body = await redis.hgetall('hash:hash1')
}

const getHashByField = async (ctx) => {
ctx.body = await redis.hget('hash:hash1', ctx.params.field)
}

const delHash = async (ctx) => {
ctx.body = await redis.del('hash:hash1')
}

const delHashByField = async (ctx) => {
ctx.body = await redis.hdel('hash:hash1', ctx.params.field)
}

router.post('/hash', setHash)
router.get('/hash', getHashAll)
router.get('/hash/:field', getHashByField)
router.delete('/hash', delHash)
router.delete('/hash/:field', delHashByField)

List

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边),列表中的元素可以重复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6379> LPUSH list:list1 hello
(integer) 1
127.0.0.1:6379> LPUSH list:list1 world my friend
(integer) 4
127.0.0.1:6379> RPUSH list:list1 right push
(integer) 6
127.0.0.1:6379> LLEN list:list1
(integer) 6
127.0.0.1:6379> LRANGE list:list1 0 5
1) "friend"
2) "my"
3) "world"
4) "hello"
5) "right"
6) "push"
127.0.0.1:6379> LPOP list:list1
"friend"
127.0.0.1:6379> RPOP list:list1
"push"
127.0.0.1:6379> LINDEX list:list1 2
"hello"

LPUSH添加到列表头部(Left Push),RPUSH添加到列表头部(Right Push)。相应的,LPOPRPOP即从列表头和列表尾移除一个元素并获取该值。LREM根据参数count的值,移除列表中与参数 value 相等的元素。

1
2
3
count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
count = 0 : 移除表中所有与 VALUE 相等的值。

语法如下:LREM key count value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> LRANGE list:list1 0 100
1) "my"
2) "world"
3) "hello"
4) "right"
5) "hello"
6) "foo"
7) "hello"
127.0.0.1:6379> LREM list:list1 -2 hello
(integer) 2
127.0.0.1:6379> LRANGE list:list1 0 100
1) "my"
2) "world"
3) "hello"
4) "right"
5) "foo"

contollers/list.js 文件:

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
const pushListAndGetAll = async (ctx) => {
for (let i = 0; i < 5; i++) {
redis.lpush('list:list1', i)
}
for (let i = 5; i < 10; i++) {
redis.rpush('list:list1', i)
}
const length = await redis.llen('list:list1')
console.log('length', length)
ctx.body = await redis.lrange('list:list1', 0, length)
}

const popList = async (ctx) => {
const lPopValue = await redis.lpop('list:list1')
const rPopValue = await redis.rpop('list:list1')
ctx.body = { lPopValue, rPopValue }
}

const deleteListByValue = async (ctx) => {
ctx.body = await redis.lrem('list:list1', 0, ctx.params.value)
}

router.post('/list', pushListAndGetAll)
router.delete('/list', popList)
router.delete('/list/:value', deleteListByValue)

Set

Redis 的 Set 是 String 类型的无序集合。集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是 O(1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> SADD set:set1 hello
(integer) 1
127.0.0.1:6379> SADD set:set1 my friend hello world
(integer) 3
127.0.0.1:6379> SMEMBERS set:set1
1) "hello"
2) "world"
3) "friend"
4) "my"
127.0.0.1:6379> SREM set:set1 my
(integer) 1
127.0.0.1:6379> SPOP set:set1
"hello"
127.0.0.1:6379> SMEMBERS set:set1
1) "world"
2) "friend"

SMEMBERS返回集合中的所有成员,SPOP移除并返回集合中的一个随机元素,SREM移除集合中一个或多个成员。

contollers/set.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const addMembersAndGetAll = async (ctx) => {
redis.sadd('set:set1', 'hello', 'world', 'my', 'friend', 'hello')
ctx.body = await redis.smembers('set:set1')
}

const popRandomMember = async (ctx) => {
ctx.body = await redis.spop('set:set1')
}

const removeMemberByValue = async (ctx) => {
ctx.body = await redis.srem('set:set1', ctx.params.value)
}

router.post('/set', addMembersAndGetAll)
router.delete('/set/random', popRandomMember)
router.delete('/set/:value', removeMemberByValue)

Zset

Redis Zset,即有序集合(sorted set),和集合一样也是 String 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 Double 类型的分数 (Score)。Redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数却可以重复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> ZADD zset:zset1 1 a
(integer) 1
127.0.0.1:6379> ZADD zset:zset1 2 b 3 c 4 d 5 e
(integer) 4
127.0.0.1:6379> ZRANGE zset:zset1 0 4
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
127.0.0.1:6379> ZREM zset:zset1 c
(integer) 1
127.0.0.1:6379> ZRANK zset:zset1 d
(integer) 2
127.0.0.1:6379> ZSCORE zset:zset1 d
"4"

ZREM移除有序集合中的一个或多个成员,ZRANK key member返回有序集合中指定成员的索引,ZSCORE返回有序集中,成员的分数值。

contollers/set.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const addMembersAndGetAll = async (ctx) => {
const students = [['Jack', 99], ['John', 90], ['Tony', 88], ['Helen', 92]]
students.forEach((item) => {
redis.zadd('zset:zset1', item[1], item[0])
})
const count = await redis.zcard('zset:zset1')
ctx.body = await redis.zrange('zset:zset1', 0, count)
}

const removeMemberByValue = async (ctx) => {
ctx.body = await redis.zrem('zset:zset1', ctx.params.value)
}

router.post('/zset', addMembersAndGetAll)
router.delete('/zset/:value', removeMemberByValue)

设置过期时间

在使用 Redis 的时候遇到了需要设置过期时间的情况,需要注意的是:Redis 只能针对最顶级的 KEY,也就是五种数据结构设置过期时间,而不能针对譬如 Hash 中的某一条映射设置。在设计数据存储类型时需要考虑到这点。

设置过期时间使用EXPIRE关键字,时间单位为秒(s),时间到后,整个 hash:hash1 键将被删除。

1
2
127.0.0.1:6379> EXPIRE hash:hash1 60
(integer) 1

可以使用TTL查看 KEY 的剩余过期时间,TTL为 -1 表示未设置过期时间。

1
2
127.0.0.1:6379> TTL hash:hash1
(integer) 46

参考