zoukankan      html  css  js  c++  java
  • 用Redis实现签到功能

    一、场景

    在很多时候我们会遇到用户签到的场景,每天用户进入应用时,需要获取用户当天的签到状态,如果没签到,用户可以进行签到,并且得到相关的奖励。我们可能需要每天的签到情况,必要的时候可能还需要统计一下每天用户签到人数。

    我们用Redis的Set数据结构可以轻松实现这个功能——以日期为key,以用户ID(对应着数据库的primary id)组成的集合为value,每当需要查询某个用户的签到状态时,只需要使用命令SISMEMBER key member就可以轻易得到想要的结果;用户签到时,使用命令SADD key member把用户ID添加到相应的日期中;统计某天用户的签到人数,可以用命令SCARD key

    以上的做法操作简便,易于理解,但是本篇要介绍的是另一种做法,使用Redis的位操作(bitmap)。

    我们都知道数据在机器上存储的最小单元是位(bit),1位可以存储0和1两种状态。这里的场景需要存储正是签到和未签到两种状态,因此一个用户只需要占用1位,也就是用位操作比用集合操作要省很多空间,下面先说一下位操作的方式,最后会给出两种方式的内存占用对比。

    Redis提供了一组位操作相关的指令,这里我们关注下面三个:

    • BITCOUNT key [start end]

    返回key的开始位置start到结束位置end之间位值为1的数量,如果key不存在,返回0;如果不指定start和end,返回整个key的位值为1的数量。

    • GETBIT key offset

    返回key的第offset位的位值。

    • SETBIT key offset value

    把key的第offset位的值设置为value,value只能是0或1。

    说明一下Redis的位操作的偏移量(offset)是从0开始算起的,而且最左边那位是第0位,这与数值的二进制有点不同(数值的二进制最右边那位是第0位)。

    二、解决方案

    有了以上两组操作之后,再回到我们的场景,这里假定我们有500w注册用户,日活又主动签到的用户只有30w,新用户的活跃度更高。如果使用redis的Set的操作,那么我们每天需要存储的数据就是这30w用户的id,一般来说,新注册的用户的活跃度会比旧用户的活跃度要高,为了方便测试,我们假定每天活跃的用户就是id最大的30w用户。下面是两种方案的具体操作:

    2.1、使用Set存储数据

    先准备30w条redis指令并且写到一个data.txt文件中,格式如下:

    SADD sign_in_20200113 4700001
    SADD sign_in_20200113 4700002
    SADD sign_in_20200113 4700003
    SADD sign_in_20200113 4700004
    SADD sign_in_20200113 4700005
    ...
    

    然后通过redis的管道命令来把数据写到redis:

    cat data.txt | redis-cli --pipe
    

    完成后可以看一下数据是否成功写到redis中:

    127.0.0.1:6379> scard sign_in_20200113
    (integer) 300000
    

    指定的key已经有30w个用户签到,同时用info命令查看一下这时redis的占用内存:

    # Memory
    used_memory:21604936
    used_memory_human:20.60M
    

    占用的内存大概是20M。

    然后我们需要查询一个用户的签到状态和用户签到都非常方便。

    2.2、使用bitmap存储数据

    接下来我们再用bitmap进行操作,同样我们准备好相关的redis指令,如下:

    SETBIT sign_in_20200113 4700001 1
    SETBIT sign_in_20200113 4700002 1
    SETBIT sign_in_20200113 4700003 1
    SETBIT sign_in_20200113 4700004 1
    SETBIT sign_in_20200113 4700005 1
    ...
    

    完成后我们可以通过bitcount命令查看一下签到人数:

    127.0.0.1:6379> bitcount sign_in_20200113
    (integer) 300000
    

    这时再看一下占用内存的情况:

    # Memory
    used_memory:2088200
    used_memory_human:1.99M
    

    只占了大约2M,和使用Set的方式相差了10倍!

    三、方案对比

    • 使用Set的方式所占用的内存只与数量相关,和存储哪些id无关
    • 使用bitmap的方式所占用的内存与数量没有绝对的关系,而是与最高位有关。比如假设id为500w的用户签到了,那么从1号用户到4999999号用户不管是否签到,所占的内存都是500w个bit,这也是bitmap的最坏情况,假如上述场景是1号用户到30w号用户签到,那么使用的内存就只是30w个bit,大约只占了940K,比最坏情况还要省一半的空间。
    • 使用bitmap存储,最大的offset是2^32-1,也就是一个bitmap格式的key最大可以存储512M的数据。
    • 使用bitmap存储的时候,有可能一开始是id较小的用户签到了,后面会有id较大的用户签到,这种情况下key的长度需要动态扩展,这需要花费一定的时间。在MBP2010上给offset为232-1分配512M的内存大约需要300ms,给offset为230-1分配128M的内存大约需要80ms,offset为228-1分配32M需要约30ms,offset为226-1分配8M大约需要8ms。当然,如果分配了可以容纳高位的空间后,使用低位时就不需要再扩容,比如一开始就通过setbit设置了第500w位的值,后面再使用offset小于500w的位都可以直接使用。
    • 如果需要另外存储,可以每天用定时任务把数据写在需要的地方,比如MySQL。

    四、适用场景

    redis的bitmap操作虽然优点明显,但局限性也是显而易见的。因为它使用1bit来存储数据,所以只适用存储只有两个状态的数据,比如用户签到,资源(视频、文章、商品)的已读或未读状态。

    关于redis的bitmap更多用法,可以参考官方文档。

  • 相关阅读:
    git 提交代码冲突解决步骤
    侯小厨的最新技术探索
    Groovy学习笔记(总索引)
    Compilation failure 找不到org.apache.http.annotation.NotThreadSafe的类文件
    Grafana 曲线图报错“parse_exception: Encountered...”
    Vue底层学习6——节点编译与属性遍历
    thanks for everything
    spring data mongodb连接
    windows docker lookup registry-1.docker.io on 192.168.65.5:53: no such host.
    antd 表单的两种校验方式
  • 原文地址:https://www.cnblogs.com/spareyaya/p/12806637.html
Copyright © 2011-2022 走看看