針對IP地址信息的數(shù)據(jù)結(jié)構(gòu)

1. 需求

需求來源:指標(biāo)平臺

由于公司的人員變動,最近接手了elfin這個基礎(chǔ)數(shù)據(jù)應(yīng)用柠横,交接后發(fā)現(xiàn)IP地理位置數(shù)據(jù)的更新邏輯一直存在缺陷,該缺陷造成指標(biāo)平臺和大數(shù)據(jù)平臺所用的數(shù)據(jù)中存在大量的重復(fù)數(shù)據(jù)和老舊數(shù)據(jù):本應(yīng)為580W條的IP地理位置數(shù)據(jù)實際在數(shù)據(jù)庫中存儲了約830W條篇恒。這些老舊數(shù)據(jù)很可能對大數(shù)據(jù)分析和指標(biāo)分析的準(zhǔn)確性造成影響媒咳。

鑒于大數(shù)據(jù)平臺和指標(biāo)平臺的調(diào)用鏈路復(fù)雜,具體影響無法估計,決定盡快修復(fù)該缺陷并重寫數(shù)據(jù)蜜徽,以確保數(shù)據(jù)的準(zhǔn)確性祝懂。IP地理位置數(shù)據(jù)中以IP段為索引,由于IP端存在增加拘鞋、刪除砚蓬、拆分、合并等多種更新情況掐禁,十分難以判斷怜械。在數(shù)據(jù)提供方IPIP的建議下我和產(chǎn)品經(jīng)理決定將IP地理位置數(shù)據(jù)改為全量更新。

在與指標(biāo)平臺溝通時傅事,了解到全量更新IP數(shù)據(jù)會極大的影響指標(biāo)平臺的性能。為保證數(shù)據(jù)的準(zhǔn)確性和指標(biāo)平臺的性能峡扩,經(jīng)過多次溝通確立了IP地理位置信息批量查詢的需求:

elfin對外提供ip地址數(shù)據(jù)的批量查詢功能:單次請求200條IP數(shù)據(jù)蹭越,集群 2000TPS 時 p99 達(dá)到 10ms 以下。

2. 失敗的嘗試

新增dubbo接口教届,提供批量ip數(shù)據(jù)的查詢功能响鹃,做壓測!案训!壓測結(jié)果很差买置,TPS和rt遠(yuǎn)遠(yuǎn)無法達(dá)到預(yù)期。

image.png

- 優(yōu)化嘗試

  • 開多線程:4核4線程强霎,提升了約 30%的性能忿项。
  • 優(yōu)化查詢算法:從遍歷部分?jǐn)?shù)據(jù)改成二分查找,性能提升不明顯城舞。

- 反思失敗

經(jīng)歷過幾次失敗的嘗試之后轩触,我意識到常規(guī)的優(yōu)化方式很可能難以顯著提高查詢性能,需要深入分析數(shù)據(jù)和算法家夺,找出性能瓶頸脱柱。

現(xiàn)有的datax格式數(shù)據(jù)是一個黑盒,IPIP方面也不愿意透露具體的數(shù)據(jù)編碼方式拉馋,只是大致知道其查詢方法是通過二進(jìn)制按位位移的方式有選擇的遍歷數(shù)據(jù)榨为,難以進(jìn)一步優(yōu)化其查詢方法。

3. 具體實現(xiàn)

- 解決思路

我決定徹底更換數(shù)據(jù)結(jié)構(gòu)和算法煌茴,拋棄IPIP提供的datax數(shù)據(jù)随闺,采取“空間換時間”的方式,使用未經(jīng)壓縮的包含全量IP地理位置信息的txtx格式數(shù)據(jù)景馁。

原始數(shù)據(jù)的特點

  1. IPv4.txtx包含了所有的的IPv4地址板壮,從0.0.0.0到255.255.255.255都有
  2. 該數(shù)據(jù)按照IP段的段起始順序排列
  3. 所有數(shù)據(jù)的IP段無縫銜接,即前一個IP段的段結(jié)束+1=當(dāng)前IP段的段起始
  4. 每條數(shù)據(jù)為 15字節(jié)(IP段起始)+15字節(jié)(IP段結(jié)束)+281(地理位置數(shù)據(jù))
  5. 數(shù)據(jù)的主要索引為 IP段的段起始合住,次要的唯一數(shù)據(jù)為 IP段的段結(jié)束绰精。

- 批量查詢請求的特點

由于查詢時入?yún)闊o規(guī)律的IP地址列表撒璧,批量IP地址查詢可以轉(zhuǎn)化為多次單條IP地址查詢,針對單條IP地址的查詢做優(yōu)化笨使。

由于IP數(shù)據(jù)是范圍匹配不是精確匹配卿樱,無法使用諸如HashMap的結(jié)構(gòu),為保障查詢效率使用TreeMap硫椰,key為IP段的段起始繁调,value存儲全量的IP地理位置信息,核心的查詢方法為:find floor entry靶草。

