PHP使用Redis的BitMap特性实现签到功能

PHP使用Redis的BitMap特性实现签到功能

一、BitMap

BitMap原理

bitMap 位图并不是一种数据结构,其实也就是 byte 数组,用二进制表示,只有 0 和 1 两个数字。基于string数据类型的按位操作符,高阶数据类型的一种。

bitMap支持的最大位数是2^32位,使用512M内存就可以存储多达42.9亿的字节信息。

BitMap操作命令
  • gitbit
    • 用法:getbit key offset
    • 含义:对key所存储的字符串值,获取指定偏移量上的位(bit)
  • setbit
    • 用法:setbit key offset value
    • 含义:对key所存储的字符串值,设置或清除指定偏移量上的位(bit)
      1. 返回值为该位在setbit之前的值
      2. value只能取0或1
      3. offset从0开始,即使原位图只能10位,offset可以取1000
  • bitcount
    • 用法:bitcount key [start end]
    • 含义:获取位图指定范围中位值为1的个数。如果不指定start与end,则取所有
  • bitop
    • 用法:bitop op destKey key1 [key2…]
    • 含义:做多个BitMap的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destKey中
  • bitpos
    • 用法:bitpos key tartgetBit [start end]
    • 含义:计算位图指定范围第一个偏移量对应的的值等于targetBit的位置
      1. 找不到返回-1
      2. start与end没有设置,则取全部
      3. targetBit只能取0或者1
  • BITFIELD
    • 用法:BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
    • 含义:自3.2.0起可用。该命令将 Redis 字符串视为一个位数组,并且能够处理具有不同位宽和任意非(必要)对齐偏移量的特定整数字段

二、BitMap对比MySql

mysql存储
  • mysql表结构

    字段描述类型存储需求
    id数据表主键bigInt8byte
    user_id用户idbigInt8byte
    sign_date签到日期DATETIME8btye
    amount连续签到int4byte
  • mysql占用存储空间

    1. 1个用户1天签到数据:28Byte
    2. 1000W用户1天签到数据:28Byte * 1000w = 268M
    3. 1000W用户1年签到数据:268M * 365 = 95.6G
    4. 1000W用户5年签到数据:95.6G * 5 = 478G
redis BitMap存储
  • redis BitMap存储

    keyvalue
    user_id_2021_040 1 1 1 1 0 1 1 1 1 1 1 1 1 0 0…(签到最多31天)
  • redis BitMap占用空间

    1. 31位需要3Byte + key(1Byte)
    2. 一个用户一个月签到数据:4Byte
    3. 1000W用户1个月签到数据: 40M
    4. 1000W用户1年签到数据:40M * 12 = 480M
    5. 1000W用户5年签到数据:480M * 5 = 2G

三、命令行实现BitMap签到

签到
  1. 用户id为123的用户2021年3月1号签到
    $ setBit sign:123:202103 0 1
    
  2. 用户id为123的用户2021年3月2号签到
    $ setBit sign:123:202103 1 1
    
获取签到
  1. 查看用户id为123的用户2021年3月1号签到

    $ getBit sign:123:202103 0
    
  2. 查看用户id为123的用户2021年3月2号签到

    $ getBit sign:123:202103 1
    
  3. 查看用户id为123的用户2021年3月 首次签到

    $ bitPos sign:123:202103 1
    
  4. 查看用户id为123的用户2021年3月 首次漏签

    $ bitPos sign:123:202103 0
    
  5. 查看用户指定范围内的签到 返回的是十进制

    bitfield sign:123:202103 get u3 6
    
统计签到
  • 获取用户当月签到次数
    $ bitCount sign:123:202103
    

四、PHP实现BitMap签到

签到
  1. 用户id为123的用户2021年3月8号签到

    <?php
    
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $key = 'sign:123:202103';
        $day = 8;
        $redis->setBit($key, $day, 1);
    ?>
    
  2. 用户id为123的用户2021年3月10号签到

    <?php
    
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $key = 'sign:123:202103';
        $day = 10;
        $redis->setBit($key, $day, 1);
    ?>
    
查看签到
  1. 查看用户id为123的用户2021年3月10号签到

    <?php
    
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $key = 'sign:123:202103';
        $day = 8;
        $sign = $redis->getBit($key, $day);
        var_dump($sign);
    ?>
    
  2. 查看用户id为123的用户2021年3月份的签到情况( bitmap 实际就是 string 类型,使用 Get)

    <?php
    
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $key = 'sign:123:202103';
        $sign = $redis->get($key); //这里查出来的是二进制字符串
        $bitmap_bin_str = StrToBin($sign);
        for($i = 1; $i <= 31; $i++) {
            echo $i.'号签到'.$bitmap_bin_str{$i-1}.PHP_EOL;
        }
        # 二进制字符串转字符
        function StrToBin($str){
            $arr = preg_split('/(?<!^)(?!$)/u', $str);
            foreach($arr as &$v){
                $temp = unpack('H*', $v); $v = base_convert($temp[1], 16, 2);
                unset($temp);
            }
            return join(' ',$arr);
        }
    ?>
    
  3. 查看用户id为123的用户2021年3月 首次签到

    <?php
    
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $key = 'sign:123:202103';
    $day = $redis->bitPos($key,1);
    var_dump($day);
    ?>
    
    1. 查看用户id为123的用户2021年3月 首次漏签
    <?php
    
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $key = 'sign:123:202103';
    $day = $redis->bitPos($key,0);
    var_dump($day);
    ?>
    
统计签到
  1. 查看用户id为123的用户2021年3月份签到次数

    <?php
    
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $key = 'sign:123:202103';
        $count = $redis->bitcount($key,0,-1);
        var_dump($count);
    ?>
    
  2. 查看用户id为123的用户2021年3月8号连续签到次数

    <?php
    
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $key = 'sign:123:202103';
        $str = '1111001110000000000000000000000'; //用户当月签到情况
        $day = 8;
        if ($str{$day-1} == '0') {
            var_dump(0);
            exit;
        }
        $count = 1;
        for ($i=$day; $i>=0; $i--) {
            // var_dump($i-1);
            $sign = ($str{$i-1}) & ($str{$i-2});
            if ($sign == 1) {
                $count += 1;
            } else {
                break;
            }
        }
        var_dump($count);
    ?>
    

五、使用经验和资料参考

使用经验
  • BitMap 是 sting 类型,最大 512 MB
  • 注意 setbit 时的偏移量,可能有较大耗时
  • 位图不是绝对好,获取不到签到的准确时间,只能判断指定日期有没有签到。
  • redis配合mysql使用,当月数据存redis,上月数据同步到mysql(一个月数据的签到存一条mysql数据)。
  • 签到日志可以配合nongoDB使用。
资料参考
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值