日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

來源于公眾號JAVA藝術 ,

作者wujiuye


我先說下結果。我現在還不敢放線上去測,這是本地測的數據,我4g內存的電腦本地開redis,一次都沒寫完過全部數據,都是寫一半后不是redis掛就是測試程序掛??梢钥隙ǖ氖强傆涗洈凳且郧f為單位的。O(log(n千萬/range))的時間復雜度,本地測的結果我并不滿意,7ms的時間,太久了。這個數量級的數據,就算內存緩存也很花時間,因為并不是簡單的key-value,涉及到范圍查找。

 

基于Redis實現范圍查詢的IP庫緩存設計方案

 

 

 

基于Redis實現范圍查詢的IP庫緩存設計方案

 

 

 

使用Sorted Set實現范圍查找

 

 

最近系統需要更新IP庫,IP庫存儲的是IP所屬的國家和城市信息,廣告主投放廣告會有區域限制,所以需要根據點擊廣告的終端ip,獲取位置信息,并判斷是否滿足廣告投放區域的要求。

 

最頭疼的是,IP信息庫是按區間存儲的,拿到一個ip得要知道它所屬的范圍才能知道它對應哪條記錄。它的表結構是這樣的:

Ip_from,ip_to,ccountry_code,country,regin,city

Ip起始段,Ip結束段,國家編碼,國家,區域(比如中國的省),城市

基于Redis實現范圍查詢的IP庫緩存設計方案

 

 

而Ip_from和ip_to是一個數值,將ip通過公式轉化后的數值。

a.b.c.d


A*(256*256*256)+b*(256*256)+c*(256)+d

 

為了效率,程序中的轉換可以寫為


a*(1<<24)+b*(1<<16)+c*(1<<8)+d

 

在此之前我都沒注意以前是怎么實現查找的,以前是用內存緩存實現,但以前的數據比較少,而查找的方式用的遞歸,先不說遞歸查找算法的缺陷。而新的ip庫數據量很大,本地debug直接OOM,沒有足夠的內存撐起這么大的ip庫。

 

如果說,一個csv文件的大小是1g多,那么讀取到jvm中,就需要4g甚至更高的內存,因為一個對象占的內存是比一行逗號隔開的字符串耗更大的內存。要實現查找算法,創建對應的數據結構,這些也會占用很大的內存。

 

綜上所述,我們考慮用redis來緩存,當然,也可以用mongodb,如果是用mongodb緩存,那就簡單了。既然要用Redis,那么就不得不面對,Redis如何實現范圍查詢,還要支持高并發。這算是一道難題了。

 

插入一段內容,關于如果使用Sorted Set實現范圍查找,就是sql中的大于等于and小于等于。(下面是我參考的一篇博客,我覺得他的實現有些雞肋,完全可以用一條:

zadd myset 1015 1011-1015-v1 替代兩條記錄)。

服務端對于客戶端不同的版本區間會做些不同的配置,那么客戶端一個版本過來怎么快速的定位是屬于哪個版本區間呢?可以利用Sorted Sets的zrangebyscore命令。

zadd myset 1011 v1_start

zadd myset 1015 v1_end

 

zadd myset 1018 v2_start

zadd myset 1023 v2_end

 

如上我們像myset里插入了4條數據,代表的意思是版本區間v1是從1011-1015版本,版本區間v2是從1018-1023版本。

 

https://www.cnblogs.com/zhanjindong/p/3549994.html

 

那么我現在如何判斷1014版本屬于哪個區間呢,使用zrangebyscore如下操作:

zrangebyscore myset 1014 +inf LIMIT 0 1

1)v1_end

 

返回v1_end說明1014屬于版本區間1,上面的這個命令的意思是在myset中查找第一個score值大于等于1014的member,如果我們查找一個不在區間內的版本,比如1016:

zrangebyscore myset 1014 +inf LIMIT 0 1

1)v2_start

 

https://www.cnblogs.com/zhanjindong/p/3549994.html

 

首先,我想到的是利用Redis的有序集合Sorted Set,存儲每條記錄的ip區間,或者叫ip范圍。ip_to列作為有序集合的score。如:

zadd ip-country-city-locations-range 3756871679 3756871424~3756871679

 

這樣就可以查詢一個ip對應的score落在哪個區間,找出符合條件的第一個區間。如:

 

(1)將ip轉為number,假設得到的值為:3756871650
(2)使用zrangebyscore命令獲取所在區間
zrangebyscore ip-country-city-locations-range 3756871650 +inf 0 1

