最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
PolarDB數(shù)據(jù)庫性能實(shí)例分析

這篇文章主要講解了“PolarDB數(shù)據(jù)庫性能實(shí)例分析”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“PolarDB數(shù)據(jù)庫性能實(shí)例分析”吧!

創(chuàng)新互聯(lián)自2013年起,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站制作、網(wǎng)站建設(shè)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢想脫穎而出為使命,1280元壽寧做網(wǎng)站,已為上家服務(wù),為壽寧各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18982081108

賽題概覽

比賽總體分成了初賽和復(fù)賽兩個(gè)階段,整體要求實(shí)現(xiàn)一個(gè)簡化、高效的 kv 存儲(chǔ)引擎

初賽要求支持 Write、Read 接口。

public abstract void write(byte[] key, byte[] value);public abstract byte[] read(byte[] key);

復(fù)賽在初賽題目基礎(chǔ)上,還需要額外實(shí)現(xiàn)一個(gè) Range 接口。

public abstract void range(byte[] lower, byte[] upper, AbstractVisitor visitor);

程序評測邏輯 分為2個(gè)階段: 1)Recover 正確性評測: 此階段評測程序會(huì)并發(fā)寫入特定數(shù)據(jù)(key 8B、value 4KB)同時(shí)進(jìn)行任意次 kill -9 來模擬進(jìn)程意外退出(參賽引擎需要保證進(jìn)程意外退出時(shí)數(shù)據(jù)持久化不丟失),接著重新打開 DB,調(diào)用 Read、Range 接口來進(jìn)行正確性校驗(yàn)

2)性能評測

  • 隨機(jī)寫入:64 個(gè)線程并發(fā)隨機(jī)寫入,每個(gè)線程使用 Write 各寫 100 萬次隨機(jī)數(shù)據(jù)(key 8B、value 4KB)

  • 隨機(jī)讀取:64 個(gè)線程并發(fā)隨機(jī)讀取,每個(gè)線程各使用 Read 讀取 100 萬次隨機(jī)數(shù)據(jù)

  • 順序讀?。?4 個(gè)線程并發(fā)順序讀取,每個(gè)線程各使用 Range 有序(增序)遍歷全量數(shù)據(jù) 2 次 注: 2.2 階段會(huì)對所有讀取的 kv 校驗(yàn)是否匹配,如不通過則終止,評測失??; 2.3 階段除了對迭代出來每條的 kv校 驗(yàn)是否匹配外,還會(huì)額外校驗(yàn)是否嚴(yán)格字典序遞增,如不通過則終止,評測失敗。

語言限定:C++ & JAVA,一起排名

 賽題剖析

關(guān)于文件 IO 操作的一些基本常識(shí),我已經(jīng)在專題文章中進(jìn)行了介紹,如果你沒有瀏覽那篇文章,建議先行瀏覽一下:文件IO操作的一些最佳實(shí)踐。再回歸賽題,先對賽題中的幾個(gè)關(guān)鍵詞來進(jìn)行解讀。

3.1 key 8B, value 4kb

key 為固定的 8 字節(jié),因此可使用 long 來表示。

value 為 4kb,這節(jié)省了我們很大的工作量,因?yàn)?4kb 的整數(shù)倍落盤是非常磁盤 IO 友好的。

value 為 4kb 的另一個(gè)好處是我們再內(nèi)存做索引時(shí),可以使用 int 而不是 long,來記錄數(shù)據(jù)的邏輯偏移量:LogicOffset = PhysicalOffset / 4096,可以將 offset 的內(nèi)存占用量減少一半。

3.2 kill -9 數(shù)據(jù)不丟失

首先賽題明確表示會(huì)進(jìn)行 kill -9 并驗(yàn)證數(shù)據(jù)的一致性,這加大了我們在內(nèi)存中做 write buffer 的難度。但它并沒有要求斷電不丟失,這間接地闡釋了一點(diǎn):我們可以使用 pageCache 來做寫入緩存,在具體代碼中我使用了 PageCache 來充當(dāng)數(shù)據(jù)和索引的寫入緩沖(兩者策略不同)。同時(shí)這點(diǎn)也限制了參賽選手,不能使用 AIO 這樣的異步落盤方式。

