前言
NoSQL伤疙、尤其是key-value NoSQL在日常開發(fā)中扮演了非常重要的角色,除非對于關(guān)系型數(shù)據(jù)或者事務(wù)之類的有著非常強的訴求瞳遍,不妨就根據(jù)業(yè)務(wù)特點試一下NoSQL氓轰,現(xiàn)在市面上的NoSQL非常多,比如說 Redis时捌、Tair季率、Rockes DB啡省、MongoDB等,每種都有自己的特點涛目。
本篇文章就K-V的NoSQL數(shù)據(jù)庫展開描述,對于常用的Redis、LevelDB嚎卫、Tair尔崔、美團KV實戰(zhàn)等進行分析秦驯,在高可用、性能優(yōu)化方面這些它們都做了哪些事情,我們之后應(yīng)該如何做技術(shù)選型和設(shè)計触菜,從這些組件中能得到哪些優(yōu)秀共性犬缨。
Redis
Redis大家應(yīng)該非常非常熟悉了念搬,相對于memcache提供了更加豐富的數(shù)據(jù)結(jié)構(gòu)支持袖外,持久化措施也做的相對完備,集群方案也盡可能的解決了可用性等問題量愧。
Reids基礎(chǔ)實現(xiàn)
先來看Redis的基礎(chǔ)原叮,對外提供單key、鏈表脐区、set、Hash默伍、大數(shù)探颈、經(jīng)緯度等多種數(shù)據(jù)接口及相關(guān)的API屏鳍,并且支持Lua腳本,能夠靈活的實現(xiàn)復(fù)合操作的原子性局服,簡單邏輯可以直接基于Redis+lua進行編程钓瞭,比較舒服。
Redis本身可以理解為是一個大Hash淫奔,內(nèi)部實現(xiàn)了SDS山涡、ziplist、quicklist唆迁、hashtable等多種高效的數(shù)據(jù)結(jié)構(gòu)鸭丛,在提供豐富數(shù)據(jù)API的基礎(chǔ)上進一步保證性能。
網(wǎng)絡(luò)連接處理方面Redis也是一個非常經(jīng)典的Reactor式網(wǎng)絡(luò)應(yīng)用唐责,并沒有像memcache直接使用了libevent這樣的庫鳞溉,而是直接裸寫的epoll(默認水平觸發(fā)、也可配置為邊緣觸發(fā))鼠哥,比起libevent更加簡單了熟菲,并且除了持久化線程Redis完全是單線程來搞的看政。
wget http://download.redis.io/releases/redis-4.0.1.tar.gz
tar -zxvf redis-4.0.1.tar.gz
vim src/server.c
ll ae*.h/cpp
具體可以看一下我之前寫的redis系列文章。
關(guān)于epoll
關(guān)于邊緣觸發(fā)抄罕、水平觸發(fā)這里也單獨說一下帽衙,后面會多次提到:
邊緣觸發(fā):
讀緩沖區(qū)狀態(tài)變化時, 讀事件觸發(fā)。寫緩沖區(qū)狀態(tài)變化時, 寫事件觸發(fā)贞绵。(只會提示一次)
accept新的連接, 同時監(jiān)聽讀寫事件厉萝,讀事件到達, 需要一直讀取數(shù)據(jù), 直到返回EAGAIN,寫事件到達, 無數(shù)據(jù)處理則不處理, 有數(shù)據(jù)待寫入則一直寫入榨崩,直到寫完或者返回EAGAIN谴垫。
效率較高,不需要頻繁開啟關(guān)閉事件母蛛。編程比較復(fù)雜翩剪,處理不當(dāng)存在丟失事件的風(fēng)險。
水平觸發(fā):
讀緩沖區(qū)不為空時, 讀事件觸發(fā)彩郊。寫緩沖區(qū)不為滿時, 寫事件觸發(fā)前弯。
也就是accept新的連接, 監(jiān)聽讀事件,讀事件到達, 處理讀事件秫逝。需要寫入數(shù)據(jù), 向fd中寫數(shù)據(jù), 一次無法寫完, 開啟寫事件監(jiān)聽恕出,寫事件到達, 繼續(xù)寫入數(shù)據(jù), 寫完后關(guān)閉寫事件。
編程簡單违帆,大量數(shù)據(jù)交互時會存在頻繁的事件開關(guān)浙巫,所以相對邊緣觸發(fā)性能較低。
關(guān)于這兩種epoll的模式不同的應(yīng)用選擇是不同的刷后,也跟其定位相關(guān):Redis-默認水平觸發(fā)的畴、nginx-邊緣觸發(fā)、go net 邊緣觸發(fā)尝胆、Java NIO-水平觸發(fā)丧裁、Netty-邊緣觸發(fā)。
持久化
回到正題含衔,Redis支持RDB煎娇、AOF持久化兩種,當(dāng)然了你也可以選擇兩種一起搞也就是混合持久化抱慌。
RDB說白了就是拉內(nèi)存快照逊桦,然后持久化到內(nèi)存中眨猎,是fork一個子進程來做這件事兒(這里會有一個新的線程出現(xiàn))抑进,很顯而易見拉快照并保存的期間的發(fā)生的數(shù)據(jù)變化是沒辦法記錄的,但是RDB這種方式恢復(fù)速度相對較快睡陪。
AOF是通過記錄操作命令來進行持久化的寺渗,并且其中做了一些類似于命令合并的優(yōu)化匿情,體積相對RDB較大,并且AOF命令一多恢復(fù)時間會無比漫長信殊。
最優(yōu)的就是RDB記錄內(nèi)存快照炬称,AOF記錄RDB期間發(fā)生的命令,以此進行數(shù)據(jù)重演是最合適的涡拘。
分布式方案
高可用對于一個系統(tǒng)來說往往是最重要的衡量標(biāo)準(zhǔn)之一玲躯,而做高可用最簡單的方式就是掛主從,然后在主從的基礎(chǔ)上做自動檢查和更替鳄乏,redis就是這么搞的跷车。
對于分布式系統(tǒng)而言,想突破處理極限橱野,做數(shù)據(jù)分片是肯定的朽缴,有水平分也有垂直分,而對于Redis這種key-value的NoSQL數(shù)據(jù)庫毫無疑問內(nèi)部是水平分片(如何做水平切分就引入了一致性hash等問題水援,想自定義切分規(guī)則就有了hashtag等標(biāo)示)
redis常見的集群方案有:碗豆?jié){codis方案密强、redis-cluster方案。
豌豆莢 codis方案
中心化配置存儲蜗元,proxy那一層控制分片規(guī)則
codis-ha實時監(jiān)測proxy的運行狀態(tài)或渤,如果有異常就會干掉,它包含了哨兵的功能(會依賴于類似k8s pod的功能奕扣,自動拉起)
codis-ha在Codis整個架構(gòu)中是沒有辦法直接操作代理和服務(wù)劳坑,因為所有的代理和服務(wù)的操作都要經(jīng)過dashboard處理
在Codis中使用的是Zookeeper來保存映射關(guān)系,由proxy上來同步配置信息成畦,其實它支持的不止zookeeper距芬,還有etcd和本地文件
Server group中包含了主從節(jié)點
codis是支持動態(tài)不停機擴容的,其實就是一個節(jié)點打標(biāo)循帐、歷史數(shù)據(jù)rehash的過程框仔,中間會涉及老hash值與新hash值請求轉(zhuǎn)發(fā)的過程。
redis-cluster方案
Redis-cluster是去中心化的沒有代理拄养,所以只能通過客戶端分片离斩。
槽跟節(jié)點的映射關(guān)系保存在每個節(jié)點上,每個節(jié)點每秒鐘會ping十次其他幾個最久沒通信的節(jié)點瘪匿,其他節(jié)點也是一樣的原理互相PING 跛梗,PING的時候一個是判斷其他節(jié)點有沒有問題,另一個是順便交換一下當(dāng)前集群的節(jié)點信息棋弥、包括槽與節(jié)點映射的關(guān)系等核偿。
客戶端操作key的時候先通過分片算法算出所屬的槽,然后隨機找一個服務(wù)端請求顽染。
圖片來源于:https://www.cnblogs.com/pingyeaa/p/11294773.html
一致性Hash
解決分布式系統(tǒng)中負載均衡的問題時候可以使用Hash算法讓固定的一部分請求落到同一server上漾岳,這樣每臺server固定處理一部分請求轰绵,起到負載均衡的作用,但是普通的余數(shù)hash伸縮性較差(新增或者減少server時映射關(guān)系會變)尼荆,所以需要對Hash算法進行改進實現(xiàn)所謂的一致性Hash
所謂的一致性Hash主要保證Hash算法的
單調(diào)性:有新的server加入到系統(tǒng)中時候左腔,應(yīng)保證原有的請求可以被映射到原有的或者新的server中去,而不會被映射到原來的其它server上去捅儒。
分散性:同一個用戶的請求盡可能落到同一個服務(wù)器上處理
平衡性:是指客戶端hash后的請求應(yīng)該能夠相對均勻分散到不同的server上去
為了做到這幾點液样,在引入hash環(huán)的思路的基礎(chǔ)上有引入了虛擬節(jié)點等措施來保證上述特性。
LevelDB
levelDB是同樣也是一個Key-value數(shù)據(jù)庫巧还,但是相對于Redis蓄愁、memcache來說,levelDB是基于內(nèi)存-磁盤來實現(xiàn)的狞悲,但在大部分場景下也表現(xiàn)出了不遜色于Redis撮抓、Memcache的性能。levelDB由google實現(xiàn)并開源摇锋,輕松支持billion量級的數(shù)據(jù)丹拯,并且性能沒有太大的衰退,下面來看一下LevelDB的具體實現(xiàn)荸恕。
LevelDB實現(xiàn)
既然是一個key-value 數(shù)據(jù)庫乖酬,顯而易見支持的api肯定有put/get/delete(delete實質(zhì)上就是put一個具有刪除標(biāo)的key)等操作,從這三個API入手去看下levelDB的實現(xiàn):
levelDB內(nèi)部存儲分為內(nèi)存存儲及磁盤存儲融求,內(nèi)存存儲的依賴的數(shù)據(jù)結(jié)構(gòu)是跳躍表(可以粗暴的理解為key有序的set集合咬像,默認字典序),一種查找時可以近似做到log(n)生宛,具備鏈表的快速增刪县昂、數(shù)組的快速查找等特性的數(shù)據(jù)結(jié)構(gòu)。圖中Mutable陷舅、Immutale實現(xiàn)都是跳表倒彰,Mutable是一種支持寫入和讀取的跳表,Mutable到達一定大小之后會觸發(fā)凍結(jié)操作來產(chǎn)生Immutale(只讀)莱睁,然后Immutale會持久化到磁盤中產(chǎn)生SSTable file(只讀)待讳,實際上就是一種不斷下沉的過程。
跳表中的key是一種復(fù)合結(jié)構(gòu)(包含value值)key: <internal_key_size,internal_key<key,sequence,type>,value_size,value>仰剿,需要單獨說的是sequence创淡,為全局自增序列號levelDB 遇到一個修改操作,全局序列號自動加一南吮。levelDB 中存儲了多個版本的value琳彩,就是靠這個序列號來標(biāo)記鍵值對的版本,序列號越大,對應(yīng)的鍵值對越新汁针。
put/delete
put/delete操作時寫入Mutable术辐,當(dāng)前Mutable已滿會產(chǎn)生一個新的供寫入砚尽,并且遵循write ahead log的原則施无,先寫入日志后寫入Mutable,如果發(fā)生機器故障時使用log文件恢復(fù)當(dāng)前Mutable必孤。
get
get操作時猾骡,先讀Mutable(毫無疑問是最新的),然后讀Immutale敷搪,最后讀SSTable file兴想,如果存在多個版本,則選擇最新的版本(序列號最大)進行返回赡勘。
細說數(shù)據(jù)下沉
這就是叫l(wèi)evelDB的原因
上面提到了數(shù)據(jù)下沉的過程嫂便,下面來仔細看一下這個過程:
磁盤內(nèi)的存儲結(jié)構(gòu)分為多層,層級的深度同容量成正比:capacity = level > 0 && 10^(level+1) M
由Mutable下沉到L0層(第一層磁盤文件)的過程稱為minor compact闸与,這個過程中完全是內(nèi)存數(shù)據(jù)直接存儲毙替,多個L0 SSTable file 中會出現(xiàn)key值重疊的情況,查找時需要比較版本號践樱。
由n層下沉到n+1層的過程稱為major compact厂画, 這個過程會對于上一層的文件進行多路歸并操作。
levelDB 性能優(yōu)化上做了哪些事情
數(shù)據(jù)內(nèi)存操作
寫操作完全基于內(nèi)存實現(xiàn)拷邢,速度無疑會很快袱院,但是相對于Redis來看,由于是多線程或者多協(xié)程操作瞭稼,會存在強鎖問題忽洛。讀操作,熱點數(shù)據(jù)內(nèi)存中大概率會讀到环肘,即使讀不到也會有下面“磁盤順序讀寫”來進一步保證性能脐瑰。
但是很顯然levelDB是一種適合寫多讀少的NoSQL數(shù)據(jù)庫。
磁盤順序讀寫
磁盤隨機讀寫和順序讀寫的性能差異是驚人的廷臼,levelDB正是利用了這一點來做的苍在。
隨機讀寫做下差異比較的話,普通磁盤的順序訪問速度跟SSD順序訪問速度差不多一致荠商,遠超隨機訪問的速度(差不多2倍多)寂恬,甚至能達到內(nèi)存隨機訪問的速度(這里舉的例子是指SAS磁盤),隨機讀寫相對于順序讀寫主要時間花費在循道上莱没,并且順序讀寫會預(yù)讀信息初肉,所以速度自然就差異很大了。
采用這用優(yōu)化思路的應(yīng)用有很多饰躲,比如Kafka消息文件的追加寫入牙咏。
精妙的細節(jié)
1臼隔、跳躍表保證了內(nèi)存操作的迅速。
2妄壶、L0層級以下摔握,分段存儲,先找文件后找key丁寄。
3氨淌、持久化到L0層時,直接拉快照數(shù)據(jù)伊磺,并不做額外操作盛正,L0層以下異步歸并下沉
4、當(dāng)Immutable持久化至磁盤成功時屑埋,會刪除對應(yīng)的log文件(減少存儲壓力)
5豪筝、LRU Cache,內(nèi)存跳躍表中讀不到時讀緩存摘能,在讀SStable文件续崖,盡可能內(nèi)存操作,由于SSTable文件不會發(fā)生變化徊哑,大膽緩存袜刷。
額外說幾句,這里的磁盤順序?qū)懠胺謱咏Y(jié)構(gòu)莺丑,其實本質(zhì)上就是一種LSM(Log-Structured Merge Tree)存儲引擎的思想著蟹,解決了B+樹隨機讀的問題,但是對應(yīng)的也犧牲了一定的讀性能梢莽,歸并操作都是為了優(yōu)化讀性能萧豆,類似的還有TSM(Time-Structured Merge Tree),有興趣可以看一下昏名,比如InfluxDB底層的存儲引擎經(jīng)歷了從LevelDB到BlotDB涮雷,再到選擇自研TSM的過程,TSM其實就是針對LSM引擎文件句柄過多轻局、無TTL機制洪鸭、減緩刪除流量壓力等所產(chǎn)生出的一種結(jié)構(gòu),本質(zhì)的思想其實還是LSM仑扑。
levelDB可靠性保證
不丟數(shù)據(jù)
本著先寫日志再寫內(nèi)存的原則览爵,宕機后也能夠保證恢復(fù)mutable、immutable镇饮,所以都是能保證數(shù)據(jù)不丟的蜓竹,也就是不會丟失更新。
上鎖 & 只讀
首先對于Mutable的操作是上鎖的,能夠保證操作的正確性俱济。Immutable和磁盤文件完全只讀嘶是,異步歸并操作時也都是只讀老文件,產(chǎn)生新的文件蛛碌。
RockesDB
RockesDB 同樣也是一個key-value的NoSQL數(shù)據(jù)庫聂喇,如果看Rockes的整體結(jié)構(gòu)與LevelDB基本上是相同的,本質(zhì)上都是基于LSM實現(xiàn)的key-value存儲機制左医,僅僅是實現(xiàn)差異上不同而已授帕。
相對于LevelDB來說同木,壓縮算法除了level的snappy還增加了zlib,bzip2浮梢,數(shù)據(jù)備份方面支持增量備份和全量備份,支持單進程中啟動多個實例彤路,可以有多個memtable秕硝,解決put和compact的速度差異瓶頸,內(nèi)存中數(shù)據(jù)結(jié)構(gòu)出了之前的跳躍表洲尊,還支持了hash+list远豺、hash+skiplist兩個結(jié)構(gòu)。
這個可以簡單粗暴的理解為LevelDB的加強版吧坞嘀,Rockes 沒有太過深入的學(xué)習(xí)過躯护,所以只說了下我對它當(dāng)前的認知,最核心的還是LSM思想和LevelDB的實現(xiàn)思路丽涩。
性能上還會有一定的差異棺滞,這個沒有驗證過,不敢下結(jié)論矢渊。
memchache
Memcached是一種基于內(nèi)存的key-value存儲继准,用來存儲小塊的任意數(shù)據(jù)(字符串、對象)矮男,整體來看memcache就是一種分布式內(nèi)存對象緩存系統(tǒng)移必,通常用來存儲數(shù)據(jù)庫調(diào)用、api接口調(diào)用毡鉴、頁面渲染屬性的緩存崔泵。
memcache的實現(xiàn)相對簡單,主要表現(xiàn)為協(xié)議簡單猪瞬、命令簡單憎瘸、內(nèi)部數(shù)據(jù)結(jié)構(gòu)簡單,memcache也相對高效撑螺,主要表現(xiàn)為基于libevent的事件處理模型(對于select含思、poll、epoll支持相對完備,熟悉C++的同學(xué)對其應(yīng)該相對熟悉)含潘、完全基于內(nèi)存處理饲做,關(guān)于memcache的使用可以直接看一下https://www.runoob.com/Memcached/Memcached-tutorial.html 菜鳥教程。
詳細說一下libevent遏弱,libevent可以簡單粗暴的理解為一個C++的網(wǎng)絡(luò)編程庫(對比Java的netty盆均、Go的net),在libevent的基礎(chǔ)上就不用手?jǐn)]epoll了漱逸,memcache的libevent的默認模式跟nginx的網(wǎng)絡(luò)連接處理比較類似泪姨,起一個主線程監(jiān)聽并建立連接,然后每個核心綁定一個work線程用于處理數(shù)據(jù)任務(wù)饰抒,這也是網(wǎng)絡(luò)并發(fā)編程最常用的模式肮砾。因為多線程本身并不會降低時延,并且會額外帶來一部分系統(tǒng)開銷袋坑,主要用于充分提升CPU使用的仗处,所以最合適的就是一個核一個線程。
memcache在我的認知范圍內(nèi)并沒有什么很經(jīng)典的高可用方案(通常來說就是掛主從保證可用枣宫,然后一致性Hash做分片分?jǐn)倖畏?wù)壓力)
Tair
Tair是由淘寶網(wǎng)自主開發(fā)的Key/Value結(jié)構(gòu)數(shù)據(jù)存儲系統(tǒng)婆誓,內(nèi)部支持四種引擎分別是:mdb、rdb也颤、kdb洋幻、ldb治拿,分別基于memcached居暖、Redis等限、Kyoto Cabinet截亦、leveldb開發(fā)完成政供。
Tair是由淘寶網(wǎng)自主開發(fā)的Key/Value結(jié)構(gòu)數(shù)據(jù)存儲系統(tǒng)掏觉,內(nèi)部支持四種引擎分別是:mdb投剥、rdb迷帜、kdb输吏、ldb权旷,分別基于memcached、Redis贯溅、Kyoto Cabinet拄氯、leveldb開發(fā)完成。
為什么需要tair
整體來看就是編程接口上的抽象它浅,對于原生K-V存儲應(yīng)用的分布式相關(guān)方案優(yōu)化译柏。
1、提供標(biāo)準(zhǔn)的編程接口姐霍,切換底層存儲引擎時對代碼的改動是非常小的鄙麦,釋放人力解決業(yè)務(wù)問題典唇。
2、當(dāng)年的redis是不包含sharding解決方案的胯府,而rair看中了這一點幫忙解決了這個問題介衔。
3、相對于原始redis骂因、memcache集群提供了多機架炎咖、多數(shù)據(jù)中心的支持。
4寒波、相對輕量級的中心節(jié)點乘盼,client對于路由信息的查詢是發(fā)生在啟動時,真正交互時訪問cache來獲取信息俄烁,所以并不依賴于傳統(tǒng)意義上的中心節(jié)點绸栅。
5、更加強大的副本備份功能支持猴娩,使用者可以自定義備份數(shù)阴幌,發(fā)生故障勺阐,容災(zāi)時會自動將請求路由到新表卷中,整個過程對用戶透明,服務(wù)不中斷渊抽。
tair整體結(jié)構(gòu)
一個Tair集群主要包括3個必選模塊:configserver蟆豫、dataserver和client,一個可選模塊:invalidserver懒闷。
簡單來看就是一種這樣的結(jié)構(gòu):
一個集群中包含2臺configserver及多臺dataServer十减。兩臺configserver互為主備并通過維護和dataserver之間的心跳獲知集群中存活可用的dataserver,構(gòu)建數(shù)據(jù)在集群中的分布信息(對照表)愤估。dataserver負責(zé)數(shù)據(jù)的存儲帮辟,并按照configserver的指示完成數(shù)據(jù)的復(fù)制和遷移工作。client在啟動的時候玩焰,從configserver獲取數(shù)據(jù)分布信息由驹,根據(jù)數(shù)據(jù)分布信息和相應(yīng)的dataserver交互完成用戶的請求。invalidserver主要負責(zé)對等集群的刪除和隱藏操作昔园,保證對等集群的數(shù)據(jù)一致蔓榄。
性能優(yōu)化
集成優(yōu)秀存儲引擎
大部分牛逼的開源組件基本都是站在巨人的肩膀上編程,tair也是如此默刚,集成了levelDB甥郑、Redis、memcache等優(yōu)秀存儲引擎的特點荤西,在數(shù)據(jù)存儲澜搅、IO方面做的都不錯伍俘。
但是需要注意的是 僅作為存儲引擎來使用的,比如redis網(wǎng)絡(luò)連接處理還有集群方案等不要扯進來勉躺。
熱點識別 & 橫向擴展能力
雖然看上去tair就是在各大優(yōu)秀存儲引擎上面包了一個殼养篓,然后提供了標(biāo)準(zhǔn)的編程接口和k-v解決方案,但是tair對于性能上也是做了一些自己的優(yōu)化的赂蕴,其中很經(jīng)典的一點就是熱點數(shù)據(jù)的識別和專項處理柳弄。
但是從業(yè)界的測評來看,tair-rdb分布式解決方案基本是比redis分布式方案性能慢1/5左右的概说,與網(wǎng)絡(luò)IO處理有關(guān)碧注。
當(dāng)幾個key出現(xiàn)熱點時,而根據(jù)hash算法的得到的結(jié)果恰巧這幾個就在一個server上糖赔,很容易拖垮集群中的某個機器甚至集群萍丐,但是在電商或者支付領(lǐng)域單熱點賬戶所帶來的熱點流量可能會非常的常見,對于這類數(shù)據(jù)tair有一套自己的解決方案:
熱點識別
要識別熱點放典,首先要定義熱點逝变。
dataServer收到客戶端的請求后,由每個具體處理請求的工作線程(Worker Thread)進行請求的統(tǒng)計奋构。工作線程用來統(tǒng)計熱點的數(shù)據(jù)結(jié)構(gòu)均為ThreadLocal模式的數(shù)據(jù)結(jié)構(gòu)壳影,完全無鎖化設(shè)計。熱點識別算法使用精心設(shè)計的多級加權(quán)LRU鏈和HashMap組合的數(shù)據(jù)結(jié)構(gòu)弥臼,在保證服務(wù)端請求處理效率的前提下進行請求的全統(tǒng)計宴咧,支持QPS熱點和流量熱點(即請求的QPS不大但是數(shù)據(jù)本身過大而造成的大流量所形成的熱點)的精準(zhǔn)識別。
大家百度一下tair熱點定義的方式径缅,多半會出現(xiàn)如下公式:
多級緩存&熱點專項處理
對于讀熱點來說掺栅,我們要處理的其實就是IO壓力、內(nèi)部數(shù)據(jù)處理壓力纳猪,對于網(wǎng)絡(luò)IO的處理tair選擇使用專用線程處理熱點IO連接氧卧,在保證效率充分發(fā)揮CPU使用率的同時盡可能不影響到其他的key的處理,對于內(nèi)部處理壓力氏堤,tair專門為熱點Key做了緩存沙绝,先訪問內(nèi)部緩存如果沒有命中再去真正的數(shù)據(jù)源。并且每臺機器的熱點數(shù)據(jù)存儲都是相同的丽猬,對于熱點數(shù)據(jù)的壓力相當(dāng)于就分?jǐn)偟搅烁鱾€機器上宿饱,一定程度上來看,讀熱點的key做到了橫向擴展脚祟。
對于寫熱點來說谬以,采用的是微批處理的思路,合并寫入由桌。
所以Tair 的整體架構(gòu)就變成了這樣:
可靠性提升
先說缺點为黎,中心節(jié)點雖然是主備高可用的邮丰,但實際上它沒有類似于分布式仲裁的機制,所以在網(wǎng)絡(luò)分割的情況下铭乾,它是有可能發(fā)生“腦裂”的剪廉。
多機架和多數(shù)據(jù)中心的支持
為了更進一步的提高系統(tǒng)存儲的可靠性,configserver在構(gòu)建對照表的時候炕檩,可以配置機房和機架信息斗蒋,這樣在配置備份數(shù)的時候就可以分不到不同的機房或者機架上,這樣就避免了同一個機房或者機架同時故障笛质,容災(zāi)可靠性指數(shù)級提升泉沾,但是這里的特性是需要結(jié)點物理分布的支持的。
針對Kyoto Cabinet的簡單補充妇押。
memcache跷究、redis、levelDB 上面都已經(jīng)詳細講過了敲霍,那KC(Kyoto Cabinet)是個啥呢俊马?
在看KC之前首先需要知道“DBM”,DBM是一個輕量級的數(shù)據(jù)庫肩杈,但是不是標(biāo)準(zhǔn)的數(shù)據(jù)庫柴我,純粹以二進制存儲常用于系統(tǒng)底層的數(shù)據(jù)庫,性能是get操作非撤嫣瘢快屯换,但是put操作比較慢,整個數(shù)據(jù)庫就是一個文件与学,寫入時就是整個文件的更新。而KC就可以簡單粗暴的理解為是一種DBM嘉抓。KC底層文件實現(xiàn)支持HASH存儲也支持B+樹存儲索守,兩種實現(xiàn)方式的差異其實就是HASH結(jié)構(gòu)做存儲與B+樹結(jié)構(gòu)做存儲的差異。
B+樹:每個操作的時間復(fù)雜度是 O(log N)抑片,但由于B+tree支持對key順序的連續(xù)訪問卵佛,這可以實現(xiàn)對字符串的前向匹配查找和整數(shù)的范圍查找。
Hash表:每個操作的時間復(fù)雜度是 O(1)敞斋,數(shù)據(jù)庫的大小小于內(nèi)存大小截汪,性能表現(xiàn)為內(nèi)存的速度。
通常外面會包一層tokyotyrant(網(wǎng)絡(luò)交互協(xié)議)植捎,這樣就能夠通過HTTP訪問或者memcache協(xié)議來訪問了衙解,別看很古老很簡陋,但是性能也是不錯的焰枢。
美團基于redis cluster的Squirrel方案
該部分資料來源于美團技術(shù)團隊蚓峦,這一篇文章寫的十分細致舌剂,我這里只做幾個點描述,大家要了解可以直接去看這篇文章:https://tech.meituan.com/2020/07/01/kv-squirrel-cellar.html
節(jié)點容災(zāi)
自定義高可用節(jié)點暑椰,將原生Redis 30秒的鑒別時間縮短為5秒霍转,HA節(jié)點負責(zé)機器的增減(包括臨時抖動或者永久性宕機),進一步縮短了部分key不可用的時間一汽。
跨地域容災(zāi)
相對于同地域機房間的網(wǎng)絡(luò)而言避消,跨地域?qū)>€很不穩(wěn)定;第二召夹,跨地域?qū)>€的帶寬是非常有限且昂貴沾谓。并且在單元化部署、異地多活架構(gòu)的背景下戳鹅,美團做了集群間的復(fù)制方案均驶,
這里可以簡單的把通過redis復(fù)制協(xié)議拉數(shù)據(jù)的集群看作是從集群(從庫)
數(shù)據(jù)遷移
對于新加入節(jié)點或者機器遷移方面,美團做了較多的事情枫虏,Migrate 命令會阻塞工作線程妇穴,對于小key的遷移,通過成功率/相應(yīng)時間來動態(tài)控制遷移速率隶债,在保證成功率的同時盡可能提升速率腾它,維持一種動平衡。
對于大Key的遷移死讹,新增了異步Migrate操作瞒滴,主線程正常處理流程,命中正在異步遷移的key時直接報錯赞警,犧牲小key保全大局妓忍。
持久化優(yōu)化
針對大數(shù)據(jù)fork子進程時的秒級阻塞、磁盤IO抖動下的AOF對成功率存在影響等問題愧旦。
日常不做RDB僅記錄BackLog世剖,業(yè)務(wù)低峰期生成RDB、異步寫AOF笤虫,會導(dǎo)致關(guān)鍵時刻可靠性下降旁瘫,但提升了日常抖動時的影響面。
熱點優(yōu)化
對于熱點key的處理思路于tair不同琼蚯,美團采用統(tǒng)計熱點后將熱點數(shù)據(jù)統(tǒng)一放置在熱點槽點的方式酬凳,主要用于熱點隔離,并且熱點機器的迅速擴容遭庶,但是實現(xiàn)方式個人感覺沒有tair巧妙(直接在現(xiàn)有存儲機器上使用閑置CPU而不是單起機器宁仔,熱點請求單獨控制一定程度上也做了隔離,單個key的熱點無法延展)
美團cellar方案
該部分資料來源于美團技術(shù)團隊罚拟,這一篇文章寫的十分細致台诗,我這里只做幾個點描述完箩,大家要了解可以直接去看這篇文章:https://tech.meituan.com/2020/07/01/kv-squirrel-cellar.html
cellar
cellar 跟tair的實現(xiàn)思路上是類似的,豐富了一些節(jié)點的能力拉队,比如在中心節(jié)點與客戶端之間新增了一層ob(與ZK的Observer類似 )弊知,把大量的業(yè)務(wù)請求與集群的大腦做了天然的隔離,防止路由表請求影響集群的管理粱快,并且ob節(jié)點能夠輕松做水平擴展秩彤,并且針對腦裂問題,在中心節(jié)點之上架設(shè)了zookeeper事哭,保證元數(shù)據(jù)的高可靠漫雷。
這里做的事兒讓我想起來前l(fā)eader經(jīng)常提到的、軟件工程里常說的“加一層能夠解決很多問題鳍咱,也能解決很多問題”降盹,這里cellar做的這些事其實就是根據(jù)架構(gòu)訴求特性加了一層,雖然解決了一些問題谤辜,但是響應(yīng)的架構(gòu)的復(fù)雜度也提升了蓄坏,我們需要額外的關(guān)注ob信息的實時性,增加zk的管理和控制丑念,機器成本相應(yīng)的增加了(出現(xiàn)故障的概率也會提升)涡戳。
Cellar 節(jié)點容災(zāi)
因為集群節(jié)點的故障往往是短暫的(機器臨時抖動、網(wǎng)絡(luò)臨時抖動等)脯倚,所以在節(jié)點機器上是存有故障前的一部分?jǐn)?shù)據(jù)的渔彰,如果恢復(fù)時做全量的恢復(fù),時間成本是很高的推正,并且減少了一臺實例對于集群來說也是存在較大風(fēng)險的恍涂,所以說如何快速恢復(fù)節(jié)點并重演數(shù)據(jù)變的十分重要,這里cellar關(guān)注的就是這一點舔稀,采用handoff機制來解決的短暫故障帶來的影響乳丰。
比如當(dāng)A節(jié)點故障之后,中心節(jié)點識別到之后内贮,請求根據(jù)路由表請求到B節(jié)點,B節(jié)點對于A故障期間的數(shù)據(jù)進行寫log汞斧,當(dāng)A節(jié)點恢復(fù)之后夜郁,B節(jié)點將故障期間的Log回寫至A節(jié)點,當(dāng)A節(jié)點重演完故障&故障恢復(fù)期間所有的數(shù)據(jù)時粘勒,A節(jié)點就可以正常處理請求了竞端。
這樣除了容災(zāi)方面外,我們更容易做節(jié)點升級庙睡,比如直接摘掉A節(jié)點做升級處理事富,然后觸發(fā)Handoff機制技俐,升級后回寫升級期間的數(shù)據(jù)即可。
Cellar 跨地域容災(zāi)
主要是由某一個階段做數(shù)據(jù)copy统台,而不是每個結(jié)點都做雕擂,這樣一定程度上減少了跨城IO專線帶寬的占用
Callar 性能優(yōu)化
快慢隊列
針對性能優(yōu)化話,cellar做了一些事情贱勃,最重要的一點就是快慢隊列井赌。
由于集群性能和可用性很多時候都是因為某一部分的請求給拖掉的,所以需要將這一部分請求做隔離(這一點跟Tair對于熱點key的思路相對類似)贵扰,根據(jù)請求的特點仇穗,拆分請求,分發(fā)到不同的隊列中戚绕,由不同的線程進行處理纹坐。
熱點key處理
熱點key的處理機制跟tair基本一致,server端做緩存(并且同步到每一個結(jié)點上)舞丛、客戶端做緩內(nèi)存
后記
整體整體看下來耘子,業(yè)界常用的k-v解決方案有 redis系列:codis、cluster方案瓷马、美團kv Squirrel方案拴还,Tair系列:tair方案、美團 kv cellar方案欧聘,底層的存儲常用的有Redis片林、LevelDB、memcache等怀骤。
我們可以根據(jù)不同的業(yè)務(wù)訴求和未來的業(yè)務(wù)發(fā)展趨勢選擇不同的方案落地费封,還是相對有選擇的,在看這些實現(xiàn)方案的落地過程中也不難總結(jié)出一些經(jīng)驗
性能優(yōu)化方面
1蒋伦、高效的數(shù)據(jù)結(jié)構(gòu)真的很管用弓摘,如果裸寫不到那種程度就用現(xiàn)成高效的組件(比如跳躍表、hash表痕届、LSM韧献、B tree)。
2研叫、網(wǎng)絡(luò)編程方面锤窑,其實就是根據(jù)業(yè)務(wù)場景合理的利用epoll,對大眾開發(fā)者來看就是合理利用這些對epoll的封裝庫嚷炉。
3渊啰、微批處理思路可以讓系統(tǒng)的吞吐量提升一個數(shù)量級(比如kafka mirc-batch、tair合并寫入)。
4绘证、熱點請求與常規(guī)請求分別處理隧膏,熱點請求掛緩存、做水平切分嚷那。
5胞枕、合理利用存儲:內(nèi)存>磁盤順序訪問>磁盤隨機訪問,越貴的存儲介質(zhì)越好车酣。
6曲稼、合理的并發(fā)模型和并發(fā)模型能解決更多問題,線程湖员、協(xié)程什么的并不是越多贫悄。
7、業(yè)務(wù)量大就做分片(帶來的性價比提升比起單機搞到頂要強很多)娘摔,并且合理的一致性Hash算法能減少分布式環(huán)境下請求的80%的問題窄坦。
8、數(shù)據(jù)復(fù)制由點及面凳寺,帶寬會少很多的鸭津,尤其是跨城IO。
9肠缨、要根據(jù)業(yè)務(wù)場景選擇合適的組件及結(jié)構(gòu)逆趋,不要啥都跟風(fēng)一把梭。
10晒奕、業(yè)務(wù)代碼很大程度決定了系統(tǒng)的性能高低闻书,數(shù)據(jù)最小化原則,并不只在合規(guī)之類的場景要用脑慧,數(shù)據(jù)存取也是如此魄眉。
高可用方面
1、先上主從闷袒、再上HA坑律,基本就能解決大部分問題了。
2囊骤、如果存在數(shù)據(jù)的強一致性訴求或者系統(tǒng)結(jié)構(gòu)中可能存在腦裂問題晃择,paxos之類的搞起來。
3也物、完善監(jiān)控藕各、報警機制,自動化做的再完備也會有意想不到的事情發(fā)生焦除,有多人工,就有多智能作彤。
4膘魄、系統(tǒng)架構(gòu)盡可能簡單乌逐,能夠滿足實際訴求,做到易擴展就做夠了创葡,不要給自己找事兒浙踢。
5、高保措施要反復(fù)考核灿渴,通過演練來做而不是理論支撐洛波,認為可能發(fā)生的問題就一定會發(fā)生,如果是強依賴訴求骚露,節(jié)點容災(zāi)蹬挤、跨城容災(zāi)這些有條件就搞起來吧。
6棘幸、性能優(yōu)化層面做好焰扳,是最大程度的保證可用性的措施,至少不會自己把系統(tǒng)寫掛误续。
7吨悍、高可用要從微觀和宏觀同時來看,保證每個端到端的可用性處理和降級兜底蹋嵌、保證系統(tǒng)可容災(zāi) 一樣重要育瓜。
其他問題
當(dāng)出現(xiàn)有問題或者訴求解決不了時,就想一想加一層(但是要能cover住加一層所帶來的問題)
當(dāng)出現(xiàn)優(yōu)化不了的問題時栽烂,就看一下其他牛逼的開源組件的實現(xiàn)躏仇,思想都是類似的。
就先說這么多吧愕鼓。