设为首页 收藏本站
查看: 1787|回复: 0

[经验分享] redis 用setbit(bitmap)统计活跃用户

[复制链接]

尚未签到

发表于 2018-11-4 12:15:45 | 显示全部楼层 |阅读模式
  getspool.com的重要统计数据是实时计算的。Redis的bitmap让我们可以实时的进行类似的统计,并且极其节省空间。在模拟1亿2千8百万用户的模拟环境下,在一台MacBookPro上,典型的统计如“日用户数”(dailyunique users)的时间消耗小于50ms, 占用16MB内存。Spool现在还没有1亿2千8百万用户,但是我们的方案可以应对这样的规模。我们想分享这是如何做到的,也许能帮到其它创业公司。
  Bitmap以及Redis Bitmaps快速入门(Crash Course on Bitmap and Redis Bitmaps)
  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 Bitmaps
  Redis允许使用二进制数据的Key(binary keys) 和二进制数据的Value(binary values)。Bitmap就是二进制数据的value。Redis的 setbit(key, offset, value)操作对指定的key的value的指定偏移(offset)的位置1或0,时间复杂度是O(1)。
  一个简单的例子:日活跃用户
  为了统计今日登录的用户数,我们建立了一个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的rediskey设计为play:yyyy-mm-dd-hh。当用户听了一首歌曲,我们只是简单地在bitmap中把标识这个用户的位置为1,时间复杂度是O(1)。
  [java]

  •   Redis.setbit(play:yyyy-mm-dd, user_id, 1)
  Redis.setbit(play:yyyy-mm-dd, user_id, 1)
  今天听过歌曲的用户就是key是play:yyyy-mm-dd的bitmap的位图计数。如果要按周或月统计,只要对这周或这个月的所有bitmap求并集,得出新的bitmap,在对它做位图计数。

  利用这些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天,一周,一个月的用户统计的时间消耗比较。
PeriodTime(ms)Daily50.2Weekly392.0Monthly1624.8  优化(Optimizations)
  前面的例子中,我们把日统计,周统计,月统计缓存到Redis,以加快统计速度。
  这是一种非常灵活的方法。这样进行缓存的额外红利是可以进行更多的统计,如每周活跃的手机用户—求手机用户的bitmap与周活跃用户的交集。或者,如果要统计过去n天的活跃用户数,缓存的日活跃用户使这样的统计变得简单——从cache中获取过去n-1天的日活跃用户bitmap和今天的bitmap,对它们做并集(Union),时间消耗是50ms。
  示例代码(SampleCode)
  下面的Java代码用来统计某个用户操作在某天的活跃用户。
  [java]

  •   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();
  •   }
  importredis.clients.jedis.Jedis;import java.util.BitSet;...    Jedis redis = new Jedis("localhost");    ...    public intuniqueCount(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();
  •   }
  importredis.clients.jedis.Jedis;import java.util.BitSet;...    Jedis redis = new Jedis("localhost");    ...    public intuniqueCount(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();    }
  References:
  [1] Redis setbit command
  About Author:
  Garyelephant
  garygaowork[at]gmail.com
  关注互联网创新、分布式、NOSQL,高并发技术。


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-630645-1-1.html 上篇帖子: redis队列缓存 + mysql 批量入库 + php离线整合 下篇帖子: Redis 性能调优相关笔记
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表