3.3 分階段測評

賽題分為了隨機(jī)寫,隨機(jī)讀,順序讀三個(gè)階段,每個(gè)階段都會(huì)重新 open,且不會(huì)發(fā)生隨機(jī)寫到一半校驗(yàn)隨機(jī)讀這樣的行為,所以我們在隨機(jī)寫階段不需要在內(nèi)存維護(hù)索引,而是直接落盤。隨機(jī)讀和順序讀階段,磁盤均存在數(shù)據(jù),open 階段需要恢復(fù)索引,可以使用多線程并發(fā)恢復(fù)。

同時(shí),賽題還有存在一些隱性的測評細(xì)節(jié)沒有披露給大家,但通過測試,我們可以得知這些信息。

3.4 清空 PageCache 的耗時(shí)

雖然我們可以使用 PageCache,但評測程序在每個(gè)階段之后都使用腳本清空了 PageCache,并且將這部分時(shí)間也算進(jìn)了最終的成績之中,所以有人感到奇怪:三個(gè)階段的耗時(shí)相加比輸出出來的成績要差,其實(shí)那幾秒便是清空 PageCache 的耗時(shí)。

#清理 pagecache (頁緩存)
sysctl -w vm.drop_caches=1
#清理 dentries(目錄緩存)和 inodes
sysctl -w vm.drop_caches=2
#清理pagecache、dentries和inodes
sysctl -w vm.drop_caches=3

這一點(diǎn)啟發(fā)我們,不能毫無節(jié)制的使用 PageCache,也正是因?yàn)檫@一點(diǎn),一定程度上使得 Direct IO 這一操作成了本次競賽的銀彈。

3.5 key 的分布

這一個(gè)隱性條件可謂是本次比賽的關(guān)鍵,因?yàn)樗婕暗?Range 部分的架構(gòu)設(shè)計(jì)。本次比賽的 key 共計(jì) 6400w,但是他們的分布都是均勻的,在《文件IO操作的一些最佳實(shí)踐》 一文中我們已經(jīng)提到了數(shù)據(jù)分區(qū)的好處,可以大大減少順序讀寫的鎖沖突,而 key 的分布均勻這一特性,啟發(fā)我們在做數(shù)據(jù)分區(qū)時(shí),可以按照 key 的搞 n 位來做 hash,從而確保 key 兩個(gè)分區(qū)之間整體有序(分區(qū)內(nèi)部無序)。實(shí)際我嘗試了將數(shù)據(jù)分成 1024、2048 個(gè)分區(qū),效果最佳。

3.6 Range 的緩存設(shè)計(jì)

賽題要求 64 個(gè)線程 Range 兩次全量的數(shù)據(jù),限時(shí) 1h,這也啟發(fā)了我們,如果不對數(shù)據(jù)進(jìn)行緩存,想要在 1h 內(nèi)完成比賽是不可能的,所以,我們的架構(gòu)設(shè)計(jì)應(yīng)該盡量以 Range 為核心,兼顧隨機(jī)寫和隨機(jī)讀。Range 部分也是最容易拉開差距的一個(gè)環(huán)節(jié)。

4 架構(gòu)詳解

首先需要明確的是,隨機(jī)寫指的是 key 的寫入是隨機(jī)的,但我們可以根據(jù) key hash,將隨機(jī)寫轉(zhuǎn)換為對應(yīng)分區(qū)文件的順序?qū)憽?/p>

/**
 * using high ten bit of the given key to determine which file it hits.
 */
public class HighTenPartitioner implements Partitionable {
    @Override
    public int getPartition(byte[] key) {
        return ((key[0] & 0xff) << 2) | ((key[1] & 0xff) >> 6);
    }
}

