如何存储大量的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
- 确定 Key: 为了区分每一天的数据,我们可以使用日期作为 key 的一部分,例如
daily_active_ips:20250903。 - 记录访问: 当一个 IP 为
192.168.1.10的用户访问时,我们先将其转换为整数3232235786。 - 执行命令:
redis SETBIT daily_active_ips:20250903 3232235786 1执行此命令后,daily_active_ips:20250903这个 key 对应的位图中第3232235786位就被设置成了1。
第 3 步:IP 地址的相关操作与统计
有了存储结构,我们可以非常高效地进行各种统计和查询。
-
查询指定 IP 是否访问过: 使用
GETBIT命令检查某个偏移量上的值是否为1。redis 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命令对多个位图进行位运算。- 首先,将两天的位图进行“与”(AND)运算,结果存入一个新的 key,例如
active_both_days。redis BITOP AND active_both_days daily_active_ips:20250903 daily_active_ips:20250904这个新位图中,只有在两个源位图中对应位都为1的位置,结果才为1。 - 然后,统计新位图中的
1的数量。redis BITCOUNT active_both_days同样,BITOP还支持OR(或)、XOR(异或) 和NOT(非) 运算,可以灵活地实现复杂的统计需求。
- 首先,将两天的位图进行“与”(AND)运算,结果存入一个新的 key,例如
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 的内存。