减少 Redis 中的内存使用

Short structures 使用更短的结构

Redis stores a serialized version of the data, which must be decoded for every read, partially re-encoded for every write, and may require moving data around in memory.

Sharded structures 分片的数据结构

Packing bits and bytes 更紧凑的位和字节

社交网站的所有用户都有位置信息,如何在 Redis 中使用更少的内存存储每一个用户的未知信息?而且要能够方便的进行聚合计算

假设用户的 ID 都是连续的数组,且只存储国家,区域/州/省信息两级信息;并使用 ISO3 中的国家代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 国家代码
COUNTRIES = '''
ABW AFG AGO AIA ALA ALB AND ARE ARG ARM ASM ATA ATF ATG AUS AUT AZE BDI
BEL BEN BES BFA BGD BGR BHR BHS BIH BLM BLR BLZ BMU BOL BRA BRB BRN BTN
'''.splt()

# 美国,加拿大州信息
STATES = {
'CAN':'''AB BC MB NB NL NS NT NU ON PE QC SK YT'''.split(),
'USA':'''AA AE AK AL AP AR AS AZ CA CO CT DC DE FL FM GA GU HI IA ID
IL IN KS KY LA MA MD ME MH MI MN MO MP MS MT NC ND NE NH NJ NM NV NY OH
OK OR PA PR PW RI SC SD TN TX UT VA VI VT WA WI WV WY'''.split(),
}

最直接的方法是直接以字符串的方式直接存储国家和州代码,每个国家代码至少 3 个字节,州信息至少 2 个字节;

解决方法:以分片的形式存储定长大小的数据

实际存储的时候,并不存储实际的国家和州代码,而只存储其在 “表”(数组) 中的索引代表的字符,这样,只用两个字节就可以存储国家和州信息了(数组长度不会超过单个字节的十进制数字表示)。

image-20201018210437144

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_code(country, state):
cindex = bisect.bisect_left(COUNTRIES, country)
if cindex > len(COUNTRIES) or COUNTRIES[cindex] != country:
cindex = -1
cindex += 1

sindex = -1
if state and country in STATES:
states = STATES[country]
sindex = bisect.bisect_left(states, state)
if sindex > len(states) or states[sindex] != state:
sindex = -1
sindex += 1)
return chr(cindex) + chr(sindex)

Redis 中 STRING 结构的值大小限制为 512 MB, 所有用户的信息存储在单个 STRING 的值中必然不可取, 通过进行分片,限制每个分片最多存储 2 百万个用户的位置信息,单个 Key 的值占用大概在 2 MB. 通过 SETRANGE, GETRANGE 命令就可以更新和获取单个用户的位置信息

这里没有限制分片的个数,只限制了每个分片的大小,考虑的是用户 ID 都是有序的,通过 ID 和每个分片的大小,就可以计算每个 ID 所属的分片, 以及分片内的偏移量;为了知道当前一共由多少分片,就要存储当前的最大用户 ID.

这样进行存储,要对这些信息进行聚合计算,比如统计所有用户的国家,州分布,就比较容易,遍历所有分片进行统计,当然由于每个分片 STRING 的值比较大,需要分批多次去读取,而不能直接去读取,会阻塞其它客户端

image-20201018211551176

image-20201018211628124

这里虽然使用的是多个字节存储的,但是某些情况下,GETBIT, SETBIT 命令来获取和设置单个二进制位可能更高效,更节省空间。存储 Boolean 型?

存储数据在 Redis 中的方式,会极大地影响所使用地内存。