明確了高位分區(qū)的前提再來看整體的架構(gòu)就變得明朗了

全局視角

PolarDB數(shù)據(jù)庫性能實(shí)例分析

分區(qū)視角

PolarDB數(shù)據(jù)庫性能實(shí)例分析

內(nèi)存視角

內(nèi)存中僅僅維護(hù)有序的 key[1024][625000] 數(shù)組和 offset[1024][625000] 數(shù)組。

上述兩張圖對整體的架構(gòu)進(jìn)行了一個(gè)很好的詮釋,利用數(shù)據(jù)分布均勻的特性,可以將全局?jǐn)?shù)據(jù) hash 成 1024 個(gè)分區(qū),在每個(gè)分區(qū)中存放兩類文件:索引文件和數(shù)據(jù)文件。在隨機(jī)寫入階段,根據(jù) key 獲得該數(shù)據(jù)對應(yīng)分區(qū)位置,并按照時(shí)序,順序追加到文件末尾,將全局隨機(jī)寫轉(zhuǎn)換為局部順序?qū)?。利用索引和?shù)據(jù)一一對應(yīng)的特性,我們也不需要將 data 的邏輯偏移量落盤,在 recover 階段可以按照恢復(fù) key 的次序,反推出 value 的邏輯偏移量。

在 range 階段,由于我們事先按照 key 的高 10 為做了分區(qū),所以我們可以認(rèn)定一個(gè)事實(shí),patition(N) 中的任何一個(gè)數(shù)據(jù)一定大于 partition(N-1) 中的任何一個(gè)數(shù)據(jù),于是我們可以采用大塊讀,將一個(gè) partition 整體讀進(jìn)內(nèi)存,供 64 個(gè) visit 線程消費(fèi)。到這兒便奠定了整體的基調(diào):讀盤線程負(fù)責(zé)按分區(qū)讀盤進(jìn)入內(nèi)存,64 個(gè) visit 線程負(fù)責(zé)消費(fèi)內(nèi)存,按照 key 的次序隨機(jī)訪問內(nèi)存,進(jìn)行 Visitor 的回調(diào)。

 隨機(jī)寫流程

介紹完了整體架構(gòu),我們分階段來看一下各個(gè)階段的一些細(xì)節(jié)優(yōu)化點(diǎn),有一些優(yōu)化在各個(gè)環(huán)節(jié)都會(huì)出現(xiàn),未避免重復(fù),第二次出現(xiàn)的同一優(yōu)化點(diǎn)我就不贅述了,僅一句帶過。

使用 pageCache 實(shí)現(xiàn)寫入緩沖區(qū)

主要看數(shù)據(jù)落盤,后討論索引落盤。磁盤 IO 類型的比賽,第一步便是測量磁盤的 IOPS 以及多少個(gè)線程一次讀寫多大的緩存能夠打滿 IO,在固定 64 線程寫入的前提下,16kb,64kb 均可以達(dá)到最理想 IOPS,所以理所當(dāng)然的想到,可以為每一個(gè)分區(qū)分配一個(gè)寫入緩存,湊齊 4 個(gè) value 落盤。但是此次比賽,要做到 kill -9 不丟失數(shù)據(jù),不能簡單地在內(nèi)存中分配一個(gè) ByteBuffer.allocate(4096*4);, 而是可以考慮使用 mmap 內(nèi)存映射出一片寫入緩沖,湊齊 4 個(gè)刷盤,這樣在 kill -9 之后,PageCache 不會(huì)丟失。實(shí)測 16kb 落盤比 4kb 落盤要快 6s 左右。

索引文件的落盤則沒有太大的爭議,由于 key 的數(shù)據(jù)量為固定的 8B,所以 mmap 可以發(fā)揮出它寫小數(shù)據(jù)的優(yōu)勢,將 pageCache 利用起來,實(shí)測 mmap 相比 filechannel 寫索引要快 3s 左右,相信如果把 polardb 這塊盤換做其他普通的 ssd,這個(gè)數(shù)值還要增加。

