An example of a Bloom filter, representing the set {x, y, z}. The colored arrows show the positions in the bit array that each set element is mapped to. The element w is not in the set {x, y, z}, because it hashes to one bit-array position containing 0. For this figure, m = 18 and k = 3.
由上图可知,存储同样数量的历史数据,BloomFilter所占内存大小大概比item list小一个数量级。测试结果表明,容量为10000,假阳性率为0.01的guava BloomFilter对象导出的字节数组的长度为11990,所占内存大约为12KB,也就是说存储一个用户10000条历史数据只占用12KB。而如果历史数据以item id list的形式存储,由于item id是一个32位字符(十六进制数)的字符串,用utf-8编码,一个字符占用一个字节,所以存储10000条item id,则会占用10000×32×1=320000B,大约是312KB,是用BloomFilter存储时占用的内存的 26 倍!也就是使用BloomFilter比item list节省了大约 96% 的内存,减少一大笔存储开销。虽然我们同时也使用pika存储item id list作为存底,但是pika是硬盘数据库,远比内存便宜,相比较redis而言几乎可以忽略不计。
效率
方案一在将数据写入redis的zset有两种方法,一是用循环将item list中的id异步添加到zset中,另一种是将item id list和对应的分数添加到一个数组,然后将该数组作为zadd参数一次性传输到redis并执行,这两种方法在效率上其实不会差很多。方案二只需要把BloomFilter导出为byte数组,然后直接以byte类型存储到redis即可。从编码角度来看,BloomFilter要简单地多;从网络传输的角度看,item id list由于数据量大,所以要比BloomFilter消耗的时间长;从redis端执行命令的角度看,方案一需要多次执行zadd命令,而方案二只需要执行一次set(bytes),所以方案二还是比方案一耗时更少。所以综合来看,方案二的效率要高得多。