真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

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

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

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

賽題概覽

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

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

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

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

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

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

2)性能評測

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

  • 隨機讀?。?4 個線程并發(fā)隨機讀取,每個線程各使用 Read 讀取 100 萬次隨機數(shù)據(jù)

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

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

 賽題剖析

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

3.1 key 8B, value 4kb

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

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

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

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

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

3.3 分階段測評

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

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

3.4 清空 PageCache 的耗時

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

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

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

3.5 key 的分布

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

3.6 Range 的緩存設(shè)計

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

4 架構(gòu)詳解

首先需要明確的是,隨機寫指的是 key 的寫入是隨機的,但我們可以根據(jù) key hash,將隨機寫轉(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ù)庫性能實例分析

分區(qū)視角

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

內(nèi)存視角

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

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

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

 隨機寫流程

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

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

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

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

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

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

 恢復(fù)流程

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

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

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

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

 隨機讀流程

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

 順序讀流程

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

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

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

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

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

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

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

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

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

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

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

Java 實現(xiàn) Direct IO

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

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

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

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

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

這一點在《文件IO操作的一些最佳實踐》中有所提及,堆外內(nèi)存的兩大好處是減少了一份內(nèi)存拷貝,并且對 gc 友好,在 Direct IO 的實現(xiàn)中,應(yīng)該配備一套堆外內(nèi)存的接口,才能發(fā)揮出最大的功效。尤其在 Range 階段,一個緩存區(qū)的大小便對應(yīng)一個 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 選手可能沒有意識去修改它,會在無形中被 gc 拖累。經(jīng)過和@阿杜的討論,最終得出的結(jié)論:

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

  2. old 區(qū)過小,會頻繁觸發(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)存再回收,這也是一筆不小的開銷。在此次比賽的場景下,沒必要大費周章地動用對象池,直接一個 ThreadLocal 即可搞定,事實上我對 key/value 的寫入和讀取都進(jìn)行了 ThreadLocal 的緩存,做到了永遠(yuǎn)不再循環(huán)中分配對象。

 減少線程切換

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

  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 左右。

 綁核

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

使用方式:

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

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

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


名稱欄目:PolarDB數(shù)據(jù)庫性能實例分析
瀏覽地址:http://weahome.cn/article/jdgojd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部