Redis如何降低20-50字符长度的字符串键的内存消耗?

3

我有一个由许多不同元素组成的密钥:

[15,000个唯一字符串]+[:]+[5个唯一字符串]+[:]+[1或0]+[:]+[15,000个唯一字符串]+[:]+[5个唯一字符串]+[:]+[1或0]=一个字符串,长度介于20到50个字符之间(例如:Vancouver:temp:1:Kelowna:high:0)

根据我的计算,将会有大约10亿种组合,每个组合将成为一个密钥。阅读redis文档(http://redis.io/topics/memory-optimization),他们建议对密钥进行哈希处理:例如,“object:11558960” =>“1”可以变成“object:1155”“8960” =>“1”。

我正在考虑最佳的内存优化方法。我的第一个想法是为字符串创建数字表示。因此,我将使用MySQL并创建查找表,其中每个字符串都将有一个相应的数字整数。这样,我可以更适当地进行哈希,因为我可以更容易地将数字分割,而不是字符串。此外,数字将创建较短的键,我认为这将节省内存。问题在于有10亿个键时,使用MySQL会产生很多开销,因为我需要创建联接等操作。
另一个解决方案是,在将其插入Redis之前,使用类似于php的gzcompress压缩我的字符串(http://labs.octivi.com/how-we-cut-down-memory-usage-by-82/)。
是否有任何最佳实践优化可用于降低我的Redis内存消耗?目前它仍然太高了。我愿意放弃CPU功率以节省更多内存。我的值只会是0-50之间的单个或双位整数。
1个回答

3

查询表已经彻底失效,不必费心。哈希算法看起来非常适合您的需求。您需要在15,000个唯一字符串之前将关键字拆分,以便为您提供足够的哈希密钥,使其值得努力。

因此,不是:

SET Vancouver:temp:1:Kelowna:high:0 10

您会使用

HSET Vancouver:temp:1 Kelowna:high:0 10

现在第一个 [1 or 0] 后面的所有内容都将成为哈希键,因此每个哈希键将有大约 150,000 种可能性。

我的计算结果与你的总密钥空间略有偏差:

15000 * 5 * 2 * 15000 * 5 * 2 == 22500000000 (22.5 billion)

这样做,您将拥有150,000个可能的键(Redis键),每个键有150,000个可能的哈希键。
在redis键和哈希键之间分割的位置越靠左,哈希键的数字就会更加倾斜。例如,如果您将其分割成如下形式:
HSET Vancouver:temp 1:Kelowna:high:0 10

您可能需要为哈希表创建75,000个Redis键,每个哈希表最多可能包含300,000个键值对。


另一种方法是使用整数作为键。如果您有两组分别由15,000个唯一字符串和5个唯一字符串构成的整数映射,则可以使用34位来表示任何键。例如:

 0000000000000   000   0   0000000000000   000   0
|      13     | | 3 | |1| |     13      | | 3 | |1|

13个比特位可以表示0-16383的范围(覆盖所需的1-15000) 3个比特位可以表示0-7的范围(覆盖所需的1-5) 1个比特位可以提供二进制1或0的范围。
假设有以下虚构值: 温哥华 == 9,987 温度 == 3 基洛纳 == 3,454 高度 == 2
你将得到:
(9987 << 21) + (3 << 18) + (1 << 17) + (3454 << 4) + (2 << 1) + (0 << 0)
==
20945229796

要获取给定键的值,您只需进行位移和掩码操作。
20945229796 >> 20
9987

(20945229796 >> 4) & ((1 << 13) - 1)
3454

这是一个简单的Python脚本,可以将值转换为整数,并将整数转换为值:
values = [9987, 3, 1, 3454, 2, 0]
bits =   [21, 18, 17, 4, 1, 0]

value_and_shift = zip(values, bits)


def key_from_values(values_and_shift):
    return sum(x << y for x, y in value_and_shift)

def extract_values(values_and_shift):
    last_shift = 35
    for value, shift in value_and_shift:
        print "Value should be:", value
        print "Value extracted:", (key >> shift) & ((1 << (last_shift - shift)) - 1)
        print
        last_shift = shift

key = key_from_values(value_and_shift)
print "Using value of:", key

extract_values(value_and_shift) 

输出

Using value of: 20945229796

Value should be: 9987
Value extracted: 9987

Value should be: 3
Value extracted: 3

Value should be: 1
Value extracted: 1

Value should be: 3454
Value extracted: 3454

Value should be: 2
Value extracted: 2

Value should be: 0
Value extracted: 0

非常好的方法和解释,关于键->整数转换!完全同意您的查找建议。 - Itamar Haber
你所说的整数映射是指Vancouver == 9987吗?如果是这个问题,那么存储位置由您决定。如果您已经将值存储在内存中,则可以使用哈希(或在PHP中使用关联数组)例如array(1 => "foo", 2 => "bar", ..., 9987 => "Vancouver")并拥有一个反向查找表来进行另一种方式的查找。我想我需要更多关于有效值存在的信息。 - sberry
@sberry 所有的值都来自MySQL。我正在使用Java,所以我考虑调用一次SQL查询来获取所有字符串,然后将它们全部放入一个哈希映射K(String)=> V(Integer)中,因为查找时间是O(1),检索是O(1)?也许ArrayList可以工作,因为我可以存储字符串并使用索引作为整数映射。所以我可以使用“get”和“indexOf”方法来查看字符串和整数?此外,从我所了解的情况来看,ArrayList在底层使用数组直接分配内存以获得良好的性能。有什么想法吗? - user2924127
这取决于您最常如何查找它们。看起来您永远不需要将整数键转换为值,因此K(String) -> V(Integer)应该可以解决问题,而且由于查找是O(1),所以我会坚持使用它。ArrayList查找将是O(N),我没有看到任何好处,因为您想将字符串转换为整数,而不是相反。 - sberry
你认为压缩整数键可以帮助节省内存吗? - user2924127
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接