因為3756871650比3756871424~3756871679區間的end值3756871679小于等于,所以匹配到這條記錄。但拿到這條記錄后并不能說明3756871650在這個區間內,所以還要比較


3756871424<=3756871650<=3756871679

 

因為會存在這種情況,該區間與前一個區間并不是連續的,比如。

(1)3756870911=>3756870656~3756870911
(2)3756871679=>3756871424~3756871679

 

明顯,這兩個區間之間存在斷層。但可以肯定的是,如果不落在區間(2)中,也不會落在區間(1)。所以不滿足就可以直接返回null了。

 

基于Redis實現范圍查詢的IP庫緩存設計方案

 

Ip庫用hash類型存儲,field取ip_from或者ip_from&ip_to,value當然就是存完整的一行記錄了。最后就可以根據拿到的區間信息獲取到對應記錄的field,使用hget命令就能獲取到最終的一行ip信息記錄。

hget ip-country-city-locations 3756871424

這并不難實現,但是,耗時卻非常嚴重,可以看下官方文檔介紹的Sorted Set的zrangebyscore的時間復雜度。IP庫保守估計百萬條記錄,如果就這樣上線,百分百又是服務雪崩。

 

改進思路:區間+Sorted Set

 

由于Sorted Set有序集合的查詢時間復雜度是O(log(n)+m),其中n是總記錄數,m是此次查詢獲取的記錄數,在limit 0 1的情況下是O(log(n)),所以一個有序集合的元素個數越多,它的查詢時間耗時越長。對于一般的應用場景,如排行榜,都是只有極少數的幾百條記錄。而如果用于ip庫的區間查詢實現,記錄上百萬條,而且還是用于高并發場景,不把服務搞垮才怪了。

 

既然我們要用Sorted Set,但又不能讓集合的元素過大,那么我們可以分n/m個區間存儲啊。假設有一百萬條記錄,每個Sorted Set存儲1000條,那就用1000個Sorted Set集合來存儲。hash的查詢時間復雜度是接近O(1)的,增加1000個key在分槽位的分布式集群下根本不算什么。

 

按照上面的思路改進后,獲取一個ip的國家城市信息就變成如下步驟:

1、根據ip計算出一個number值

比如:3756871650

2、根據區間大小(這一步的區間指的是每個Sorted Set的最大大小),計算出該number所在的集合的key


比如:ip-country-city-locations-range-375

3、根據這個key,去Sorted Set查詢number所屬的區間。

比如:zrangebyscore ip-country-city-locations-range 3756871650 +inf 0 1

 

5、拿到區間信息后,從區間信息獲取ip范圍位置信息的 field(hash類型存儲)

比如查詢結果區間信息為:3756871424~3756871679拿到field就是:3756871424

 

6、根據key和field拿到目標記錄。

hget ip-country-city-locations 3756871424

 

編碼實現

 

我將實現封裝成一個組件,目的是對外提供更簡單的使用方式,封裝復雜的實現邏輯,同時,內部的改動對使用者無感知。通過SPI+分層設計,利用靜態代理模式等,使得組件具有極強的擴展性,如果某天想改成使用mongodb或者內存緩存,只需要實現幾個接口就可以了。

 

基于Redis實現范圍查詢的IP庫緩存設計方案

 

 

下面是README.MD的內容

 

關于數據源表的初始化

使用需要配置update啟動參數:

-Dip.database.table.update=true

如:

java -Xss256k-jar -Dip.database.table.update=true xxx-1.0.0.jar

true: 首次啟動就會從指定的url文件讀取解析記錄,插入數據表 false: 表示已經確認表存在記錄了,不需要再更新。(也不會去解析文件) 默認:false

解析記錄與插入表是異步的,后臺開啟一個線程執行。耗時根據文件大小決定,我測的是86s

配置使用表

使用了java的SPI

需要指定使用哪個文件解析器,也就對應使用哪種類型的表

配置redis操作實現類

使用了java的SPI

如果解析配置使用了

com.chestnut.ip.database.parser.RedisIP2LocationFileParser

則需要自己實現RedisOperation,并在

com.chestnut.ip.database.suppor.IP2LocationRedisOperation

文件中配置redis操作的實現類

緩存的key

如果使用redis存儲數據,則key固定為

ip-country-city-locations // 存儲真實記錄ip-country-city-locations-range-* // 存儲范圍與真實記錄的key的映射

其中ip-country-city-locations-range-后面的代表的分區索引

分享到:
標簽:Redis
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定