3萬(wàn)字聊聊什么是RocketMQ(四)
大家好,我是Leo。
上一篇我們介紹了
消息積壓?jiǎn)栴}如何處理 閱讀源碼的小技巧 異步方案提升系統(tǒng)性能 MQ的緩存策略
繼上篇RocketMQ技術(shù)總結(jié)三,這篇主要聊一下
如何正常用鎖保護(hù)共享數(shù)據(jù) 中間件中常見(jiàn)的時(shí)間換空間的算法

本章概括

如何正確用鎖保護(hù)數(shù)據(jù)
我們知道,使用異步和并發(fā)的設(shè)計(jì)可以大幅提升程序的性能,但我們?yōu)榇烁冻龅拇鷥r(jià)是,程序比原來(lái)更加復(fù)雜了,多線程在并行執(zhí)行的時(shí)候,帶來(lái)了很多不確定性。特別是對(duì)于一些需要多個(gè)線程并發(fā)讀寫(xiě)的共享數(shù)據(jù),如果處理不好,很可能會(huì)產(chǎn)出不可預(yù)期的結(jié)果,這肯定不是我們想要的。
分享一下李玥老師使用鎖的第一條原則:如果能不用鎖,就不用鎖;如果你不確定是不是應(yīng)該用鎖,那也不要用鎖。為什么這么說(shuō)呢?因?yàn)椋?雖然說(shuō)使用鎖可以保護(hù)共享資源,但是代價(jià)還是不小的。
加鎖和解鎖過(guò)程都是需要 CPU 時(shí)間的,這是一個(gè)性能的損失。另外,使用鎖就有可能導(dǎo)致線程等待鎖,等待鎖過(guò)程中線程是阻塞的狀態(tài),過(guò)多的鎖等待會(huì)顯著降低程序的性能。 如果對(duì)鎖使用不當(dāng),很容易造成死鎖,導(dǎo)致整個(gè)程序“卡死”,這是非常嚴(yán)重的問(wèn)題。本來(lái)多線程的程序就非常難于調(diào)試,如果再加上鎖,出現(xiàn)并發(fā)問(wèn)題或者死鎖問(wèn)題,你的 程序?qū)⒏与y調(diào)試。
鎖的用法
在訪問(wèn)共享資源之前,先獲取鎖。 如果獲取鎖成功,就可以訪問(wèn)共享資源了。 最后,需要釋放鎖,以便其他線程繼續(xù)訪問(wèn)共享資源。
用完鎖,一定要釋放它。一定要考慮好所有的分支情況。確保不管發(fā)生任何情況,都會(huì)釋放鎖
死鎖的產(chǎn)生:?很多語(yǔ)言都有異常機(jī)制,當(dāng)拋出異常的時(shí)候,不再執(zhí)行后面的代碼。如果在訪問(wèn)共享資源時(shí) 拋出異常,那后面釋放鎖的代碼就不會(huì)被執(zhí)行,這樣,鎖就一直無(wú)法釋放,形成死鎖。
避免死鎖的建議:
再次強(qiáng)調(diào)一下,避免濫用鎖,程序里用的鎖少,寫(xiě)出死鎖 Bug 的幾率自然就低。 對(duì)于同一把鎖,加鎖和解鎖必須要放在同一個(gè)方法中,這樣一次加鎖對(duì)應(yīng)一次解鎖,代碼清晰簡(jiǎn)單,便于分析問(wèn)題。 盡量避免在持有一把鎖的情況下,去獲取另外一把鎖,就是要盡量避免同時(shí)持有多把鎖。 如果需要持有多把鎖,一定要注意加解鎖的順序,解鎖的順序要和加鎖順序相反。比如,獲取三把鎖的順序是 A、B、C,釋放鎖的順序必須是 C、B、A。 給你程序中所有的鎖排一個(gè)順序,在所有需要加鎖的地方,按照同樣的順序加解鎖。比如我剛剛舉的那個(gè)例子,如果兩個(gè)線程都按照先獲取 lockA 再獲取 lockB 的順序加 鎖,就不會(huì)產(chǎn)生死鎖。
數(shù)據(jù)壓縮:時(shí)間換空間
數(shù)據(jù)壓縮不僅能節(jié)省存儲(chǔ)空間,還可以用于提升網(wǎng)絡(luò)傳輸性能。這種使用壓縮來(lái)提升 系統(tǒng)性能的方法,不僅限于在消息隊(duì)列中使用,我們?nèi)粘i_(kāi)發(fā)的應(yīng)用程序也可以使用。
比如,我們的程序要傳輸大量的數(shù)據(jù),或者要在磁盤、數(shù)據(jù)庫(kù)中存儲(chǔ)比較大的數(shù)據(jù),這些情況 下,都可以考慮使用數(shù)據(jù)壓縮來(lái)提升性能,還能節(jié)省網(wǎng)絡(luò)帶寬和存儲(chǔ)空間。
什么情況適合使用數(shù)據(jù)壓縮?
在使用壓縮之前,首先你需要考慮,當(dāng)前這個(gè)場(chǎng)景是不是真的適合使用數(shù)據(jù)壓縮。
比如,進(jìn)程之間通過(guò)網(wǎng)絡(luò)傳輸數(shù)據(jù),這個(gè)數(shù)據(jù)是不是需要壓縮呢?我們可以對(duì)比一下
不壓縮直接傳輸需要的時(shí)間是:傳輸未壓縮數(shù)據(jù)的耗時(shí)。 使用數(shù)據(jù)壓縮需要的時(shí)間是:壓縮耗時(shí) + 傳輸壓縮數(shù)據(jù)耗時(shí) + 解壓耗時(shí)
到底是壓縮快,還是不壓縮快呢?其實(shí)不好說(shuō)。影響的因素非常多,比如數(shù)據(jù)的壓縮率、網(wǎng) 絡(luò)帶寬、收發(fā)兩端服務(wù)器的繁忙程度等等。
壓縮和解壓的操作都是計(jì)算密集型的操作,非常耗費(fèi) CPU 資源。如果你的應(yīng)用處理業(yè)務(wù)邏 輯就需要耗費(fèi)大量的 CPU 資源,就不太適合再進(jìn)行壓縮和解壓。
又比如說(shuō),如果你的系統(tǒng)的瓶頸是磁盤的 IO 性能,CPU 資源又很閑,這種情況就非常適 合在把數(shù)據(jù)寫(xiě)入磁盤前先進(jìn)行壓縮。
但是,如果你的系統(tǒng)讀寫(xiě)比嚴(yán)重不均衡,你還要考慮,每讀一次數(shù)據(jù)就要解壓一次是不是劃 算。
壓縮它的本質(zhì)是資源的置換,是一個(gè)時(shí)間換空間,或者說(shuō)是 CPU 資源換存儲(chǔ)資源的游戲。
就像木桶的那個(gè)短板一樣,每一個(gè)系統(tǒng)它都有一個(gè)性能瓶頸資源,可能是磁盤 IO,網(wǎng)絡(luò)帶 寬,也可能是 CPU。如果使用壓縮,能用長(zhǎng)板來(lái)?yè)Q一些短板,那總體上就能提升性能,這 樣就是劃算的。如果用了壓縮之后,短板更短了,那就不劃算了,不如不用。
如果通過(guò)權(quán)衡,使用數(shù)據(jù)壓縮確實(shí)可以提升系統(tǒng)的性能,接下來(lái)就需要選擇合適的壓縮算法。
應(yīng)該選擇什么壓縮算法?
壓縮算法可以分為有損壓縮和無(wú)損壓縮。有損壓縮主要是用來(lái)壓縮音視頻,它壓縮之后是會(huì) 丟失信息的。我們這里討論的全都是無(wú)損壓縮,也就是說(shuō),數(shù)據(jù)經(jīng)過(guò)壓縮和解壓過(guò)程之后, 與壓縮之前相比,是 100% 相同的。
數(shù)據(jù)為什么可以被壓縮呢?各種各樣的壓縮算法又是怎么去壓縮數(shù)據(jù)的呢?我舉個(gè)例子來(lái)簡(jiǎn) 單說(shuō)明一下。
比如說(shuō),下面這段數(shù)據(jù):
00000000000000000000
我來(lái)給你人肉壓縮一下:
20 個(gè) 0
20 個(gè)字符就被壓縮成了 4 個(gè)字符,并且是可以無(wú)損還原的。當(dāng)然,我舉的例子比較極端, 我的壓縮算法也幾乎沒(méi)什么實(shí)用性,但是,這確實(shí)是一個(gè)壓縮算法,并且和其他的壓縮算法 本質(zhì)是沒(méi)什么區(qū)別的。
目前常用的壓縮算法包括:ZIP,GZIP,SNAPPY,LZ4 等等。選擇壓縮算法的時(shí)候,主要需要考慮數(shù)據(jù)的壓縮率和壓縮耗時(shí)。一般來(lái)說(shuō),壓縮率越高的算法,壓縮耗時(shí)也越高。如果 是對(duì)性能要求高的系統(tǒng),可以選擇壓縮速度快的算法,比如 LZ4;如果需要更高的壓縮 比,可以考慮 GZIP 或者壓縮率更高的 XZ 等算法。
壓縮樣本對(duì)壓縮速度和壓縮比的影響也是比較大的,同樣大小的一段數(shù)字和一段新聞的文本,即使是使用相同的壓縮算法,壓縮率和壓縮時(shí)間的差異也是比較大的。所以,有的時(shí)候 在選擇壓縮算法的之前,用系統(tǒng)的樣例業(yè)務(wù)數(shù)據(jù)做一個(gè)測(cè)試,可以幫助你找到最合適的壓縮 算法。
在這里,我不會(huì)去給你講某一種壓縮算法,因?yàn)閴嚎s算法都很復(fù)雜,一般來(lái)說(shuō)也不需要我們 來(lái)實(shí)現(xiàn)某種壓縮算法,如果你感興趣的話,可以去學(xué)習(xí)一下最經(jīng)典壓縮算法:哈夫曼編碼 (也叫霍夫曼編碼,Huffman Coding)。
如何選擇合適的壓縮分段?
大部分的壓縮算法,他們的區(qū)別主要是,對(duì)數(shù)據(jù)進(jìn)行編碼的算法,壓縮的流程和壓縮包的結(jié) 構(gòu)大致一樣的。而在壓縮過(guò)程中,你最需要了解的就是如何選擇合適的壓縮分段大小。
在壓縮時(shí),給定的被壓縮數(shù)據(jù)它必須有確定的長(zhǎng)度,或者說(shuō),是有頭有尾的,不能是一個(gè)無(wú) 限的數(shù)據(jù)流,如果要對(duì)流數(shù)據(jù)進(jìn)行壓縮,那必須把流數(shù)據(jù)劃分成多個(gè)幀,一幀一幀的分段壓 縮。
主要原因是,壓縮算法在開(kāi)始?jí)嚎s之前,一般都需要對(duì)被壓縮數(shù)據(jù)從頭到尾進(jìn)行一次掃描, 掃描的目的是確定如何對(duì)數(shù)據(jù)進(jìn)行劃分和編碼,一般的原則是重復(fù)次數(shù)多、占用空間大的內(nèi) 容,使用盡量短的編碼,這樣壓縮率會(huì)更高。
另外,被壓縮的數(shù)據(jù)長(zhǎng)度越大,重碼率會(huì)更高,壓縮比也就越高。這個(gè)很好理解,比如我們 這篇文章,可能出現(xiàn)了幾十次“壓縮”這個(gè)詞,如果將整篇文章壓縮,這個(gè)詞的重復(fù)率是幾 十次,但如果我們按照每個(gè)自然段來(lái)壓縮,那每段中這個(gè)詞的重復(fù)率只有二三次。顯然全文 壓縮的壓縮率肯定高于分段壓縮。
當(dāng)然,分段也不是越大越好,實(shí)際上分段大小超過(guò)一定長(zhǎng)度之后,再增加長(zhǎng)度對(duì)壓縮率的貢獻(xiàn)就不太大了,這是一個(gè)原因。另外,過(guò)大的分段長(zhǎng)度,在解壓縮的時(shí)候,會(huì)有更多的解壓 浪費(fèi)。比如,一個(gè) 1MB 大小的壓縮文件,即使你只是需要讀其中很短的幾個(gè)字節(jié),也不得不把整個(gè)文件全部解壓縮,造成很大的解壓浪費(fèi)。
所以,你需要根據(jù)你的業(yè)務(wù),選擇合適的壓縮分段,在壓縮率、壓縮速度和解壓浪費(fèi)之間找 到一個(gè)合適的平衡。
確定了如何對(duì)數(shù)據(jù)進(jìn)行劃分和壓縮算法之后,就可以進(jìn)行壓縮了,壓縮的過(guò)程就是用編碼來(lái) 替換原始數(shù)據(jù)的過(guò)程。壓縮之后的壓縮包就是由這個(gè)編碼字典和用編碼替換之后的數(shù)據(jù)組成 的。
這就是數(shù)據(jù)壓縮的過(guò)程。解壓的時(shí)候,先讀取編碼字典,然后按照字典把壓縮編碼還原成原始的數(shù)據(jù)就可以了。
充電分享
低谷時(shí),用樂(lè)觀的態(tài)度,渡己。
困境時(shí),用豁達(dá)的態(tài)度,悅?cè)恕?/strong>
每個(gè)人都有一段異常艱難的時(shí)光,生活的壓力,工作的失意,學(xué)業(yè)的壓力,愛(ài)的惶惶不可終日。
挺過(guò)來(lái)的,人生就會(huì)豁然開(kāi)朗,挺不過(guò)來(lái)的,時(shí)間也會(huì)教你,怎么與他們握手言和,所以不必害怕。
決定上限的,是情緒價(jià)值的高低。當(dāng)我們?cè)谛闹胁ハ乱活w積極情緒的種子,便會(huì)收獲欣欣向榮的生活。
來(lái)源:楊絳:“情緒價(jià)值”越高的人,幸福感就越高
結(jié)尾
有些不懂的地方或者不對(duì)的地方,麻煩各位指出,一定修改優(yōu)化!
非常歡迎大家加我個(gè)人微信有關(guān)后端方面的問(wèn)題我們?cè)谌簝?nèi)一起討論!?我們下期再見(jiàn)!
長(zhǎng)按上方掃碼二維碼,加我微信,拉你進(jìn)群

