位图篇

Redis位图(Bitmaps)是一种用于存储二进制逻辑和状态的紧凑型数据结构。它提供了在给定位置获取和设置位值的命令,以及在多个位图键之间执行 AND、OR、XOR 和 NOT 操作的命令。

redis-bitmap

场景

位图的使用场景大部分都和统计过滤有关系。

在线用户统计

可以使用位图来记录用户的在线状态,每个用户对应一个位,位的值表示用户是否在线,可以用于快速统计在线用户数、查询用户是否在线等。

// 设置用户id 1000的为在线状态
$redis->setBit('online:user', 1000, 1);

// 用户 1000是否在线
if ($redis->getBit('online:user', 1000) === 1) {

}

$redis->setBit('online:user', 1001, 0);

$redis->setBit('online:user', 1003, 1);

// 获取在线用户数量: bitCount 只会统计非空的数值
$count = $redis->bitCount('online:user');


用户签到系统

位图可以用来记录用户的签到情况,每个位表示一天,用户在某一天签到时将对应位设置为1,可以方便地统计用户的连续签到天数、查询用户某天是否签到等。

// 比如用户 2023年03月签到记录
$redis->setBit('user:1000:signed:202403', 0, 1); // 第1天
$redis->setBit('user:1000:signed:202403', 2, 1); // 第3天
$redis->setBit('user:1000:signed:202403', 5, 1); // 第6天
$redis->setBit('user:1000:signed:202403', 6, 1); // 第7天
$redis->setBit('user:1000:signed:202403', 7, 1); // 第8天
$redis->setBit('user:1000:signed:202403', 8, 1); // 第9天
$redis->setBit('user:1000:signed:202403', 9, 1); // 第10天

// 第9天用户否签到
if ($redis->getBit('user:1000:signed:202403', 8) === 1) {

}
// BITFIELD user:1000:signed:202403 get u10 0
// 取出用户偏移量为0取10位的结果,u代表无符号
// 127.0.0.1:6379> BITFIELD user:1000:signed:202403 get u10 0
// 1) (integer) 671
$count = $redis->rawCommand('BITFIELD', 'user:1000:signed:202403', 'get', 'u10', 0)[0];
// 671对应的二进制 1010011111 每一位都代表一天
$loop = 0; // 连续签到天数
while (true) {
    if (($count & 1) == 0) { // 最后一位与1 为0说明没有断签
        break;
    } else {
        $loop++;
    }
    $count = $count >> 1; // 匹配完右移一位
}

IP 地址访问统计

可以使用位图记录IP地址的访问情况,每个位表示一个时间段(比如一天),位的值表示该时间段内是否有访问记录,可以用于统计IP地址的活跃度、检测异常访问等。

活跃用户统计

可以使用位图记录用户的活跃情况,每个位表示一天,用户在某一天活跃时将对应位设置为 1,可以用于统计用户的活跃度、分析用户行为等。 这个有点类似签到,比如记录某月用户的活跃度。


// 第10天标为活跃
$redis->setBit('user:1000:active:202403', 9, 1); 

// 同时将用户加入到月度活跃集合中, 这样就可以取出月活用户列表。
$redis->sAdd('user:active:202403', 1000); 

消息已读标记

位图可以用来记录用户对消息的已读状态,每个位表示一条消息,用户读取某条消息时将对应位设置为 1,可以快速查询用户的未读消息数、标记已读消息等。


// 标记ID为9的消息已读
$redis->setBit('user:1000:message:view', 9, 1); 

// 未读消息总数 = 数据库消息总数 - $redis->bitCount('user:1000:message:view')

布隆过滤器

位图可以作为布隆过滤器的底层实现,用于快速判断一个元素是否可能存在于集合中,常用于缓存穿透和去重场景或者限流。查看HyperLogLog

数据压缩

在一些需要节省存储空间的场景下,位图可以用来存储大量的布尔值,节省存储空间的同时也能提高访问效率。

比如物联网中在线设备,以设备hash值哈希结构key存在线状态远没有以bitmap存在线状态来节省空间。

缓存预热

可以使用位图记录某些数据的缓存预热情况,每个位表示一个数据项,当某个数据项被预热时将对应位设置为1,可以用于监控预热进度、重启后的缓存加载等。

优缺点

优点

  • 节省空间: Bitmap使用极少的内存来存储大量的布尔值,比传统的数据结构如列表、集合等更节省空间。这对于需要存储大量布尔值的场景非常有用,例如在线用户状态、用户签到记录等。

  • 高效的位运算: Redis提供了丰富的位操作命令,可以对Bitmap进行高效的位运算操作,例如对位进行设置、清除、翻转、计数等操作,这些操作在某些场景下可以大幅提高性能。

  • 快速的集合操作: 由于Bitmap本质上是一个二进制集合,因此可以使用位操作命令进行集合操作,例如求交集、并集、差集等,这些操作在某些场景下比传统的集合操作更快速。

  • 快速的计数功能: Redis提供了位计数命令,可以快速计算Bitmap中设置为1的位的数量,这对于统计在线用户数量、活跃用户数量等指标非常有用。

  • 持久化支持: Bitmap可以通过Redis的持久化机制进行持久化存储,可以保证数据在重启后不丢失。

缺点

  • 不适合存储稀疏数据: 如果需要存储的数据大部分为 0,只有少量的 1,那么 Bitmap 的优势就不太明显,可能会浪费大量的内存空间。

  • 不支持动态扩容: 一旦创建了Bitmap,其大小是固定的,并且无法动态扩容,因此在设计时需要考虑好数据规模,避免空间浪费或者溢出。

  • 不支持复杂的数据结构: Bitmap只能存储 0 或 1 的二进制值,不支持存储复杂的数据结构,如字符串、哈希等。

  • 有限的位数: Bitmap的位数受Redis字符串长度的限制,最多只能存储2^32 - 1位(约4GB),因此对于超大规模的数据存储可能会有限制。

协作

如果你有更多的场景使用用例,可以通过github提交pr请求。有问题可以开issue编辑此页面