redis~有序集(ji)合處理ip范圍的查詢(xun)問題
目(mu)前有兩種(zhong)方(fang)式對 IP 以及歸屬地信息進行(xing)緩存:
- 第一種是將起始 IP,結束 IP 以及中間所有 IP 轉換成整型,然后以字符串方式,用轉換后的 IP 作為 key,歸屬地信息作為 value 存入 Redis;
- 第二種是采用有序集合和散列方式,首先將起始 IP 和結束 IP 添加到有序集合 ip2cityid,城市 ID 作為成員,轉換后的 IP 作為分值,然后再將城市 ID 和歸屬地信息添加到散列 cityid2city,城市 ID 作為 key,歸屬地信息作為 value。
第一種方式就(jiu)不多做介紹了,簡單粗(cu)暴(bao),非常不推薦。查詢速度當然很快,毫秒級(ji)別,但缺點也十分(fen)明(ming)顯,我用(yong) 1000 條(tiao)數據做了測試,緩存時間長(chang),大概 20 分(fen)鐘,占(zhan)用(yong)空間大,將近 1G。
下面介紹第二種方式直接看代碼
python語言實現
# generate_to_redis.py
# -*- coding:utf-8 -*-
import time
import json
from redis import Redis
def ip_to_num(x):
return sum([256 ** j * int(i) for j, i in enumerate(x.split('.')[::-1])])
# 連接 Redis
conn = Redis(host='127.0.0.1', port=6379, db=10)
start_time = time.time()
# 文件格式
# 1.0.0.0|1.0.0.255|澳大利亞|0|0|0|0
# 1.0.1.0|1.0.3.255|中國|0|福建省|福州市|電信
with open('./ip.merge.txt', 'r') as f:
i = 1
for line in f.readlines():
item = line.strip().split('|')
# 將起始 IP 和結束 IP 添加到有序集合 ip2cityid
# 成員分別是城市 ID 和 ID + #, 分值是根據 IP 計算的整數值
conn.zadd('ip2cityid', str(i), ip_to_num(item[0]), str(i) + '#', ip_to_num(item[1]) + 1)
# 將城市信息添加到散列 cityid2city,key 是城市 ID,值是城市信息的 json 序列
conn.hset('cityid2city', str(i), json.dumps([item[2], item[3], item[4], item[5]]))
i += 1
end_time = time.time()
print 'start_time: ' + str(start_time) + ', end_time: ' + str(end_time) + ', cost time: ' + str(end_time - start_time)
java語言實現
以下(xia)是通(tong)過(guo) Java 來實現將 IP 起始(shi)值(zhi)和結(jie)束值(zhi)通(tong)過(guo)有序(xu)集(ji)(ji)合(he)(he)存儲,并(bing)根據(ju)傳入(ru)的 IP 返回這個 IP 是否在(zai)(zai) IP 范圍集(ji)(ji)合(he)(he)中存在(zai)(zai)的示(shi)例代碼:
- redis中存儲的圖

- java代碼如下
package com.lind.redis;
import com.lind.redis.config.LettuceRedisAutoConfigure;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 關于ip地址范圍檢索的測試
*
* @author lind
* @date 2024/3/6 11:14
* @since 1.0.0
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { LettuceConnectionFactory.class, LettuceRedisAutoConfigure.class })
public class IpRangeTest {
private static final String IP_RANGE_KEY = "ip_ranges";
@Autowired
RedisTemplate redisTemplate;
// 將 IP 地址轉換為 long 類型的分數值
public static double convertIPToScore(String ip) {
String[] ipParts = ip.split("\\.");
if (ipParts.length != 4) {
throw new IllegalArgumentException("Invalid IP address format");
}
long score = 0;
for (int i = 0; i < 4; i++) {
long partValue = Long.parseLong(ipParts[i]);
score = (score << 8) + partValue; // 將每個部分的值左移8位并相加
}
return score;
}
@Test
public void testRangeIp() {
redisTemplate.delete(IP_SCORE_RANGE_KEY);
// 存儲 IP 范圍到 Redis
storeIPScoreRangeToRedis("103.159.125.66", "103.159.125.177");
storeIPScoreRangeToRedis("114.250.19.131", "116.250.19.255");
storeIPScoreRangeToRedis("10.10.10.131", "10.10.20.255");
storeIPScoreRangeToRedis("192.10.10.131", "192.10.10.255");
// 檢查用戶真實 IP 是否在 IP 集合中
String userIP = "114.250.19.132";
boolean inBlacklist = isIpInRange(userIP);
if (inBlacklist) {
System.out.println(userIP + " is in the IP blacklist.");
}
else {
System.out.println(userIP + " is not in the IP blacklist.");
}
}
private void storeIPScoreRangeToRedis(String startIP, String endIP) {
long startScore = convertIPToScore(startIP);
double score = Double.valueOf(startScore);
redisTemplate.opsForZSet().add(IP_SCORE_RANGE_KEY, startIP + "-" + endIP, score);
}
// 判斷某個IP是否在ZSet的范圍內
// 通過起始值先過濾掉大于指定IP的,再剩下的集合進行遍歷
public boolean isIpInRange(String ipAddress) {
ZSetOperations<String, String> opsForZSet = redisTemplate.opsForZSet();
double ip = (double) convertIPToScore(ipAddress);
Set<ZSetOperations.TypedTuple<String>> result = opsForZSet.rangeByScoreWithScores(IP_SCORE_RANGE_KEY, 0, ip);
for (ZSetOperations.TypedTuple<String> o : result) {
String[] arr = o.getValue().split("-");
long start = convertIPToScore(arr[0]);
long end = convertIPToScore(arr[1]);
if (ip >= start && ip <= end) {
System.out.println("find rangeL" + arr[0] + "-" + arr[1]);
return true;
}
}
return false;
}
}