- 優(yōu)化數(shù)據(jù)結(jié)構(gòu)

在初步構(gòu)建完Demo后我遇到了一個問題:由于IPv4.txtx數(shù)據(jù)本身有近600M而且在不斷增加蹄胰,在未來1-2年內(nèi)可能增加到1G,將所有數(shù)據(jù)全放到一個TreeMap中會導(dǎo)致該TreeMap極大奕翔,每日數(shù)據(jù)更新時會造成多次長時間的FGC裕寨,極為影響系統(tǒng)性能。

我在一次內(nèi)部周會上我提出了這個問題派继,希望能得到一些解決思路宾袜。孫奇根據(jù)需求和數(shù)據(jù)特點提出了一個良好的解決思路:拆分IP段。

解決思路:

參考IP子網(wǎng)劃分和路由的規(guī)則驾窟,將IP的前兩字節(jié)拆分出來作為索引庆猫,指向一顆較小的 TreeMap。在更新過程中逐步替換就可以避免FGC绅络。同時極大的降低了TreeMap的深度月培,提升了查詢效率。

數(shù)據(jù)結(jié)構(gòu):

二維數(shù)組+TreeMap昨稼。 其中节视,二維數(shù)組為 256x256 ,分別對應(yīng)IPv4的前兩字節(jié)假栓,這樣會有65536個TreeMap寻行。每個TreeMap的key為無符號長整型的IP段起始,value存儲IP地理位置信息匾荆。

private TreeMap<Long, IpRangeEntity>[][] treeMaps;

查詢邏輯:

根據(jù)IP的前兩段找到對應(yīng)的TreeMap拌蜘,而后使用 TreeMap 自身的 findfloor 方法來找到對應(yīng)的IP段和IP地理位置信息。

 public IpRangeEntity getValue(int ipFirst, int ipSecond, Long k) {
        TreeMap<Long, IpRangeEntity> tmp = treeMaps[ipFirst][ipSecond];
        Map.Entry<Long, IpRangeEntity> result = tmp == null ? null : tmp.floorEntry(k);
        return result == null ? null : result.getValue();
    }

數(shù)據(jù)插入邏輯:

由于IPv4數(shù)據(jù)是按照IP順序存儲的牙丽,我決定逐行讀取數(shù)據(jù)简卧、解析、并存入TreeMap烤芦。當(dāng)IP段起始的前兩字節(jié)產(chǎn)生變化時举娩,說明此時的IP不屬于當(dāng)前的TreeMap,需要將當(dāng)前TreeMap存入二維數(shù)組,構(gòu)造一個新的TreeMap铜涉,并將此段數(shù)據(jù)存入新TreeMap智玻。
插入分為兩種情況:IP段不跨TreeMap和IP段跨TreeMap。
* 如果一個IP段全部從屬于同一個TreeMap芙代,只需要獲取此段IP的段起始作為key吊奢,將數(shù)據(jù)存入TreeMap即可。

    if (first == getIpPart(ipEnd, 0) && second == getIpPart(ipEnd, 1)) {
            currentMap.put(key, ipRangeEntity);
        }

如果一個IP段同時從屬于兩個或多個TreeMap纹烹,需要將此IP段拆分成多個子段页滚,分別插入到不同的TreeMap中。拆分時只修改IP段的段起始和段結(jié)尾铺呵,子段包含的地理位置信息與拆分前相同裹驰。

else {
            // 65536 = 256^2 16777216 = 256^3
            long lastEnd = Utils.ip2Long(ipRangeEntity.getIpEnd());
            long nextStart = Utils.ip2Long(ipRangeEntity.getIpStart());
            long nextEnd = Math.min(lastEnd, nextStart + 65535);

            IpRangeEntity restIpRange = new IpRangeEntity(ipRangeEntity);
            restIpRange.setIpStart(Utils.long2Ip(nextStart));
            restIpRange.setIpEnd(Utils.long2Ip(nextEnd));
            currentMap.put(nextStart, restIpRange);

            while (nextEnd <= lastEnd) {
                switchTreeMap();

                nextStart = nextEnd + 1;
                nextEnd = Math.min(lastEnd, nextStart + 65535);
                if (nextStart >= nextEnd) break;

                first = (int) (nextStart / (16777216)); // 取ip第一個字段
                second = (int) (nextStart / (65536)) % 256; // 取ip第二個字段
                currentMap = new TreeMap<>();
                restIpRange = new IpRangeEntity(ipRangeEntity);
                restIpRange.setIpStart(Utils.long2Ip(nextStart));
                restIpRange.setIpEnd(Utils.long2Ip(nextEnd));
                currentMap.put(nextStart, restIpRange);
            }
        }

- 優(yōu)化更新邏輯