寫入時(shí)不維護(hù)內(nèi)存索引,不寫入數(shù)據(jù)偏移

一開始審題不清,在隨機(jī)寫之后誤以為會(huì)立刻隨機(jī)讀,實(shí)際上每個(gè)階段都是獨(dú)立的,所以不需要在寫入時(shí)維護(hù)內(nèi)存索引;其次,之前的架構(gòu)圖中也已經(jīng)提及,不需要寫入連帶 key+offset 一起寫入文件,recover 階段可以按照恢復(fù)索引的順序,反推出 data 的邏輯偏移,因?yàn)槲覀兊?key 和 data 在同一個(gè)分區(qū)內(nèi)的位置是一一對應(yīng)的。

 恢復(fù)流程

recover 階段的邏輯實(shí)際上包含在程序的 open 接口之中,我們需要再數(shù)據(jù)庫引擎啟動(dòng)時(shí),將索引從數(shù)據(jù)文件恢復(fù)到內(nèi)存之中,在這之中也存在一些細(xì)節(jié)優(yōu)化點(diǎn)。

由于 1024 個(gè)分區(qū)的存在,我們可以使用 64 個(gè)線程 (經(jīng)驗(yàn)值) 并發(fā)地恢復(fù)索引,使用快速排序?qū)?key[1024][62500]  數(shù)組和 offset[1024][62500]  進(jìn)行 sort,之后再 compact,對 key 進(jìn)行去重。需要注意的一點(diǎn)是,不要使用結(jié)構(gòu)體,將 key 和 offset 封裝在一起,這會(huì)使得排序和之后的二分效率非常低,這之中涉及到 CPU 緩存行的知識(shí)點(diǎn),不了解的讀者可以翻閱我之前的博客: 《CPU Cache 與緩存行》

// wrongpublic class KeyOffset {    long key;    int offset;}

整個(gè) recover 階段耗時(shí)為 1s,跟 cpp 選手交流后發(fā)現(xiàn)恢復(fù)流程比之慢了 600ms,這中間讓我覺得比較詭異,加載索引和排序不應(yīng)該這么慢才對,最終也沒有優(yōu)化成功。

 隨機(jī)讀流程

隨機(jī)讀流程沒有太大的優(yōu)化點(diǎn),優(yōu)化空間實(shí)在有限,實(shí)現(xiàn)思路便是先根據(jù) key 定位到分區(qū),之后在有序的 key 數(shù)據(jù)中二分查找到 key/offset,拿到 data 的邏輯偏移和分區(qū)編號(hào),便可以愉快的隨機(jī)讀了,隨機(jī)讀階段沒有太大的優(yōu)化點(diǎn),但仍然比 cpp 選手慢了 2-3s,可能是語言無法越過的差距。

 順序讀流程

Range 環(huán)節(jié)是整個(gè)比賽的大頭,也是拉開差距的分水嶺。前面我們已經(jīng)大概提到了 Range 的整體思路是一個(gè)生產(chǎn)者消費(fèi)者模型,n 個(gè)生成者負(fù)責(zé)從磁盤讀數(shù)據(jù)進(jìn)入內(nèi)存(n 作為變量,通過 benchmark 來確定多少合適,最終實(shí)測 n 為 4 時(shí)效果最佳),64 個(gè)消費(fèi)者負(fù)責(zé)調(diào)用 visit 回調(diào),來驗(yàn)證數(shù)據(jù),visit 過程就是隨機(jī)讀內(nèi)存的過程。在 Range 階段,剩余的內(nèi)存還有大概 1G 左右,所以我分配了 4 個(gè)堆外緩沖,一個(gè) 256M,從而可以緩存 4 個(gè)分區(qū)的數(shù)據(jù),并且,我為每一個(gè)分區(qū)分配了一個(gè)讀盤線程,負(fù)責(zé) load 數(shù)據(jù)進(jìn)入緩存,供 64 個(gè)消費(fèi)者消費(fèi)。

