大批量IP查询和IP区域快速查询

    相信作互联网开发的不少人都有一个需求,那就是获取用户的ip,并定位用户访问是哪一个省哪一个市的。从这个需求来看,首先须要有ip数据库,其次对于某些查不到的ip还可以按期更新ip数据库到最新的,最后就是能快速查询。php

    一、ip数据库网上都有,我这里也有一个,是从淘宝数据库更新来的,稍后提供mysql版本的数据库脚本下载。java

    二、ip更新有几个网站,我用的是淘宝提供的,经过定时任务,去查询这些没有查询到ip区域的ip信息。     mysql

         http://ip.taobao.com/service/getIpInfo.php?ip=, 经过解析返回的数据,获取要查询ip区域信息,而后存入数据库。git

    三、对于快速查询,有些人说数据库创建索引不就解决了查询问题。但是当一天的数据量有千万级别记录增长的时候,这么屡次的ip区域查询一定会给数据库带来很大压力,有一位数据库优化的牛人说过,最好的优化办法就是减小数据库访问github

     全部源码和初始化数据文件可在github上找到:https://github.com/wuskyfantasy/ip.queryredis

     先看看数据库区域的表设计:算法

CREATE TABLE `ip_area_dictionary` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `from` varchar(16) NOT NULL COMMENT '起始IP',
  `to` varchar(16) NOT NULL COMMENT '结束IP',
  `country` varchar(30) NOT NULL COMMENT '国家',
  `area` varchar(50) NOT NULL COMMENT '区域',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立日期',
  `updated_at` timestamp NULL DEFAULT NULL COMMENT '更新日期',
  `area_id` bigint(20) DEFAULT NULL COMMENT '区域ID',
  `from_number` bigint(20) DEFAULT NULL COMMENT 'ip起始整数',
  `to_number` bigint(20) DEFAULT NULL COMMENT 'ip结束整数',
  PRIMARY KEY (`id`),
  KEY `idx_from` (`from`),
  KEY `idx_to` (`to`),
  KEY `idx_from_to` (`from`,`to`) USING BTREE,
  KEY `idx_from_to_number` (`from_number`,`to_number`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

    索引idx_from,idx_to,idx_from_to是在直接使用ip来进行查询时使用的,也就是我最初使用的查询方法,如今ip转换为整数后,直接使用索引idx_from_to_number,这3个索引能够去除了:sql

  KEY `idx_from` (`from`),
  KEY `idx_to` (`to`),
  KEY `idx_from_to` (`from`,`to`) USING BTREE,

    查询出来的数据截图:数据库

    

    要知道专门去查询ip在哪个段以内比较麻烦,因此就能够把IP转成一个长整数,如此某一个区域的IP范围就是在两个长整数之间了,经过这种方式创建索引查询较快。缓存

    转换IP为长整数的工具类:IpTool.java,调用setIP(ip)就能够把ip转换成长整数了。

    这样对from_bumber和to_number创建索引,数据库查询就比较快了。但正如前面所说,咱们不知足于这点速度。

     四、改进访问mysql查询的办法就是使用缓存,我这里使用memcache+本地缓存。通常状况下,对于一些耗时的查询,会使用分布式缓存,如memcache、redis等,下次查询时便可直接使用分布式缓存。可是因为用户访问的ip变化太大,命中的几率较低,所以这里不适用,转而采起memcache+本地缓存。

          大致想法是把ip区域记录(大概40多万条),在系统启动时加载到一个集合中(如List),而后把这个集合放到memcache(都已经读取出来了,为什么要放到memcache,缘由是有不少项目都会用到这个ip区域集合,第一次读取数据库后把ip集合放到memcache,后续访问memcache来获取这个集合将比访问mysql获取更快),并把读取的这个集合放到本地内存。

           读取ip集合记录的代码是:LoadService.java,须要设置在系统启动时运行这个类的加载方法。

           把读取的ip集合放到本地内存,就是把这个大集合List放到map中,便于下次获取。

设置本地缓存的方法:SystemCache.java,调用setCache便可设置。总结起来,访问速度对比: 本地缓存 > 分布式缓存 > 数据库。

           有了这个集合后,该怎么去查询呢,之前直接查询数据库索引就能够了,如今给这么大的集合,该怎么去查询呢。简单来讲,就是你怎么去40多万记录中的某一条,办法就是使用二分查找法,为了可以适应二分查找法,查询出来的ip区域记录须要进行排序,而且进行了简化,简化后是这样的:

private Long id;
private Long f;// IP段开始 原始字段from_number
private Long t;// IP段结束 原始字段to_number
private Long a_id;// 区域ID area_id

这里之因此对字段名称进行简化,是由于在存放到memcache中时,这些属性名称和类路径也会序列化,这会带来很大的内存开销,而且这个类(IP.java)的类路径也缩短了,道理和简化属性名称同样。

       如此的话,在数据库查询记录的时候,就按from_number进行排序,这样就能够知足二分查找时记录是排序的要求了。有人怀疑这样作必定会比访问mysql快么,毕竟mysql有索引啊。我认为首先mysql的链接耗时就较大(单词访问耗时少,可千万级别的访问对数据库形成的压力会致使耗时剧增),另外mysql索引也是使用相似的算法查找记录的。

      二分查找ip区域记录的源码:Dichotomy.java

      如此作可大量减小访问mysql,坏处是有这么一个大对象一直存在,不过占用不了多少内存,相比带来的好处是值得尝试的。

     ip数据库脚本下载地址:http://pan.baidu.com/s/1dDsuL01,初始化脚本init.sql

     没有提供具体的ip记录表,是由于已经没有使用的意义,只须要ip区域记录表和区域信息表便可。

     有任何想法或者意见欢迎拍砖,也欢迎分享本身的新的,哪怕是一个小的改进点也能够,说不定你的分享能够减小别人不少的工做。 

相关文章
相关标签/搜索