getspool.com的重要统计数据是实时计算的。Redis的bitmap让咱们能够实时的进行相似的统计,而且极其节省空间。在模拟1亿2千8百万用户的模拟环境下,在一台MacBookPro上,典型的统计如“日用户数”(dailyunique users) 的时间消耗小于50ms, 占用16MB内存。Spool如今尚未1亿2千8百万用户,可是咱们的方案能够应对这样的规模。咱们想分享这是如何作到的,也许能帮到其它创业公司。
java
Bitmap(即Bitset)
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR以及其它位操做。
位图计数(Population Count)
位图计数统计的是bitmap中值为1的位的个数。位图计数的效率很高,例如,一个bitmap包含10亿个位,90%的位都置为1,在一台MacBook Pro上对其作位图计数须要21.1ms。SSE4甚至有对整形(integer)作位图计数的硬件指令。
redis
为了统计今日登陆的用户数,咱们创建了一个bitmap,每一位标识一个用户ID。当某个用户访问咱们的网页或执行了某个操做,就在bitmap中把标识此用户的位置为1。在Redis中获取此bitmap的key值是经过用户执行操做的类型和时间戳得到的。
缓存
这个简单的例子中,每次用户登陆时会执行一次redis.setbit(daily_active_users, user_id, 1)。将bitmap中对应位置的位置为1,时间复杂度是O(1)。统计bitmap结果显示有今天有9个用户登陆。Bitmap的key是 daily_active_users,它的值是1011110100100101。
由于日活跃用户天天都变化,因此须要天天建立一个新的bitmap。咱们简单地把日期添加到key后面,实现了这个功能。例如,要统计某一天有多少个用户 至少听了一个音乐app中的一首歌曲,能够把这个bitmap的redis key设计为play:yyyy-mm-dd-hh。当用户听了一首歌曲,咱们只是简单地在bitmap中把标识这个用户的位置为1,时间复杂度是 O(1)。
[java]
并发
Redis.setbit(play:yyyy-mm-dd, user_id, 1)
今天听过歌曲的用户就是key是play:yyyy-mm-dd的bitmap的位图计数。若是要按周或月统计,只要对这周或这个月的全部bitmap求并集,得出新的bitmap,在对它作位图计数。
app
利用这些bitmap作其它复杂的统计也很是容易。例如,统计11月听过歌曲的高级用户(premium user):
(play:2011-11-01∪ play:2011-11-02∪ … ∪ play:2011-11-30)∩premium:2011-11分布式
1亿2千8百万用户的性能比较(Performance comparison using 128 million users)高并发
下面的表格显示了在1亿2千8百万用户上完成的时间粒度为1天,一周,一个月的用户统计的时间消耗比较。性能
前面的例子中,咱们把日统计,周统计,月统计缓存到Redis,以加快统计速度。
优化
下面的Java代码用来统计某个用户操做在某天的活跃用户。
[java]
spa
import redis.clients.jedis.Jedis; import java.util.BitSet; ... Jedis redis = new Jedis("localhost"); ... public int uniqueCount(String action, String date) { String key = action + ":" + date; BitSet users = BitSet.valueOf(redis.get(key.getBytes())); return users.cardinality(); }
下面的Java代码用来统计某个用户操做在一个指定多个日期的活跃用户。
[java]
import redis.clients.jedis.Jedis; import java.util.BitSet; ... Jedis redis = new Jedis("localhost"); ... public int uniqueCount(String action, String... dates) { BitSet all = new BitSet(); for (String date : dates) { String key = action + ":" + date; BitSet users = BitSet.valueOf(redis.get(key.getBytes())); all.or(users); } return all.cardinality(); }
Garyelephant
garygaowork[at]gmail.com
关注互联网创新、分布式、NOSQL,高并发技术。