Redis 的 Big Key 问题

Big Key 问题

指 key 对应的 value 所占的内存空间比较大

  • 对于字符串类型的 value,一般认为超过 10KB 属于 bigkey

  • 非字符串类型(哈希、列表、集合、有序集合),体现在元素个数过多

造成的影响

  • 内存空间不均匀(在 Redis Cluster 中,bigkey 会造成节点的内存空间使用不均匀)
  • 超时阻塞:由于 Redis 单线程的特性,操作 bigkey 比较耗时,意味着阻塞 Redis 可能性增大
  • 网络阻塞:每次获取 bigkey 产生的网络流量较大,假设一个 bigkey 为 1MB,每秒访问量为 1000,那么每秒产生 1000MB 的流量,对于普通的千兆网卡(128 MB/s)的服务器来说简直是灭顶之灾,而且一般服务器会采用单机多实例的方式来部署,也就是说一个 bigkey 可能会对其它实例造成影响

解决方案

示例

通过用户 user_id 获取对应用户的宠物信息(假设用户、宠物信息存在 Redis 中,且一个用户只有一只宠物)

方案一

用户信息与宠物信息存在一起

把所有数据存入一个集合(特殊情况下这个集合的数据量可达上万甚至几万)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
127.0.0.1:6379>HSET user_info 666 {
"id": 666,
"username": "小明",
"avatar": "https://abc.com/cde.png",
"birthday": "1996-06-05",
"sex": "男",
"height": 175,
"weight": 120,
"hometown": "山东-济南",
"education": "本科",
"hobby": "跑步",
"pet": {
"id": 8888,
"nickname": "大黄",
"color": "yellow",
"type": "金毛",
"birthday": "2020-03-01",
"sex": "公",
"weight": 20
}
}

# 获取对应用户的信息
127.0.0.1:6379>HGET user_info 666

方案二

用户信息与宠物信息分开用不同的 key 存

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
127.0.0.1:6379>HSET user_info 666 {
"id":666,
"username":"小明",
"avatar":"https://abc.com/cde.png",
"birthday":"1996-06-05",
"sex":"男",
"height": 175,
"weight": 120,
"hometown":"山东-济南",
"education":"本科",
"hobby":"跑步",
"pet_id": 8888
}

127.0.0.1:6379>HSET pet_info 8888 {
"id": 8888,
"nickname": "大黄",
"color": "yellow",
"type": "金毛",
"birthday":"2020-03-01",
"sex":"公",
"weight": 20
}

# 先获取用户对应的宠物信息
127.0.0.1:6379>HGET user_info 666
# 代码中取出宠物 ID
# 再用宠物 ID 获取对应的宠物信息
127.0.0.1:6379>HGET pet_info 888

方案三

使用 Redis+Lua 操作

vim demo.lua

1
2
3
4
5
6
7
8
9
10
local user_key = KEYS[1]
local user_id = KEYS[2]
local pet_key = KEYS[3]

local user_info = redis.call('hget', user_key, user_id)
local user_info_obj = cjson.decode(user_info)

local pet_id = user_info_obj["pet_id"]
local pet_info = redis.call('hget', pet_key, pet_id)
return pet_info
1
2
3
4
5
redis-cli script load "$(cat demo.lua)"
14d025173dddb3a23a499b61cdf0bc24b42862e4


127.0.0.1:6379>evalsha 14d025173dddb3a23a499b61cdf0bc24b42862e4 3 user_info 666 pet_info

执行结果如下:

1
2
3
4
5
6
7
8
9
{
"id": 8888,
"nickname": "大黄",
"color": "yellow",
"type": "金毛",
"birthday":"2020-03-01",
"sex":"公",
"weight": 20
}

Redis+Lua 说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#eval 
# 示例 1:不接收 key
127.0.0.1:6379>eval 'return "hello world";' 0
hello world

# 示例2:接收 2 个 key、1 个 value
127.0.0.1:6379>eval 'return "hello " .. KEYS[1] .. " " .. KEYS[2] .. " " .. ARGV[1]' 2 redis world abc
hello redis world abc

#【evalsha】
# 示例 1:
$ redis-cli script load 'return "hello world";'
7b3381f971eda3ebbc2206c2765613851930ed87
$ 127.0.0.1:6379>evalsha 0
hello world

# 示例 2:
redis-cli script load 'return "hello " .. KEYS[1] .. " " .. KEYS[2] .. " " .. ARGV[1]'
659f0bd43c5320285d26e8cf6999eafdc62ca8f4
$ 127.0.0.1:6379>evalsha 659f0bd43c5320285d26e8cf6999eafdc62ca8f4 2 redis world abc
hello redis world abc