Redis 位图
Redis 位图简介
位图不是一个实际的数据类型,而是在字符串类型上定义的一组面向位的操作,该字符串类型被视为位向量。由于字符串是二进制安全的 Blob,并且它们的最大长度为 512 MB,因此它们适合设置多达 2^32 个不同的位。
您可以在一个或多个字符串上执行按位操作。位图用例的一些示例包括
- 对于集合成员对应于整数 0-N 的情况,有效地表示集合。
- 对象权限,其中每个位代表一个特定的权限,类似于文件系统存储权限的方式。
基本命令
查看 完整的位图命令列表。
示例
假设您有 1000 名骑自行车的人在乡村骑行,他们的自行车上的传感器标记为 0-999。您想快速确定给定的传感器是否在该小时内已 ping 过跟踪服务器,以检查骑车人。
您可以使用位图来表示这种情况,其键引用当前小时。
- 骑车人 123 在 2024 年 1 月 1 日 00:00 时 ping 过服务器。然后,您可以确认骑车人 123 ping 过服务器。您还可以检查骑车人 456 是否在同一小时内 ping 过服务器。
> SETBIT pings:2024-01-01-00:00 123 1
(integer) 0
> GETBIT pings:2024-01-01-00:00 123
1
> GETBIT pings:2024-01-01-00:00 456
0
"""
Code samples for Bitmap doc pages:
https://redis.ac.cn/docs/latest/develop/data-types/bitmaps/
"""
import redis
r = redis.Redis(decode_responses=True)
res1 = r.setbit("pings:2024-01-01-00:00", 123, 1)
print(res1) # >>> 0
res2 = r.getbit("pings:2024-01-01-00:00", 123)
print(res2) # >>> 1
res3 = r.getbit("pings:2024-01-01-00:00", 456)
print(res3) # >>> 0
r.setbit("pings:2024-01-01-00:00", 123, 1)
res4 = r.bitcount("pings:2024-01-01-00:00")
print(res4) # >>> 1
import assert from 'assert';
import { createClient } from 'redis';
const client = createClient();
await client.connect();
const res1 = await client.setBit("pings:2024-01-01-00:00", 123, 1)
console.log(res1) // >>> 0
const res2 = await client.getBit("pings:2024-01-01-00:00", 123)
console.log(res2) // >>> 1
const res3 = await client.getBit("pings:2024-01-01-00:00", 456)
console.log(res3) // >>> 0
await client.setBit("pings:2024-01-01-00:00", 123, 1)
const res4 = await client.bitCount("pings:2024-01-01-00:00")
console.log(res4) // >>> 1
package io.redis.examples;
import org.junit.Assert;
import org.junit.Test;
import redis.clients.jedis.UnifiedJedis;
public class BitMapsExample {
public void run() {
UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379");
boolean res1 = jedis.setbit("pings:2024-01-01-00:00", 123, true);
System.out.println(res1); // >>> false
boolean res2 = jedis.getbit("pings:2024-01-01-00:00", 123);
System.out.println(res2); // >>> true
boolean res3 = jedis.getbit("pings:2024-01-01-00:00", 456);
System.out.println(res3); // >>> false
long res4 = jedis.bitcount("pings:2024-01-01-00:00");
System.out.println(res4); // >>> 1
}
}
using NRedisStack.Tests;
using StackExchange.Redis;
public class Bitmap_tutorial
{
[SkipIfRedis(Is.OSSCluster)]
public void run()
{
var muxer = ConnectionMultiplexer.Connect("localhost:6379");
var db = muxer.GetDatabase();
bool res1 = db.StringSetBit("pings:2024-01-01-00:00", 123, true);
Console.WriteLine(res1); // >>> 0
bool res2 = db.StringGetBit("pings:2024-01-01-00:00", 123);
Console.WriteLine(res2); // >>> True
bool res3 = db.StringGetBit("pings:2024-01-01-00:00", 456);
Console.WriteLine(res3); // >>> False
// Tests for 'ping' step.
bool res4 = db.StringSetBit("pings:2024-01-01-00:00", 123, true);
long res5 = db.StringBitCount("pings:2024-01-01-00:00");
Console.WriteLine(res5); // >>> 1
// Tests for 'bitcount' step.
}
}
位操作
位操作分为两类:常数时间单比特操作,例如将一个比特设置为 1 或 0,或获取其值;以及对比特组的操作,例如计算给定比特范围内的已设置比特数(例如,人口计数)。
位图最大的优势之一是它们在存储信息时通常可以节省大量的空间。例如,在一个系统中,不同的用户由增量用户 ID 表示,可以使用 512 MB 的内存来记住 40 亿用户的单个比特信息(例如,知道用户是否希望接收新闻简报)。
SETBIT
命令以比特号作为第一个参数,以要将比特设置为的值作为第二个参数,该值为 1 或 0。如果目标比特位于当前字符串长度之外,该命令会自动扩展字符串。
GETBIT
只返回指定索引处的比特值。超出范围的比特(寻址位于存储到目标键的字符串长度之外的比特)始终被视为零。
有三个命令作用于比特组
BITOP
在不同的字符串之间执行按位运算。提供的操作包括 AND、OR、XOR 和 NOT。
BITCOUNT
执行人口计数,报告设置为 1 的比特数。
BITPOS
查找具有指定 0 或 1 值的第一个比特。
BITPOS
和 BITCOUNT
都可以对字符串的字节范围进行操作,而不是在字符串的整个长度上运行。我们可以轻松地看到位图中已设置的比特数。
> BITCOUNT pings:2024-01-01-00:00
(integer) 1
"""
Code samples for Bitmap doc pages:
https://redis.ac.cn/docs/latest/develop/data-types/bitmaps/
"""
import redis
r = redis.Redis(decode_responses=True)
res1 = r.setbit("pings:2024-01-01-00:00", 123, 1)
print(res1) # >>> 0
res2 = r.getbit("pings:2024-01-01-00:00", 123)
print(res2) # >>> 1
res3 = r.getbit("pings:2024-01-01-00:00", 456)
print(res3) # >>> 0
r.setbit("pings:2024-01-01-00:00", 123, 1)
res4 = r.bitcount("pings:2024-01-01-00:00")
print(res4) # >>> 1
import assert from 'assert';
import { createClient } from 'redis';
const client = createClient();
await client.connect();
const res1 = await client.setBit("pings:2024-01-01-00:00", 123, 1)
console.log(res1) // >>> 0
const res2 = await client.getBit("pings:2024-01-01-00:00", 123)
console.log(res2) // >>> 1
const res3 = await client.getBit("pings:2024-01-01-00:00", 456)
console.log(res3) // >>> 0
await client.setBit("pings:2024-01-01-00:00", 123, 1)
const res4 = await client.bitCount("pings:2024-01-01-00:00")
console.log(res4) // >>> 1
package io.redis.examples;
import org.junit.Assert;
import org.junit.Test;
import redis.clients.jedis.UnifiedJedis;
public class BitMapsExample {
public void run() {
UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379");
boolean res1 = jedis.setbit("pings:2024-01-01-00:00", 123, true);
System.out.println(res1); // >>> false
boolean res2 = jedis.getbit("pings:2024-01-01-00:00", 123);
System.out.println(res2); // >>> true
boolean res3 = jedis.getbit("pings:2024-01-01-00:00", 456);
System.out.println(res3); // >>> false
long res4 = jedis.bitcount("pings:2024-01-01-00:00");
System.out.println(res4); // >>> 1
}
}
using NRedisStack.Tests;
using StackExchange.Redis;
public class Bitmap_tutorial
{
[SkipIfRedis(Is.OSSCluster)]
public void run()
{
var muxer = ConnectionMultiplexer.Connect("localhost:6379");
var db = muxer.GetDatabase();
bool res1 = db.StringSetBit("pings:2024-01-01-00:00", 123, true);
Console.WriteLine(res1); // >>> 0
bool res2 = db.StringGetBit("pings:2024-01-01-00:00", 123);
Console.WriteLine(res2); // >>> True
bool res3 = db.StringGetBit("pings:2024-01-01-00:00", 456);
Console.WriteLine(res3); // >>> False
// Tests for 'ping' step.
bool res4 = db.StringSetBit("pings:2024-01-01-00:00", 123, true);
long res5 = db.StringBitCount("pings:2024-01-01-00:00");
Console.WriteLine(res5); // >>> 1
// Tests for 'bitcount' step.
}
}
例如,假设您想了解网站用户的每日访问量的最长连胜。您从零开始计算天数,即您将网站公开的那一天,并在用户每次访问网站时使用 SETBIT
设置一个比特。作为比特索引,您只需获取当前的 Unix 时间,减去初始偏移量,然后除以一天中的秒数(通常为 3600*24)。
这样,对于每个用户,您都有一个包含每天访问信息的小字符串。使用 BITCOUNT
,可以轻松获得给定用户访问网站的天数,而使用几次 BITPOS
调用,或简单地获取和分析客户端的位图,就可以轻松地计算最长连胜。
位图很容易拆分为多个键,例如为了对数据集进行分片,并且因为通常最好避免使用大型键。要将位图拆分为不同的键,而不是将所有比特存储到一个键中,一个简单的策略就是每个键存储 M 个比特,并使用 bit-number/M
获取键名,使用 bit-number MOD M
获取键中要寻址的第 N 个比特。
SETBIT
和 GETBIT
是 O(1)。BITOP
是 O(n),其中 n 是比较中字符串的最长长度。
了解更多