阅读 2526

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

先赞后看,养成习惯❤️

BitMap 是什么

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

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

Redis 中的 BitMap

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

setbit 命令介绍

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

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

在一台 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

使用场景

统计活跃用户

使用时间作为 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

用户签到、用户在线状态

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

有哪些需要注意

需要压缩运算

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

Redis Bitmap 的好在于 ta 压缩存储空间。在日常用法中,这种压缩的代价是要经过 CPU 运算的。

大量数据的 setBit 会造成大量的网络请求。所以一般是 程序中 把 id 数组 pack() 设进位图变成一个 String。再一次性 set 进 Redis。

这就意味着 取出来的时候需要 unpack() 把 String 解压成 id 数组。不过得益于算法,这一步并不算太复杂

存储数据有限

另外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 => 01 => 12 => 03 => 1,
)
复制代码

可以用的的地方只有 'login20191230' 和 数组里的 key

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

关注下面的标签,发现更多相似文章
评论