redis-bitmap在签到和统计状态项目中的妙用

1、使用redis-bitmap背景和优点

1.项目背景

  • 最近作的需求中,有个每日签到功能,须要用户每日签到,而且统计累计签到了多少天等这样的信息。
  • 在思考项目方案的时候,最简单的就是mysql,记录下用户天天签到的数据,每一个用户天天签到的时候都insert一条数据,利用mysql统计起来也超级方便,然而;
  • 思考一个问题,一个用户一年的签到数据将是365条,若是用户数大的话,将是365*用户数这么多条记录,这些记录仅仅是一个状态数据,一个签到状态实际上用“1”或者“0”这样的数字就能彻底表示出来,那么有没有更好更节省存储空间的方式呢?
  • 实际上,遇到这种只须要存储 “是”或者“否” 状态的数据的时候,能够尝试是否可使用bitmap这种数据结构来存储数据。

2.什么是bitmap

  • bitmap能够想象成一个围棋盘这种点位阵,每一个格子表明一个bit位,只能存储1或者0两种数据。
  • 所以,bitmap使用场景实际上就是只能存储“状态”类型数据,好比咱们将在项目中用到的签到数据,由于签到状态只有两种,一种已签到能够用“1”表示,一种未签到能够用“0”表示。

3.bitmap优点

  • bitmap最大的优点就是能够存储海量数据而且快速检索到具体的数据。
  • 咱们再回到以前说的签到功能的mysql方案,一个用户一天的签到数据包含uid、sign_status、created_at,最起码三个int类型字段,一个int类型是4个字节,三个int就是12个字节,也就是一个用户一天的数据最低占12个字节,千万用户一年的数据是1000w x 365 x 12=438000w字节=42773M,请记住这个42773M,是mysql存储占用的空间,大约41G。
  • 那么咱们来计算一下用bitmap存储千万用户一年的数据占据多少空间。假如咱们用户uid大部分是连续的,那么天天全部的用户签到数据都存在一个key,好比uid=1000的用户签到,就将这个key的第1000位设置为1,这样实际上全部用户一年的数据只有365条,一条数据保存1000w个bit,因为一个字节8个bit,就是125w字节,那么千万用户一年的数据是125w x 365=45625w字节=445M。
  • 以上,咱们知道,使用mysql保存一年千万用户最低42773M空间,而使用bitmap保存一年千万用户最低445M,bitmap空间只占了mysql的1%,这个差异真的是天差地别。
  • 另外bitmap查询数据的时候,redis提供了相关命令能够直接统计一个key中有多少个1,也能够直接获取到具体那个位bit的值。
  • 其中,在一台2010MacBook Pro上,offset为2^32-1(分配512MB)须要~300ms,offset为2^30-1(分配128MB)须要~80ms,offset为2^28-1(分配32MB)须要~30ms,offset为2^26-1(分配8MB)须要8ms。因此查询某个位的值的时候速度也是至关快,redis中一个string类型的key限制不能超过512M。