具體的順序讀架構(gòu)可以參見下圖:

PolarDB數(shù)據(jù)庫性能實(shí)例分析

大體來看,便是 4 個(gè) fetch 線程負(fù)責(zé)讀盤,fetch thread n 負(fù)責(zé) partitionNo%4==n 編號(hào)的分區(qū),完成后通知 visit 消費(fèi)。這中間充斥著比較多的互斥等待邏輯,并未在圖中體現(xiàn)出來,大體如下:

  1. fetch thread 1~4 加載磁盤數(shù)據(jù)進(jìn)入緩存是并發(fā)的

  2. visit group 1~64 訪問同一個(gè) buffer 是并發(fā)的

  3. visit group 1~64 訪問不同 partition 對應(yīng)的 buffer 是按照次序來進(jìn)行的(打到全局有序)

  4. 加載 partitonN 會(huì)阻塞 visit bufferN,visit bufferN 會(huì)阻塞加載 partitionN+4(相當(dāng)于復(fù)用4塊緩存)

大塊的加載讀進(jìn)緩存,最大程度復(fù)用,是 ReadSeq 部分的關(guān)鍵。順序讀兩輪的成績在 196~198s 左右,相比 C++ 又慢了 4s 左右。

魔鬼在細(xì)節(jié)中

這兒是個(gè)分水嶺,介紹完了整體架構(gòu)和四個(gè)階段的細(xì)節(jié)實(shí)現(xiàn),下面就是介紹下具體的優(yōu)化點(diǎn)了。

Java 實(shí)現(xiàn) Direct IO

由于這次比賽將 drop cache 的時(shí)間算進(jìn)了測評程序之中,所以在不必要的地方應(yīng)當(dāng)盡量避免 pageCache,也就是說除了寫索引之外,其他階段不應(yīng)該出現(xiàn) pageCache。這對于 Java 選手來說可能是不小的障礙,因?yàn)?Java 原生沒有提供 Direct IO,需要自己封裝一套 JNA 接口,封裝這套接口借鑒了開源框架 jaydio 的思路,感謝@塵央的協(xié)助,大家可以在文末的代碼中看到實(shí)現(xiàn)細(xì)節(jié)。這一點(diǎn)可以說是攔住了一大票 Java 選手。

Direct IO 需要注意的兩個(gè)細(xì)節(jié):

  1. 分配的內(nèi)存需要對齊,對應(yīng) jna 方法:posix_memalign

  2. 寫入的數(shù)據(jù)需要對齊通常是 pageSize 的整數(shù)倍,實(shí)際使用了 pread 的 O_DIRECT

 直接內(nèi)存優(yōu)于堆內(nèi)內(nèi)存

這一點(diǎn)在《文件IO操作的一些最佳實(shí)踐》中有所提及,堆外內(nèi)存的兩大好處是減少了一份內(nèi)存拷貝,并且對 gc 友好,在 Direct IO 的實(shí)現(xiàn)中,應(yīng)該配備一套堆外內(nèi)存的接口,才能發(fā)揮出最大的功效。尤其在 Range 階段,一個(gè)緩存區(qū)的大小便對應(yīng)一個(gè) partition 數(shù)據(jù)分區(qū)的大?。?56M,大塊的內(nèi)存,更加適合用 DirectByteBuffer 裝載。

 JVM 調(diào)優(yōu)

-server -Xms2560m -Xmx2560m -XX:MaxDirectMemorySize=1024m -XX:NewRatio=4 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:-UseBiasedLocking

眾所周知 newRatio 控制的是 young 區(qū)和 old 區(qū)大小的比例,官方推薦參數(shù)為 -XX:NewRatio=1,很多不注意的 Java 選手可能沒有意識(shí)去修改它,會(huì)在無形中被 gc 拖累。經(jīng)過和@阿杜的討論,最終得出的結(jié)論:

  1. young 區(qū)過大,對象在年輕代待得太久,多次拷貝

  2. old 區(qū)過小,會(huì)頻繁觸發(fā) old 區(qū)的 cms gc

