`

redis范围查询应用-查找IP所在城市

阅读更多


需求
根据IP找到对应的城市

原来的解决方案
oracle表(ip_country):




查询IP对应的城市:

1.把a.b.c.d这样格式的IP转为一个数字,例如为把210.21.224.34转为3524648994
2. select city from ip_country where ipstartdigital <= 3524648994 and 3524648994 <=ipenddigital

redis解决方案

我们先把上面的表简化一下:

id	city	min	max
1	P1	0	100
2	P2	101	200
3	P3	201	300
4	P4	301	400


(注意:min/max组成的range之间不能有重叠)

主要思路就是用hmset存储表的每一行,并为每一行建立一个id(作为key)
然后把ip_end按顺序从小到大存储在sorted set当中,score对应该行的id
查询时,利用redis sorted set的范围查询特性,从sorted set中查询到id,再根据id去hmget

实验
//存储表的每一行
127.0.0.1:6379> hmset {ip}:1 city P1 min 0 max 100
OK
127.0.0.1:6379> hmset {ip}:2 city P2 min 101 max 200
OK
127.0.0.1:6379> hmset {ip}:3 city P3 min 201 max 300
OK
127.0.0.1:6379> hmset {ip}:4 city P4 min 301 max 400
OK

//建立sorted set(score-member,例如score=100,member=1等等)
127.0.0.1:6379> zadd {ip}:end.asc 100 1 200 2 300 3 400 4
(integer) 4
127.0.0.1:6379> zrange {ip}:end.asc 0 -1
1) "1"
2) "2"
3) "3"
4) "4"

//查询对应的区间(score)
127.0.0.1:6379> zrangebyscore {ip}:end.asc 90 +inf LIMIT 0 1
1) "1"
127.0.0.1:6379> zrangebyscore {ip}:end.asc 123 +inf LIMIT 0 1
1) "2"
127.0.0.1:6379> zrangebyscore {ip}:end.asc 100 +inf LIMIT 0 1
1) "1"
//解释:
//zrangebyscore {ip}:end.asc 90 +inf LIMIT 0 1
//表示查找score大于等于90的第一个值。(+inf在Redis中表示正无穷大)
//该语句返回值member=1,与hmset当中的id对应,因此可以通过hmget查找城市了:

//查找城市
127.0.0.1:6379> hmget {ip}:1 city
1) "P1"

注意在设计redis key时,采用了统一的前缀:{ip}
这是为了使得这些IP相关的数据都落在同一台redis server中(我们的redis以集群形式部署且采取一致性哈希),往后数据迁移什么的会更方便。
同时要注意,如果{ip}:end.asc当中的ip是不连续的,则需要检查,例如:
数据:
id	city	min	  max
1	 P1	    0	  100
3	 P3	   201	  300
4	 P4	   301	  400

//查找150在哪个区间:
127.0.0.1:6379> zadd {ip}:end.asc:miss 100 1 300 3 400 4
(integer) 3
127.0.0.1:6379> zrange {ip}:end.asc:miss 0 -1
1) "1"
2) "3"
3) "4"
127.0.0.1:6379> zrangebyscore {ip}:end.asc:miss 150 +inf LIMIT 0 1
1) "3"
//那么返回的member就会是3,但150显然不在(201,300)这个区间,应该返回的查询结果是“无对应记录”。因此查得member后需要检查(后续代码中有做这一步)。

实操

从数据库中导出的得到的文本是这样的(选取几行为例子):
ipcountry_tab_orderby_end_asc.txt:
"IPSTART"	"IPSTARTDIGITAL"	"IPEND"	"IPENDDIGITAL"	"COUNTRY"	"CITY"	"TYPE"	"REGISTRY"	"ADRESS"	"PROVINCE"
"1.184.0.0"	28835840	"1.184.127.255"	28868607	"中国"	"广州市"	""	""	""	"广东省"
"1.184.128.0"	28868608	"1.184.255.255"	28901375	"中国"	"广州市"	""	""	""	"广东省"
"1.185.0.0"	28901376	"1.185.95.255"	28925951	"中国"	"南宁市"	""	""	""	"广西省"

1.生成批量的hmset命令及zadd命令
写个小程序来生成:
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

public class IpCountryRedisImport {
	
	public static void main(String[] args) throws IOException {
		File file = new File("E:/doc/ipcountry_tab_orderby_end_asc.txt");
		File hmsetFile = new File("E:/doc/ip_country_redis_cmd.txt");
		File zaddFile = new File("E:/doc/ip_country_redis_zadd.txt");
		
		List<String> lines = FileUtils.readLines(file);
		int i = 0;
		StringBuilder rows = new StringBuilder();
		StringBuilder ends = new StringBuilder();
		for (String str : lines) {
			if (StringUtils.isEmpty(str)) {
				continue;
			}
			
			//skip first line
			if (i == 0) {
				i++;
				continue;
			}
			
			i++;
			
			//"IPSTART"	"IPSTARTDIGITAL"	"IPEND"	"IPENDDIGITAL"	"COUNTRY"	"CITY"	"TYPE"	"REGISTRY"	"ADRESS"	"PROVINCE"
			//0               1                2         3              4          5       6         7         8            9 
			String[] parts = str.split("\t");
			String start = parts[1];
			String end = parts[3];
			String country = parts[4];
			String city = parts[5];
			String type = parts[6];
			String registry = parts[7];
			String address = parts[8];
			String province = parts[9];
			
			//String cmd = "hmset {ip}:" + (i++) + " start " + start + " end " + end + " country " + country + " city " + city + " type " + type + " registry " + registry + " address " + address + " province " + province;
			
			rows.append("*18\r\n");
			
			rows.append(format("hmset"));
			
			rows.append(format("{ip}:" + i));
			
			rows.append(format("start"));
			rows.append(format(start));
			
			rows.append(format("end"));
			rows.append(format(end));
			
			rows.append(format("country"));
			rows.append(format(country));
			
			rows.append(format("city"));
			rows.append(format(city));
			
			rows.append(format("type"));
			rows.append(format(type));
			
			rows.append(format("registry"));
			rows.append(format(registry));
			
			rows.append(format("address"));
			rows.append(format(address));
			
			rows.append(format("province"));
			rows.append(format(province));
			
			
			//zadd {ip}:end.asc 1234 1
			ends.append("*4\r\n");
			ends.append(format("zadd"));
			ends.append(format("{ip}:end.asc"));
			ends.append(format(end));
			ends.append(format("" + i));
			
		}
		FileUtils.writeStringToFile(hmsetFile, rows.toString(), "UTF-8");
		FileUtils.writeStringToFile(zaddFile, ends.toString(), "UTF-8");
		System.out.println(1);
	}
	
	private static String format(String value) throws UnsupportedEncodingException {
		String trimValue = value.replace("\"", "");
		return "$" + trimValue.getBytes("UTF-8").length+ "\r\n" + trimValue + "\r\n";
	}

}


需要注意的是,format方法里面,值的长度不是字符串的长度,而是字符串转化为字节之后的长度

生成hmset结果举例(ip_country_redis_cmd.txt,每一行都是以\r\n结尾):
*18
$5
hmset
$8
{ip}:645
$5
start
$8
28835840
$3
end
$8
28868607
$7
country
$6
中国
$4
city
$9
广州市
$4
type
$0

$8
registry
$0

$7
address
$0

$8
province
$9
广东省

生成的zadd命令举例(ip_country_redis_zadd.txt):
*4
$4
zadd
$12
{ip}:end.asc
$8
16777471
$1
2


需要注意的是,txt文件通过SecureCRT上传到linux后,\r\n可能就只剩\n了,可以替换一下:
perl -pi -e 's/\n/\r\n/' ip_country_redis_cmd.txt 
perl -pi -e 's/\n/\r\n/' ip_country_redis_zadd.txt


2.导入redis

文件生成完毕后,执行以下命令导入:
cat ip_country_redis_cmd.txt | redis-cli –pipe
cat ip_country_redis_zadd.txt | redis-cli --pipe


40万行的数据,花费时间不到一分钟,redis的mass insertion还是很强大的

在这里要提一下的是,redis文档中关于批量导入的说明可能会有误导:
文档是这样的:
SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN


我刚开始以为像上面那样,只要把批量redis命令写在同一个文本文件,然后直接导入就可以了:
cat cmd.txt | redis-cli –pipe

实际上不是的,要符合redis protocol才可以
protocol语法:
*<args><cr><lf>
$<len><cr><lf>
<arg0><cr><lf>
<arg1><cr><lf>
...
<argN><cr><lf>

举例:
*3<cr><lf>
$3<cr><lf>
SET<cr><lf>
$3<cr><lf>
key<cr><lf>
$5<cr><lf>
value<cr><lf>


说明:
*后面的数字表示该条redis命令有多少参数,
例如:
set ab 1234参数个数是3
hmset name google.com 1 baidu.com 2的参数个数是6
接下来就是命令的每一部分(空格分隔),先是长度,后是值:
以“set ab 1234”为例:
set的长度是3,ab的长度是2,1234的长度是4,因此最终内容为:
*3
$3
set
$2
ab
$4
1234


注意每一行都是以<cr><lf>(也就是\r\n)结尾


3.查询
使用spring redis

关键代码:
long min = ip;	//转换成数字的IP
        long max = Long.MAX_VALUE;
        long offset = 0;
        long count = 1;
        Set<String> result = redisTemplate.opsForZSet().rangeByScore(zSetName, min, max, offset, count);
        String ipIndex = null;
        if (result != null && result.size() > 0) {
            ipIndex = result.iterator().next();
        }
        
        
        if (ipIndex != null) {
            final String ipKey = redisIprowPrefix + ipIndex;
            Collection<String> fields = new ArrayList<String>();
            fields.add("ipstart");
            fields.add("ipstartdigital");
            fields.add("ipend");
            fields.add("ipenddigital");
            fields.add("country");
            fields.add("city");
            fields.add("type");
            fields.add("registry");
            fields.add("adress");
            fields.add("province");
            fields.add("latitude");
            fields.add("longitude");
            fields.add("addresstype");
            
            List<String> fieldValues = redisTemplate.<String, String>opsForHash().multiGet(ipKey, fields);
            
            
            if (fieldValues != null && fieldValues.size()==fields.size()) {
                String startDigital = fieldValues.get(1);
                if (StringUtils.isNotBlank(startDigital)) {
                    long ipStartDigital = Long.parseLong(startDigital);
					
					//检查是否确实在区间内:start <= x <= end
                    if (ipStartDigital > ip) {
                        logger.info("ip is not in this range(that is, ip < ipstartdigital), ipstartdigital={}, ip={}", ipStartDigital, ip);
                        return null;
                    }
                }
                
                String endDigital = fieldValues.get(3);
                IpcountryResp resp = new IpcountryResp();
                if (StringUtils.isNotBlank(endDigital)) {
                    resp.setIpenddigital(Long.parseLong(endDigital));
                }
                
                resp.setIpstart(fieldValues.get(0));
                resp.setIpend(fieldValues.get(2));
                
                
                resp.setCountry(fieldValues.get(4));
                resp.setCity(fieldValues.get(5));
                resp.setType(fieldValues.get(6));
                resp.setRegistry(fieldValues.get(7));
                resp.setAdress(fieldValues.get(8));
                resp.setProvince(fieldValues.get(9));
                resp.setLatitude(fieldValues.get(10));
                resp.setLongitude(fieldValues.get(11));
                resp.setAddresstype(fieldValues.get(12));
                

                

                
                return resp;
            }
            
        }
			
			


redisTemplate需要配置序列化相关的property:
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
        p:connection-factory-ref="jedisConnFactory">
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property> 
</bean>




参考
http://stackoverflow.com/questions/9989023/store-ip-ranges-in-redis
  • 大小: 47.2 KB
1
1
分享到:
评论

相关推荐

    安装NumPy教程-详细版

    附件是安装NumPy教程_详细版,文件绿色安全,请大家放心下载,仅供交流学习使用,无任何商业目的!

    语音端点检测及其在Matlab中的实现.zip

    语音端点检测及其在Matlab中的实现.zip

    C#文档打印程序Demo

    使用C#完成一般文档的打印,带有页眉,页脚文档打印,表格打印,打印预览等

    DirectX修复工具-4-194985.zip

    directx修复工具 DirectX修复工具(DirectX repair)是系统DirectX组件修复工具,DirectX修复工具主要是用于检测当前系统的DirectX状态,若发现异常情况就可以马上进行修复,非常快捷,使用效果也非常好。

    Python手动实现人脸识别算法

    人脸识别的主要算法 其核心算法是 欧式距离算法使用该算法计算两张脸的面部特征差异,一般在0.6 以下都可以被认为是同一张脸 人脸识别的主要步骤 1 获得人脸图片 2 将人脸图片转为128D的矩阵(这个也就是人脸特征的一种数字化表现) 3 保存人脸128D的特征到文件中 4 获取其他人脸转为128D特征通过欧式距离算法与我们保存的特征对比,如果差距在0.6以下就说明两张脸差距比较小

    全国大学生信息安全竞赛知识问答-CISCN 题库.zip

    ciscn 全国大学生信息安全竞赛知识问答-CISCN 题库.zip

    JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译).zip

    JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)JAVA+SQL离散数学题库管理系统(源代码+LW+外文翻译)

    strcmp函数应用.zip

    strcmp函数应用.zip

    蓝桥杯单片机第十一届国赛设计题试做

    蓝桥杯单片机第十一届国赛设计题试做

    基于MATLAB的pca人脸识别.zip

    基于MATLAB的pca人脸识别.zip

    520.html

    520.html

    JAVA在线考试管理系统(源代码+LW+开题报告+外文翻译+英文文献+答辩PPT).zip

    JAVA在线考试管理系统(源代码+LW+开题报告+外文翻译+英文文献+答辩PPT)

    STR710的定时器编程C语言例子,开发环境为IAR EWARM。.zip

    STR710的定时器编程C语言例子,开发环境为IAR EWARM。.zip

    基于物品的协同过滤推荐算法(Python).zip

    协同过滤算法(Collaborative Filtering)是一种经典的推荐算法,其基本原理是“协同大家的反馈、评价和意见,一起对海量的信息进行过滤,从中筛选出用户可能感兴趣的信息”。它主要依赖于用户和物品之间的行为关系进行推荐。 协同过滤算法主要分为两类: 基于物品的协同过滤算法:给用户推荐与他之前喜欢的物品相似的物品。 基于用户的协同过滤算法:给用户推荐与他兴趣相似的用户喜欢的物品。 协同过滤算法的优点包括: 无需事先对商品或用户进行分类或标注,适用于各种类型的数据。 算法简单易懂,容易实现和部署。 推荐结果准确性较高,能够为用户提供个性化的推荐服务。 然而,协同过滤算法也存在一些缺点: 对数据量和数据质量要求较高,需要大量的历史数据和较高的数据质量。 容易受到“冷启动”问题的影响,即对新用户或新商品的推荐效果较差。 存在“同质化”问题,即推荐结果容易出现重复或相似的情况。 协同过滤算法在多个场景中有广泛的应用,如电商推荐系统、社交网络推荐和视频推荐系统等。在这些场景中,协同过滤算法可以根据用户的历史行为数据,推荐与用户兴趣相似的商品、用户或内容,从而提高用户的购买转化率、活跃度和社交体验。 未来,协同过滤算法的发展方向可能是结合其他推荐算法形成混合推荐系统,以充分发挥各算法的优势。

    JAVA文件传输(lw+源代码).zip

    FTP(File Transfer Protocol)是文件传输协议的简称。 FTP的主要作用,就是让用户连接上一个远程计算机(这些计算机上运行着FTP服务器程序)查看远程计算机有哪些文件,然后把文件从远程计算机上拷到本地计算机,或把本地计算机的文件送到远程计算机去。 目前FTP服务器软件都为国外作品,例如Server_U、IIS,国内成熟的FTP服务器软件很少,有一些如(Crob FTP Server),但从功能上看来远不能和那些流行的服务器软件媲美。

    python项目源码-深度学习tensorflow的滚动轴承故障诊断方法源码(高分大作业).rar

    本项目基于深度学习TensorFlow框架,针对滚动轴承故障诊断方法进行研究。项目采用了卷积神经网络(CNN)对轴承振动信号进行特征提取和分类,实现了对滚动轴承不同故障类型的自动诊断。 在技术实现上,项目利用TensorFlow搭建了一个高效的CNN模型,通过多层卷积、池化操作以及全连接层,自动学习轴承振动信号中的故障特征。同时,采用交叉熵损失函数优化模型参数,提高故障识别率。此外,项目还集成了数据预处理、模型训练、测试评估等功能模块,方便用户快速上手并进行实验研究。 经过运行测试,该项目代码运行稳定,诊断效果良好,可广泛应用于滚动轴承故障诊断领域。对于计算机相关专业的在校学生、老师或企业员工来说,该项目是一份难得的高分大作业资源,同时也是小白学习和实际项目借鉴的优秀参考资料。请放心下载使用,为您的学习和工作提供帮助!

    超详细的SpringBoot框架入门教程 Spring Boot框架快速入门教程以大量示例讲解了Spring Boot在各类情境

    超详细的SpringBoot框架入门教程 Spring Boot框架快速入门教程以大量示例讲解了Spring Boot在各类情境中的应用,让大家可以跟着老师的思维和代码快速理解并掌握。适用于Java 开发人员,尤其是初学Spring Boot的人员和需要从传统 Spring 转向 Spring Boot 开发的技术人员。 下边是动力节点的SpringBoot教程非常适合初学入门,讲的非常详细,而且全程无废话!

    毕业设计[主机域名]ISPConfig 3.0.1.3_ispconfig3-codepub.zip

    毕业设计[主机域名]ISPConfig 3.0.1.3_ispconfig3-codepub.zip

    matlab开发-用交叉熵优化多变量宏观模型随机多极值优化.zip

    matlab开发-用交叉熵优化多变量宏观模型随机多极值优化.zip

    矩阵特征值的计算方法.zip

    矩阵特征值的计算方法.zip

Global site tag (gtag.js) - Google Analytics