相信作互聯網開發的不少人都有一個需求,那就是獲取用戶的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區域記錄表和區域信息表便可。
有任何想法或者意見歡迎拍磚,也歡迎分享本身的新的,哪怕是一個小的改進點也能夠,說不定你的分享能夠減小別人不少的工做。