從Redis的架構(gòu)看Redis使用優(yōu)化方面的幾個(gè)要點(diǎn)
最近的一些優(yōu)化和運(yùn)維項(xiàng)目中都有Redis,看樣子不論是互聯(lián)網(wǎng)架構(gòu)的應(yīng)用還是傳統(tǒng)架構(gòu)的應(yīng)用,都已經(jīng)意識(shí)到了訪問(wèn)頻繁,數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單的熱數(shù)據(jù)使用合理的訪問(wèn)方式是十分重要的。既然客戶有需求,我們就需要去深入的研究一下怎么把Redis用好,優(yōu)化好。做一個(gè)運(yùn)維對(duì)象的分析其實(shí)也是有套路的,并不一定都是需要從十年八年的積累中才可以獲得,特別是針對(duì)Redis這樣比較簡(jiǎn)單的內(nèi)存數(shù)據(jù)庫(kù)。
一般來(lái)說(shuō),對(duì)于這類(lèi)相對(duì)簡(jiǎn)單的運(yùn)維對(duì)象,我們?cè)趯W(xué)習(xí)和梳理其要點(diǎn)的時(shí)候會(huì)首先從管理類(lèi)、配置類(lèi)、技術(shù)類(lèi)三方面去了解它。把這些東西搞清楚了,這個(gè)運(yùn)維對(duì)象的一些基本的運(yùn)維,管理,優(yōu)化就差不多了。當(dāng)然要做這些事情之前的,一個(gè)十分重要的工作就是理解這個(gè)運(yùn)維對(duì)象的架構(gòu)。我覺(jué)得理解一個(gè)運(yùn)維對(duì)象的架構(gòu)對(duì)于今后去運(yùn)維管理,做優(yōu)化都是十分關(guān)鍵的。我和很多使用Redis開(kāi)發(fā)應(yīng)用系統(tǒng)的人聊過(guò),他們大多數(shù)都沒(méi)有關(guān)注過(guò)Redis的架構(gòu),反正給我變成接口,告訴我一些基本的操作,我就開(kāi)干了,架構(gòu)啥的我不關(guān)注。事實(shí)上,一個(gè)想把Redis用好的程序員,也是需要去深入的理解Redis的架構(gòu)的。
Redis是一個(gè)輕量級(jí)的內(nèi)存緩沖組件,被廣泛的用作內(nèi)存數(shù)據(jù)庫(kù)、緩沖、消息代理、消息隊(duì)列等。Redis可以提供亞毫秒級(jí)的響應(yīng)時(shí)間,支持?jǐn)?shù)十萬(wàn)甚至上百萬(wàn)級(jí)別的并發(fā)訪問(wèn)。不過(guò)很可能很多朋友都沒(méi)有關(guān)注到,Redis的核心從本質(zhì)上來(lái)說(shuō)是單線程架構(gòu)的。
這是網(wǎng)上都可以找到的十分典型的Redis單實(shí)例架構(gòu)的邏輯架構(gòu)圖,是不是顯得太簡(jiǎn)單了一點(diǎn),不過(guò)事實(shí)上Redis就是這樣的,十分簡(jiǎn)單。實(shí)際上大多數(shù)內(nèi)存數(shù)據(jù)庫(kù),哪怕是timesten這樣的內(nèi)存關(guān)系型數(shù)據(jù)庫(kù),都會(huì)和普通的磁盤(pán)庫(kù)在體系架構(gòu)上有巨大的不同,這是因?yàn)閮?nèi)存與磁盤(pán)訪問(wèn)在延時(shí)上有成千上萬(wàn)倍的不同。Redis作為一種內(nèi)存KV數(shù)據(jù)庫(kù),更需要十分簡(jiǎn)單的方式來(lái)充分利用內(nèi)存的低延時(shí)特性,提供高吞吐量的訪問(wèn)??赡苓€是有朋友無(wú)法理解為什么Redis設(shè)計(jì)之初不設(shè)計(jì)成多線程架構(gòu),讓Redis可以具有更高的吞吐能力。這個(gè)爭(zhēng)論早在5、6年前就有過(guò)了,最典型的是2014年在Quora上針對(duì)Redis架構(gòu)的爭(zhēng)論,我看過(guò)之后受益良多。其實(shí)在多線程架構(gòu)的數(shù)據(jù)庫(kù)中,鎖沖突是十分高開(kāi)銷(xiāo)的爭(zhēng)用。相對(duì)于磁盤(pán)的IO延時(shí)來(lái)說(shuō),Enqueue的開(kāi)銷(xiāo)可能還可以接受,而對(duì)于內(nèi)存的訪問(wèn)速度來(lái)說(shuō),鎖爭(zhēng)用帶來(lái)的負(fù)面影響可能遠(yuǎn)超多線程帶來(lái)的好處。因此Redis在設(shè)計(jì)之初就選擇了無(wú)鎖的串行單線程訪問(wèn)數(shù)據(jù)的架構(gòu)。甚至最初的Redis整體都是單線程架構(gòu)的。隨著Redis的發(fā)展,Redis也出現(xiàn)了一些多線程的特性,比如4.0開(kāi)始,延遲大鍵的刪除操作,采用單獨(dú)的后臺(tái)進(jìn)程來(lái)處理,另外多線程也被用于一些較滿的IO操作。不管怎么發(fā)展Redis的核心數(shù)據(jù)訪問(wèn)還是串行單線程,無(wú)鎖方式的訪問(wèn)。這種單線程的架構(gòu)也讓?xiě)?yīng)用開(kāi)發(fā)變得十分簡(jiǎn)單,因?yàn)闊o(wú)需考慮鎖的問(wèn)題,也不需要考慮回滾和提交。
這種單線程架構(gòu)決定了Redis是不怎么消耗CPU的,因此你無(wú)需為單個(gè)的Redis實(shí)例配置過(guò)多的CPU,一般來(lái)說(shuō),2-4顆邏輯CPU線程就完全足夠應(yīng)付任何場(chǎng)景的并發(fā)訪問(wèn)了。
不過(guò)對(duì)于這種單線程架構(gòu),命令是串行執(zhí)行的,因此平均每條命令執(zhí)行的時(shí)間長(zhǎng)度決定了單個(gè)Redis實(shí)例的并發(fā)訪問(wèn)量,比如我們一條命令平均延時(shí)為20ns,那么一秒鐘有1000000ns,執(zhí)行命令的總數(shù)理論上限是1000000/20=5萬(wàn)。比如下面的這個(gè)例子:
從報(bào)告上可以看出,平均每秒可以執(zhí)行2萬(wàn)多條命令,而這些命令的執(zhí)行中位數(shù)是35ns,算起來(lái)20106*35大概是0.7秒左右。
從單線程架構(gòu)上我們也可以看出,Redis的并發(fā)訪問(wèn)是需要串行排隊(duì)的,因此相同的命令,其執(zhí)行時(shí)間是不穩(wěn)定的,如果前面排隊(duì)的命令比較多,那么排在前面的這條命令的總體執(zhí)行時(shí)間比排在隊(duì)伍后面的快十倍也是很正常的。因此對(duì)于Redis應(yīng)用的性能分析,不能看單次的執(zhí)行時(shí)間,更重要的是要看平均時(shí)間,中位數(shù)時(shí)間,90分位時(shí)間等指標(biāo)。如果你的應(yīng)用的中位數(shù)執(zhí)行時(shí)間超過(guò)100ns,或者99分位數(shù)執(zhí)行時(shí)間超過(guò)2毫秒,那么你的應(yīng)用的性能是不能接受的,這會(huì)大大影響整個(gè)Redis實(shí)例上的應(yīng)用的性能。如果說(shuō)普通的數(shù)據(jù)庫(kù)某條SQL慢點(diǎn)可能影響面有限,對(duì)于單線程的Redis來(lái)說(shuō),某些特別慢的命令是不能接受的,必須進(jìn)行優(yōu)化或者進(jìn)行隔離,否則一顆老鼠屎可能會(huì)壞了一鍋湯。
從Redis的單線程架構(gòu),也給我們的應(yīng)用的橫向擴(kuò)展能力提出了要求。剛才我們也計(jì)算過(guò)了,單一的Redis實(shí)例的最大并發(fā)量是有限的,我們能夠?qū)?yīng)用做的優(yōu)化也是有極限的。因此使用Redis的應(yīng)用,如果需要支撐較大的并發(fā)量的話,一定要能夠很方便的橫向擴(kuò)展的。我們可以通過(guò)Redis Cluster來(lái)做分片處理,通過(guò)多個(gè)Redis的集群來(lái)成倍的擴(kuò)充Redis服務(wù)的并發(fā)量。
從Redis的單線程架構(gòu)上來(lái)看,Redis數(shù)據(jù)庫(kù)是內(nèi)存敏感的,我們一定要確保Redis服務(wù)器的操作系統(tǒng)內(nèi)存的充足,Redis也提供了大了的監(jiān)控信息來(lái)幫我們分析內(nèi)存是否足夠。當(dāng)服務(wù)器內(nèi)存不足的時(shí)候,OOM KILLER要?dú)⒌目隙ㄊ荝edis服務(wù),因此我們也要確保Redis服務(wù)不會(huì)成為首先被殺的對(duì)象。
mem_fragmentation_ratio是一個(gè)十分值得關(guān)注的指標(biāo),這個(gè)指標(biāo)出現(xiàn)異常,會(huì)引發(fā)REDIS的性能問(wèn)題。如果這個(gè)指標(biāo)超過(guò)1.5,說(shuō)明Redis數(shù)據(jù)庫(kù)存在較大的碎片,碎片會(huì)引起內(nèi)存訪問(wèn)性能問(wèn)題,從而影響數(shù)據(jù)庫(kù)的總體性能。而如果這個(gè)指標(biāo)小于1,說(shuō)明數(shù)據(jù)庫(kù)中有一部分內(nèi)存被放入swap了,這更會(huì)引發(fā)更大的Redis性能問(wèn)題。我們這臺(tái)服務(wù)器上除了跑Redis外還有我們的一些其他的應(yīng)用,包括postresql數(shù)據(jù)庫(kù)、tomcat服務(wù)器等,最近總會(huì)出現(xiàn)內(nèi)存不足的情況,swap使用率經(jīng)常超過(guò)50%。可以看出,某些時(shí)段里,Redis出現(xiàn)了mem_fragmentation_ratio小于1的情況。如果你們的生產(chǎn)系統(tǒng)出現(xiàn)這種情況,那么給服務(wù)器或者虛擬機(jī)擴(kuò)內(nèi)存是十分必要的。
另外一點(diǎn),從Redis是單線程的內(nèi)核態(tài)訪問(wèn)為主的應(yīng)用,那么其CPU資源消耗上,應(yīng)該大部分的CPU都是可心態(tài)的訪問(wèn),因此對(duì)于一臺(tái)只是跑Redis數(shù)據(jù)庫(kù)的服務(wù)器來(lái)說(shuō),sys的cpu比例應(yīng)該很高。
在這個(gè)監(jiān)控指標(biāo)中,我們看出sys和user差不多,這是因?yàn)槲覀兊姆?wù)器上還有PG數(shù)據(jù)庫(kù)的原因。如果我們?cè)谧约旱腞edis服務(wù)器上發(fā)現(xiàn)了這種現(xiàn)象,那么就需要分析一下到底哪些非Redis實(shí)例在消耗CPU資源了。
原本今天早上準(zhǔn)備用半小時(shí)寫(xiě)篇小文,于是考慮寫(xiě)寫(xiě)比較簡(jiǎn)單的Redis,沒(méi)想打一下子就到9點(diǎn)了,馬上有很多事要做,先到此打住吧。哪怕是這么簡(jiǎn)單的單線程的Redis,寫(xiě)了半天好像剛剛開(kāi)了個(gè)頭。IT基礎(chǔ)設(shè)施的運(yùn)維確實(shí)還是挺費(fèi)勁的。
本文轉(zhuǎn)載自微信公眾號(hào)「白鱔的洞穴」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系
公眾號(hào)。