在這里說下,大家有什么問題可以直接添加我的個(gè)人微信:coder_saver,備注”讀者”即可,feel free to contact me。
在這篇文章中博主提到了Intel有一款新設(shè)備,即能當(dāng)內(nèi)存用也能像磁盤一樣持久化內(nèi)存數(shù)據(jù),很多同學(xué)咨詢這種新硬件的編程問題,那么在這篇文章中小風(fēng)哥就給大家聊聊這話題。
在這里簡單說下,這種新硬件就是Intel推出的傲騰持久內(nèi)存,Intel Optane Persistent Memory ,這種硬件可以當(dāng)做內(nèi)存來用,和普通內(nèi)存一樣,但同時(shí)也具有非易失特性,像磁盤或者SSD一樣斷電后內(nèi)存中的數(shù)據(jù)不會丟失,就是這么的神奇。
對于這類持久化內(nèi)存來講就真的沒有重啟一說了,因?yàn)閿?shù)據(jù)會一直保存在內(nèi)存里,加電后直接用即可,你的程序就再也沒有啟動(dòng)或者初始化一說了,這是一種全新的設(shè)備,對程序員來說有一定的挑戰(zhàn)。實(shí)際上數(shù)據(jù)結(jié)構(gòu)或者說數(shù)據(jù)存放在兩個(gè)地方:內(nèi)存以及存儲設(shè)備,這里的存儲設(shè)備就是程序員熟悉的磁盤或者SSD。對于需要將數(shù)據(jù)存放在存儲設(shè)備的程序員來說,通常必須小心的維護(hù)數(shù)據(jù)的一致性,為什么呢?對于高可靠程序來說你必須能隨時(shí)應(yīng)對斷電或者程序崩潰,如果數(shù)據(jù)沒有及時(shí)的從內(nèi)存刷入磁盤,那么此時(shí)你的數(shù)據(jù)將會丟失;而如果在寫入磁盤的過程中發(fā)生了斷電或者程序崩潰crash,那么此時(shí)寫入磁盤就是不完整的數(shù)據(jù)。
對于面向存儲設(shè)備編程的程序員來說解決上述問題有一個(gè)常用的方法,那就是write-ahead logging。你不是會隨時(shí)斷電或者隨時(shí)程序崩潰嗎,我在真正寫入磁盤之前先寫一段log,這段log的內(nèi)容可能是這樣的:“我要往磁盤中寫入一句話,這句話是“小風(fēng)哥太帥了!””。那么假設(shè)在將真正的數(shù)據(jù)“小風(fēng)哥太水了!”這個(gè)字符串寫入磁盤的過程中機(jī)房斷電或者程序崩潰,放心,在這種情況下是丟失不了數(shù)據(jù)的,此后程序在重啟時(shí)通過再次讀取該log:“我要往磁盤中寫入一句話“小風(fēng)哥太帥了!”,該程序就能獲得足夠的信息來再次往磁盤中寫數(shù)據(jù),這就是write-ahead logging的妙用。到這里我先應(yīng)該能大體明白這類程序員所面臨的挑戰(zhàn)了。而對于面向內(nèi)存編程,也就是通常不需要關(guān)心數(shù)據(jù)持久存儲問題的程序員來說也沒那么容易,雖然你不需要關(guān)心數(shù)據(jù)持久存儲所面臨的一致性問題,但你需要在程序運(yùn)行過程中解決多線程訪問的一致性問題。
當(dāng)多個(gè)線程訪問同一段內(nèi)存時(shí),程序員通常需要加鎖,這樣當(dāng)一個(gè)程序修改這段內(nèi)存時(shí)可以確保其它線程不會看到中間狀態(tài)——也就是修改到一半時(shí)的內(nèi)存數(shù)據(jù)。當(dāng)斷電或者程序崩潰后內(nèi)存中的內(nèi)容就消失了,因此你不需要去關(guān)心持久存儲所面臨的一致性問題。內(nèi)存數(shù)據(jù)斷電后消失、程序崩潰后內(nèi)存內(nèi)容消失以及磁盤數(shù)據(jù)可以持久存儲這些特性對于當(dāng)前的程序員來說已經(jīng)像空氣一樣習(xí)以為常了。現(xiàn)在告訴你,有一種新硬件,這種硬件能讓你直接當(dāng)內(nèi)存來用,也就是可以直接字節(jié)尋址但與此同時(shí)斷電后內(nèi)容又不消失,你覺得會怎樣?我想會有很多同學(xué)大呼神奇,該技術(shù)可以讓你獲得大量廉價(jià)內(nèi)存,同時(shí)內(nèi)存中的數(shù)據(jù)在斷電以及程序崩潰時(shí)內(nèi)容不丟失。
但神奇的不止是這種硬件,針對該硬件進(jìn)行編程同樣需要編程思維上的轉(zhuǎn)變。從特性上看,該硬件即是內(nèi)存又是磁盤,因此上面關(guān)于持久數(shù)據(jù)一致性以及多線程一致性的考慮都適用于該硬件,也就是說針對該硬件進(jìn)行編程時(shí)你即需要考慮多線程訪問一致性,也需要考慮持久數(shù)據(jù)一致性。于此同時(shí),最讓C/C++的程序員頭疼的問題之一,即內(nèi)存泄漏在持久內(nèi)存的場景下就更有挑戰(zhàn)了,在普通內(nèi)存下內(nèi)存泄漏后大不了重啟,而在持久內(nèi)存場景下,如果出現(xiàn)了內(nèi)存泄漏,那就是持久的內(nèi)存泄漏,重啟不再起作用。這些程序員來說一個(gè)極大的挑戰(zhàn)。假設(shè)這段代碼出自銀行的賬戶系統(tǒng),定義了一個(gè)簡單的結(jié)構(gòu)體:結(jié)構(gòu)體包含兩項(xiàng):用戶姓名和賬戶余額:struct account { string name; int money;};
當(dāng)有新用戶存錢時(shí),那么需要?jiǎng)?chuàng)建一個(gè)實(shí)例然后更新姓名和賬戶余額:struct account *xfg = new account();xfg->name = "xiaofengge";xfg->money = 100000000;
是的,你沒有看錯(cuò),小風(fēng)哥在這段代碼里已經(jīng)財(cái)務(wù)自由了
。第一行代碼從堆上分配一段內(nèi)存用來構(gòu)建data對象,后兩行用來初始化各個(gè)字段,簡單吧。假設(shè)此時(shí)程序在執(zhí)行到第3行時(shí)機(jī)器斷電,或者系統(tǒng)崩潰,那么此時(shí)內(nèi)存會一掃而空,不會再有mydata的數(shù)據(jù)存在,小風(fēng)哥我在這家銀行不會有任何信息存在,當(dāng)然還包括我的1億巨款。但假設(shè)該程序不是運(yùn)行在普通內(nèi)存而是持久內(nèi)存當(dāng)中會怎么樣呢?struct data *xfg = new data();xfg->name = "xiaofengge";xfg->money = 100000000;
假設(shè)在執(zhí)行到第三行時(shí)機(jī)房斷電了,注意,此時(shí)程序的數(shù)據(jù)都保存在持久內(nèi)存中,那么此時(shí)斷電小風(fēng)哥的賬戶名稱已經(jīng)保存下來了,還不錯(cuò),但最重要的1億元卻沒有保存下來,那么當(dāng)程序再次啟動(dòng)時(shí)小風(fēng)哥就只能看到一個(gè)空的賬戶了。現(xiàn)在你應(yīng)該意識到基于持久內(nèi)存進(jìn)行編程的難點(diǎn)了吧。程序員在基于持久內(nèi)存進(jìn)行編程時(shí)需要時(shí)刻意識到這是一塊內(nèi)存,因此需要維護(hù)多線程訪問的一致性,但與此同時(shí)這又是一塊存儲設(shè)備,需要維護(hù)在運(yùn)行時(shí)以及持久化的數(shù)據(jù)一致性。基于此現(xiàn)狀,當(dāng)前的支持持久內(nèi)存的庫都支持這樣一種特性,也即原子特性,atomic。
原子在不用的應(yīng)用場景下有不同的語義,在多線程編程場景下,原子也即意味著除了當(dāng)前線程之外沒有任何一個(gè)線程更看到數(shù)據(jù)的中間狀態(tài),換句話說就是不會有多個(gè)線程同時(shí)去修改一塊內(nèi)存。但原子在持久內(nèi)存下的語言就不太一樣了,原子在這種場景下的語義是說不管在任何時(shí)刻斷電也好、程序崩潰也好,當(dāng)程序重啟后不會看到數(shù)據(jù)的中間狀態(tài),該數(shù)據(jù)要么已經(jīng)正確的持久化要么還沒有開始持久化。這里的數(shù)據(jù)和上面一樣,小到一個(gè)字節(jié),大到一個(gè)非常復(fù)雜的結(jié)構(gòu)體。多線程中的鎖只能保證內(nèi)存更新的原子性,但不能保證數(shù)據(jù)持久化的原子性。為實(shí)現(xiàn)數(shù)據(jù)持久化原子性,持久內(nèi)存編程SDK通常從數(shù)據(jù)中借鑒一個(gè)叫做事務(wù)的概念,transaction。事務(wù)的意思是這樣的:假設(shè)某個(gè)數(shù)據(jù)可能需要經(jīng)過A、B、C、D幾個(gè)步驟才能修改完畢,我們把這四個(gè)步驟打包放到事務(wù)中,那么事務(wù)就可以確保這四個(gè)步驟要么全部執(zhí)行完畢,要么全部都不去執(zhí)行。這樣即使在任意一個(gè)步驟斷電或者程序崩潰都不會影響到數(shù)據(jù)的一致性問題。如果你對持久化內(nèi)存編程非常感興趣,關(guān)注公眾號碼農(nóng)的荒島求生并回復(fù)pmem即可下載詳細(xì)編程資料。
值得注意的是,程序員常用的磁盤flush操作只是確保當(dāng)該函數(shù)執(zhí)行完成后數(shù)據(jù)已經(jīng)被寫到了磁盤,但flush并不等同于事務(wù),因?yàn)槿绻趂lush過程中如果斷電或者系統(tǒng)崩潰那么數(shù)據(jù)就處于薛定諤狀態(tài),可能數(shù)據(jù)已經(jīng)被完全寫到磁盤了,也可能只寫了一部分,當(dāng)然,也可能什么都沒寫。
有的同學(xué)可能不理解為什么讀寫磁盤時(shí)要flush,原因在于操作系統(tǒng)會把內(nèi)存當(dāng)做磁盤的緩存用,出于性能的考慮你寫到磁盤中的數(shù)據(jù)并不會立即刷入磁盤,而是會有一個(gè)異步任務(wù)來完成寫磁盤操作,這就是Linux下的page cache機(jī)制,關(guān)于這類機(jī)制的實(shí)現(xiàn)原理請參見博主的深入理解操作系統(tǒng),關(guān)注公眾號碼農(nóng)的荒島求生并回復(fù)操作系統(tǒng)即可。本文介紹一種全新的內(nèi)存設(shè)備,這類設(shè)備可以被操作系統(tǒng)識別為內(nèi)存,但又像磁盤一樣斷電后內(nèi)容不丟失,這類設(shè)備尤其適用于對內(nèi)存容量要求高以及程序啟動(dòng)時(shí)間長的場景。但,這類設(shè)備在編程上對程序員來說是一大挑戰(zhàn),這種持久內(nèi)存在未來是否會成為主流也尚待觀察。