前言:
在分布式Web程序設(shè)計中,解決高并發(fā)以及內(nèi)部解耦的關(guān)鍵技術(shù)離不開緩存和隊列,而緩存角色類似計算機硬件中CPU的各級緩存。如今的業(yè)務(wù)規(guī)模稍大的互聯(lián)網(wǎng)項目,即使在最初beta版的開發(fā)上,都會進(jìn)行預(yù)留設(shè)計。但是在諸多應(yīng)用場景里,也帶來了某些高成本的技術(shù)問題,需要細(xì)致權(quán)衡。
本系列主要圍繞 分布式系統(tǒng)中服務(wù)端緩存相關(guān)技術(shù) ,也會結(jié)合朋友間的探討提及自己的思考細(xì)節(jié)。文中若有不妥之處,懇請指正。本文是本系列的第一篇,打算盡可能詳細(xì)地談?wù)劸彺孀陨淼幕A(chǔ)設(shè)計應(yīng)用,以及相關(guān)的操作細(xì)節(jié)等(具體應(yīng)用主要以Redis舉例)。
一、服務(wù)端數(shù)據(jù)緩存
1
一種區(qū)分
緩存基于不同的條件有很多種劃分方式,本地緩存(Local cache)和分布式緩存(Distributed cache)是一種常見分類,兩者自身又包含很多細(xì)類。
本地并不是指程序所在本地服務(wù)器(從嚴(yán)格概念來說),而是更細(xì)粒度的指位于程序自身的內(nèi)部存儲空間,而分布式更多強調(diào)的是存儲在進(jìn)程之外的一個或者多個服務(wù)器上,彼此交互通信,在具體軟件項目的設(shè)計和應(yīng)用中,多數(shù)時候是混合一體。當(dāng)然,個人認(rèn)為對緩存本質(zhì)的理解才是最重要的,至于概念上的分類只是一個不同理解下的劃分而已。
2
一些技術(shù)成本
在具體項目架構(gòu)設(shè)計時,單純使用前者(本地緩存)的開發(fā)成本毋庸置疑是極低的,主要考慮的是本機的內(nèi)存負(fù)載或者極少量的磁盤I/O影響。而后者的設(shè)計初心是為了利于分布式程序之間緩存數(shù)據(jù)的高效共享和管理,除了考慮緩存所在服務(wù)器自身的內(nèi)存負(fù)載,設(shè)計時更需要充分考慮網(wǎng)絡(luò)I/O、CPU的負(fù)載,以及某些場景下的磁盤I/O的代價,同時還在具體設(shè)計時盡可能規(guī)避和權(quán)衡整體穩(wěn)定性和效率,這些不僅僅只是作為緩存服務(wù)器的硬件成本和技術(shù)維護(hù)。需要謹(jǐn)慎考慮的底層問題包括緩存間通信、網(wǎng)絡(luò)負(fù)載和延遲等各種需要權(quán)衡的細(xì)節(jié)。
其實如果理解了緩存本質(zhì)就該知道,任何存儲介質(zhì)在適當(dāng)?shù)膱鼍跋露伎梢猿洚?dāng)一個高效的緩存角色并進(jìn)行項目集成和緩存間集群。常見主流的Memcached和Redis等均是屬于后者范疇,甚至可以包括如基于NoSQL設(shè)計的MongoDB這類文檔數(shù)據(jù)庫(但這是從角色角度講,而狹義劃分上這是基于磁盤的存儲庫,需要注意,各有專攻)。
這些第三方緩存在進(jìn)行項目集成和緩存間集群,也需要解決一些問題。甚至項目迭代到了后期階段,往往還需要具備較高專業(yè)知識的運維同時參與,并且在開發(fā)中的邏輯設(shè)計和代碼實現(xiàn)也會增加一定的工作量。所以有時候在具體項目的設(shè)計上,一方面要盡可能預(yù)留,一方面還得根據(jù)實際情況盡可能精簡。
額外說下其他體會:在個人有限的技術(shù)學(xué)習(xí)和實踐里,關(guān)于節(jié)點數(shù)據(jù)交互,尤其是服務(wù)間通信,是不存在完美的閉環(huán)的,理論上也都是在“當(dāng)前階段”面向“高一致”的權(quán)衡罷了(大概跟生活是一樣的吧)。
二、緩存數(shù)據(jù)庫結(jié)構(gòu)的設(shè)計細(xì)節(jié)
由于目前個人工作中大多數(shù)情況應(yīng)用的是Redis 3.x,以下若有特性關(guān)聯(lián),均是以此作為參照說明。
1
實例(Instance)
根據(jù)業(yè)務(wù)場景,公共數(shù)據(jù)和業(yè)務(wù)耦合數(shù)據(jù),一定分別使用不同的實例。如果是單實例,才可以考慮以DB劃分。當(dāng)你使用的是Redis,那么DB在Redis里是有數(shù)據(jù)隔離,但沒有嚴(yán)格權(quán)限限制,所以劃庫只是一種選擇。在Cluster集群里則是保持默認(rèn)單個庫,不過實際中我會嘗試根據(jù)項目大小來調(diào)整,至于在哪個開發(fā)階段則是作為預(yù)留設(shè)計。
額外需要注意的是,作為重度依賴服務(wù)器內(nèi)存的緩存產(chǎn)品,如果開啟了持久化(后面會提到),并且在為并發(fā)量極大的服務(wù)提供支持時,服務(wù)器硬件資源會出現(xiàn)大量搶占,請結(jié)合持久策略配置,考慮實例是否進(jìn)行分盤存儲。
持久化本質(zhì)是將內(nèi)存數(shù)據(jù)同步寫入硬盤(刷盤),而磁盤I/O實在有限,被迫的寫入阻塞除了造成線程阻塞和服務(wù)超時,還會導(dǎo)致額外異常甚至波及其他底層依賴服務(wù)。當(dāng)然,我的建議是,如果條件允許,最好是在項目初期設(shè)計時就進(jìn)行規(guī)劃并確定。
2
緩存“表”(Table)
一般緩存中并沒有傳統(tǒng)RDBMS中直觀的表概念(往往以鍵值對“KV”形式存在),但從結(jié)構(gòu)上來講,鍵值對本身就可以組裝為各種表結(jié)構(gòu)。一般我會先生成數(shù)據(jù)庫表關(guān)系圖,然后分析什么時候存儲字符串,什么時候存儲對象,然后使用緩存鍵(KEY)進(jìn)行表和字段(列)分割。
假定需要存儲一個登錄服務(wù)器表數(shù)據(jù),包含字段(列):name、sign、addr,那么可以考慮將數(shù)據(jù)結(jié)構(gòu)拆分為以下形式:
{ key : "server:name" , value : "xxxx" }
{ key : "server:sign" , value : "yyyy" }
{ key : "server:addr" , value : "zzzz" }
需要注意的是,往往在分布式緩存產(chǎn)品中,例如Redis,存在多種數(shù)據(jù)結(jié)構(gòu)(如String、Hash等),還需要根據(jù)數(shù)據(jù)關(guān)聯(lián)性和列的數(shù)量,來選擇對應(yīng)緩存的存儲數(shù)據(jù)結(jié)構(gòu),相關(guān)存儲空間和時間復(fù)雜度是完全不同的,而這個在初期階段是很難感受到的。
同時,就算緩存的內(nèi)存設(shè)置的足夠大,剩余也很多,也同樣需要考慮類似RDBMS中的單表容量問題,控制條目數(shù)量不能無限增長(比如預(yù)知到存儲條目可以輕松達(dá)到百萬級),“分庫分表”的設(shè)計思路都是相通的。
3
緩存鍵(Key)
上面提到了基于緩存鍵來設(shè)計表,這里再單獨說明一下鍵相關(guān)的個人規(guī)范。在鍵長度足夠簡短的前提下,如果關(guān)聯(lián)相同業(yè)務(wù)模塊,則必須設(shè)計為以同一個標(biāo)識(代號)開頭,目的是方便查找和統(tǒng)計管理。
如用戶登錄服務(wù)器列表:
{ key : "ul:server:a" , value : "xxxx" }
{ key : "ul:server:b" , value : "yyyy" }
另外,每個獨立業(yè)務(wù)系統(tǒng)可考慮配置一個唯一的通用前綴標(biāo)識。當(dāng)然,這里不是必需,若實際工作中,如果使用的是不同庫,則可以忽略。
4
緩存值(value)
緩存中的值(這里指單一條目)的大小沒有平均標(biāo)準(zhǔn),但Size自然是越小越好(若使用的是Redis,一次操作的value較大會直接影響整個Redis的響應(yīng)時間,不僅僅是指網(wǎng)絡(luò)I/O)。如果存儲占用空間直達(dá)10M+,建議考慮關(guān)聯(lián)的業(yè)務(wù)場景是否可以拆分為熱點和非熱點數(shù)據(jù)。
5
持久化(Permanence)
上面也簡單提了下,一般來說,持久和緩存本身是沒有直接關(guān)系的,可以粗略想象為一個面向硬盤一個面向內(nèi)存。但如今的Web項目里,有些業(yè)務(wù)場景是高度依賴緩存的,持久化可以一方面幫助提高緩存服務(wù)重啟后的快速恢復(fù),另一方面提供特定場景下的存儲特性。當(dāng)然,由于持久化必然需要犧牲一些性能,包括CPU的搶占和硬盤I/O影響。不過大多數(shù)時候是利大于弊,建議在應(yīng)用緩存的時候,沒有特別情況的話,盡量搭配持久化,無論是使用自身機制還是第三方來實現(xiàn)。
如果是使用的Redis,其自身就具備相關(guān)持久策略,包含AOF和RDB,我在大多數(shù)情況下是兩者同時配置的(當(dāng)然,最新官方版本本身也提供了混合模式)。如果在一些非高并發(fā)的場景下,或者說在一些中小項目的管理模塊里,僅僅只是作為優(yōu)化手段,確定了不需持久,也可以直接設(shè)置關(guān)閉,節(jié)約性能開銷損耗,但建議在程序中將該實例做好標(biāo)注,確保該實例的公共使用范圍。
6
淘汰(Eliminate)
緩存如果無限制的增長,即使設(shè)置了較短的過期(Expiration ),在一些時間點上,高并發(fā)的一批大數(shù)據(jù)會在較短時間內(nèi)就達(dá)到了可使用內(nèi)存的峰頂,此時程序中與緩存服務(wù)器的交互會出現(xiàn)大量延遲和錯誤,甚至給服務(wù)器自身都帶來了嚴(yán)重的不穩(wěn)定性。所以在生產(chǎn)環(huán)境里盡量給緩存配置大內(nèi)存限制,以及適當(dāng)?shù)奶蕴呗浴?/p>
如果使用的是Redis,自身淘汰策略選擇比較靈活。
個人的設(shè)計是,在數(shù)據(jù)呈現(xiàn)類似冪律分布情況下,總有大量數(shù)據(jù)訪問較低,我會選擇配置allkeys-lru、volatile-lru,將最少訪問的數(shù)據(jù)進(jìn)行淘汰。再比如緩存是作為日志應(yīng)用的,那么我一般是項目前期是配置no-enviction,后期會配置為volatile-ttl。
當(dāng)然,我也見過一種特殊業(yè)務(wù)下的設(shè)計,緩存直接用來作為輕量的持久數(shù)據(jù)庫使用,而且是終端,開始覺得有些新奇,后來發(fā)現(xiàn)是非常符合業(yè)務(wù)設(shè)計的(比如幾乎沒有任何復(fù)雜邏輯和強事務(wù))。所以合情合理,確實不應(yīng)該禁錮在傳統(tǒng)設(shè)計里,畢竟架構(gòu)總是基于業(yè)務(wù)去實時組合和改變的。
順便在此給大家推薦一個Java架構(gòu)方面的交流學(xué)習(xí)群: 698581634 ,里面會分享一些資深架構(gòu)師錄制的視頻資料:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化這些成為架構(gòu)師必備的知識體系,主要針對Java開發(fā)人員提升自己,突破瓶頸,相信你來學(xué)習(xí),會有提升和收獲。
三、一級緩存的基礎(chǔ)CURD及相關(guān)
1
新增(Create)
如果沒有特殊業(yè)務(wù)需求(如上面提到的),插入必須設(shè)置過期時間。同時,盡量保證過期隨機性。如果是進(jìn)行批量緩存,則個人的做法是保證設(shè)置的過期時間上至少是分散的,目的是為了降低緩存雪崩等風(fēng)險和影響(關(guān)于這些我會在以后的擴展篇里嘗試闡述)。
如:批量緩存的對象是一個結(jié)果集,條目有10萬條,緩存時間基礎(chǔ)為 60*60*2(sec),現(xiàn)在需要同時進(jìn)行緩存。我的做法是默認(rèn)生成一個隨機數(shù),如random(范圍 0 - 1000),過期時間則設(shè)置為( 60*60*2 + random ) 。
2
修改(Update)
更新一條緩存的數(shù)據(jù),注意是否需要重新調(diào)整過期時間。同時在很多場合,如多個緩存間同步時,建議直接刪除該緩存,而不是更新緩存。修改操作很多時候是關(guān)聯(lián)到DB間的同步操作的,相對考究的多一些,需要權(quán)衡分布式事務(wù)上的問題,后續(xù)文章里會寫到。
3
讀?。≧ead)
查找緩存時,如果存在多條,并確定數(shù)據(jù)量不大,務(wù)必使用嚴(yán)格匹配key的模式,而盡量不要使用通配符方式。雖然發(fā)送指令的key數(shù)據(jù)變長了,但卻避免了不必要的緩存內(nèi)的搜索性能損耗。
例如單純相信Redis里自身的存儲優(yōu)化,無限制的使用 keys pattern而不考慮時間復(fù)雜度,同時造成大量線程阻塞(這里與主從復(fù)制無關(guān))。如果折中使用scan分頁替代,也并非一種“無憂”的實現(xiàn),一是需要在程序代碼的封裝里設(shè)置較低的容量,二是請務(wù)必在程序邏輯里對數(shù)據(jù)幻讀等潛在問題做相關(guān)的管控處理。
另外可以額外類比一種場景,操作DB中的大表,命中的熱點數(shù)據(jù)分布靠后。
4
刪除/清空(Delete/Clear)
刪除緩存,一般有直接移除和設(shè)置時間過期(并不是任何時候都是滑動增加過期)兩種方式,沒什么細(xì)節(jié)上的說明。不過我倒是聽過一種特殊業(yè)務(wù)場合,批量請求同類數(shù)據(jù),并且即時性沒有很高要求,設(shè)置過期時間并將時間稍作分散。
清空緩存,我在項目里目前并未應(yīng)用,甚至也不提倡直接使用。但是假如在應(yīng)用時,需要慎重考慮兩個地方:一是清理時機,二是清理時效(若在Redis里,無論是flushdb或者flushall,都會形成一定阻塞)
5
鎖/信號(Locking)
本身無關(guān)緩存,屬于一些并發(fā)特性實現(xiàn),有一定的適用場景。這在Redis中有一些基于原子的實現(xiàn),但與本系列討論無關(guān)。
6
發(fā)布-訂閱(Publish-Subscribe)
為什么提到這個跟生產(chǎn)消費(Produce-Consume)相關(guān)的動作呢?這個機制本身是不屬于緩存自身的范疇的,而是更相關(guān)于消息隊列(Message Queue)。之所以提到,是因為如今主流的緩存產(chǎn)品都自帶這一特性,很多場景使用起來較方便,配置也簡單,效率也夠快。只是,往往會造成濫用。最關(guān)鍵是不必要的強耦合也降低了整體靈活性和性能,擴展性也實在有限。當(dāng)然,這是我目前的看法。
我的建議是:如果沒有特殊的場景應(yīng)用,盡量不使用。至少本人是不會優(yōu)先推薦使用緩存自身的發(fā)布訂閱的,甚至在緩存集群系統(tǒng)中,需要考究的細(xì)節(jié)更多。
而推薦的方式是,使用其他專業(yè)中間件解決,如基于MQ的產(chǎn)品替代方案。具體的候選有優(yōu)秀的開源作品如RabbitMQ、Kafka等,包括有朋友提到的近兩年國內(nèi)阿里研發(fā)的RocketMQ等等,但是個人目前使用較多的依然是RabbitMQ。當(dāng)然,這里不去過多贅述了,根據(jù)場景選擇,合適的場景選用最合適的技術(shù)方案即可吧。
原文鏈接:www.cnblogs.com/bsfz/p/9568591.html
分享名稱:以Redis為例,詳談分布式系統(tǒng)緩存的細(xì)枝末節(jié)-創(chuàng)新互聯(lián)
網(wǎng)站URL:http://www.rwnh.cn/article14/ddcjde.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁設(shè)計公司、小程序開發(fā)、網(wǎng)站制作、移動網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、做網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容