一 數(shù)據(jù)庫發(fā)展史
起初,數(shù)據(jù)的管理方式是文件系統(tǒng),數(shù)據(jù)存儲(chǔ)在文件中,數(shù)據(jù)管理和維護(hù)都由程序員完成。后來發(fā)展出樹形結(jié)構(gòu)和網(wǎng)狀結(jié)構(gòu)的數(shù)據(jù)庫,但都存在著難以擴(kuò)展和維護(hù)的問題。直到七十年代,關(guān)系數(shù)據(jù)庫理論的提出,以表格形式組織數(shù)據(jù),數(shù)據(jù)之間存在關(guān)聯(lián)關(guān)系,具有了良好的結(jié)構(gòu)化和規(guī)范化特性,成為主流數(shù)據(jù)庫類型。
先來看一張數(shù)據(jù)庫發(fā)展史圖鑒:
隨之高并發(fā)大數(shù)據(jù)時(shí)代的來臨,數(shù)據(jù)庫按照各種應(yīng)用場景進(jìn)行了更細(xì)粒度的拆分和演進(jìn),數(shù)據(jù)庫細(xì)分領(lǐng)域的典型代表:
類型 | 產(chǎn)品代表 | 適用場景 |
---|---|---|
層次數(shù)據(jù)庫(NDB) | IMS/IDMS | 以樹形結(jié)構(gòu)組織數(shù)據(jù),數(shù)據(jù)之間存在父子關(guān)系,查詢速度快,但難以擴(kuò)展和維護(hù) |
關(guān)系型數(shù)據(jù)庫(RDBMS) | Oracle/MySQL | 事務(wù)的一致性需求場景 |
鍵值數(shù)據(jù)庫(KVDB) | Redis/Memcached | 針對(duì)高性能并發(fā)讀寫場景 |
文檔數(shù)據(jù)庫(DDB) | MongoDB/CouchDB | 針對(duì)海量復(fù)雜數(shù)據(jù)訪問場景 |
圖數(shù)據(jù)庫(GDB) | Neo4j | 以點(diǎn)、邊為基礎(chǔ)存儲(chǔ)單元,高效存儲(chǔ)、查詢圖數(shù)據(jù)場景 |
時(shí)序數(shù)據(jù)庫(TSDB) | InfluxDB/OpenTSDB | 針對(duì)時(shí)序數(shù)據(jù)的持久化和多維度的聚合查詢等場景 |
對(duì)象數(shù)據(jù)庫(ODB) | Db4O | 支持完整的面向?qū)ο?OO)概念和控制機(jī)制,目前使用場景較少 |
搜索引擎(SE) | ElasticSearch/Solr | 適合于以搜索為主的業(yè)務(wù)場景 |
列數(shù)據(jù)庫(WCDB) | HBase/ClickHouse | 分布式存儲(chǔ)的海量數(shù)據(jù)存儲(chǔ)和查詢場景 |
XML數(shù)據(jù)庫(NXD) | MarkLogic | 支持對(duì)XML格式文檔進(jìn)行存儲(chǔ)和查詢等操作場景 |
內(nèi)容倉庫(CDB) | Jackrabbit | 大規(guī)模高性能的內(nèi)容倉庫 |
二 數(shù)據(jù)庫名詞概念
RDBS
1970年的6月,IBM 公司的研究員埃德加·考特 (Edgar Frank Codd) 發(fā)表了那篇著名的《大型共享數(shù)據(jù)庫數(shù)據(jù)的關(guān)系模型》(A Relational Model of Data for Large Shared Data Banks)的論文,拉開了關(guān)系型數(shù)據(jù)庫(Relational DataBase Server)軟件革命的序幕(之前是層次模型和網(wǎng)狀模型數(shù)據(jù)庫為主)。直到現(xiàn)在,關(guān)系型數(shù)據(jù)庫在基礎(chǔ)軟件應(yīng)用領(lǐng)域仍是最主要的數(shù)據(jù)存儲(chǔ)方式之一。
關(guān)系型數(shù)據(jù)庫建立在關(guān)系型數(shù)據(jù)模型的基礎(chǔ)上,是借助于集合代數(shù)等數(shù)學(xué)概念和方法來處理數(shù)據(jù)的數(shù)據(jù)庫。在關(guān)系型數(shù)據(jù)庫中,實(shí)體以及實(shí)體間的聯(lián)系均由單一的結(jié)構(gòu)類型來表示,這種邏輯結(jié)構(gòu)是一張二維表。關(guān)系型數(shù)據(jù)庫以行和列的形式存儲(chǔ)數(shù)據(jù),這一系列的行和列被稱為表,一組表組成了數(shù)據(jù)庫。
NoSQL
NoSQL(Not Only SQL) 數(shù)據(jù)庫也即非關(guān)系型數(shù)據(jù)庫,它是在大數(shù)據(jù)的時(shí)代背景下產(chǎn)生的,它可以處理分布式、規(guī)模龐大、類型不確定、完整性沒有保證的“雜亂”數(shù)據(jù),這是傳統(tǒng)的關(guān)系型數(shù)據(jù)庫遠(yuǎn)遠(yuǎn)不能勝任的。NoSQL數(shù)據(jù)庫并沒有一個(gè)統(tǒng)一的模型,是以犧牲事務(wù)機(jī)制和強(qiáng)一致性機(jī)制,來獲取更好的分布式部署和橫向擴(kuò)展能力,使其在不同的應(yīng)用場景下,對(duì)特定業(yè)務(wù)數(shù)據(jù)具有更強(qiáng)的處理性能。常用數(shù)據(jù)模型示例如下:
類型 | 產(chǎn)品代表 | 應(yīng)用場景 | 數(shù)據(jù)模型 | 優(yōu)缺點(diǎn) |
---|---|---|---|---|
鍵值數(shù)據(jù)庫 | Redis/Memcached | 內(nèi)容緩存,如會(huì)話,配置文件等; 頻繁讀寫,擁有簡單數(shù)據(jù)模型的應(yīng)用; | 鍵值對(duì),通過散列表來實(shí)現(xiàn) | 優(yōu)點(diǎn): 擴(kuò)展性和靈活性好,性能高; 缺點(diǎn): 數(shù)據(jù)無結(jié)構(gòu)化,只能通過鍵來查詢 |
列簇?cái)?shù)據(jù)庫 | HBase/ClickHouse | 分布式數(shù)據(jù)存儲(chǔ)管理 | 以列簇存儲(chǔ),將同一列存在一起 | 優(yōu)點(diǎn): 簡單,擴(kuò)展性強(qiáng),查詢速度快 缺點(diǎn): 功能局限,不支持事務(wù)的強(qiáng)一致性 |
文檔數(shù)據(jù)庫 | MongoDB/CouchDB | Web應(yīng)用,存儲(chǔ)面向文檔或半結(jié)構(gòu)化數(shù)據(jù) | 鍵值對(duì),value是JSON結(jié)構(gòu)文檔 | 優(yōu)點(diǎn): 數(shù)據(jù)結(jié)構(gòu)靈活 缺點(diǎn): 缺乏統(tǒng)一查詢語法 |
圖形數(shù)據(jù)庫 | Neo4j/InfoGrid | 社交網(wǎng)絡(luò),應(yīng)用監(jiān)控,推薦系統(tǒng)等專注構(gòu)建關(guān)系圖譜 | 圖結(jié)構(gòu) | 優(yōu)點(diǎn): 支持復(fù)雜的圖形算法 缺點(diǎn): 復(fù)雜性高,支持?jǐn)?shù)據(jù)規(guī)模有限 |
NewSQL
NewSQL 是一類新的關(guān)系型數(shù)據(jù)庫, 是各種新的可擴(kuò)展和高性能的數(shù)據(jù)庫的簡稱。它不僅具有 NoSQL 數(shù)據(jù)庫對(duì)海量數(shù)據(jù)的存儲(chǔ)管理能力,同時(shí)還保留了傳統(tǒng)數(shù)據(jù)庫支持的 ACID 和 SQL 特性,典型代表有TiDB和OceanBase。
OLTP
聯(lián)機(jī)事務(wù)處理過程(On-Line Transaction Processing):也稱為面向交易的處理過程,其基本特征是前臺(tái)接收的用戶數(shù)據(jù)可以立即傳送到計(jì)算中心進(jìn)行處理,并在很短的時(shí)間內(nèi)給出處理結(jié)果,是對(duì)用戶操作快速響應(yīng)的方式之一。
OLAP
聯(lián)機(jī)分析處理(On-Line Analytical Processing)是一種面向數(shù)據(jù)分析的處理過程,它使分析人員能夠迅速、一致、交互地從各個(gè)方面觀察信息,以達(dá)到深入理解數(shù)據(jù)的目的。它具有FASMI(Fast Analysis of Shared Multidimensional Information),即共享多維信息的快速分析的特征。
關(guān)于OLTP和OLAP的區(qū)別,借用一張表格對(duì)比如下:
HTAP
HTAP (Hybrid Transactional/Analytical Processing) 混合型數(shù)據(jù)庫基于新的計(jì)算存儲(chǔ)框架,能夠同時(shí)支撐OLTP和OLAP場景,避免傳統(tǒng)架構(gòu)中大量數(shù)據(jù)交互造成的資源浪費(fèi)和沖突。
三 領(lǐng)域數(shù)據(jù)庫
列式數(shù)據(jù)庫
傳統(tǒng)的以行形式保存的數(shù)據(jù)主要滿足OLTP應(yīng)用,列形式保存的數(shù)據(jù)主要滿足以查詢?yōu)橹鞯腛LAP應(yīng)用。在列式數(shù)據(jù)庫中,數(shù)據(jù)按列存儲(chǔ),而每個(gè)列中的數(shù)據(jù)類型相同。這種存儲(chǔ)方式使列式數(shù)據(jù)庫能夠更高效地處理大量的數(shù)據(jù),特別是需要進(jìn)行大規(guī)模的數(shù)據(jù)分析和處理時(shí)(如金融、醫(yī)療、電信、能源、物流等行業(yè))。
兩種存儲(chǔ)結(jié)構(gòu)的區(qū)別如下圖:
列式數(shù)據(jù)庫的主要優(yōu)點(diǎn):
?更高的壓縮比率:由于每個(gè)列中的數(shù)據(jù)類型相同,列式數(shù)據(jù)庫可以使用更高效的壓縮算法來壓縮數(shù)據(jù)(壓縮比可達(dá)到5~20倍),從而減少存儲(chǔ)空間的使用。
?更快的查詢速度:列式數(shù)據(jù)庫可以只讀取需要的列,而不需要讀取整行數(shù)據(jù),從而加快查詢速度。
?更好的擴(kuò)展性:列式數(shù)據(jù)庫可以更容易地進(jìn)行水平擴(kuò)展,即增加更多的節(jié)點(diǎn)和服務(wù)器來處理更大規(guī)模的數(shù)據(jù)。
?更好的數(shù)據(jù)分析支持:由于列式數(shù)據(jù)庫可以處理大規(guī)模的數(shù)據(jù),它可以支持更復(fù)雜的數(shù)據(jù)分析和處理操作,例如數(shù)據(jù)挖掘、機(jī)器學(xué)習(xí)等。
列式數(shù)據(jù)庫的主要缺點(diǎn):
?更慢的寫入速度:由于數(shù)據(jù)是按列存儲(chǔ),每次寫入都需要寫入整個(gè)列,而不是單個(gè)行,因此寫入速度可能較慢。
?更復(fù)雜的數(shù)據(jù)模型:由于數(shù)據(jù)是按列存儲(chǔ),數(shù)據(jù)模型可能比行式數(shù)據(jù)庫更復(fù)雜,需要更多的設(shè)計(jì)和開發(fā)工作。
列式數(shù)據(jù)庫的應(yīng)用場景:
?金融:金融行業(yè)的交易數(shù)據(jù)和市場數(shù)據(jù),例如股票價(jià)格、外匯匯率、利率等。列式數(shù)據(jù)庫可以更快速地處理這些數(shù)據(jù),并且支持更復(fù)雜的數(shù)據(jù)分析和處理操作,例如風(fēng)險(xiǎn)管理、投資分析等。
?醫(yī)療:醫(yī)療行業(yè)的病歷數(shù)據(jù)、醫(yī)療圖像和實(shí)驗(yàn)數(shù)據(jù)等。列式數(shù)據(jù)庫可以更高效地存儲(chǔ)和處理這些數(shù)據(jù),并且支持更復(fù)雜的醫(yī)學(xué)研究和分析操作。
?電信:電信行業(yè)的用戶數(shù)據(jù)和通信數(shù)據(jù),例如電話記錄、短信記錄、網(wǎng)絡(luò)流量等。列式數(shù)據(jù)庫可以更快速地處理這些數(shù)據(jù),并且支持更復(fù)雜的用戶行為分析和網(wǎng)絡(luò)優(yōu)化操作。
?能源:能源行業(yè)的傳感器數(shù)據(jù)、監(jiān)測數(shù)據(jù)和生產(chǎn)數(shù)據(jù)等。列式數(shù)據(jù)庫可以更高效地存儲(chǔ)和處理這些數(shù)據(jù),并且支持更復(fù)雜的能源管理和控制操作。
?物流:物流行業(yè)的運(yùn)輸數(shù)據(jù)、庫存數(shù)據(jù)和訂單數(shù)據(jù)等。列式數(shù)據(jù)庫可以更快速地處理這些數(shù)據(jù),并且支持更復(fù)雜的物流管理和優(yōu)化操作。
總之,列式數(shù)據(jù)庫是一種高效處理大規(guī)模數(shù)據(jù)的數(shù)據(jù)庫管理系統(tǒng),但需要權(quán)衡寫入速度、數(shù)據(jù)模型復(fù)雜度和成本等因素。 隨著傳統(tǒng)關(guān)系型數(shù)據(jù)庫與新興的分布式數(shù)據(jù)庫不斷的發(fā)展,列式存儲(chǔ)與行式存儲(chǔ)會(huì)不斷融合,數(shù)據(jù)庫系統(tǒng)呈現(xiàn)雙模式數(shù)據(jù)存放方式。
時(shí)序數(shù)據(jù)庫
時(shí)序數(shù)據(jù)庫全稱為時(shí)間序列數(shù)據(jù)庫 ( Time Series Database),用于存儲(chǔ)和管理時(shí)間序列數(shù)據(jù)的專業(yè)化數(shù)據(jù)庫,是優(yōu)化用于攝取、處理和存儲(chǔ)時(shí)間戳數(shù)據(jù)的數(shù)據(jù)庫。其跟常規(guī)的關(guān)系數(shù)據(jù)庫SQL相比,最大的區(qū)別在于:時(shí)序數(shù)據(jù)庫是以時(shí)間為索引的規(guī)律性時(shí)間間隔記錄的數(shù)據(jù)庫。
時(shí)序數(shù)據(jù)庫在物聯(lián)網(wǎng)和互聯(lián)網(wǎng)應(yīng)用程序監(jiān)控(APM)等場景應(yīng)用比較多,以監(jiān)控?cái)?shù)據(jù)采集來舉例,如果數(shù)據(jù)監(jiān)控?cái)?shù)據(jù)采集時(shí)間間隔是1s,那一個(gè)監(jiān)控項(xiàng)每天會(huì)產(chǎn)生86400個(gè)數(shù)據(jù)點(diǎn),若有10000個(gè)監(jiān)控項(xiàng),則一天就會(huì)產(chǎn)生864000000個(gè)數(shù)據(jù)點(diǎn)。在物聯(lián)網(wǎng)場景下,這個(gè)數(shù)字會(huì)更大,整個(gè)數(shù)據(jù)的規(guī)模,是TB甚至是PB級(jí)的。
時(shí)序數(shù)據(jù)庫發(fā)展史:
當(dāng)下最常見的Kubernetes容器管理系統(tǒng)中,通常會(huì)搭配普羅米修斯(Prometheus)進(jìn)行監(jiān)控,Prometheus就是一套開源的監(jiān)控&報(bào)警&時(shí)間序列數(shù)據(jù)庫的組合。
圖數(shù)據(jù)庫
圖數(shù)據(jù)庫(Graph Database)是基于圖論實(shí)現(xiàn)的一種新型NoSQL數(shù)據(jù)庫。它的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)和數(shù)據(jù)的查詢方式都是以圖論為基礎(chǔ)的。圖論中圖的基本元素為節(jié)點(diǎn)和邊,在圖數(shù)據(jù)庫中對(duì)應(yīng)的就是節(jié)點(diǎn)和關(guān)系。
圖數(shù)據(jù)庫在反欺詐多維關(guān)聯(lián)分析場景,社交網(wǎng)絡(luò)圖譜,企業(yè)關(guān)系圖譜等場景中可以做一些非常復(fù)雜的關(guān)系查詢。這是由于圖數(shù)據(jù)結(jié)構(gòu)表現(xiàn)的是實(shí)體聯(lián)系本身,它表現(xiàn)了現(xiàn)實(shí)世界中事物聯(lián)系的本質(zhì),它的聯(lián)系在節(jié)點(diǎn)創(chuàng)建時(shí)就已經(jīng)建立,所以在查詢中能以快捷的路徑返回關(guān)聯(lián)數(shù)據(jù),從而表現(xiàn)出非常高效的查詢性能。
目前市面上較為流行的圖數(shù)據(jù)庫產(chǎn)品有以下幾種:
與傳統(tǒng)的關(guān)系數(shù)據(jù)庫相比,圖數(shù)據(jù)庫具有以下優(yōu)點(diǎn):
1.更快的查詢速度:圖數(shù)據(jù)庫可以快速遍歷圖數(shù)據(jù),找到節(jié)點(diǎn)之間的關(guān)聯(lián)和路徑,因此查詢速度更快。
2.更好的擴(kuò)展性:圖數(shù)據(jù)庫可以輕松地?cái)U(kuò)展到大規(guī)模的數(shù)據(jù)集,因?yàn)樗鼈兛梢苑植际酱鎯?chǔ)和處理數(shù)據(jù)。
3.更好的數(shù)據(jù)可視化:圖數(shù)據(jù)庫可以將數(shù)據(jù)可視化為圖形,使用戶更容易理解和分析數(shù)據(jù)。
4.更好的數(shù)據(jù)一致性:圖數(shù)據(jù)庫可以確保數(shù)據(jù)的一致性,因?yàn)樗鼈兛梢栽诠?jié)點(diǎn)和邊之間建立強(qiáng)制性的關(guān)系。
四 數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
前面簡單介紹了數(shù)據(jù)庫相關(guān)的基礎(chǔ)知識(shí),下面再介紹幾種我們常見的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)相關(guān)的應(yīng)用實(shí)踐:拉鏈表,位運(yùn)算和環(huán)形隊(duì)列。
4.1 拉鏈表
拉鏈表是一種數(shù)據(jù)倉庫中常用的數(shù)據(jù)模型,用于記錄維度數(shù)據(jù)的變化歷史。我們以一個(gè)人員變動(dòng)的場景舉例,假設(shè)有一個(gè)員工信息表,其中包含了員工的姓名、工號(hào)、職位、部門、入職時(shí)間等信息。如果需要記錄員工的變動(dòng)情況,就可以使用拉鏈表來實(shí)現(xiàn)。
首先,在員工信息表的基礎(chǔ)上新增兩個(gè)字段:生效時(shí)間和失效時(shí)間。當(dāng)員工信息發(fā)生變動(dòng)時(shí),不再新增一條記錄,而是修改原有記錄的失效時(shí)間,同時(shí)新增一條新的記錄。如下表所示:
姓名 | 工號(hào) | 職位 | 部門 | 入職時(shí)間 | 生效時(shí)間 | 失效時(shí)間 |
---|---|---|---|---|---|---|
張三 | 001 | 經(jīng)理 | 技術(shù) | 2010-01-01 | 2010-01-01 | 2012-12-31 |
張三 | 001 | 總監(jiān) | 技術(shù) | 2013-01-01 | 2013-01-01 | 2015-12-31 |
張三 | 001 | 總經(jīng)理 | 技術(shù) | 2016-01-01 | 2016-01-01 | 9999-12-31 |
這里的生效時(shí)間指的是該記錄生效的時(shí)間,失效時(shí)間指的是該記錄失效的時(shí)間。例如,張三最初是技術(shù)部經(jīng)理,生效時(shí)間為入職時(shí)間,失效時(shí)間為2012年底,之后晉升為技術(shù)部總監(jiān),生效時(shí)間為2013年初,失效時(shí)間為2015年底,最后又晉升為技術(shù)部總經(jīng)理,生效時(shí)間為2016年初,失效時(shí)間為9999年底。
通過這種方式,可以記錄員工變動(dòng)的歷史信息,并能夠方便地查詢某個(gè)時(shí)間點(diǎn)的員工信息。例如,如果需要查詢張三在2014年的職位和部門信息,只需查詢生效時(shí)間小于2014年且失效時(shí)間大于2014年的記錄即可。
拉鏈表通常包括以下幾個(gè)字段:
1.主鍵:唯一標(biāo)識(shí)每個(gè)記錄的字段,通常是一個(gè)或多個(gè)列的組合。 2.生效時(shí)間:記錄的生效時(shí)間,即該記錄開始生效的時(shí)間。 3.失效時(shí)間:記錄的失效時(shí)間,即該記錄失效的時(shí)間。 4.版本號(hào):記錄的版本號(hào),用于標(biāo)識(shí)該記錄的版本。 5.其他維度屬性:記錄的其他維度屬性,如客戶名、產(chǎn)品名、員工名等。
當(dāng)一個(gè)記錄的維度屬性發(fā)生變化時(shí),不再新增一條記錄,而是修改原有記錄的失效時(shí)間,同時(shí)新增一條新的記錄。新記錄的生效時(shí)間為變化的時(shí)間,失效時(shí)間為9999年底。這樣就能夠記錄每個(gè)維度屬性的歷史變化信息,同時(shí)保證查詢時(shí)能夠正確獲取某個(gè)時(shí)間點(diǎn)的維度屬性信息。
拉鏈表與傳統(tǒng)的流水表相比,它們的主要區(qū)別在于:
1.數(shù)據(jù)結(jié)構(gòu)不同:流水表是一張只有新增和更新操作的表,每次更新都會(huì)新增一條記錄,記錄中包含了所有的歷史信息。而拉鏈表則是一張有新增、更新和刪除操作的表,每個(gè)記錄都有一個(gè)生效時(shí)間段和失效時(shí)間段,記錄的歷史信息通過時(shí)間段的變化來體現(xiàn)。
2.查詢方式不同:流水表的查詢方式是基于時(shí)間點(diǎn)的查詢,即查詢某個(gè)時(shí)間點(diǎn)的記錄信息。而拉鏈表的查詢方式是基于時(shí)間段的查詢,即查詢某個(gè)時(shí)間段內(nèi)的記錄信息。
3.存儲(chǔ)空間不同:由于流水表需要記錄所有歷史信息,所以存儲(chǔ)空間相對(duì)較大。而拉鏈表只記錄生效時(shí)間段和失效時(shí)間段,所以存儲(chǔ)空間相對(duì)較小。
4.數(shù)據(jù)更新方式不同:流水表只有新增和更新操作,每次更新都會(huì)新增一條記錄,不會(huì)對(duì)原有記錄進(jìn)行修改。而拉鏈表有新增、更新和刪除操作,每次更新會(huì)修改原有記錄的失效時(shí)間,同時(shí)新增一條新的記錄。
4.2 巧用位運(yùn)算
借助于計(jì)算機(jī)位運(yùn)算的特性,可以巧妙的解決某些特定問題,使實(shí)現(xiàn)更加優(yōu)雅,節(jié)省存儲(chǔ)空間的同時(shí),也可以提高運(yùn)行效率,典型應(yīng)用場景:壓縮存儲(chǔ)、位圖索引、數(shù)據(jù)加密、圖形處理和狀態(tài)判斷等,下面介紹幾個(gè)典型案例。
4.2.1 位運(yùn)算
?使用位運(yùn)算實(shí)現(xiàn)開關(guān)和多選項(xiàng)疊加(資源權(quán)限)等應(yīng)用場景。一個(gè)int類型有32個(gè)位,理論上可以表示32個(gè)開關(guān)狀態(tài)或業(yè)務(wù)選項(xiàng);以用戶每個(gè)月的簽到場景舉例:用一個(gè)int字段來表示用戶一個(gè)月的簽到情況,0表示未簽到,1表示簽到。想知道某一天是否簽到,則只需要判斷對(duì)應(yīng)的比特位上是否為1。計(jì)算一個(gè)月累計(jì)簽到了多少次,只需要統(tǒng)計(jì)有多少個(gè)比特位為1就可以了。這種設(shè)計(jì)巧妙的數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)在后面的位圖(BitMap)中,還會(huì)進(jìn)行更為詳細(xì)的介紹。
?使用位運(yùn)算實(shí)現(xiàn)業(yè)務(wù)優(yōu)先級(jí)計(jì)算:
public abstract class PriorityManager { // 定義業(yè)務(wù)優(yōu)先級(jí)常量 public static final int PRIORITY_LOW = 1; // 二進(jìn)制:001 public static final int PRIORITY_NORMAL = 2; // 二進(jìn)制:010 public static final int PRIORITY_HIGH = 4; // 二進(jìn)制:100 // 定義用戶權(quán)限常量 public static final int PERMISSION_READ = 1; // 二進(jìn)制:001 public static final int PERMISSION_WRITE = 2; // 二進(jìn)制:010 public static final int PERMISSION_DELETE = 4;// 二進(jìn)制:100 // 定義用戶權(quán)限和業(yè)務(wù)優(yōu)先級(jí)的組合值 public static final int PERMISSION_LOW_PRIORITY = PRIORITY_LOW | PERMISSION_READ; // 二進(jìn)制:001 | 001 = 001 public static final int PERMISSION_NORMAL_PRIORITY = PRIORITY_NORMAL | PERMISSION_READ | PERMISSION_WRITE; // 二進(jìn)制:010 | 001 | 010 = 011 public static final int PERMISSION_HIGH_PRIORITY = PRIORITY_HIGH | PERMISSION_READ | PERMISSION_WRITE | PERMISSION_DELETE; // 二進(jìn)制:100 | 001 | 010 | 100 = 111 // 判斷用戶權(quán)限是否滿足業(yè)務(wù)優(yōu)先級(jí)要求 public static boolean checkPermission(int permission, int priority) { return (permission & priority) == priority; } }
?其它使用位運(yùn)算的典型場景:HashMap中的隊(duì)列長度的設(shè)計(jì)和線程池ThreadPoolExcutor中使用AtomicInteger字段ctl,存儲(chǔ)當(dāng)前線程池狀態(tài)和線程數(shù)量(高3位表示當(dāng)前線程的狀態(tài),低29位表示線程的數(shù)量)。
4.2.2 BitMap
位圖(BitMap)是一種常用的數(shù)據(jù)結(jié)構(gòu),在索引,數(shù)據(jù)壓縮等方面有廣泛應(yīng)用?;舅枷刖褪怯靡粋€(gè)bit位來標(biāo)記某個(gè)元素對(duì)應(yīng)的Value,而Key即是該元素。由于采用了Bit為單位來存儲(chǔ)數(shù)據(jù),因此可以大大節(jié)省存儲(chǔ)空間,是少有的既能保證存儲(chǔ)空間又能保證查找速度的數(shù)據(jù)結(jié)構(gòu)(而不必空間換時(shí)間)。
舉個(gè)例子,假設(shè)有這樣一個(gè)需求:在20億個(gè)隨機(jī)整數(shù)中找出某個(gè)數(shù)m是否存在其中,并假設(shè)32位操作系統(tǒng),4G內(nèi)存,在Java中,int占4字節(jié),1字節(jié)=8位(1 byte = 8 bit)。
?如果每個(gè)數(shù)字用int存儲(chǔ),那就是20億個(gè)int,因而占用的空間約為 (2000000000*4/1024/1024/1024)≈7.45G
?如果按位存儲(chǔ)就不一樣了,20億個(gè)數(shù)就是20億位,占用空間約為 (2000000000/8/1024/1024/1024)≈0.233G
存儲(chǔ)空間可以壓縮節(jié)省31倍!那么它是如何通過二進(jìn)制位實(shí)現(xiàn)數(shù)字標(biāo)記的呢? 其原理是用每個(gè)二進(jìn)制位(下標(biāo))表示一個(gè)真實(shí)數(shù)字,0表示不存在,1表示存在,這樣我們可以很容易表示{1,2,4,6}這幾個(gè)數(shù):
計(jì)算機(jī)內(nèi)存分配的最小單位是字節(jié),也就是8位,那如果要表示{12,13,15}怎么辦呢?可以另申請(qǐng)一個(gè)字節(jié)b[1]:
通過一個(gè)二維數(shù)組來實(shí)現(xiàn)位數(shù)疊加,1個(gè)int占32位,那么我們只需要申請(qǐng)一個(gè)int數(shù)組長度為 int index[1+N/32] 即可存儲(chǔ),其中N表示要存儲(chǔ)的這些數(shù)中的最大值:
index[0]:可以表示0~31
index[1]:可以表示32~63
index[2]:可以表示64~95
以此類推...如此一來,給定任意整數(shù)M,那么M/32就得到下標(biāo),M%32就知道它在此下標(biāo)的哪個(gè)位置。
BitMap數(shù)據(jù)結(jié)構(gòu)通常用于以下場景:
1.壓縮存儲(chǔ)大量布爾值:BitMap可以有效地壓縮大量的布爾值,從而減少內(nèi)存的使用;
2.快速判斷一個(gè)元素是否存在:BitMap可以快速地判斷一個(gè)元素是否存在,只需要查找對(duì)應(yīng)的位即可;
3.去重:BitMap可以用于去重操作,將元素作為索引,將對(duì)應(yīng)的位設(shè)置為1,重復(fù)元素只會(huì)對(duì)應(yīng)同一個(gè)位,從而實(shí)現(xiàn)去重;
4.排序:BitMap可以用于排序,將元素作為索引,將對(duì)應(yīng)的位設(shè)置為1,然后按照索引順序遍歷位數(shù)組,即可得到有序的元素序列;
5.ElasticSearch和Solr等搜索引擎中,在設(shè)計(jì)搜索剪枝時(shí),需要保存已經(jīng)搜索過的歷史信息,可以使用位圖減小歷史信息數(shù)據(jù)所占空間;
4.2.3 布隆過濾器
位圖(Bitmap)這種數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),如果數(shù)據(jù)量大到一定程度,比如64bit類型的數(shù)據(jù),簡單算一下存儲(chǔ)空間就知道,海量硬件資源要求,已經(jīng)不太現(xiàn)實(shí)了:
所以另一個(gè)著名的工業(yè)實(shí)現(xiàn) -布隆過濾器(Bloom Filter)出現(xiàn)了。如果說BitMap對(duì)于每一個(gè)可能的整型值,通過直接尋址的方式進(jìn)行映射,相當(dāng)于使用了一個(gè)哈希函數(shù),那布隆過濾器就是引入了k ( k > 1 )個(gè)相互獨(dú)立的哈希函數(shù),保證在給定的空間和誤判率情況下,完成元素判重的過程。下圖中是k = 3 時(shí)的布隆過濾器:
布隆過濾器的內(nèi)部依賴于哈希算法,當(dāng)檢測某一條數(shù)據(jù)是否見過時(shí),有一定概率出現(xiàn)假陽性(False Positive),但一定不會(huì)出現(xiàn)假陰性(False Negative)。也就是說,當(dāng) 布隆過濾器認(rèn)為一條數(shù)據(jù)出現(xiàn)過,那么該條數(shù)據(jù)很可能出現(xiàn)過;但如果布隆過濾器認(rèn)為一條數(shù)據(jù)沒出現(xiàn)過,那么該條數(shù)據(jù)一定沒出現(xiàn)過。布隆過濾器通過引入一定錯(cuò)誤率,使得海量數(shù)據(jù)判重在可以接受的內(nèi)存代價(jià)中得以實(shí)現(xiàn)。
上圖中,x,y,z經(jīng)由哈希函數(shù)映射將各自在Bitmap中的3個(gè)位置置為1,當(dāng)w出現(xiàn)時(shí),僅當(dāng)3個(gè)標(biāo)志位都為1時(shí),才表示w在集合中。圖中所示的情況,布隆過濾器將判定w不在集合中。
常見實(shí)現(xiàn)
?Java中Guava工具包中實(shí)現(xiàn);
?Redis 4.0開始以插件形式提供布隆過濾器功能;
適用場景
?網(wǎng)頁爬蟲對(duì)URL的去重,避免爬去相同的URL地址,比如Chrome瀏覽器就是使用了一個(gè)布隆過濾器識(shí)別惡意鏈接;
?垃圾郵件過濾,從數(shù)十億個(gè)垃圾郵件列表中判斷某郵箱是否是殺垃圾郵箱;
?解決數(shù)據(jù)庫緩存擊穿,黑客攻擊服務(wù)器時(shí),會(huì)構(gòu)建大量不存在于緩存中的key向服務(wù)器發(fā)起請(qǐng)求,在數(shù)據(jù)量足夠大的時(shí)候,頻繁的數(shù)據(jù)庫查詢會(huì)導(dǎo)致掛機(jī);
?谷歌Bigtable、Apache HBase、Apache Cassandra和PostgreSQL使用布隆過濾器來減少對(duì)不存在的行或列的磁盤查找;
?秒殺系統(tǒng),查看用戶是否重復(fù)購買;
4.2.4 小結(jié)
?位運(yùn)算有著執(zhí)行速度快,占用空間小,代碼實(shí)現(xiàn)簡潔等優(yōu)點(diǎn),往往能起到四兩撥千斤的效果。同樣也有著代碼可讀性差,使用范圍和可維護(hù)性受限等不足;
?在BitMap中,占用空間大小還與實(shí)際應(yīng)用場景有關(guān),這種結(jié)構(gòu)無法容忍誤判,只能判斷一個(gè)元素是否存在,如果數(shù)據(jù)離散度過高,空間利用率反而更低;
?布隆過濾器則有著空間利用率高,可以容忍一定的誤判率的優(yōu)點(diǎn)。與BitMap相比,也存在著無法刪除元素,誤判率無法達(dá)到0等不足;
4.3 環(huán)形隊(duì)列
環(huán)形隊(duì)列是一種用于表示一個(gè)固定尺寸、頭尾相連的數(shù)據(jù)結(jié)構(gòu),很適合緩存數(shù)據(jù)流。在通信開發(fā)(Socket,TCP/IP,RPC開發(fā)),在內(nèi)核的進(jìn)程間通信(IPC),視頻音頻播放等各種場景中,都有其身影。日常開發(fā)過程中使用的Dubbo、Netty、Akka、Quartz、ZooKeeper 、Kafka等各種中間件,也都有環(huán)形隊(duì)列的思想。下面介紹兩種常用的環(huán)形數(shù)據(jù)結(jié)構(gòu):Hash環(huán)和時(shí)間輪。
4.3.1 一致性Hash環(huán)
先來看一下,典型Hash算法結(jié)構(gòu)如下:
以上圖Hash策略為例,當(dāng)節(jié)點(diǎn)數(shù)N發(fā)生變化的時(shí)候 之前所有的 hash映射幾乎全部失效,如果集群是無狀態(tài)的服務(wù),倒是沒什么事情,但是如果是分布式緩存這種場景,就會(huì)導(dǎo)致比較嚴(yán)重的問題。比如 Key1原本是路由到Node1上,命中緩存的Value1數(shù)據(jù)。但是當(dāng)N節(jié)點(diǎn)變化后,Key1可能就路由到了Node2節(jié)點(diǎn),這就產(chǎn)生了緩存數(shù)據(jù)無法命中的問題。而無論是機(jī)器故障還是緩存擴(kuò)容,都會(huì)導(dǎo)致節(jié)點(diǎn)數(shù)的變化。
如何解決上面場景的問題呢?就是接下來介紹的一致性Hash算法。
一致性哈希將整個(gè)哈希值空間組織成一個(gè)虛擬的圓環(huán),假設(shè)某哈希函數(shù)H的值空間為0-2^32-1(即哈希值是一個(gè)32位無符號(hào)整型),所有的輸入值都被映射到 0-2^32-1 之間,組成一個(gè)圓環(huán)。整個(gè)哈??臻g環(huán)如下:
路由數(shù)據(jù)的過程如下:將數(shù)據(jù)key使用相同的函數(shù)Hash計(jì)算出哈希值,并確定此數(shù)據(jù)在環(huán)上的位置,從此位置沿環(huán)順時(shí)針“行走”,遇到的第一個(gè)節(jié)點(diǎn)就是其應(yīng)該定位到的服務(wù)器。如果某個(gè)節(jié)點(diǎn)的服務(wù)器故障,其影響范圍也不再是所有集群,而是限定在故障節(jié)點(diǎn)與其上游節(jié)點(diǎn)的部分區(qū)域。
當(dāng)某個(gè)節(jié)點(diǎn)宕機(jī)后,原本屬于它的請(qǐng)求都會(huì)被重新hash映射到下游節(jié)點(diǎn),會(huì)突然造成下游節(jié)點(diǎn)壓力過大有可能也會(huì)造成下游節(jié)點(diǎn)宕機(jī),從而容易形成雪崩,為此引入了虛擬節(jié)點(diǎn)來解決這個(gè)問題。
根據(jù)Node節(jié)點(diǎn)生成很多的虛擬節(jié)點(diǎn)分布在圓環(huán)上,,一個(gè)真實(shí)節(jié)點(diǎn)映射對(duì)應(yīng)多個(gè)虛擬節(jié)點(diǎn)。這樣當(dāng)某個(gè)節(jié)點(diǎn)掛了后原本屬于它的請(qǐng)求,會(huì)被均衡的分布到其他節(jié)點(diǎn)上降低了產(chǎn)生雪崩的情況,也解決了物理節(jié)點(diǎn)數(shù)少,導(dǎo)致請(qǐng)求分布不均的問題。
帶有虛擬節(jié)點(diǎn)的Hash環(huán):
一致性Hash算法由于均衡性,持久性的映射特點(diǎn)被廣泛應(yīng)用于負(fù)載均衡領(lǐng)域,比如nginx、dubbo等內(nèi)部都有一致性hash的實(shí)現(xiàn)。
4.3.2 時(shí)間輪分片
時(shí)間輪(TimeWheel)是一種實(shí)現(xiàn)延遲功能(定時(shí)器)的精妙的算法,可以實(shí)現(xiàn)高效的延時(shí)隊(duì)列。以Kafka中的時(shí)間輪實(shí)現(xiàn)方案為例,它是一個(gè)存儲(chǔ)定時(shí)任務(wù)的環(huán)形隊(duì)列,底層采用數(shù)組實(shí)現(xiàn),數(shù)組中的每個(gè)元素可以存放一個(gè)定時(shí)任務(wù)列表(TimerTaskList)。TimerTaskList是一個(gè)環(huán)形的雙向鏈表,鏈表中的每一項(xiàng)表示的都是定時(shí)任務(wù)項(xiàng)(TimerTaskEntry),其中封裝了真正的定時(shí)任務(wù)TimerTask。
通過上圖可以發(fā)現(xiàn),時(shí)間輪算法不再任務(wù)隊(duì)列作為數(shù)據(jù)結(jié)構(gòu),輪詢線程不再負(fù)責(zé)遍歷所有任務(wù),而是僅僅遍歷時(shí)間刻度。時(shí)間輪算法好比指針不斷在時(shí)鐘上旋轉(zhuǎn)、遍歷,如果一個(gè)發(fā)現(xiàn)某一時(shí)刻上有任務(wù)(任務(wù)隊(duì)列),那么就會(huì)將任務(wù)隊(duì)列上的所有任務(wù)都執(zhí)行一遍。
假設(shè)相鄰bucket到期時(shí)間的間隔為bucket=1s,從0s開始計(jì)時(shí),1s后到期的定時(shí)任務(wù)掛在bucket=1下,2s后到期的定時(shí)任務(wù)掛在bucket=2下,當(dāng)檢查到時(shí)間過去了1s時(shí),bucket=1下所有節(jié)點(diǎn)執(zhí)行超時(shí)動(dòng)作,當(dāng)時(shí)間到了2s時(shí),bucket=2下所有節(jié)點(diǎn)執(zhí)行超時(shí)動(dòng)作。時(shí)間輪使用一個(gè)表盤指針(pointer),用來表示時(shí)間輪當(dāng)前指針跳動(dòng)的次數(shù),可以用tickDuration * (pointer + 1)來表示下一次到期的任務(wù),需要處理此bucket所對(duì)應(yīng)的 TimeWheel中的所有任務(wù)。
時(shí)間輪的優(yōu)點(diǎn)
1.任務(wù)的添加與移除,都是O(1)級(jí)的復(fù)雜度;
2.只需要有一個(gè)線程去推進(jìn)時(shí)間輪,不會(huì)占用大量的資源;
3.與其他任務(wù)調(diào)度模式相比,CPU的負(fù)載和資源浪費(fèi)減少;
適用場景
時(shí)間輪是為解決高效調(diào)度任務(wù)而產(chǎn)生的調(diào)度模型。在周期性定時(shí)任務(wù),延時(shí)任務(wù),通知任務(wù)等場景都可以發(fā)揮效用。
五 總結(jié)
本文針對(duì)數(shù)據(jù)存儲(chǔ)相關(guān)名詞概念進(jìn)行了解釋,重點(diǎn)介紹了數(shù)據(jù)庫技術(shù)的發(fā)展史。為了豐富文章的可讀性以及實(shí)用性,又從數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)層面進(jìn)行了部分技術(shù)實(shí)戰(zhàn)能力的外延擴(kuò)展,闡述了拉鏈表,位運(yùn)算,環(huán)形隊(duì)列等相關(guān)數(shù)據(jù)結(jié)構(gòu)在軟件開發(fā)領(lǐng)域的應(yīng)用,希望本文給你帶來收獲。
注:本文個(gè)別圖片來自互聯(lián)網(wǎng)
審核編輯 黃宇
-
數(shù)據(jù)庫
+關(guān)注
關(guān)注
7文章
3794瀏覽量
64360 -
數(shù)據(jù)結(jié)構(gòu)
+關(guān)注
關(guān)注
3文章
573瀏覽量
40121 -
架構(gòu)師
+關(guān)注
關(guān)注
0文章
47瀏覽量
4620
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論