字符串

字符串键是 Redis 中最基本的键值对类型,被关联的 k-v 既可以是普通文本数据,有可以是二进制数据。

基本命令

SET

设置 k-v 字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 有引号
SET message "hello world"
SET homepage "hanzhang2566.github.io"
SET price "2.56"

-- 无引号
SET name hanzhang

-- 覆盖 name
SET name Jeremy

-- 参数
-- 1. NX:当 key 不存在的情况下生效;否则返回 nil
SET name hanzhang NX
-- 2. XX:当 key 存在的情况下生效;否则返回 nil
SET name hanzhang XX

时间复杂度:O(1)

MSET

设置多个 k-v 字符串。

1
2
3
4
-- 设置 name == hanzhang
-- address == shanghai
-- school == shzu
MSET name hanzhang address shanghai school shzu

时间复杂度:O(n)

  • 提高操作效率;
  • 方便多个 k-v 操作;

MSETNX

==所有== k 不存在的情况下,设置多个 k-v 字符串

1
2
-- name 和 age 都不存在的情况下,设置成功,返回 true;否则,返回 false
MSETNX name zhanghan age 28

时间复杂度:O(n)

GET

查询 k-v 字符串。

1
2
3
4
5
6
GET message
GET homepage
GET price

-- 不存在 addres,返回 nil
GET address

时间复杂度:O(1)

MGET

查询多个 k-v 字符串。

1
2
-- 同时查询 name address school
MGET name address school

时间复杂度:O(n)

  • 提高操作效率;
  • 方便多个 k-v 操作;

GETSET

返回 k 对应的字符串,并设置新值。

1
2
3
4
5
6
7
8
9
10
-- 设置 number == 1
SET number 1
-- 返回 number == 1,同时设置 number == 2
GETSET number 2

-- number == 2
GET number

-- counter 不存在,返回 nil,同时设置 counter == 20
GETSET counter 20

时间复杂度:O(1)

STRLEN

获取字符串长度。

1
2
3
4
-- 返回 len(name)
STRLEN name
-- 不存在,返回 0
STRLEN gender

时间复杂度:O(n)

GETRANGE

获取字符串索引指定闭区间的内容。

1
2
3
SET message "hello world"
-- 获取 message[0,4]闭区间内容
GETRANGE message 0 4

时间复杂度:O(n)

SETRANGE

设置字符串指定索引开始的字符串。

当设置字符串 > 当前字符串长度时,会自动扩容。

1
2
3
4
SET message "hello world"

-- greet world
SETRANGE message 0 "greet"

时间复杂度:O(n)

APPEND

k 存在时,就添加;不存在时,就执行 SET 操作。

1
2
3
4
5
6
7
8
SET message "Redis"
-- APPEND
APPEND message " is a database"
GET message

-- 类似于 SET
APPEND name "hanzhang"
GET name

时间复杂度:O(n)

存储方式

如果 k-v 字符串中,v 能够使用 C 语言中 long long int 或者 long double 时,redis 可以把 v 当作数字来处理;否则都是字符串存储。

INCR

自增 + 1。

1
2
3
4
5
6
7
SET num 100
-- 101
INCR num

-- 不存在,会先初始化为 0,计算后返回
-- 1
INCR i

时间复杂度:O(1)

INCRBY

增加指定数字。

1
2
3
4
5
6
7
SET num 100
-- 200
INCRBY num 100

-- 不存在,会先初始化为 0,计算后返回
-- 10
INCRBY i 10

时间复杂度:O(1)

INCRBYFLOAT

1
2
3
4
5
6
7
SET num 100
-- 103.14
INCRBYFLOAT num 3.14

-- 不存在,会先初始化为 0,计算后返回
-- 3.14
INCRBYFLOAT i 3.14

时间复杂度:O(1)

DECR

对应 INCR,只是减少。

DECRBY

对应 INCRBY,只是减少。

DECRBYFLOAT

对应 INCRBYFLOAT,只是减少。

示例

缓存

将数据存储再内存 Redis,而不是硬盘 MySQL。

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
func put(key, value string, expiration int) error {
rdb := redis.NewClient(&redis.Options{
Addr: "43.142.129.110:6379",
Password: "", // no password set
DB: 0, // use default DB
})

return rdb.Set(context.Background(), key, value, 0).Err()
}

func get(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.Get(context.Background(), key).Result()
}

// Test
func TestPut(t *testing.T) {
err := put("name", "hanzhang", 0)
assert.Equal(t, nil, err)
}

func TestGet(t *testing.T) {
value, err := get("name")
assert.Equal(t, nil, err)
assert.Equal(t, "hanzhang", value)
}

分布式锁

不同进程通过设置字符串键和删除字符串键来模拟获取锁和释放锁。

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
34
35
func acquire(key string) (bool, error) {
rdb := redis.NewClient(&redis.Options{
Addr: "43.142.129.110:6379",
Password: "", // no password set
DB: 0, // use default DB
})
// NX 参数确保了代表锁的字符串键只有在没有值的情况下被设置
return rdb.SetNX(context.Background(), key, "locked", 0).Result()
}

func release(key 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.Del(context.Background(), key).Result()
}

// Test
func TestAcquire(t *testing.T) {
b, err := acquire("lock")
assert.Nil(t, err)
assert.True(t, b)
b, err = acquire("lock")
assert.Nil(t, err)
assert.False(t, b)
}

func TestRelease(t *testing.T) {
i, err := release("lock")
assert.Nil(t, err)
assert.NotEqual(t, 0, i)
}
  • 无法保证该锁只能是锁的持有者才能释放;
  • 无法设置锁超时时间;

保存 Blog

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
34
35
36
37
38
39
func SaveBlog(title, author string, id int) (bool, error) {
rdb := redis.NewClient(&redis.Options{
Addr: "43.142.129.110:6379",
Password: "", // no password set
DB: 0, // use default DB
})

values := map[string]string{
"blog::" + strconv.Itoa(id) + "::id": strconv.Itoa(id),
"blog::" + strconv.Itoa(id) + "::title": title,
"blog::" + strconv.Itoa(id) + "::author": author,
}
return rdb.MSetNX(context.Background(), values).Result()
}

func GetBlog(id int) ([]interface{}, error) {
rdb := redis.NewClient(&redis.Options{
Addr: "43.142.129.110:6379",
Password: "", // no password set
DB: 0, // use default DB
})
return rdb.MGet(context.Background(),
"blog::"+strconv.Itoa(id)+"::id",
"blog::"+strconv.Itoa(id)+"::title",
"blog::"+strconv.Itoa(id)+"::author").Result()
}

// Test
func TestSaveBlog(t *testing.T) {
b, err := SaveBlog("redis-string", "hanzhang", 1)
assert.Nil(t, err)
assert.True(t, b)
}

func TestGetBlog(t *testing.T) {
blog, err := GetBlog(1)
assert.Nil(t, err)
fmt.Println(blog)
}

总结

  • k 和 v 都是既可以存储二进制,又可以存储字符串;
  • SET、SETNX 和 SETXX;
  • MSET、MSETNX 和 MGET 可以减少网络请求,提升程序效率;
  • 字符串值中正数索引 0 开始,负数索引 -1 开始;
  • GETRANGE 是返回索引闭区间;
  • SETRANGE 当长度不够时,会自动扩展;
  • APPEND 处理键不存在时执行 SET;键存在时,执行 APPEND;
  • 字符串值满足 C 语言中 long long int 或者 long double 时,当作数字来处理;