`

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
分享到:
评论

相关推荐

    Redis稳定版 Redis-x64-5.0.14.1.zip

    在实际应用中,开发者可以根据业务需求,选择合适的数据结构,利用Redis的特性优化系统性能,如使用List作为消息队列,Set实现交集、并集和差集操作,Sorted Set进行范围查询等。同时,通过监控Redis的内存使用、...

    Redis-x64-5.0.14.msi和Redis-x64-5.0.14.zip

    解压后,你可以找到包括`redis-server.exe`、`redis-cli.exe`等在内的可执行文件,以及配置文件`redis.conf`。这种方式适合于需要自定义配置或手动管理服务的用户。通过编辑`redis.conf`,你可以调整Redis的各项参数...

    redis-stack-server 7.2.0 安装包合集

    redis-stack-server-7.2.0-v9.arm64.snap redis-stack-server-7.2.0-v9.bionic.arm64.tar.gz redis-stack-server-7.2.0-v9.bionic.x86_64.tar.gz redis-stack-server-7.2.0-v9.bullseye.x86_64.tar.gz redis-stack-...

    Redis-x64-5.0.10.zip、Redis-x64-5.0.10.msi

    本文将详细介绍Redis的特性、版本5.0.10的关键改进以及安装Redis-x64-5.0.10.msi的过程。 Redis支持多种数据类型,包括字符串、哈希、列表、集合、有序集合,这些数据结构使得它在缓存、消息队列、计数器等场景中...

    redis安装文件Redis-x64-3.2.10、Redis-x64-3.0.50

    Redis-x64-3.0.504.zip和Redis-x64-3.2.100.msi是两个不同版本的Redis安装文件,分别对应Redis的3.0.504和3.2.10版本。这两个版本之间的主要区别在于功能增强、性能优化和修复了已知的问题。例如,从3.0到3.2,Redis...

    Redis windows下载 Redis-x64-3.2.100.zip

    redis-server --service-install redis.windows-service.conf --loglevel verbose 2 常用的redis服务命令。 卸载服务:redis-server --service-uninstall 开启服务:redis-server --service-start 停止服务:redis-...

    redis校验工具redis-full-check

    **Redis 全面检查工具:redis-full-check** Redis 是一款高性能的键值存储系统,广泛应用于缓存、数据库和消息中间件等场景。在实际应用中,为了确保 Redis 的稳定性和数据一致性,需要定期对 Redis 实例进行健康...

    Redis-x64-3.2.100.zip

    本压缩包"Redis-x64-3.2.100.zip"提供了适用于Windows操作系统的Redis 3.2.100版本,该版本为64位系统设计。 1. **Redis的基础概念**: - **键值存储**:Redis基于键值对进行数据存储,键是唯一的标识,值可以是...

    Redis-x64-3.0.504安装包

    Redis-x64-3.0.504安装包是一个针对64位操作系统的Redis数据库...总之,Redis-x64-3.0.504安装包是部署Redis服务的基础,它的使用不仅可以提升应用的响应速度,还可以利用其丰富的数据结构和功能来简化复杂的系统设计。

    Redis-x64-5.0.14.1.msi

    总结来说,Redis-x64-5.0.14.1.msi 是 Redis 在 Windows 平台上的安装包,提供了高效的数据存储和处理能力,适用于各种应用场景,如缓存、计数器、发布订阅等。正确安装并配置 Redis,可以极大地提升应用程序的响应...

    Redis-x64-5.0.9.zip

    这个“Redis-x64-5.0.9.zip”压缩包包含的是适用于64位操作系统的Redis服务器的5.0.9版本。在本文中,我们将深入探讨Redis的主要特性和功能,以及如何安装和使用这个版本。 1. **Redis的数据类型**: - 字符串...

    Redis-x64-5.0.14 windows

    在Windows环境下,Redis-x64-5.0.14是Redis为64位Windows操作系统编译的一个版本,提供了在Windows上运行Redis的能力。本文将深入探讨Redis的基本概念、功能特性、安装与配置,以及在Windows平台上的使用方法。 ...

    Redis-x64-3.0.504.msi.zip

    在Windows环境下,Redis的安装通常以MSI(Microsoft Installer)格式的文件进行,就像提供的"Redis-x64-3.0.504.msi"。这个文件是专门为64位Windows操作系统设计的Redis 3.0.504版本的安装程序。 1. **Redis的基本...

    Redis-x64-7.0.5-windows11

    4. **运行服务**:通过`redis-server`命令启动Redis服务,使用`redis-cli`进行客户端交互。 5. **配置文件**:修改`redis.conf`配置文件,根据需求设置端口、日志级别、持久化策略等参数。 6. **安全考虑**:默认...

    Redis-x64-3.0.504

    Redis-x64-3.0.504是一款专为Windows操作系统设计的64位版本的Redis数据库。Redis,全称Remote Dictionary Server,是一个开源的、高性能的键值存储系统,广泛应用于缓存、数据库、消息中间件等多种场景。3.0.504是...

    Redis-x64-5.0.14.1 for Windows

    之前项目中要使用window下的...找了好久都只有Redis-x64-3.2.100的,后来发现有更高版本的。这里附件里面包括Redis-x64-5.0.14.1 for Windows版本 与及 原始下载地址。如果有新的版本的话,可以到在原始下载地址找到。

    Redis-x64-3.0.504 MSI ZIP

    Redis-x64-3.0.504 MSI ZIP 是一个专门为Windows 64位操作系统设计的Redis安装包。Redis,全称为Remote Dictionary Server,是一个高性能的键值存储系统,它由Salvatore Sanfilippo开发,使用ANSI C语言编写,支持...

    最新版windows Redis-x64-5.0.14.1.zip

    本压缩包"windows Redis-x64-5.0.14.1.zip"包含了最新版的Windows 64位Redis服务器,具体版本号为5.0.14.1,其核心亮点在于提供稳定、高效的服务。 Redis的特点包括但不限于以下几点: 1. **内存存储**:Redis是一...

    Redis-x64-3.2.100下载

    Redis-x64-3.2.100版本,你们可以下载,如果实在没有积分可以私聊我。

Global site tag (gtag.js) - Google Analytics