对付于不同的数据量,作为开拓职员,我们该当如何选择得当的数据构造去完成需求。
开拓职员考虑的层面,一是数据量,便是存储这些数据须要暂用内存块的大小;二是这种数据构造的插入、删除、修正、统计的韶光繁芜度的问题。
统计用户UV,从业务上我们常常会想到利用凑集(Set),凑集存储的数据不能重复,以是将用户IP直接存储到 Set 可以很完美地办理问题。

下面我们通过Set、HyperLogLogs 两个例子来体会一下。
数据量100W,存储1-100w的值。
有序列表Set
将100w的数据量写入Set构造。
代码
<?php//连接本地的 Redis 做事$redis = new Redis();$redis->connect('redis', 6379);// 由于写入数据量较大,以是我们采取 pipeline 来优化写入性能 $pipline = $redis->multi(Redis::PIPELINE);for($i = 0; $i < 1000000; $i++){ $pipline->sAdd('num' , $i); }$pipline->exec();
这里同时插入100W条数据,写入压力较大,以是利用 pipeline 作了优化。
命令行实行
php ceshi.php
结果截图
Set构造干系信息
截图里面最主要的信息是 set 占用了 4934341 B的内存空间,也便是 4M 的内存空间。
set在存储大数据量的时候利用的底层数据构造是 字典(dict),插入的韶光繁芜度为O(1),
统计的韶光繁芜度为O(1)。
HyperLogLogs
将100w的数据量写入 HyperLogLogs构造。
代码
<?php//连接本地的 Redis 做事$redis = new Redis();$redis->connect('redis', 6379);// 由于写入数据量较大,以是我们采取 pipeline 来优化写入性能 $pipline = $redis->multi(Redis::PIPELINE);for($i = 0; $i < 1000000; $i++){ $redis->rawCommand('pfadd','numpf', $i);}$pipline->exec();
命令行实行
php ceshi.php
结果截图
HyperLogLogs构造干系信息
这里我们程序插入的数据数量为 100w条, 这里却是 1009972 条。内存占用为 10587B,大约10KB。
hyperLogLogs是基于 概率统计 的,统计的结果数量偏差在 1% 以内。这个数据构造是为了利用小的内存空间统计大量的元素。
hyperLogLogs利用的底层数据构造为 bit,该构造利用 2^14 个桶,2^6 表示一个桶,统计2^64 的数据,数据量较大时,单个key固定占用的内存为12K。
hyperLogLogs的插入韶光繁芜度为O(1),统计的 均匀繁芜度 为O(1)。
Set\HyperLogLogs比拟两者在插入时的性能是一样的。统计时Set的性能会比HyperLogLogs高。由于 dict 构造体有一个字段 used 可以直接返回。HyperLogLogs 统计时会根据缓存是否过期(每次新增数据时会设置过期)来判断是否重新打算数量,如果过期重新打算,如果没有过期则直接返回 card 构造体字段。内存占用方面,同样100w数据,Set 占用 4M 空间, HyperLogLogs 占用 10K空间。dict 构造在数据逐渐增大的时候,会 rehash 扩容,对性能的损耗是比较大的。
综合比拟之下,在统计系统UV的场景中,HyperLogLogs 构造更加得当。