前言: 前段时间听过了纯真ip数据库,只知道是一个qqwry.dat文件,里面有一些网友收集的数据,可以用来查询ip的大致位置,即ip定位。正好,我最近在一个项目里面看到了这个数据库,它就是将qqwrSEO靠我y.dat集成到springboot项目里面,做成一个查询服务来使用的。所以,我也想来试试,但是我不是直接使用qqwry.dat这个文件。我换一种方式,我才用将qqwry.dat中的ip数据导入mysSEO靠我ql数据库中,通过数据库的查询来提供ip位置查询功能。
gitee代码地址: 纯真ip库服务搭建
纯真ip数据库
纯真数据库收集了包括中国电信、中国移动、中国联通、长城宽带、聚友宽带等 ISP 的 SEO靠我IP 地址数据,包括网吧数据。希望能够通过大家的共同努力打造一个没有未知数据,没有错误数据的QQ IP。IP数据库每5天更新快速,请大家定期更新IP数据库!
因为IP地址数据是民间收集的,各运营商也会不SEO靠我时的更改IP段,所以有点遗漏、错误是难免的。随数据库附送IP解压,查询软件。假如发现IP地址有不对的,或想提供新的IP地址,请到纯真IP小秘书提供IP地址数据,或者登陆纯真时空论坛“电脑板块”中的“代SEO靠我理及IP板”块告诉我们。以便及时更新IP数据库。谢谢!
这个数据库目前2020.12月版本的含有52万+条ip数据。包括了0.0.0.0-255.255.255.255的所有ip,但是我们都知道ipv4SEO靠我地址总数是:2^32,大概42亿多条。所以,这里的52多万条只是一部分,它是按照ip地址段来区分的,可以这样来理解:把0.0.0.0-255.255.255.255想象成坐标轴上一个长度为42亿的区间SEO靠我,然后分成52万份(ip段),每一份属于一个地区。 它的作用就算是这个,通过ip知道此ip属于哪个地区(当然了,精度不是太高,不适合于精确定位。)
注意:
1.一个ip段是连续的,包括一个ip段起始地址和一SEO靠我个ip段结束地址。
2.纯真数据库对于国外的ip地址,只能定位到国家,对于国内的ip地址是可以有省市县的,毕竟主要收集的还是国内的地址。关于ip定位的精度:
因为ip地址(发达国家就占据了大部分了)不够用SEO靠我,所以它实际上是动态分配的,所以一个人的ip一直都会变化的,但是在一段时间内是不变的(政府、学校、公司等机构会具有固定的ip,个人一般没有这个权力),所以定位精度不是很好,但是如果你在国内发现某个人的SEO靠我ip地址在国外,这并不是数据出了问题,而是因为他的ip地址基本就是在国外。但是,ip地址的分配记录是保存下来的,如果有法律权限是可以查看的,因此上网需要规范言行,互联网不是法外之地,举头三尺有神明!
如SEO靠我何使用我们的ip地址必在0.0.0.0-255.255.255.255之间(这里不考虑ipv6的地址),所以只要判断某个ip是否在ipstart和ipend之间就行了(即一个ip段内),就知道它属于哪SEO靠我里了。
sql查询方式即为:
注:INET_ATON函数 会将一个有效的ip地址转成整数,所以不用自己手动转换ip地址了,直接传递点分式ip地址就行了。
但是不推荐这样使用,前面那种方式是直接将ip转为整数SEO靠我存入数据库,下面这种方式是直接存入ip的点分式,然后查询时转换。这样的话,速度非常慢,因为它相当于要对每一个ip地址做转换才能查询,这是错误的使用方式。
首先,需要准备qqwry.dat这个文件SEO靠我,因为数据在它里面。但是,我发现了一个更好用的软件,这里就不直接使用这个文件了。
注意:鉴于网络上面资源下载容易踩坑,这里推荐到这个网站去下载,点击页面中的下载即可。如果有能力的话,也可以选择捐助作者。SEO靠我
下载完成之后,解压安装这个软件,就可以使用ip查询了。但是它是一个桌面软件,我们这里的目的是做成web服务,即像这个网站一样的查询服务。这样的ip查询服务有很多,但是如果使用量大的话,自己搭建一个还是SEO靠我非常好的,成本低、可靠!并且,这个数据库是一直在更新的,而且更新频率也还高,说明它的效果还是很好的。
测试一下,结果同上面的web页面这里我们不直接使用这个软件,而是使用它的一个功能,在主界面上有一个解SEO靠我压按钮,点击将ip数据导出成文本(我这里命名成qqwry.txt)。
qqwry.txt的内容格式如下
因为这个qqwry.txt的格式,直接在数据库中查询不方便,所以需要处理一下,它目前的格式是:4列(SEO靠我ip段起始地址 ip段结束地址 area地区 location位置)。
如果直接将它们存入数据库中,那么查询就会变得麻烦,而且基本上也无法使用索引了,对于性能影响很大。这里我们采用网上别人的方式,将ipSEO靠我起始地址和ip结束地址转成数字。所以需要先使用程序对上面的文本数据进行处理一下,我看别人的博客处理数据都是基于php的,但是我并不会世界上最好的语言,所以我就采用java来处理了。
注意:由于java语SEO靠我言没有无符号数的概念,所以ip地址并不能转成java的int型。ipv4地址是32位,int型也是32位,但是java的int中第一位是符号位,所以实际可以表示的大小是其它语言中int的一半。这也导致SEO靠我了,如果将ip地址转换为int的话,会有许多数据溢出,变成负数,所以这里需要将ip地址转成long型。
注意:toString方法是转成符合数据库导入格式的数据,toRedis是转成符号redis导入的格式(我不太确定这种方式的效率,并SEO靠我且我最终没有采用这种方法)。
说明:
1.这里的处理逻辑很简单,就是将文件分行读入内存,然后对每一行切分成4块,然后调用编写的ip2Long方法进行处理即可。
2.如果可以使用正则表达式,尽量用正则表达式,SEO靠我特别是这种数据处理,速度差距非常之大!
3.这个ip2Long方法,是我从jdk的ObjectInpustream里面拿来的,随便修改了一下,原来的代码使用的是byte,但是这里如果传入一个尝试把一个大SEO靠我于127的数转成int会抛出异常,所以使用int数组代替了byte数组,并且由于我这里只有32位,所以我就删除了后32位的处理代码。执行结果:
这个notepad++打开大文件感觉还行,40多m也能流畅SEO靠我使用。我这里使用excel打开就比较慢了,而且中文乱码了,就不放截图了。如果使用excel打开的话和普通的excel文件显示没有什么区别。我这里转成csv的原因是:
a).csv文件本身就是文本格式,使SEO靠我用程序直接输出即可。
b).我使用的sqlyog社区版无法导入excel数据,只能导入csv格式的数据。csv格式数据
redis格式的数据,直接就是一条命令,但是它总共是52万+的数据,想必一条一条的执SEO靠我行,时间上也会接受不了,但是这个给我了启发,我后来在redis中使用了这种格式来存储值(json数据)。
redis格式数据(没有使用)
因为我使用的是sqlyog,数据导入有一点麻烦,如果使用其SEO靠我它的数据库客户端应该会简单一些。
这个建表语句参考了别人的博客,做了一些修改(添加了原始的ip起始地址和结束地址、修改了表的字符编码)
CREATE TABLE `ip_data` ( SEO靠我 `ipstart` INT(10) UNSIGNED NOT NULL, # 开始ip地址 `ipend` INT(10) UNSIGNED NOT NULL, # 结束ip地址SEO靠我 `area` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, # 区域 SEO靠我`location` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, # 位置 `ipsSEO靠我tartstr` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, # 开始ip地址的点分表示方式 SEO靠我 `ipendstr` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, # 结束ip地址的点分表示方SEO靠我式 PRIMARY KEY (`ipstart`), INDEX `ip` (`ipstart`, `ipend`) USING BTREE ) SEO靠我 ENGINE=INNODB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci;我直接使用sqlyog的SEO靠我导入csv,但是没有成功。它的图形界面导入功能感觉就很鸡肋,我就没成功过,最后没办法,从网上查找到了如下命令,然后导入了csv文件,速度还行!
LOAD DATA LOCAL INFILE "C:/UsSEO靠我ers/Alfred/Desktop/data/test/qqwry.csv" INTO TABLE `poem_land`.`qqwry_ip` FIELDS TERMINATED SEO靠我BY , LINES TERMINATED BY \r\n;数据行数量 简单查看几行数据 查询数据 好了,还算不错!
关系型数据库的查询效果可能不够理想,这里使用redis这个nosSEO靠我ql来提高查询性能,首先要做的还是数据导入。我发现这个数据导入真的是麻烦哎,昨天这个东西搞了我好久,当然也因为我对redis并不熟悉。
上面说了,我没有采用那种一条一条执行命令的方式,而是使用了一种更加SEO靠我快速的方式——redis的管道技术。网上看了好多资料才搞成,第一次听说这个东西,效果确实很好!这里我们要做的是使用redis的管道技术将mysql数据导入到redis。导入数据命令
sudo mysqlSEO靠我 -uroot -ppassword poem_land --skip-column-names --raw < data.sql | ./redis-4.0.11/src/redis-cli -h SEO靠我host -p port -a password --pipe参数:
password:mysql数据库密码、redis密码
host:redis所在主机
port:redis所用端口
data.sql:一个SEO靠我sql文件,执行它会将数据库数据转成适合于redis导入的形式。data.sql 文件
注意:redis_member 就是存入redis的值,这里我采用数据库的CONCAT函数把所有列拼成了一个jsoSEO靠我n字符串的形式,这样的话查询就可以获取到全部数据了,我是第一次这样用,不知道其它更好的方式(不过,导入时间却耗费了好几分钟,估计是拼接字符串影响了性能)。
SELECT CONCAT(*4\r\n,$,SEO靠我LENGTH(redis_cmd),\r\n,redis_cmd,\r\n,$,LENGTH(redis_key),\r\n,redis_key,\r\n,$,LENGTH(redis_score),SEO靠我\r\n,redis_score,\r\n,$,LENGTH(redis_member),\r\n,redis_member,\r ) FROM (SELECT ZADD AS redSEO靠我is_cmd,qqwry AS redis_key,ipend AS redis_score,CONCAT({"ipstart":, ipstart, ,"ipend":, ipend,,"area"SEO靠我:", `area`, ","location":", location, "}) AS redis_member FROM `poem_land`.`qqwry_ip` ) AS NSEO靠我AME导入结果 查询数据
我最终的目的,还是要提供一个查询的接口来使用。现在就将它作为服务提供了,让我们开始吧!
控制器代码
这里我使用的是mybatis_plus和它的生成器,所SEO靠我以代码是自动生成的,基本上只需要看这个Controller的代码就行了。
package org.dragon.ip_seek.controller;import java.util.Map; SEO靠我 import java.util.Objects; import java.util.Set;import javax.servlet.http.HttpServletRequSEO靠我est;import org.dragon.ip_seek.entity.QqwryIp; import org.dragon.ip_seek.service.IQqwryIpServSEO靠我ice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sprSEO靠我ingframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.SEO靠我RedisTemplate; import org.springframework.http.MediaType; import org.springframeworkSEO靠我.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMappiSEO靠我ng; import org.springframework.web.bind.annotation.RestController;import com.baomidou.mybatiSEO靠我splus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkitSEO靠我.Wrappers; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasSEO靠我terxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMaSEO靠我pper;/*** <p>* 前端控制器* </p>** @author Alfred* @since 2020-12-08*/ @RestController @ReSEO靠我questMapping("/seeker/") public class QqwryIpController {private static final Logger logger SEO靠我= LoggerFactory.getLogger(QqwryIpController.class);private static final String ipRegex = "\\."; // 匹SEO靠我配ip的正则表达式@Autowiredprivate IQqwryIpService iqqwryIpService; // mysql 服务调用@Autowiredprivate RedisTempSEO靠我late<String, String> redisTemplate; // redis 服务调用@GetMapping(value = "/poem1", produces = MediaType.SEO靠我APPLICATION_JSON_VALUE)public String test01(HttpServletRequest request) {// 本地局域网测试,ip地址是内网地址,无法使用,这SEO靠我里我先传递一个模拟的ip地址。String host = request.getRemoteHost();logger.info("当前用户的ip地址为:{}", host);Map<String, SEO靠我String> ipInfo = iqqwryIpService.getIpInfo("60.174.231.10");ipInfo.forEach((k, v) -> {logger.info("KSEO靠我:{} V:{}", k, v);});return "{\"poem\": \"一日不见兮,思之如狂。\"}";}@GetMapping(value = "/poem_mysql", produceSEO靠我s = MediaType.APPLICATION_JSON_VALUE)public Map<String, Object> test02(HttpServletRequest request) {SEO靠我String host = request.getRemoteHost(); // 本地局域网测试,ip地址是内网地址,无法使用,这里我先传递一个模拟的ip地址。// 如果部署到云服务器上,去掉此行即SEO靠我可。host = "60.174.231.10";LambdaQueryWrapper<QqwryIp> wrapper = Wrappers.lambdaQuery();// 忘记了,eclipseSEO靠我自己的编译器不支持这个 SFunction接口wrapper.select(QqwryIp::getArea, QqwryIp::getLocation).apply("INET_ATON({0}) SEO靠我between ipstart and ipend", host);Map<String, Object> info = iqqwryIpService.getMap(wrapper);Map<StrSEO靠我ing, Object> ipInfoMap = new java.util.LinkedHashMap<>(); // 我想要指定map序列化的键值对顺序为:ip area locationipInSEO靠我foMap.put("ip", host);if (Objects.nonNull(info)) {ipInfoMap.putAll(info);}logger.info("通过mysql查询 ip:SEO靠我 {}", ipInfoMap.get("ip"));return ipInfoMap; // "{\"poem\": \"一日不见兮,思之如狂。\"}";}/*** 采用 Redis 缓存的方式进行SEO靠我服务调用* @param <T>* */@GetMapping(value = "/poem_redis", produces = MediaType.APPLICATION_JSON_VALUE)pSEO靠我ublic Map<String, Object> test03(HttpServletRequest request) {String host = request.getRemoteHost();SEO靠我host = "116.62.188.176"; // 本来想测试一个错误数据的:256.256.266.255,结果long都溢出了,直接变成了0// 从redis中查询数据Set<String> SEO靠我resultSet = redisTemplate.opsForZSet().rangeByScore("qqwry", ip2Long(host), Double.MAX_VALUE, 0, 1);SEO靠我logger.info("通过reids查询 ip: {}", host);return transfer(resultSet, host);}/*** 对redis查询数据做一个转换,使之格式更加友SEO靠我好* */@SuppressWarnings("unchecked")private Map<String, Object> transfer(Set<String> resultSet, StrinSEO靠我g host) {Map<String, Object> ipInfoMap = new java.util.LinkedHashMap<>(); // 我想要指定map序列化的键值对顺序为:ip aSEO靠我rea locationipInfoMap.put("ip", host);if (resultSet.isEmpty()) {ipInfoMap.put("area", "NAN");ipInfoMSEO靠我ap.put("location", "NAN");return ipInfoMap;}String result = resultSet.toArray(new String[0])[0];Map<SEO靠我String, Object> info = null;ObjectMapper mapper = new ObjectMapper();try {info = mapper.readValue(reSEO靠我sult, Map.class); // 这里泛型会被擦除,直接赋值会报编译器警告,但是我也不知道Class<T>这种参数该如何传递了,就先抑制一下编译器警告吧if (Objects.nonNull(SEO靠我info)) {ipInfoMap.putAll(info);}} catch (JsonMappingException e) {e.printStackTrace();} catch (JsonPSEO靠我rocessingException e) {e.printStackTrace();}return ipInfoMap;}private long ip2Long(String ip) {StrinSEO靠我g[] strs = ip.split(ipRegex);int[] bs = new int[4];for (int i = 0; i < 4; i++) {bs[i] = Integer.parsSEO靠我eInt(strs[i]); // Byte.parseByte(strs[i]); 这个方法转成的字节范围是 -128-127,真是太坑了!}return ((bs[3] & 0xFFL) ) + SEO靠我// 这个转换方法是从jdk源码里面偷来的,挺好用的!((bs[2] & 0xFFL) << 8) +((bs[1] & 0xFFL) << 16) +((bs[0] & 0xFFL) << 24);SEO靠我} }说明:
test01是传统的mybatis的写法,后来发现mybatis_plus可以使用更加简单的方法,就换成了test02,但是保留了test01。test03是从redis中SEO靠我进行数据查询。启动项目 test01——mysql
这里查询的结果,我是使用在日志上了,本来是打算在后台使用的,但是暂时也没啥想法,就算了。
test02——mysql test03——redis注意SEO靠我:因为我本地测试,它获取的是本地的ip地址(127.0.0.1),所以我就在后台硬编码了一个公网ip地址来查询。
我最近看到了一个基于java的开源软件——JMeter。它可以用来做压力测试,但是我SEO靠我并不会。但是,这并不妨碍我来玩一玩!
下面的内容仅供参考,我只是玩一玩!
步骤:启动JMeter,然后创建一个线程组(1000个线程,50s,循环两次),添加一个Http请求,添加一个查看结果树,添加一个SEO靠我聚合报告,启动。
接口:poem_mysql 接口:poem_redis
说明这里是两千个请求,从聚合报告里面看似乎redis确实性能是远远超过了mysql(我听别人说也是这样的),但是由于这个测试不严谨,SEO靠我我也不会JMeter,所以就不作为一个有效的参考了。
参数资源:
纯真数据库查询
查询网ip查询
通过管道传输快速将MySQL的数据导入Redis
使用redis有序集合搭建自有ip定位解析库
将mysql表数据SEO靠我批量导入redis zset结构中
IPLook解析纯真数据库,我没有使用这个软件,因为我是手动解析数据
mysql快速导入大量数据问题
一个简单的方式,但是有效率问题
ip location介绍
redis的SEO靠我简单配置
jackson json map object 之间互相转换网站备案号:浙ICP备17034767号-2