美文网首页技术分享
redis精进 - 玩归玩、闹归闹,别拿Bitmap开玩笑

redis精进 - 玩归玩、闹归闹,别拿Bitmap开玩笑

作者: java菲菲 | 来源:发表于2019-12-30 21:20 被阅读0次

    推荐阅读:

    1. 为何面试“字节”屡次惨败?都是“算法”在搞鬼,你却不知道!
    2. 终极手撕之架构大全:分布式+框架+微服务+性能优化,够不够?

    01 BitMap 是什么

    8 个 bit 组成一个 Byte,所以 bitmap 极大的节省储存空间
    你可以把它理解为一个特殊处理过的 字符串
    key代表业务属性、标签。一个 bit 位来表示某个元素对应的值或者状态。
    举个例子:登记每天活跃用户,key 代表 登录时间, 1、2、3...代表 用户id


    image.png

    那么根据上面位图可以得出:
    用户id:1 在 20191231 登录过
    用户id:2 在 20191230,20200101 登录过
    用户id:4 在 20191231 登录过
    其余没有登录

    02 Redis 中的 BitMap

    已经是源于 2.2.0 版本的 "新技术"。我觉得你会看到那个 双引号 不会当真的
    新增了setbit,getbit,bitcount等几个 bitmap 相关命令。但其实 setbit 等命令只不过是在 set 上的扩展而已。

    2.1 setbit 命令介绍

    指令 SETBIT key offset value
    复杂度 O(1)
    设置或者清空 key 的 value(字符串)在 offset 处的 bit 值(只能只 0 或者 1)。

    2.2 空间占用、以及第一次分配空间需要的时间

    在一台 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。
    -- <来自官方文档>
    大概的空间占用计算公式是:($offset/8/1024/1024)MB

    03 使用场景

    3.1 统计活跃用户

    使用时间作为 cacheKey,然后用户 ID 为 offset,如果当日活跃过就设置为 1
    那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个 redis 的命令
    命令 BITOP operation destkey key [key ...]
    说明:对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
    说明:BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数

    //日期对应的活跃用户
    $data = array(
        '2020-01-10' => array(1,2,3,4,5,6,7,8,9,10),
        '2020-01-11' => array(1,2,3,4,5,6,7,8),
        '2020-01-12' => array(1,2,3,4,5,6),
        '2020-01-13' => array(1,2,3,4),
        '2020-01-14' => array(1,2)
    );
    //批量设置活跃状态
    foreach($data as $date=>$uids) {
        $cacheKey = sprintf("stat_%s", $date);
        foreach($uids as $uid) {
            $redis->setBit($cacheKey, $uid, 1);
        }
    }
    
    $redis->bitOp('AND', 'stat', 'stat_2020-01-10', 'stat_2020-01-11', 'stat_2020-01-12');
    
    //总活跃用户:6
    echo "总活跃用户:" . $redis->bitCount('stat') . PHP_EOL;
    
    $redis->bitOp('AND', 'stat1', 'stat_2020-01-10', 'stat_2020-01-11', 'stat_2020-01-14') . PHP_EOL;
    
    //总活跃用户:2
    echo "总活跃用户:" . $redis->bitCount('stat1') . PHP_EOL;
    
    $redis->bitOp('AND', 'stat2', 'stat_2020-01-10', 'stat_2020-01-11') . PHP_EOL;
    
    //总活跃用户:8
    echo "总活跃用户:" . $redis->bitCount('stat2') . PHP_EOL;
    

    假设当前站点有 5000W 用户,那么一天的数据大约为 50000000/8/1024/1024=6MB

    3.2 用户签到、用户在线状态

    这些都大同小异我就不,慢性谋杀你们的时间了。Peace&Love

    04 有哪些需要注意

    4.1 需要压缩运算

    我觉得 人对事物的认知,得经过懵懂但美好憧憬 -> 被欺骗(坑)感情 -> 再次审视自己和事物 才能建立起一个 客观正确 的认知 -- 越欣赏越懂欣赏

    Redis Bitmap 的好在于 ta 压缩存储空间。在日常用法中,这种压缩的代价是要经过 CPU 运算的。
    大量数据的 setBit 会造成大量的网络请求。所以一般是 程序中 把 id 数组 pack() 设进位图变成一个 String。再一次性 set 进 Redis。
    这就意味着 取出来的时候需要 unpack() 把 String 解压成 id 数组。不过得益于算法,这一步并不算太复杂

    4.2 存储数据有限

    另外ta 存储的数据相当有限,举个例子:

    // 正常情况的 用户 id:1、3 登录数组:
    'login20191230' => array(
        1 => array(
            'user_id' => 1,
            'login_ip' => 'x.x.x.x',
            'usage_agent' => 'xxx'
        ),
        3 => array(
            'user_id' => 3,
            'login_ip' => 'x.x.x.x',
            'usage_agent' => 'xxx'
        ),
    )
    
    // bitmap 的 登录数组
    'login20191230' => array(
        0 => 0,
        1 => 1,   
        2 => 0,
        3 => 1,
    )
    

    可以用的的地方只有 'login20191230' 和 数组里的 key
    你会问 数组里的 value 不是看起来也能改么
    我给你个表情自己领会😑。硬要折腾是可以的,只是收支不平衡就是了

    作者:锐玩道
    原文链接:https://juejin.im/post/5e09496df265da33b071914a

    相关文章

      网友评论

        本文标题:redis精进 - 玩归玩、闹归闹,别拿Bitmap开玩笑

        本文链接:https://www.haomeiwen.com/subject/oaqvoctx.html