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.jupiter.api.Test;
import redis.clients.jedis.UnifiedJedis;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
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
jedis.close();
}
}
package example_commands_test
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func ExampleClient_ping() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
res1, err := rdb.SetBit(ctx, "pings:2024-01-01-00:00", 123, 1).Result()
if err != nil {
panic(err)
}
fmt.Println(res1) // >>> 0
res2, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 123).Result()
if err != nil {
panic(err)
}
fmt.Println(res2) // >>> 1
res3, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 456).Result()
if err != nil {
panic(err)
}
fmt.Println(res3) // >>> 0
}
func ExampleClient_bitcount() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
res4, err := rdb.BitCount(ctx, "pings:2024-01-01-00:00",
&redis.BitCount{
Start: 0,
End: 456,
}).Result()
if err != nil {
panic(err)
}
fmt.Println(res4) // >>> 1
}
using NRedisStack.Tests;
using StackExchange.Redis;
public class Bitmap_tutorial
{
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.jupiter.api.Test;
import redis.clients.jedis.UnifiedJedis;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
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
jedis.close();
}
}
package example_commands_test
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func ExampleClient_ping() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
res1, err := rdb.SetBit(ctx, "pings:2024-01-01-00:00", 123, 1).Result()
if err != nil {
panic(err)
}
fmt.Println(res1) // >>> 0
res2, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 123).Result()
if err != nil {
panic(err)
}
fmt.Println(res2) // >>> 1
res3, err := rdb.GetBit(ctx, "pings:2024-01-01-00:00", 456).Result()
if err != nil {
panic(err)
}
fmt.Println(res3) // >>> 0
}
func ExampleClient_bitcount() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
res4, err := rdb.BitCount(ctx, "pings:2024-01-01-00:00",
&redis.BitCount{
Start: 0,
End: 456,
}).Result()
if err != nil {
panic(err)
}
fmt.Println(res4) // >>> 1
}
using NRedisStack.Tests;
using StackExchange.Redis;
public class Bitmap_tutorial
{
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 是比较中最长字符串的长度。
了解更多