redis缓存原理与实现_基于Redis实现范围查询的IP库缓存设计方案
生活随笔
收集整理的這篇文章主要介紹了
redis缓存原理与实现_基于Redis实现范围查询的IP库缓存设计方案
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
點(diǎn)擊上方“碼農(nóng)沉思錄”? 發(fā)現(xiàn)更多精彩我先說(shuō)下結(jié)果。我現(xiàn)在還不敢放線(xiàn)上去測(cè),這是本地測(cè)的數(shù)據(jù),我4g內(nèi)存的電腦本地開(kāi)redis,一次都沒(méi)寫(xiě)完過(guò)全部數(shù)據(jù),都是寫(xiě)一半后不是redis掛就是測(cè)試程序掛。可以肯定的是總記錄數(shù)是以千萬(wàn)為單位的。O(log(n千萬(wàn)/range))的時(shí)間復(fù)雜度,本地測(cè)的結(jié)果我并不滿(mǎn)意,7ms的時(shí)間,太久了。這個(gè)數(shù)量級(jí)的數(shù)據(jù),就算內(nèi)存緩存也很花時(shí)間,因?yàn)椴⒉皇呛?jiǎn)單的key-value,涉及到范圍查找。使用Sorted Set實(shí)現(xiàn)范圍查找最近系統(tǒng)需要更新IP庫(kù),IP庫(kù)存儲(chǔ)的是IP所屬的國(guó)家和城市信息,廣告主投放廣告會(huì)有區(qū)域限制,所以需要根據(jù)點(diǎn)擊廣告的終端ip,獲取位置信息,并判斷是否滿(mǎn)足廣告投放區(qū)域的要求。最頭疼的是,IP信息庫(kù)是按區(qū)間存儲(chǔ)的,拿到一個(gè)ip得要知道它所屬的范圍才能知道它對(duì)應(yīng)哪條記錄。它的表結(jié)構(gòu)是這樣的:Ip_from,ip_to,ccountry_code,country,regin,cityIp起始段,Ip結(jié)束段,國(guó)家編碼,國(guó)家,區(qū)域(比如中國(guó)的省),城市而Ip_from和ip_to是一個(gè)數(shù)值,將ip通過(guò)公式轉(zhuǎn)化后的數(shù)值。a.b.c.dA*(256*256*256)+b*(256*256)+c*(256)+d為了效率,程序中的轉(zhuǎn)換可以寫(xiě)為a*(1<<24)+b*(1<<16)+c*(1<<8)+d在此之前我都沒(méi)注意以前是怎么實(shí)現(xiàn)查找的,以前是用內(nèi)存緩存實(shí)現(xiàn),但以前的數(shù)據(jù)比較少,而查找的方式用的遞歸,先不說(shuō)遞歸查找算法的缺陷。而新的ip庫(kù)數(shù)據(jù)量很大,本地debug直接OOM,沒(méi)有足夠的內(nèi)存撐起這么大的ip庫(kù)。如果說(shuō),一個(gè)csv文件的大小是1g多,那么讀取到j(luò)vm中,就需要4g甚至更高的內(nèi)存,因?yàn)橐粋€(gè)對(duì)象占的內(nèi)存是比一行逗號(hào)隔開(kāi)的字符串耗更大的內(nèi)存。要實(shí)現(xiàn)查找算法,創(chuàng)建對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),這些也會(huì)占用很大的內(nèi)存。綜上所述,我們考慮用redis來(lái)緩存,當(dāng)然,也可以用mongodb,如果是用mongodb緩存,那就簡(jiǎn)單了。既然要用Redis,那么就不得不面對(duì),Redis如何實(shí)現(xiàn)范圍查詢(xún),還要支持高并發(fā)。這算是一道難題了。插入一段內(nèi)容,關(guān)于如果使用Sorted Set實(shí)現(xiàn)范圍查找,就是sql中的大于等于and小于等于。(下面是我參考的一篇博客,我覺(jué)得他的實(shí)現(xiàn)有些雞肋,完全可以用一條:zadd myset 1015 1011-1015-v1 替代兩條記錄)。服務(wù)端對(duì)于客戶(hù)端不同的版本區(qū)間會(huì)做些不同的配置,那么客戶(hù)端一個(gè)版本過(guò)來(lái)怎么快速的定位是屬于哪個(gè)版本區(qū)間呢?可以利用Sorted Sets的zrangebyscore命令。zadd myset 1011 v1_startzadd myset 1015 v1_end?zadd myset 1018 v2_start?zadd myset 1023 v2_end?如上我們像myset里插入了4條數(shù)據(jù),代表的意思是版本區(qū)間v1是從1011-1015版本,版本區(qū)間v2是從1018-1023版本。https://www.cnblogs.com/zhanjindong/p/3549994.html那么我現(xiàn)在如何判斷1014版本屬于哪個(gè)區(qū)間呢,使用zrangebyscore如下操作:?zrangebyscore myset 1014 +inf LIMIT 0 1?1)v1_end?返回v1_end說(shuō)明1014屬于版本區(qū)間1,上面的這個(gè)命令的意思是在myset中查找第一個(gè)score值大于等于1014的member,如果我們查找一個(gè)不在區(qū)間內(nèi)的版本,比如1016:?zrangebyscore myset 1014 +inf LIMIT 0 1?1)v2_starthttps://www.cnblogs.com/zhanjindong/p/3549994.html首先,我想到的是利用Redis的有序集合Sorted Set,存儲(chǔ)每條記錄的ip區(qū)間,或者叫ip范圍。ip_to列作為有序集合的score。如:zadd?ip-country-city-locations-range?3756871679?3756871424~3756871679這樣就可以查詢(xún)一個(gè)ip對(duì)應(yīng)的score落在哪個(gè)區(qū)間,找出符合條件的第一個(gè)區(qū)間。如:(1)將ip轉(zhuǎn)為number,假設(shè)得到的值為:3756871650(2)使用zrangebyscore命令獲取所在區(qū)間zrangebyscore?ip-country-city-locations-range?3756871650?+inf?0 1因?yàn)?756871650比3756871424~3756871679區(qū)間的end值3756871679小于等于,所以匹配到這條記錄。但拿到這條記錄后并不能說(shuō)明3756871650在這個(gè)區(qū)間內(nèi),所以還要比較3756871424<=3756871650<=3756871679因?yàn)闀?huì)存在這種情況,該區(qū)間與前一個(gè)區(qū)間并不是連續(xù)的,比如。(1)3756870911=>3756870656~3756870911(2)3756871679=>3756871424~3756871679明顯,這兩個(gè)區(qū)間之間存在斷層。但可以肯定的是,如果不落在區(qū)間(2)中,也不會(huì)落在區(qū)間(1)。所以不滿(mǎn)足就可以直接返回null了。// [0] <= midNumber <= [1]if?(midNumber.compareTo(Long.valueOf(rangeSn[0]))????||?midNumber.compareTo(Long.valueOf(rangeSn[1]))?>?0)?{ return null;}Ip庫(kù)用hash類(lèi)型存儲(chǔ),field取ip_from或者ip_from&ip_to,value當(dāng)然就是存完整的一行記錄了。最后就可以根據(jù)拿到的區(qū)間信息獲取到對(duì)應(yīng)記錄的field,使用hget命令就能獲取到最終的一行ip信息記錄。hget ip-country-city-locations 3756871424這并不難實(shí)現(xiàn),但是,耗時(shí)卻非常嚴(yán)重,可以看下官方文檔介紹的Sorted Set的zrangebyscore的時(shí)間復(fù)雜度。IP庫(kù)保守估計(jì)百萬(wàn)條記錄,如果就這樣上線(xiàn),百分百又是服務(wù)雪崩。改進(jìn)思路:區(qū)間+Sorted Set由于Sorted Set有序集合的查詢(xún)時(shí)間復(fù)雜度是O(log(n)+m),其中n是總記錄數(shù),m是此次查詢(xún)獲取的記錄數(shù),在limit 0 1的情況下是O(log(n)),所以一個(gè)有序集合的元素個(gè)數(shù)越多,它的查詢(xún)時(shí)間耗時(shí)越長(zhǎng)。對(duì)于一般的應(yīng)用場(chǎng)景,如排行榜,都是只有極少數(shù)的幾百條記錄。而如果用于ip庫(kù)的區(qū)間查詢(xún)實(shí)現(xiàn),記錄上百萬(wàn)條,而且還是用于高并發(fā)場(chǎng)景,不把服務(wù)搞垮才怪了。既然我們要用Sorted Set,但又不能讓集合的元素過(guò)大,那么我們可以分n/m個(gè)區(qū)間存儲(chǔ)啊。假設(shè)有一百萬(wàn)條記錄,每個(gè)Sorted Set存儲(chǔ)1000條,那就用1000個(gè)Sorted Set集合來(lái)存儲(chǔ)。hash的查詢(xún)時(shí)間復(fù)雜度是接近O(1)的,增加1000個(gè)key在分槽位的分布式集群下根本不算什么。按照上面的思路改進(jìn)后,獲取一個(gè)ip的國(guó)家城市信息就變成如下步驟:1、根據(jù)ip計(jì)算出一個(gè)number值比如:37568716502、根據(jù)區(qū)間大小(這一步的區(qū)間指的是每個(gè)Sorted Set的最大大小),計(jì)算出該number所在的集合的key比如:ip-country-city-locations-range-3753、根據(jù)這個(gè)key,去Sorted Set查詢(xún)number所屬的區(qū)間。比如:zrangebyscore ip-country-city-locations-range 3756871650 +inf 0 15、拿到區(qū)間信息后,從區(qū)間信息獲取ip范圍位置信息的 field(hash類(lèi)型存儲(chǔ))比如查詢(xún)結(jié)果區(qū)間信息為:3756871424~3756871679拿到field就是:37568714246、根據(jù)key和field拿到目標(biāo)記錄。hget ip-country-city-locations 3756871424編碼實(shí)現(xiàn)我將實(shí)現(xiàn)封裝成一個(gè)組件,目的是對(duì)外提供更簡(jiǎn)單的使用方式,封裝復(fù)雜的實(shí)現(xiàn)邏輯,同時(shí),內(nèi)部的改動(dòng)對(duì)使用者無(wú)感知。通過(guò)SPI+分層設(shè)計(jì),利用靜態(tài)代理模式等,使得組件具有極強(qiáng)的擴(kuò)展性,如果某天想改成使用mongodb或者內(nèi)存緩存,只需要實(shí)現(xiàn)幾個(gè)接口就可以了。下面是README.MD的內(nèi)容
關(guān)于數(shù)據(jù)源表的初始化
使用需要配置update啟動(dòng)參數(shù):-Dip.database.table.update=true
如:java -Xss256k-jar -Dip.database.table.update=true xxx-1.0.0.jar
true: 首次啟動(dòng)就會(huì)從指定的url文件讀取解析記錄,插入數(shù)據(jù)表 false: 表示已經(jīng)確認(rèn)表存在記錄了,不需要再更新。(也不會(huì)去解析文件) 默認(rèn):false解析記錄與插入表是異步的,后臺(tái)開(kāi)啟一個(gè)線(xiàn)程執(zhí)行。耗時(shí)根據(jù)文件大小決定,我測(cè)的是86s配置使用表
使用了java的SPI需要指定使用哪個(gè)文件解析器,也就對(duì)應(yīng)使用哪種類(lèi)型的表配置redis操作實(shí)現(xiàn)類(lèi)
使用了java的SPI如果解析配置使用了com.chestnut.ip.database.parser.RedisIP2LocationFileParser
則需要自己實(shí)現(xiàn)RedisOperation,并在com.chestnut.ip.database.suppor.IP2LocationRedisOperation
文件中配置redis操作的實(shí)現(xiàn)類(lèi)緩存的key
如果使用redis存儲(chǔ)數(shù)據(jù),則key固定為ip-country-city-locations // 存儲(chǔ)真實(shí)記錄
ip-country-city-locations-range-* // 存儲(chǔ)范圍與真實(shí)記錄的key的映射
其中ip-country-city-locations-range-后面的代表的分區(qū)索引點(diǎn)個(gè)在看吧,證明你還愛(ài)我總結(jié)
以上是生活随笔為你收集整理的redis缓存原理与实现_基于Redis实现范围查询的IP库缓存设计方案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 无限超越超级机器人nds_阿里重新定义个
- 下一篇: 现代软件工程 2012 北航 项目复审模