Skip to content

如何存储大量的ip地址

使用 Redis 位图存储大量 IP 地址

使用位图存储 IP 地址的核心思想是:将每个 IP 地址转换成一个整数,然后将这个整数作为位图中的偏移量(offset),通过将该偏移量位置的二进制位设置为 1 来表示该 IP 地址存在。

方案步骤

第 1 步:将 IP 地址转换为整数

IPv4 地址本质上是一个 32 位的无符号整数。标准点分十进制表示法(如 192.168.1.10)是为了方便人类阅读。我们需要一个函数将其转换为整数。

转换公式为: 整数 = a*256³ + b*256² + c*256¹ + d*256⁰

例如,IP 地址 192.168.1.10 对应的整数是: 192 * (2^24) + 168 * (2^16) + 1 * (2^8) + 10 = 3232235786

这个整数 3232235786 将作为我们在位图中的唯一偏移量。

第 2 步:将 IP 地址存入位图

使用 SETBIT 命令来记录一个 IP 地址的访问。该命令会将指定 key 的位图中,在特定偏移量(offset)上的二进制位设置为指定值(0 或 1)。

  • 命令格式: SETBIT key offset value
  • 时间复杂度: O(1)

场景示例:记录 2025 年 9 月 3 日的网站独立访客 IP

  1. 确定 Key: 为了区分每一天的数据,我们可以使用日期作为 key 的一部分,例如 daily_active_ips:20250903
  2. 记录访问: 当一个 IP 为 192.168.1.10 的用户访问时,我们先将其转换为整数 3232235786
  3. 执行命令: redis SETBIT daily_active_ips:20250903 3232235786 1 执行此命令后,daily_active_ips:20250903 这个 key 对应的位图中第 3232235786 位就被设置成了 1

第 3 步:IP 地址的相关操作与统计

有了存储结构,我们可以非常高效地进行各种统计和查询。

  • 查询指定 IP 是否访问过: 使用 GETBIT 命令检查某个偏移量上的值是否为 1redis GETBIT daily_active_ips:20250903 3232235786 如果返回 1,则表示该 IP 访问过;返回 0 则表示未访问过。

  • 统计当天的独立 IP 总数: 使用 BITCOUNT 命令可以快速统计一个位图中被设置为 1 的位的数量。 redis BITCOUNT daily_active_ips:20250903 该命令会返回当天所有独立 IP 的总数,性能极高。

  • 统计连续多天都活跃的用户: 假设我们需要统计在 9 月 3 日和 9 月 4 日都访问过的 IP 数量。可以使用 BITOP 命令对多个位图进行位运算。

    1. 首先,将两天的位图进行“与”(AND)运算,结果存入一个新的 key,例如 active_both_daysredis BITOP AND active_both_days daily_active_ips:20250903 daily_active_ips:20250904 这个新位图中,只有在两个源位图中对应位都为 1 的位置,结果才为 1
    2. 然后,统计新位图中的 1 的数量。 redis BITCOUNT active_both_days 同样,BITOP 还支持 OR (或)、XOR (异或) 和 NOT (非) 运算,可以灵活地实现复杂的统计需求。

Redis 位图的底层实现

1. 底层数据结构是字符串(String)

Redis 的位图在底层是借助字符串类型(String)实现的。 Redis 中的字符串是二进制安全的动态字符串(SDS),可以看作是一个字节数组。 Redis 正是利用了这个特性,允许对字符串的每一个二进制位进行直接操作。

你可以将位图想象成一个以“位”为单位的数组,而这个数组实际上存储在一个字符串中。

2. 寻址与空间分配机制

当你执行 SETBIT mykey 100 1 命令时,Redis 内部会执行以下操作:

  • 计算字节位置 (Byte Index): Redis 需要确定第 100 位位于哪个字节。计算方式是 offset / 8。所以 100 / 8 = 12,这意味着它在第 12 个字节(索引从 0 开始)。
  • 计算位位置 (Bit Index): 接着确定是这个字节中的第几位。计算方式是 offset % 8。所以 100 % 8 = 4,这意味着它是该字节的第 4 位(索引从 0 开始)。
  • 读写操作: Redis 读取第 12 个字节的现有值,然后通过位运算将第 4 位设置为 1
  • 自动扩容: 如果 mykey 不存在,或者其底层字符串的长度不足以容纳第 12 个字节,Redis 会自动将其扩容,并将所有新分配的内存空间用 0 填充,然后再执行设置操作。 这也是为什么即使你只设置了一个很大的偏移量,位图也会占用相应大小的内存。

3. 空间占用

位图占用的空间大小取决于设置过的最大偏移量(max_offset)。 例如,如果最大的偏移量是 3232235786(对应 IP 192.168.1.10),那么需要的位数就是 3232235786 + 1

占用的字节数可以通过公式 (max_offset / 8) + 1 计算。 对于整个 IPv4 地址空间(约 2^32),如果需要全部映射,则需要 2^32 位,换算下来就是 (2^32 / 8 / 1024 / 1024) = 512MB 的内存。