系統(tǒng)發(fā)布后發(fā)現(xiàn),線上虛擬機(jī)在更新時CPU使用過高片挂,可能導(dǎo)致查詢請求的rt增高邦马,甚至數(shù)秒內(nèi)服務(wù)不可用。為解決這一問題宴卖,需要引入分布式鎖,將集群中的各個機(jī)器的更新時間錯開邻悬,并且在更新時調(diào)整該機(jī)的負(fù)載均衡權(quán)重症昏,使得該機(jī)流量降低。

4. 總結(jié)

- 數(shù)據(jù)結(jié)構(gòu)的優(yōu)缺點

  • 優(yōu)點:
    • 10用戶并發(fā)的情況下父丰,批量查詢200條IP的地理位置信息的耗時可以控制在7ms以內(nèi)肝谭,相比原有數(shù)據(jù)結(jié)構(gòu)的162ms,查詢速度提升20多倍蛾扇。
image.png
  • 缺點:
    • 原有系統(tǒng)平時常駐內(nèi)存僅需要不到2G內(nèi)存攘烛,現(xiàn)有系統(tǒng)平時常駐內(nèi)存需要約4G內(nèi)存,更新時需要約6G內(nèi)存镀首。

- 后續(xù)改進(jìn)方案

針對內(nèi)存占用量過大的問題坟漱,可以從以下兩個方向去解決:

  1. 將每條IP地址段數(shù)據(jù)的地理位置信息進(jìn)行壓縮,查詢時再解壓更哄。為節(jié)約查詢時的解壓的時間芋齿,可以考慮 LZ4 和 LZ4hc。
  2. 將重復(fù)的地理位置信息進(jìn)行統(tǒng)計成翩,構(gòu)成一個獨立的地理位置信息表觅捆。每條IP地址段中存儲指向該表的一個索引。

- 經(jīng)驗總結(jié)

  1. 遇到問題時多問問同事的意見麻敌,不同的人往往可以從不同角度去看待問題栅炒,有時候就能找到更好的解決方案。
  2. 用戶提出的需求不一定合理,甚至不一定是必須達(dá)到的赢赊,可以通過反復(fù)的溝通對需求進(jìn)行調(diào)整乙漓。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市域携,隨后出現(xiàn)的幾起案子簇秒,更是在濱河造成了極大的恐慌,老刑警劉巖秀鞭,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趋观,死亡現(xiàn)場離奇詭異,居然都是意外死亡锋边,警方通過查閱死者的電腦和手機(jī)皱坛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豆巨,“玉大人剩辟,你說我怎么就攤上這事⊥樱” “怎么了贩猎?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萍膛。 經(jīng)常有香客問我吭服,道長,這世上最難降的妖魔是什么蝗罗? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任艇棕,我火速辦了婚禮,結(jié)果婚禮上串塑,老公的妹妹穿的比我還像新娘沼琉。我一直安慰自己,他們只是感情好桩匪,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布打瘪。 她就那樣靜靜地躺著,像睡著了一般吸祟。 火紅的嫁衣襯著肌膚如雪瑟慈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天屋匕,我揣著相機(jī)與錄音葛碧,去河邊找鬼。 笑死过吻,一個胖子當(dāng)著我的面吹牛进泼,可吹牛的內(nèi)容都是我干的蔗衡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乳绕,長吁一口氣:“原來是場噩夢啊……” “哼绞惦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洋措,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤济蝉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后菠发,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體王滤,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年滓鸠,在試婚紗的時候發(fā)現(xiàn)自己被綠了雁乡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡糜俗,死狀恐怖踱稍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悠抹,我是刑警寧澤珠月,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站楔敌,受9級特大地震影響桥温,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜梁丘,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旺韭。 院中可真熱鬧氛谜,春花似錦、人聲如沸区端。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽织盼。三九已至杨何,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沥邻,已是汗流浹背危虱。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留唐全,地道東北人埃跷。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓蕊玷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弥雹。 傳聞我的和親對象是個殘疾皇子垃帅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,958評論 6 13
  • # 圖解TCP/IP 標(biāo)簽(空格分隔): 2018招聘 --- ##第1章 網(wǎng)絡(luò)基礎(chǔ)知識 ### ### 1.1 ...
    Kai_a3da閱讀 1,441評論 0 2
  • 網(wǎng)絡(luò)層簡介 1. 概念 為解決經(jīng)由多條鏈路的交付問題,從而設(shè)計了網(wǎng)絡(luò)層剪勿。其主要負(fù)責(zé)主機(jī)到主機(jī)的交付贸诚,并且在分組經(jīng)過...
    顧慎為閱讀 3,103評論 0 0
  • 個人認(rèn)為,Goodboy1881先生的TCP /IP 協(xié)議詳解學(xué)習(xí)博客系列博客是一部非常精彩的學(xué)習(xí)筆記厕吉,這雖然只是...
    貳零壹柒_fc10閱讀 5,054評論 0 8
  • 我發(fā)現(xiàn)什么都 不重要 你會不會寫詩 不重要 重要的是 你有詩意的生活 就好 你漂亮不漂亮 不重要 有人欣賞 就好 ...
    小墉正閱讀 725評論 67 82