在比賽中這顯得尤為重要, -XX:NewRatio=4 放大老年代可以有效的減少 cms gc 的次數(shù),將 126 次 cms gc,下降到最終的 5 次。

 池化對象

無論是 apache 的 ObjectPool 還是 Netty 中的 Recycler,還是 RingBuffer 中預(yù)先分配的對象,都在傳達(dá)一種思想,對于那些反復(fù)需要 new 出來的東西,都可以池化,分配內(nèi)存再回收,這也是一筆不小的開銷。在此次比賽的場景下,沒必要大費(fèi)周章地動(dòng)用對象池,直接一個(gè) ThreadLocal 即可搞定,事實(shí)上我對 key/value 的寫入和讀取都進(jìn)行了 ThreadLocal 的緩存,做到了永遠(yuǎn)不再循環(huán)中分配對象。

 減少線程切換

無論是網(wǎng)絡(luò) IO 還是磁盤 IO,io worker 線程的時(shí)間片都顯得尤為的可貴,在我的架構(gòu)中,range 階段主要分為了兩類線程:64 個(gè) visit 線程并發(fā)隨機(jī)讀內(nèi)存,4 個(gè) io 線程并發(fā)讀磁盤。木桶效應(yīng),我們很容易定位到瓶頸在于 4 個(gè) io 線程,在 wait/notify 的模型中,為了盡可能的減少 io 線程的時(shí)間片流失,可以考慮使用 while(true) 進(jìn)行輪詢,而 visit 線程則可以 sleep(1us) 避免 cpu 空轉(zhuǎn)帶來的整體性能下降,由于評測機(jī)擁有 64 core,所以這樣的分配算是較為合理的,為此我實(shí)現(xiàn)了一個(gè)簡單粗暴的信號(hào)量。

  1. public class LoopQuerySemaphore {

  2.    private volatile boolean permit;

  3.    public LoopQuerySemaphore(boolean permit) {

  4.        this.permit = permit;

  5.    }

  6.    // for 64 visit thread

  7.    public void acquire() throws InterruptedException {

  8.        while (!permit) {

  9.            Thread.sleep(0,1);

  10.        }

  11.        permit = false;

  12.    }

  13.    // for 4 fetch thread

  14.    public void acquireNoSleep() throws InterruptedException {

  15.        while (!permit) {

  16.        }

  17.        permit = false;

  18.    }

  19.    public void release() {

  20.        permit = true;

  21.    }

  22. }

正確的在 IO 中 acquireNoSleep,在 Visit 中 acquire,可以讓成績相比使用普通的阻塞 Semaphore 提升 6s 左右。

 綁核

線上機(jī)器的抖動(dòng)在所難免,避免 IO 線程的切換也并不僅僅能夠用依靠 while(true) 的輪詢,一個(gè) CPU 級別的優(yōu)化便是騰出 4 個(gè)核心專門給 IO 線程使用,完全地避免 IO 線程的時(shí)間片爭用。在 Java 中這也不難實(shí)現(xiàn),依賴萬能的 github,我們可以輕松地實(shí)現(xiàn) Affinity。

使用方式:

try (final AffinityLock al2 = AffinityLock.acquireLock()) {    // do fetch ...}

這個(gè)方式可以讓你的代碼快 1~2 s,并且保持測評的穩(wěn)定性。

感謝各位的閱讀,以上就是“PolarDB數(shù)據(jù)庫性能實(shí)例分析”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對PolarDB數(shù)據(jù)庫性能實(shí)例分析這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!


分享題目:PolarDB數(shù)據(jù)庫性能實(shí)例分析
文章鏈接:http://fisionsoft.com.cn/article/jdgojd.html