Redis 位图(Bitmap)

Redis 位图(Bitmap)是一种特殊的数据结构,利用字符串的每个字节的位来存储二进制信息。位图通常用于表示大规模的布尔数据,如用户签到、事件记录、状态标记等。尽管 Redis 位图本质上是字符串的一种扩展,但它提供了一些特定的位操作命令。

场景示例

  1. 用户签到

    • 示例:记录用户每天的签到状态。
      SETBIT user:123:sign 1 1
      SETBIT user:123:sign 2 1
      GETBIT user:123:sign 1
      GETBIT user:123:sign 2
      
    • 说明:使用 SETBIT 设置签到状态,GETBIT 获取签到状态。位图的每个位代表某一天的签到状态。
  2. 活跃用户统计

    • 示例:记录用户的活跃状态。
      SETBIT active_users 1 1
      SETBIT active_users 2 1
      BITCOUNT active_users
      
    • 说明:使用 SETBIT 设置用户活跃状态,BITCOUNT 统计活跃用户的数量。
  3. 权限管理

    • 示例:使用位图表示用户权限。
      SETBIT user:123:permissions 0 1  # 权限 0
      SETBIT user:123:permissions 1 0  # 权限 1
      BITFIELD user:123:permissions GET u8 0
      BITFIELD user:123:permissions GET u8 1
      
    • 说明:使用 SETBIT 设置权限,BITFIELD 获取权限信息。
  4. 防刷票系统

    • 示例:防止用户重复投票。
      SETBIT votes 12345 1  # 设置用户 ID 12345 的投票状态
      GETBIT votes 12345
      
    • 说明:使用 SETBIT 设置用户投票状态,GETBIT 检查用户是否已经投票。
  5. 监控标记

    • 示例:记录机器的健康状态。
      SETBIT machine_health 0 1  # 机器健康
      SETBIT machine_health 1 0  # 机器故障
      BITPOS machine_health 1
      
    • 说明:使用 SETBIT 设置机器健康状态,BITPOS 查找第一个健康机器的位置。

底层实现

Redis 位图实际上是对字符串的位操作,底层利用了 Redis 字符串的字节存储。每个位图操作都通过对字符串字节的位进行读写来完成。

  1. 位图的内存布局

    • 位图将数据存储在 Redis 字符串中,每个字节(8 位)代表一组布尔值。例如,一个长度为 1 的位图可以表示 8 个布尔值。
    • 位图的内部数据结构(以字节为单位)如下:
      typedef struct bitmap {
          unsigned char *bits;   // 位图的字节数组
          size_t length;         // 位图的长度(以位为单位)
      } bitmap;
      
  2. 位图操作

    • SETBIT:设置指定偏移量的位为 1 或 0。
      void setbit(bitmap *bm, size_t offset, int value) {
          size_t byteIndex = offset / 8;
          size_t bitIndex = offset % 8;
          if (value) {
              bm->bits[byteIndex] |= (1 << bitIndex);
          } else {
              bm->bits[byteIndex] &= ~(1 << bitIndex);
          }
      }
      
    • GETBIT:获取指定偏移量的位的值。
      int getbit(bitmap *bm, size_t offset) {
          size_t byteIndex = offset / 8;
          size_t bitIndex = offset % 8;
          return (bm->bits[byteIndex] >> bitIndex) & 1;
      }
      
    • BITCOUNT:统计位图中位为 1 的数量。
      size_t bitcount(bitmap *bm) {
          size_t count = 0;
          for (size_t i = 0; i < bm->length / 8; i++) {
              count += __builtin_popcount(bm->bits[i]);
          }
          return count;
      }
      
    • BITPOS:查找位图中第一个为 1 的位的偏移量。
      size_t bitpos(bitmap *bm, int value) {
          for (size_t i = 0; i < bm->length / 8; i++) {
              if (value == 1) {
                  if (bm->bits[i] != 0) {
                      return (i * 8) + __builtin_ctz(bm->bits[i]);
                  }
              } else {
                  if (bm->bits[i] != 0xFF) {
                      return (i * 8) + __builtin_ctz(~bm->bits[i]);
                  }
              }
          }
          return -1;
      }
      

Redis 的位图操作具有高效的内存使用和性能表现,适合用于处理大规模的布尔数据。

Go 中使用 Redis 位图

在 Go 语言中,可以使用 go-redis 库来操作 Redis 位图。以下是一些基本操作示例。

  1. 连接 Redis

    package main
    
    import (
        "github.com/go-redis/redis/v8"
        "context"
        "fmt"
    )
    
    var ctx = context.Background()
    
    func main() {
        rdb := redis.NewClient(&redis.Options{
            Addr:     "localhost:6379",
            Password: "", // no password set
            DB:       0,  // use default DB
        })
    
        // 示例代码调用
        example(rdb)
    }
    
    func example(rdb *redis.Client) {
        // 示例操作
    }
    
  2. 设置位

    func example(rdb *redis.Client) {
        err := rdb.SetBit(ctx, "bitmap", 10, 1).Err()
        if err != nil {
            panic(err)
        }
    }
    
  3. 获取位

    func example(rdb *redis.Client) {
        bit, err := rdb.GetBit(ctx, "bitmap", 10).Result()
        if err != nil {
            panic(err)
        }
        fmt.Println("Bit value:", bit)
    }
    
  4. 统计位数

    func example(rdb *redis.Client) {
        count, err := rdb.BitCount(ctx, "bitmap", nil).Result()
        if err != nil {
            panic(err)
        }
        fmt.Println("Bit count:", count)
    }
    
  5. 查找位

    func example(rdb *redis.Client) {
        pos, err := rdb.BitPos(ctx, "bitmap", 1, nil).Result()
        if err != nil {
            panic(err)
        }
        fmt.Println("First set bit position:", pos)
    }
    

Redis 位图在处理布尔数据时具有高效的性能和内存利用率,适用于各种需要大规模布尔标记的场景。