為什么最近好多網(wǎng)站維護(hù)沈陽疫情最新消息
引入
基礎(chǔ)理論的進(jìn)步,是推動技術(shù)實(shí)現(xiàn)重大突破,促使相關(guān)領(lǐng)域的技術(shù)達(dá)成跨越式發(fā)展的核心。
在發(fā)展日新月異的大數(shù)據(jù)領(lǐng)域,基礎(chǔ)理論的核心無疑是算法。不管是技術(shù)設(shè)計,還是工程實(shí)踐,都必須仰仗相關(guān)算法的支持,才能夠真的落地應(yīng)用。
下面我們就看看大數(shù)據(jù)相關(guān)領(lǐng)域有哪些核心的算法。
存儲類算法
大數(shù)據(jù)存儲相關(guān)的核心算法,主要是為了高效存儲和管理海量數(shù)據(jù),以及提升數(shù)據(jù)讀寫性能和存儲利用率等。
以下我們來看看大數(shù)據(jù)領(lǐng)域最經(jīng)典的存儲類算法:
B樹及其變種(B+樹、B* 樹)
原理:
- B樹:是一種自平衡的多路搜索樹,每個節(jié)點(diǎn)可以有多個子節(jié)點(diǎn)。它的所有葉子節(jié)點(diǎn)都在同一層,并且包含了所有的數(shù)據(jù)。
- B+樹:是 B樹的一種變種,它的非葉子節(jié)點(diǎn)只存儲索引信息,所有的數(shù)據(jù)都存儲在葉子節(jié)點(diǎn)中,葉子節(jié)點(diǎn)之間通過指針相連,形成一個有序鏈表,便于范圍查詢。(最通用)
- B*樹:在 B+樹的基礎(chǔ)上,對節(jié)點(diǎn)的分裂規(guī)則進(jìn)行了優(yōu)化,提高了空間利用率。
應(yīng)用場景:廣泛應(yīng)用于關(guān)系型數(shù)據(jù)庫的索引結(jié)構(gòu),能夠高效地支持點(diǎn)查詢和范圍查詢。
優(yōu)點(diǎn):查詢、插入和刪除操作的時間復(fù)雜度都是 O (log n),性能穩(wěn)定。
缺點(diǎn):對于大規(guī)模數(shù)據(jù)的寫入操作,可能會導(dǎo)致頻繁的節(jié)點(diǎn)分裂和合并,影響性能。
B+樹是一種平衡的、多叉的樹形結(jié)構(gòu),能夠支持O(logn)的插入和查詢時間復(fù)雜度。B+樹的整個結(jié)構(gòu)是有序存儲的,這使得B+樹能夠高效地支持范圍查詢;在空間放大維度,B+樹能夠達(dá)到70%的空間利用率。綜上所述,B+樹有較好的綜合性能,在現(xiàn)代的諸多存儲系統(tǒng)中,B+樹索引很常見,例如關(guān)系數(shù)據(jù)庫MySQL的默認(rèn)存儲引擎InnoDB。
在大數(shù)據(jù)領(lǐng)域是避免不了使用多線程與高并發(fā)場景的,所以需要對B+樹索引進(jìn)行并發(fā)控制。由于B+樹的樹形結(jié)構(gòu)會不斷動態(tài)調(diào)整,要實(shí)現(xiàn)一個正確的多線程B+樹,存在著較大的設(shè)計挑戰(zhàn)。
目前來說,實(shí)現(xiàn)B+樹的并發(fā),可以采用以下3種機(jī)制:
- 鎖耦合
鎖耦合機(jī)制是B+樹中應(yīng)用最為廣泛的一種加鎖方式。鎖耦合機(jī)制就是一種節(jié)點(diǎn)級別的加鎖方式,但是路徑上的節(jié)點(diǎn)的鎖會更早地釋放,同時能保證線程安全。在鎖耦合機(jī)制中,每個線程同時最多擁有兩個節(jié)點(diǎn)的鎖,分別為父節(jié)點(diǎn)和孩子節(jié)點(diǎn)。父節(jié)點(diǎn)的節(jié)點(diǎn)可以在孩子節(jié)點(diǎn)的鎖獲取之后釋放,這樣可以充分減少每個節(jié)點(diǎn)加鎖和釋放的臨界區(qū)大小,從而最大化多線程性能。- 樂觀鎖機(jī)制
采用鎖耦合機(jī)制,每個讀/寫線程仍然是互相阻塞的,而樂觀鎖機(jī)制則是為了減少寫線程對讀線程的阻塞,并進(jìn)一步減少加鎖的數(shù)量。內(nèi)部節(jié)點(diǎn)除節(jié)點(diǎn)內(nèi)部的鎖字段之外,還額外維護(hù)一個寫版本號。每當(dāng)寫線程對節(jié)點(diǎn)完成修改之后,先對寫版本號完成自增操作,隨后釋放寫鎖。每當(dāng)讀線程訪問一個節(jié)點(diǎn)的時候,首先記錄節(jié)點(diǎn)版本號,在完成對節(jié)點(diǎn)的訪問之后檢測節(jié)點(diǎn)版本號是否發(fā)生變化,如果節(jié)點(diǎn)寫版本號發(fā)生變化,讀線程重做對該節(jié)點(diǎn)的訪問,否則意味著節(jié)點(diǎn)訪問過程中該節(jié)點(diǎn)并未發(fā)生寫操作,因此讀節(jié)點(diǎn)操作成功執(zhí)行。- 無鎖機(jī)制。
通過無鎖的方式來操作B+樹,提升隨機(jī)讀和范圍查詢的性能。它的核心的思想是把B+樹的頁(page)通過page id(PID)映射到map,map的[key,value]變成[PID,page value]?,把直接對page的修改,變成一個修改的操作記錄,加入到“page value”?。所以“page value”可能是一個“base page”?,即page原始的內(nèi)容,和一串對page修改形成的記錄的鏈表,而在修改記錄鏈表中加入一個修改記錄節(jié)點(diǎn)可以很容易變成一個無鎖的方式來實(shí)現(xiàn)。另外,對B+樹的split和merge操作也通過類似的原理,把具體的操作細(xì)化成好幾個原子操作,避免傳統(tǒng)的加鎖方式。
SkipList(跳表)
原理
- SkipList 是一種可以用來快速查找數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),它基于有序鏈表,并通過在鏈表節(jié)點(diǎn)上增加多層索引來提高查找效率。在 SkipList 中,每個節(jié)點(diǎn)都可能有多個指針,這些指針指向不同層次的下一個節(jié)點(diǎn),高層的指針可以跳過更多的節(jié)點(diǎn),從而加快查找速度。
- 構(gòu)建 SkipList 時,會按照一定的概率隨機(jī)決定每個節(jié)點(diǎn)在不同層次出現(xiàn)的概率。例如,一個節(jié)點(diǎn)可能以 50% 的概率出現(xiàn)在第一層,以 25% 的概率出現(xiàn)在第二層,以 12.5% 的概率出現(xiàn)在第三層,以此類推。這樣就形成了一個類似金字塔形狀的多層結(jié)構(gòu),使得查找操作可以在對數(shù)時間內(nèi)完成。
應(yīng)用場景:由于 SkipList 在插入、刪除和查找操作上都具有較高的效率,適合在內(nèi)存中存儲和操作大量的有序數(shù)據(jù),能夠快速地根據(jù)分?jǐn)?shù)對元素進(jìn)行排序和查找;在分布式哈希表(DHT)等分布式數(shù)據(jù)結(jié)構(gòu)中,SkipList 可以用于實(shí)現(xiàn)節(jié)點(diǎn)之間的快速路由和數(shù)據(jù)查找。通過在不同節(jié)點(diǎn)上構(gòu)建 SkipList 結(jié)構(gòu),可以高效地定位數(shù)據(jù)所在的節(jié)點(diǎn),提高分布式系統(tǒng)的性能和可擴(kuò)展性。
優(yōu)點(diǎn):SkipList 的插入、刪除和查找操作的平均時間復(fù)雜度為 O (log n),與平衡樹(如紅黑樹)等數(shù)據(jù)結(jié)構(gòu)相當(dāng),但 SkipList 的實(shí)現(xiàn)相對簡單,代碼復(fù)雜度較低,易于理解和維護(hù)。而且 SkipList 支持動態(tài)擴(kuò)展和收縮,能夠方便地適應(yīng)數(shù)據(jù)量的變化。
缺點(diǎn):SkipList 的空間復(fù)雜度相對較高,因?yàn)槊總€節(jié)點(diǎn)可能包含多個指針,需要額外的空間來存儲這些指針。此外,由于 SkipList 的節(jié)點(diǎn)層數(shù)是隨機(jī)生成的,在極端情況下可能會出現(xiàn)查找性能下降的情況,但這種情況發(fā)生的概率較低。
跳躍表(SkipList)是一種能高效實(shí)現(xiàn)插入、刪除、查找的內(nèi)存數(shù)據(jù)結(jié)構(gòu),這些操作的期望復(fù)雜度都是O(logN)。與紅黑樹以及其他的二分查找樹相比,跳躍表的優(yōu)勢在于實(shí)現(xiàn)簡單,而且在并發(fā)場景下加鎖粒度更小,從而可以實(shí)現(xiàn)更高的并發(fā)性。正因?yàn)檫@些優(yōu)點(diǎn),跳躍表廣泛使用于KV數(shù)據(jù)庫中,諸如Redis、LevelDB、HBase都把跳躍表作為一種維護(hù)有序數(shù)據(jù)集合的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。
LSM樹(Log-Structured Merge Tree)
原理:將數(shù)據(jù)的寫入操作先記錄在內(nèi)存中(通常是一個有序的數(shù)據(jù)結(jié)構(gòu),如跳表),當(dāng)內(nèi)存中的數(shù)據(jù)達(dá)到一定閾值后,再批量地將數(shù)據(jù)寫入磁盤,形成一個有序的數(shù)據(jù)文件(SSTable,Sorted String Table)。磁盤上的數(shù)據(jù)會按層級進(jìn)行組織,不同層級的數(shù)據(jù)會定期進(jìn)行合并操作,以減少數(shù)據(jù)冗余和提高查詢效率。
應(yīng)用場景:適用于寫多讀少的場景,如日志存儲、時間序列數(shù)據(jù)存儲等。
優(yōu)點(diǎn):寫入性能高,能夠快速處理大量的寫入請求;
缺點(diǎn):讀取時可能需要合并多個 SSTable,讀取性能相對較低,并且在合并過程中會產(chǎn)生一定的 I/O 開銷。
2000年年初,Google發(fā)表了Bigtable的論文,論文中的創(chuàng)新點(diǎn)之一就是它所使用的文件組織方式,即LSM樹。
算法的核心也是基于硬件特性來,才能真正的解決落地的問題。對于磁盤讀寫來說,順序讀寫要遠(yuǎn)比隨機(jī)讀寫快,LSM樹通過將隨機(jī)寫轉(zhuǎn)化為順序?qū)?#xff0c;消去隨機(jī)的本地更新操作來提高寫入性能,但查詢(包括點(diǎn)查詢和范圍查詢)性能會有一定程度的下降,因?yàn)橐淮尾樵儾僮骺赡芤闅v磁盤中的許多個不同的SST文件。針對查詢性能問題,在不同應(yīng)用實(shí)現(xiàn)時會有一些優(yōu)化,比如在HBase中設(shè)計了異步的compaction來降低文件個數(shù),來提高讀取性能。
LSM樹本質(zhì)上和B+樹一樣,是一種磁盤數(shù)據(jù)的索引結(jié)構(gòu)。但和B+樹不同的是,LSM樹的索引對寫入請求更友好。因?yàn)闊o論是何種寫入請求,LSM樹都會將寫入操作處理為一次順序?qū)憽?/em>
LSM樹的索引一般由兩部分組成,一部分是內(nèi)存部分,一部分是磁盤部分。內(nèi)存部分一般采用跳躍表來維護(hù)一個有序的KeyValue集合。磁盤部分一般由多個內(nèi)部KeyValue有序的文件組成。
哈希算法(Hash Tables)
原理:通過哈希函數(shù)將鍵映射到一個固定大小的數(shù)組中,數(shù)組中的每個位置稱為一個槽(Slot)。當(dāng)插入、查找或刪除數(shù)據(jù)時,先計算鍵的哈希值,然后根據(jù)哈希值找到對應(yīng)的槽。如果發(fā)生哈希沖突(即不同的鍵映射到了同一個槽),可以采用開放尋址法、鏈地址法等方法來解決。
應(yīng)用場景:適用于快速查找和插入的場景,如緩存系統(tǒng)、分布式哈希表(DHT)等。在分布式系統(tǒng)中,一致性哈希算法是一種常用的哈希算法,用于實(shí)現(xiàn)數(shù)據(jù)的均勻分布和節(jié)點(diǎn)的動態(tài)擴(kuò)展。
優(yōu)點(diǎn):平均查找、插入和刪除操作的時間復(fù)雜度為 O (1),性能高效;
缺點(diǎn):哈希函數(shù)的設(shè)計比較關(guān)鍵,如果哈希函數(shù)設(shè)計不當(dāng),可能會導(dǎo)致哈希沖突頻繁,影響性能。并且哈希表不支持范圍查詢。
哈希表是一種無序的數(shù)據(jù)結(jié)構(gòu),它提供了快速的插入操作和查找操作。
一個好的哈希表能夠保證插入和查找的時間復(fù)雜度為O(1),即插入和查詢性能與哈希表中的數(shù)據(jù)量無關(guān)。這種設(shè)計可以實(shí)現(xiàn)高效的寫性能和查詢性能,但是它犧牲了范圍查詢性能。
哈希表結(jié)構(gòu)設(shè)計中最關(guān)鍵的問題是:
- 如何選擇合適的哈希函數(shù);
- 如何選擇合適的哈希沖突處理機(jī)制。
常見的哈希沖突解決機(jī)制有四種:
- 鏈地址法。
在鏈地址法下,哈希表的每個桶由一個鏈表構(gòu)成。鏈表中存儲的是所有哈希值相同的鍵值對。因此在進(jìn)行查詢操作時,可以通過遍歷該鏈表查詢對應(yīng)的鍵值對。- 線性探測法。
在線性探測法下,哈希表是一個連續(xù)的桶數(shù)組,對于任意一個哈希鍵,根據(jù)哈希函數(shù)定位到一個映射位置,插入和查找都基于該地址進(jìn)行向后探測。當(dāng)插入一個鍵值時,判斷映射地址是否為空,如果該地址為空,則在映射地址插入鍵值對,否則向后探測直到找到空桶,并將該鍵值對放入該空桶。查詢操作則從映射地址開始向后掃描所有鍵值對,直到找到待查詢鍵值對或者遇到一個空桶。- 雙選擇法。
雙選擇法采用兩個獨(dú)立的哈希函數(shù),對于每個鍵值對,都有兩個可插入的桶。當(dāng)執(zhí)行插入的時候,根據(jù)兩個哈希函數(shù)分別將哈希鍵映射到兩個桶a和b中。根據(jù)桶a和桶b的填充度,選擇填充度更低的桶插入鍵值對。同樣,執(zhí)行查詢操作時,只需要遍歷兩個桶即可定位到查詢鍵值。- 布谷鳥探測法。
布谷鳥探測法是雙選擇法的一種變種。它同樣采用兩個哈希函數(shù)。當(dāng)執(zhí)行鍵值對插入時,根據(jù)兩個哈希函數(shù)分別將哈希鍵映射到兩個桶a和b中。如果桶a和b存在空閑位置,則將鍵值對插入到空閑位置中;否則,隨機(jī)挑選一個桶中的鍵值對,將其踢出該桶,并存入待插入鍵值對,被踢出的鍵值對則嘗試插入到其對應(yīng)的另一個桶中。采用不同哈希沖突解決方式,在查詢性能、插入性能、哈希表填充度三個維度會有不同的表現(xiàn),解決哈希沖突的方案也是沒有“銀彈”。
鏈地址法的插入性能更優(yōu),并且對于空間的占用是逐漸增長的;線性探測法的填充度可以做到最優(yōu),但是這是以犧牲查詢和插入性能為前提的;在查詢性能上,布谷鳥和雙選擇法會比其他方法更優(yōu)。在實(shí)際的鍵值數(shù)據(jù)庫中,不同的設(shè)計會采用不同的哈希函數(shù)和哈希沖突解決機(jī)制。Redis采用的就是鏈地址法,這使得Redis的空間占用更為緩慢,空間管理也更為靈活。
LRU(Least Recently Used)和 LFU(Least Frequently Used)緩存算法
原理:
- LRU:基于 “最近最少使用” 的原則,當(dāng)緩存空間滿時,優(yōu)先淘汰最近最少使用的數(shù)據(jù)。通常使用雙向鏈表和哈希表來實(shí)現(xiàn),雙向鏈表用于維護(hù)數(shù)據(jù)的訪問順序,哈希表用于快速查找數(shù)據(jù)。
- LFU:基于 “最不經(jīng)常使用” 的原則,當(dāng)緩存空間滿時,優(yōu)先淘汰使用頻率最低的數(shù)據(jù)??梢允褂枚鄠€鏈表和哈希表來實(shí)現(xiàn),每個鏈表存儲相同使用頻率的數(shù)據(jù)。
應(yīng)用場景:常用于緩存系統(tǒng)中,如數(shù)據(jù)庫緩存、Web 服務(wù)器緩存等,以提高數(shù)據(jù)的訪問速度。
優(yōu)點(diǎn):
- LRU:實(shí)現(xiàn)簡單,能夠較好地反映數(shù)據(jù)的訪問局部性。
- LFU:能夠更好地適應(yīng)數(shù)據(jù)的使用頻率。
缺點(diǎn):
- LRU:對于某些特殊的訪問模式,可能會導(dǎo)致性能下降。
- LFU:實(shí)現(xiàn)相對復(fù)雜,并且在數(shù)據(jù)訪問模式發(fā)生變化時,需要一定的時間來調(diào)整。
總結(jié)
今天提到的都是存儲相關(guān)最核心的算法,本文主要是拋磚引玉,后續(xù)在分享大數(shù)據(jù)相關(guān)組件底層實(shí)現(xiàn)原理時,有涉及到相關(guān)算法的時候,我們再深入看看。