跳转至

Redis Big Key 问题

问题:

Redis中要存入大量相同结构的数据: 方案一:把所有数据存入一个集合(特殊情况下这个集合的数据量可达上万甚至几万)

方案二:把这些数据分开成两个 key 来存,一个 key 做索引,另一个 key 存储值,通过索引去取另一个 key 的值(只读一部分值)

示例

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

方案一:用户信息与宠物信息存在一起

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 存

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
}

# pipeline
# round trip time
# 两次网络往返
# 北京 client -> 上海 server
# 1300KM -> 2/3 * 30W KM/s => 13 ms
# 1s 80 次
# Redis
# 网络<- (微秒) <- 网络 

# 先获取用户对应的宠物信息
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

# nginx+lua => openresty
# golang <- lua

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

redis-cli script load $(cat demo.lua)

857f238ff59adcc7f3a0541356090909fb3edd2d

>127.0.0.1:6379>evalsha 857f238ff59adcc7f3a0541356090909fb3edd2d 3 pet_info 666 user_info

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

Redis+Lua 说明

#【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

Big Key 问题

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

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

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

造成的问题

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

参考

《Redis 开发与运维》付磊、张益军,机械工业出版社