2、redis-bitmap相关命令介绍

  1. Setbitphp

     语法:setbit key offset value
    
      描述:
    
        对key所储存的字符串值,设置或清除指定偏移量上的位(bit)。
    
        位的设置或清除取决于 `value` 参数,能够是 `0` 也能够是 `1` 。
    
        当 `key` 不存在时,自动生成一个新的字符串值。
    
        字符串会进行伸展(grown)以确保它能够将 `value` 保存在指定的偏移量上。当字符串值进行伸展时,空白位置以 `0` 填充。
    
     注意:
    
        `offset` 参数必须大于或等于 `0` ,小于 2^32 (bit 映射被限制在 512 MB 以内)。
    
        由于 Redis 字符串的大小被限制在 512 兆(megabytes)之内, 因此用户可以使用的最大偏移量为 2^29-1(536870911) , 若是你须要使用比这更大的空间, 请使用多个 `key。`
    
        当生成一个很长的字符串时, Redis 须要分配内存空间,  该操做有时候可能会形成服务器阻塞(block)。 在2010年出产的Macbook Pro上, 设置偏移量为 536870911(512MB  内存分配)将耗费约 300 毫秒, 设置偏移量为 134217728(128MB 内存分配)将耗费约 80 毫秒, 设置偏移量  33554432(32MB 内存分配)将耗费约 30 毫秒, 设置偏移量为 8388608(8MB 内存分配)将耗费约 8 毫秒。
  2. getbitmysql

    语法:getbit key offset
    
       描述:
    
        对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
    
        当 offset 比字符串值的长度大,或者 key 不存在时,返回 0
  3. bitcountredis

    语法:bitcount key [start] [end]
    
      返回值:被设置为 1 的位的数量
    
      描述:
    
        计算给定字符串中,被设置为 1 的比特位的数量
    
        通常状况下,给定的整个字符串都会被进行计数,经过指定额外的 start 或 end 参数,可让计数只在特定的字节上进行。注意不是bit位,是字节。
          例如:假如key1的value是00001100 11001000 11110000
          <1> bitcount key1 0 0 
              这个是获取key1中第0个字节组中bit为1的count,也就是00001100 中查询,返回2
          <2> bitcount key1 0 1 
              这个是获取key1中第0-1个字节组中bit为1的count,也就是00001100 11001000中查询,返回5  
          <3> bitcount key1 1 2 
              这个是获取key1中第1-2个字节组中bit为1的count,也就是11001000 11110000中查询,返回7   
    
        start 和 end 参数的设置和 GETRANGE key start end 命令相似,均可以使用负数值: 好比 -1表示最后一个bit, -2 表示倒数第二个bit,以此类推。
    
        不存在的 key 被当成是空字符串来处理,所以对一个不存在的 key 进行 BITCOUNT 操做,结果为 0 。
  4. bitpossql

    语法: bitpos key bit [start] [end] 
         
      返回值:返回字符串里面第一个被设置为1或者0的bit位。
    
      描述:
    
        返回一个位置,把字符串当作一个从左到右的字节数组,第一个符合条件的在位置0,其次在位置8,等等。
    
        默认状况下整个字符串都会被检索一次,只有在指定start和end参数(指定start和end位是可行的),该范围被解释为一个字节的范围,而不是一系列的位。因此start=0 而且 end=2是指前三个字节范围内查找。
  5. bitop数组

    语法:bitop operation destkey key [key ...]
    
        operation 能够是 AND 、 OR 、 NOT 、 XOR 这四种操做中的任意一种:
    
              BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
              BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
              BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
              BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
    
        除了 NOT 操做以外,其余操做均可以接受一个或多个 key 做为输入。    
    
      返回值:保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等
    
      描述:
    
        对一个或多个保存二进制位的字符串 key 进行位元操做,并将结果保存到 destkey 上。
    
      注意:处理不一样长度的字符串
    
        当 BITOP 处理不一样长度的字符串时,较短的那个字符串所缺乏的部分会被看做 0 。
    
        空的 key 也被看做是包含 0 的字符串序列。
  6. bitfield服务器

    语法:bitfield key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
    
         
    
      描述:
    
        该命令将 Redis 字符串视为一个位数组,而且可以处理具备不一样位宽和任意非(必要)对齐偏移量的特定整数字段。
        实际上,使用此命令能够将位偏移量为1234的带符号5位整数设置为特定值,从偏移量4567中检索31位无符号整数。相似地,该命令处理指定整数的递增和递减,提供保证和良好指定的溢出和下溢行为,用户能够配置。
    
      注意:详情能够查看文章:https://cloud.tencent.com/developer/section/1374165

3、bitmap应用场景

1.用户每日签到

  • 用户每日签到,可使用redis-bitmap,关于key的保存方式,有不一样方案,一种是一个key保存一天内全部用户的签到状态,这种方式注意,若是大部分签到的用户uid并不连续,那么整个key看到将会有大量的00000000,只有零星的1,因此这种方式适用于连续uid数据保存;
  • 另外一种key存值方式,就是以key=uid_year_month保存一个用户一个月的签到数据,这种适合用户量少的方式。

2.统计用户活跃用户

  • 使用时间做为key,用户uid做为offset,活跃过就设置为1
  • 遇到一样的问题,就是uid不连续怎么办?
  • 这种方式能够考虑分片保存:例如uid对1000取模获得的数都是小于1000的,uid小于1000的做为分片1,uid在1000~2000做为分片2,那么能够设置key=分片id,具体的位就是uid对1000取模获得的值,这样保存的时候只是须要先肯定当前uid在哪一个分片便可。

4、思考及其余注意的点

  1. 具体的bitmap保存方式,要尽可能考虑本身业务状况,经过计算具体哪一种方式保存更节省空间来肯定。
  2. redis中一个key限制不能大于512M,bit位个数尽可能小于2^29-1(536870911),大约5亿,别最后超限制了。
相关文章
相关标签/搜索