版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第1第1??架構(gòu)的分類??2第3??精通?門語(yǔ)?第4?緩沖I/O和直接?????絡(luò)I/O?實(shí)現(xiàn)層?的?絡(luò)I/O?Reactor模式與Preactor?????鎖(內(nèi)存屏障與??第5?HTTP?HTTP1.0?Keep-Alive機(jī)制與Content-Length?HTTP?連接復(fù)?與Chunk?Pipeline與Head-of-lineBlocking?HTTP/2?“?來(lái)多回”???與HTTP1.1??????????根證書與CA?SSL/TLS????TCP的“假”連接(狀態(tài)機(jī)?三次握?(?絡(luò)2將軍問題???不丟包(Raid5算法和Raid6算法?更少的?6????分布式ID??Join??B+?B+?B+??????事務(wù)實(shí)現(xiàn)原理之1:Redo??RedoLog?Physiological?I/O寫?的原?性(Double?RedoLogBlock?事務(wù)、LSN與LogBlock?事務(wù)Rollback與崩潰恢復(fù)(ARIES算法?事務(wù)實(shí)現(xiàn)原理之2:Undo?UndoLog?Undo?UndoLog不是?UndoLog與RedoLog??Binlog?Binlog與RedoLog?內(nèi)部XA-Binlog與RedoLog??第7???軟件與中間件第3第8??側(cè)重于“?并發(fā)讀”?側(cè)重于“?并發(fā)寫”?同時(shí)側(cè)重于“?并發(fā)讀”和“?并發(fā)寫”??策略1?策略2?策略3?總結(jié):讀寫分離(CQRS架構(gòu)??策略1?策略2?策略3?策略4?策略5:串?化+多進(jìn)程單線程+異步???9????監(jiān)控體系與?志報(bào)警第10????最終?致性(消息中間件??事務(wù)狀態(tài)表+調(diào)??重試+??妥協(xié)?案:弱?致性+?妥協(xié)?案:重試+回滾+報(bào)警+???Kafka?Kafka?Paxos?Paxos???BasicPaxos?MultiPaxos?Raft?為“可理解性”???階段1:Leader?階段2?階段3??Zab?ReplicatedStateMachinevs.Primary-Backup??“序”:亂序提交vs.?Leader選舉:FLE?正常階段:2??三種算法對(duì)?第12CAP理論?CAP??4第13?產(chǎn)品經(jīng)理vs.?什么叫作?個(gè)“業(yè)務(wù)?“業(yè)務(wù)架構(gòu)”?14?“偽”?????功能性需求分析(以終為始?視?(橫看成嶺側(cè)成峰???第15??為什么要“領(lǐng)域驅(qū)動(dòng)?“業(yè)務(wù)流程”不等于“系統(tǒng)流程??領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)與微服務(wù)架構(gòu)的“合?領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)與讀寫分離??管道—??業(yè)務(wù)切?/業(yè)務(wù)閉環(huán)架構(gòu)模式第5第16??影響?的塑造17???第1在這樣的認(rèn)知基礎(chǔ)上,我們將先從架構(gòu)的職業(yè)分類、技術(shù)?向分類、架構(gòu)的道與術(shù)等??對(duì)架構(gòu)做?個(gè)概覽;之后,著眼于“?型?站”或者“型的在線業(yè)務(wù)系統(tǒng)”領(lǐng)域,系統(tǒng)化地探討這個(gè)領(lǐng)域的架構(gòu)包含了哪些思路和?法。當(dāng)然,這些思路和?法在其他技術(shù)領(lǐng)域也同樣適?,正所謂?道相第1然后看??單位對(duì)應(yīng)聘者?作年限的要求,有些是3~5年,有些是~10?個(gè)簡(jiǎn)單的“架構(gòu)師”習(xí)?法。這種系統(tǒng)化的思維?法既可以幫助開發(fā)?員形成系統(tǒng)化的?法?數(shù)據(jù)架構(gòu)。例如開源的Hadoop?態(tài)體系,Hive、SparkStorm、Flink圖1-1?型在線業(yè)務(wù)系統(tǒng)。例如搜索、推薦、即時(shí)通信、電商、游戲、?告、企業(yè)ERP或CRM等。··對(duì)于第三層的劃分,此處并不是很絕對(duì),因?yàn)楝F(xiàn)實(shí)中軟件的種類實(shí)本書聚焦在?型在線業(yè)務(wù)系統(tǒng)的架構(gòu),即圖1-1中第三層的第三部第2假設(shè)?Java語(yǔ)?繪制?個(gè)?型?站,其典型架構(gòu)如圖2-1圖2-1道的東西往往會(huì)?較虛,可能說(shuō)了半天對(duì)?還是不知道你說(shuō)的是什?致性理論,?通俗的語(yǔ)?表達(dá)出來(lái),讓讀者可以從?個(gè)“最樸素的視?”?定的程度往往會(huì)上升到哲學(xué)?度。本書會(huì)盡可能把道局限在解決“業(yè)務(wù)的技術(shù)問題”層?,盡可能“?之有物”,避免??空洞。在平常?中耀武揚(yáng)威,?旦跟??過招,往往只需?個(gè)回合就會(huì)原形畢在中國(guó)哲學(xué)(主要來(lái)?儒家)中,?直有?個(gè)核?思想:“知?合?”。個(gè)?覺得?這個(gè)來(lái)表達(dá)道和術(shù)的關(guān)系會(huì)更為貼切。知,是理論,是套路,是解決問題的?法論;?,是實(shí)踐,是操作,?于解決?個(gè)個(gè)實(shí)際問題。先有實(shí)踐,然后總結(jié)出理論,?理論指導(dǎo)新的實(shí)踐,在新的實(shí)踐中再總結(jié)出新的理論。如此循環(huán)往復(fù),即是歸納和演繹循環(huán)往復(fù)的過程,也是螺旋式上升的過程。的結(jié)果,想要看到道的效果卻需要長(zhǎng)期地修煉,必須到了?個(gè)頓悟的拐除了從“知”和“?”的?度去看待,還可以從“問題”和“答案”的?度去看待。術(shù)偏重“答案”,是?具,是錘?;道偏重“問題”,是釘?。可能是拿著錘?去找釘?,也可能是遇到釘?再去想找個(gè)錘?或造個(gè)錘?。最后總結(jié)?下兩者的關(guān)系,如表2-1表2-1第2對(duì)于?個(gè)上層系統(tǒng)開發(fā)者來(lái)講,熟悉操作系統(tǒng)、?絡(luò)、數(shù)據(jù)庫(kù)的原個(gè)“可靠”的通道,?如數(shù)據(jù)庫(kù)如何利?Write-aheadLog解決I/O問題,利?Checksum保證?志完整性,利?MVCC(CopyOnWrite)解決?并發(fā)問第3對(duì)于計(jì)算機(jī)相關(guān)專業(yè)的學(xué)?來(lái)說(shuō),接觸的第?門語(yǔ)?通常是C;其他專業(yè)的學(xué)?,?先接觸到的可能是Fortran、VisualBasic或PHP。在職業(yè)?在?站領(lǐng)域,?量使?LAMP架構(gòu),其中的語(yǔ)?是?頁(yè)游戲?的時(shí)候,F(xiàn)LASHiPhone起來(lái)了,ObjectC跟著?,現(xiàn)在又有了Android起來(lái)了,Java繼續(xù)跟著?,現(xiàn)在又有了AI、機(jī)器學(xué)習(xí)起來(lái)了,學(xué)習(xí)Python基于Java的完善?態(tài)體系和JVM虛擬機(jī)的跨平臺(tái)性,JavaJVM虛擬機(jī)語(yǔ)?也是豐富多彩,如Scala、Groovy、在追求性能的編程領(lǐng)域,Go、Rust也在?點(diǎn)點(diǎn)地蠶?C和C++的份?先,?乎所有的現(xiàn)代?級(jí)編程語(yǔ)?都有?些典型的共同特征,例都有?個(gè)基本數(shù)據(jù)類型的集合(?如Java是8種基本數(shù)據(jù)類型都有類、對(duì)象、封裝、繼承、多態(tài)(如果是?向?qū)ο蟮亩加?個(gè)常?數(shù)據(jù)結(jié)構(gòu)的庫(kù)(數(shù)組、棧、隊(duì)列、鏈表、都有?個(gè)常?的I/O都有?個(gè)常?的線程庫(kù)(協(xié)程庫(kù)第4緩沖I/O和直接表4-1列出了緩沖I/O與直接I/O對(duì)應(yīng)的API接?列表,緩沖I/O是C語(yǔ)?提供的庫(kù)函數(shù),均以f打頭;直接I/O是Linux的系統(tǒng)API,但因?yàn)椴僮飨到y(tǒng)的API也是?C語(yǔ)?編寫的,所以導(dǎo)致開發(fā)者往往?法區(qū)分這兩類I/O,在原理上實(shí)際差異很?。表4-1緩沖I/O與直接I/O對(duì)應(yīng)的API圖4-1展?了緩沖I/O與直接I/O內(nèi)核緩沖區(qū):Linux操作系統(tǒng)的PageCache。為了加快磁盤的I/O,寫:應(yīng)?程序內(nèi)存?戶緩沖區(qū)內(nèi)核緩沖區(qū)寫:應(yīng)?程序內(nèi)存內(nèi)核緩沖區(qū)圖4-1緩沖I/O與直接I/O關(guān)于緩沖I/O和直接I/O這意味著?論緩沖I/O,還是直接I/O,如果在寫數(shù)據(jù)之后不調(diào)?緩沖區(qū)??,操作系統(tǒng)還沒來(lái)得及刷到磁盤。后?講數(shù)據(jù)庫(kù)、數(shù)據(jù)?致對(duì)于直接I/O,也有read/write和pread/pwrite兩組不同的API。戶空間不再有物理內(nèi)存,直接拿應(yīng)?程序的邏輯內(nèi)存地址映射到Linux操數(shù)據(jù)拷貝次數(shù)從緩沖I/O的3次,到直接I/O的2次,再到內(nèi)存映射?寫:內(nèi)核緩沖區(qū)圖4-2在Linux系統(tǒng)中,內(nèi)存映射?件對(duì)應(yīng)的系統(tǒng)API零拷貝(ZeroCopy)是提升I/O效率的又?利器,熟悉Kafka實(shí)現(xiàn)原理實(shí)現(xiàn)?法1:利?直接I/O如圖4-3所?,整個(gè)過程會(huì)有4次數(shù)據(jù)拷貝,讀進(jìn)來(lái)2次,寫回去又2磁盤內(nèi)核緩沖區(qū)應(yīng)?程序內(nèi)存Socket緩沖區(qū)圖4-3直接I/O實(shí)現(xiàn)?法2但如果?零拷貝,可能連內(nèi)核緩沖區(qū)到Socket緩沖區(qū)的拷貝也省略了。如圖4-5所?,在內(nèi)核緩沖區(qū)和Socket圖4-4內(nèi)存中復(fù)制到另外?塊內(nèi)存?;映射相當(dāng)于只是持有了數(shù)據(jù)的?個(gè)引?(或者叫地址),數(shù)據(jù)本?只有1圖4-5在這?,我們看到雖然叫零拷貝,2次數(shù)據(jù)拷貝,1次是從磁盤到內(nèi)核緩沖區(qū),1次是從內(nèi)核緩沖區(qū)到?絡(luò)。之所以叫零拷貝,是從內(nèi)存的?度來(lái)看的,數(shù)據(jù)在內(nèi)存中沒有發(fā)?過數(shù)據(jù)拷貝,只在內(nèi)存和I/O傳輸。最后總結(jié)?下,對(duì)于把?件數(shù)據(jù)發(fā)送到?絡(luò)的這個(gè)場(chǎng)景,直接I/O存映射?件、零拷貝對(duì)應(yīng)的數(shù)據(jù)拷貝次數(shù)分別是4次、3次、2次,內(nèi)存拷貝次數(shù)分別是2次、1次、0次。在Linux系統(tǒng)中,零拷貝的系統(tǒng)API?絡(luò)I/O?絡(luò)I/O模型存在諸多概念,有的是操作系統(tǒng)層?的,有的是應(yīng)?框架層?的,這些概念往往容易混淆,本章將對(duì)?絡(luò)I/O實(shí)現(xiàn)層?的?絡(luò)I/O說(shuō)到?絡(luò)I/O模型,?家往往會(huì)混淆阻塞和?阻塞、同步和異步這兩認(rèn)為?阻塞I/O(Non-BlockingIO)和異步I/O(asynchronousIO)是認(rèn)為L(zhǎng)inux系統(tǒng)下的select、poll、epoll這類I/O多路復(fù)?是“異步I/O”存在?種I/O模型,叫“異步阻塞I/O”(實(shí)際沒有這種模型)的JDK層?的模型,有的說(shuō)的是上層框架封裝的模型(?如Netty、下?要講的?絡(luò)I/O模型,主要是“Linux系統(tǒng)”的語(yǔ)境,主要的參考?獻(xiàn)來(lái)?兩部著作AdvancedProgrammingintheUNIXEnvironment和UNIX?NetworkProgrammingVolume。第?種模型:同步阻塞I/O第?種模型:同步?阻塞I/OI/OAPI,fdK參數(shù)。于是,當(dāng)調(diào)?read和write函數(shù)的時(shí)候,如果沒有準(zhǔn)備好數(shù)據(jù),會(huì)理解返回,不會(huì)阻塞,然后讓應(yīng)?程序不斷地去輪詢。第三種模型:I/O多路復(fù)?(IOMultiplexing)需要處理很多的fd(連接數(shù)可以達(dá)??萬(wàn)甚?百萬(wàn))。如果使?同步阻塞I/O,要處理這么多的fdfd?同步?阻塞I/O,要應(yīng)?程序輪詢這么?規(guī)模的fd。這兩種辦法都不?,所以就有了I/O在Linux系統(tǒng)中,有三種I/O多路復(fù)?的辦法:select、poll、epoll,它們的原理有?定差異,后?會(huì)專門分析。這?先以select為例介紹其?法:該函數(shù)是阻塞調(diào)?,?次性把所有的fd傳進(jìn)去,當(dāng)有fd可讀或者可序哪些fd上?可讀或者可寫,然后下?步應(yīng)?程序調(diào)?read和writeI/O多路復(fù)?是現(xiàn)在Linux系統(tǒng)上最成熟的?絡(luò)I/O模型,在三種?式第四種模型:異步I/O這樣說(shuō)可能還不太容易理解,下?看?個(gè)異步I/O的例?:C++中的asio?絡(luò)庫(kù)。asio是?個(gè)跨平臺(tái)的C++?絡(luò)庫(kù),也是boost的?部分,在async_read/async_writechat_session這個(gè)類的兩個(gè)成員handle_read_header/handle_write。當(dāng)然,asio是?個(gè)上層框架層的“異步I/O”,或者說(shuō)是模擬出來(lái)的“異步I/O”,在Linux系統(tǒng)上還是由epoll實(shí)現(xiàn)的。舉這個(gè)例?主要是想說(shuō)明所表4-2?絡(luò)I/O異步:讀寫由操作系統(tǒng)完成,完成之后,回調(diào)或者事件通知應(yīng)?程I/O多路復(fù)?(select、poll、epoll)都是同步I/O,因?yàn)閞ead和write函數(shù)操作都是應(yīng)?程序完成的,同時(shí)也是阻塞I/O,因?yàn)閟elect、除了上?的四種I/O模型,還經(jīng)常會(huì)聽到“事件驅(qū)動(dòng)”?詞。這個(gè)詞在不同的語(yǔ)境中有不同的意思。?如Nginx中所講的“事件驅(qū)動(dòng)”,其實(shí)是所以,當(dāng)講?絡(luò)I/O模型的時(shí)候,?定要注意講的是操作系統(tǒng)層?的I/O模型,還是上層的?絡(luò)框架封裝出來(lái)的I/O模型(?如asio,又?如Java另外,對(duì)于“異步I/O”?詞,在操作系統(tǒng)的語(yǔ)境和在上層應(yīng)?的語(yǔ)境種真正的異步,epoll不被認(rèn)為是異步I/O;但在上層應(yīng)?的語(yǔ)境?,異步所以在本書后續(xù)的章節(jié)中提到的“異步I/O”,主要指應(yīng)?層?的語(yǔ)境(底層可能是epoll,也可能是真正的異步I/O)Proactor模式。它們是?絡(luò)框架的兩種設(shè)計(jì)模式,?論操作系統(tǒng)的?絡(luò)I/OReactor模式:主動(dòng)模式。所謂主動(dòng),是指應(yīng)?程序不斷地輪詢,詢問操作系統(tǒng)或者?絡(luò)框架、I/O是否就緒。Linux系統(tǒng)下的select、交給操作系統(tǒng)或者?絡(luò)框架,實(shí)際的I/O操作由操作系統(tǒng)或?絡(luò)框架完成,之后再回調(diào)應(yīng)?程序。asio庫(kù)就是典型的Proactor模式。所以,上?提到的應(yīng)?層?的語(yǔ)境中所說(shuō)的“異步I/O”是Proactor模因?yàn)閑poll是Linux服務(wù)器開發(fā)的主流?絡(luò)I/O模型,JavaNIO在Linux平因?yàn)閒d是?個(gè)int值,所以fd_set其實(shí)是?個(gè)bit數(shù)組,每1?個(gè)fd位,告知應(yīng)?程序到底哪個(gè)fd上?有事件,應(yīng)?程序需要??從0到每次當(dāng)select調(diào)?返回后,在下?次調(diào)?之前,要重新維護(hù)readfds和writefds整個(gè)epoll(1)事件注冊(cè)。通過函數(shù)epoll_ctl實(shí)現(xiàn)。對(duì)于服務(wù)器??,是accept、read、write三種事件;對(duì)于客戶端??,是connect、read、write輪詢這三個(gè)事件是否就緒。通過函數(shù)epoll_wait事件就緒,執(zhí)?實(shí)際的I/O操作。通過函數(shù)accept/read/write實(shí)這?要特別解釋?下什么是“事件就緒write事件就緒:是指本地的socket寫緩沖區(qū)是否可寫。如果寫緩沖區(qū)沒有滿,則?直是可寫的,write事件?直是就緒的,可以調(diào)?write函數(shù)。只有當(dāng)遇到發(fā)送??件的場(chǎng)景,socket寫緩沖區(qū)被占滿時(shí),write事件才不是就緒狀態(tài)。accept事件就緒:有新的連接進(jìn)?,需要調(diào)?acceptepoll??有兩種模式:LT(?平觸發(fā))和ET(邊緣觸發(fā))。?平觸寫緩存區(qū)塞滿了,之后緩存區(qū)可以寫了,就會(huì)發(fā)??次從滿到不滿的切LT模式,要避免“寫的死循環(huán)”問題:寫緩沖區(qū)為滿的概率寫時(shí),它會(huì)?直觸發(fā),因此在LT模式下寫完數(shù)據(jù)?定要取消寫事件。對(duì)于ET模式,要避免“shortread”問題:例如?戶收到100節(jié),它觸發(fā)1次,但?戶只讀到了50個(gè)字節(jié),剩下的50個(gè)字節(jié)不讀,它也不會(huì)再次觸發(fā)。因此在ET模式下,?定要把“讀緩沖區(qū)”的數(shù)據(jù)?次性讀完。在實(shí)際開發(fā)中,?家?般都傾向于?LT,這也是默認(rèn)的模式,Java如圖4-6所?,整個(gè)服務(wù)器有1+N+M個(gè)線程,?個(gè)監(jiān)聽線程,N個(gè)I/O圖4-6服務(wù)器編程的1+N+M監(jiān)聽線程:負(fù)責(zé)accept事件的注冊(cè)和處理。和每?個(gè)新進(jìn)來(lái)的客戶端建?socket連接,然后把socket連接移交給I/O線程,完成任務(wù),繼續(xù)監(jiān)聽新的客戶端;I/O線程:負(fù)責(zé)每個(gè)socket連接上?read/write事件的注冊(cè)和實(shí)際的socket的讀寫。把讀到的Reqeust放?Request隊(duì)列,交由Worker線程處圖4-6只是展?了1+N+M的?種實(shí)現(xiàn)?式,實(shí)際上不同系統(tǒng)的實(shí)現(xiàn)?I/O線程只負(fù)責(zé)read/write事件的注冊(cè)和監(jiān)聽,執(zhí)?了epoll??的前兩個(gè)階段,第三個(gè)階段是在orker線程??做的。I/O線程監(jiān)聽到?個(gè)連接上有讀事件,于是把socket移交給orker線程,oker線程讀出數(shù)據(jù),處理完業(yè)務(wù)邏輯,直接返回給客戶端。之所以可以這么做,是因?yàn)镮/O已經(jīng)檢測(cè)到讀事件就緒,所以當(dāng)orker線程在讀的時(shí)候不會(huì)等待。I/O線程和orker線程之間交互,不再需要?來(lái)?回兩個(gè)隊(duì)列,直接是?個(gè)socket集合。有興趣的讀者可以參看omcat6NIO模塊的源代碼,對(duì)此模型進(jìn)?更為仔細(xì)的分析。對(duì)于編寫服務(wù)器程序,?論?epoll,還是JavaNIO,或者基于Netty?絡(luò)框架,?體都是按照1+N+M?的M可能又會(huì)按業(yè)務(wù)職責(zé)分成?組不同的線程,就變成了……?Java的?通常寫的是“單進(jìn)程多線程”的程序;??C++的?,寫的是“單進(jìn)程多線程”“多進(jìn)程單線程”“多進(jìn)程多線程”的程序(這?主要指Linux系統(tǒng)上的服務(wù)器程序)。之所以會(huì)有這樣的差異,是因?yàn)镴ava程序并不直接運(yùn)?在Linux系統(tǒng)上,?是運(yùn)?在JVM之上。??個(gè)JVM實(shí)例是互不通信。所以Java?C++直接運(yùn)?在Linux系統(tǒng)上,可以直接利?Linux系統(tǒng)提供的強(qiáng)?的進(jìn)程間通信機(jī)制(IPC),很容易創(chuàng)建多個(gè)進(jìn)程,并實(shí)現(xiàn)進(jìn)程間的通信。理并沒有差異,所以接下來(lái)只討論“單進(jìn)程多線程”和“多進(jìn)程單線程”編程模型,對(duì)?“多進(jìn)程”和“多線程”(1)提?CPU利?率。通俗地講,不能讓CPU空閑著。當(dāng)?個(gè)線程(2)提?I/O吞吐。典型的場(chǎng)景是,應(yīng)?程序連接的Redis或者鎖(悲觀鎖、樂觀鎖、互斥鎖、讀寫鎖、?旋鎖、公平鎖、?公平鎖等)。封裝出各式各樣的線程安全的數(shù)據(jù)結(jié)構(gòu),?如阻塞隊(duì)列、并發(fā)HashMap在并發(fā)編程領(lǐng)域,?直有?個(gè)很重要的設(shè)計(jì)原則:“不要通過共享內(nèi)存來(lái)實(shí)現(xiàn)通信,?應(yīng)通過通信實(shí)現(xiàn)共享內(nèi)存?!边@句話不太好理解,換成通俗?點(diǎn)的說(shuō)法就是:“盡可能通過消息通信,?不是共享內(nèi)存來(lái)實(shí)現(xiàn)進(jìn)程或者線程之間的同步?!边M(jìn)程是資源分配的基本單位,進(jìn)程間不共享資源,通過管道或者來(lái)實(shí)現(xiàn)同步。雖然在多線程領(lǐng)域也有這種思想的實(shí)現(xiàn),?如Akka框架,多進(jìn)程模型的典型例?是Nginx。Nginx有?個(gè)Master進(jìn)程,N個(gè)Master進(jìn)程不接收請(qǐng)求,負(fù)責(zé)管理功能;各個(gè)Worker?地接收客戶端的請(qǐng)求,也不需要像多線程那樣在不同的CPU核間切有了多進(jìn)程之后,在每個(gè)進(jìn)程內(nèi)部,可能是單線程,也可能是多線程,這往往取決于I/O。?如Redis就是單進(jìn)程單線程的模型(這?說(shuō)的單線程模型,不是指整個(gè)Redis服務(wù)器只有?個(gè)線程,?是指接收并處理客戶端請(qǐng)求的線程只有?個(gè))。之所以單線程可以?持,是因?yàn)樵谡?qǐng)求接收的地??的是epoll的I/O多路復(fù)?,在請(qǐng)求處理的地?又完全是內(nèi)存操作,沒有磁盤或者?絡(luò)I/O多個(gè)Redis實(shí)例就可以了。但對(duì)于I/O密集型的應(yīng)?,要提?I/O異步I/O。如果客戶端、服務(wù)器都是??寫的,?如RPC?,則可以把所有的I/O都異步化(利?epoll或者真正的異步I/O)。異步多線程。I/O不?持異步,就只能開多個(gè)線程,每個(gè)線程都是同步地調(diào)?I/O,實(shí)際上是?多線程模擬了異步I/O。典型例?是eb應(yīng)?服務(wù)器調(diào)?Redis或MySQL。多線程除鎖的問題之外,還有?個(gè)問題是線程太多,切換的開銷很更好地利?CPU:線程的調(diào)度是由操作系統(tǒng)完成的,應(yīng)?程序?預(yù)不了,協(xié)程可以由應(yīng)?程序??調(diào)度。更好地利?內(nèi)存:協(xié)程的堆棧??不是固定的,?多少申請(qǐng)多少,內(nèi)存利?率更??,F(xiàn)代的編程語(yǔ)?像Go、Rust,原?就有協(xié)程的?持,但偏傳統(tǒng)的Java、C++等語(yǔ)?沒有原??持。因此,產(chǎn)??些第三?的?案,?如Java的QuasarFiber、微信團(tuán)隊(duì)為C++研發(fā)的libco等,但普及程度還?較最后,表4-3表4-3?鎖(內(nèi)存屏障與下?是Linux內(nèi)核的kfifo.c的源代碼的?部分。這是?個(gè)RingBuffer,允許?個(gè)線程寫、?個(gè)線程讀,整個(gè)代碼沒有任何的加鎖,也沒有CAS(CompareAndSet),但線程是安全的,這是如何做到的?讀可以多線程,寫必須單線程,也稱為Single-WriterPrinciple。如果在上?的基礎(chǔ)上,使?了內(nèi)存屏障。也就是smp_wmb()代碼第2代碼第4在第2?代碼和第3?代碼之間插??個(gè)內(nèi)存屏障,這樣前兩?代碼就不會(huì)跑到后兩?代碼的后?去執(zhí)?。雖然第1?、第2?之間可能被重排序;第3?、第4?可能被重排序,但第1?、第2?不會(huì)跑到第3?的后?去。回到上?的kfifo,它有兩個(gè)指針fifo->infifo->out,分別對(duì)應(yīng)隊(duì)列的頭部和尾部,寫的線程操作fifo->in,讀的線程操作fifo->out,通過fifo->in和fifo->out的?較,就知道隊(duì)列是空還是滿。在這?,內(nèi)存屏障起到兩個(gè)作?:?個(gè)線程改了fifo->in或者fifo->out之后,另外?個(gè)線程要?即可out的值。為此,在memcpy和修改fifo->in/fifo->out之間插?了內(nèi)存屏基于內(nèi)存屏障,有了Java中的volatile關(guān)鍵字,再加上單線程寫的原在CPU層?提供的?個(gè)硬件原?指令,實(shí)現(xiàn)對(duì)同?個(gè)值的Compare和Set兩下?展?了JDK6中,CAS函數(shù)的源代碼,unsafecompareAndSwapInt在不同的JDK版本中,不同操作系統(tǒng)上?,該本地?法的實(shí)現(xiàn)有差Fast,andPracticalNon-BlockingandBlockingConcurrentQueue線程要?jiǎng)h除節(jié)點(diǎn),要安全地實(shí)現(xiàn)并發(fā)并?易事。有興趣的讀者可以參考TimothyL.Harris編寫的Apragmaticimplementationofnon-blockinglinked第5?絡(luò)協(xié)議有很多種,但對(duì)互聯(lián)?來(lái)說(shuō),?得最多的就是HTTPHTTP主要有1.0、1.1、2三個(gè)版本,在HTTP之上有HTTPS1996年,HTTP1.0協(xié)議規(guī)范RFC19451999年,HTTP1.1協(xié)議規(guī)范RFC26162015年,HTTP/2協(xié)議規(guī)范RFC7540/7541主流的協(xié)議?直是HTTP1.1。接下來(lái)將對(duì)HTTP協(xié)議的發(fā)展脈絡(luò)進(jìn)?梳HTTPHTTP1.0TCP連接,在連接上?發(fā)?個(gè)HTTPRequest到服務(wù)器,服務(wù)器返回?個(gè)HTTPResponse,然后連接關(guān)閉。每來(lái)?個(gè)請(qǐng)求,就要開?個(gè)連接,請(qǐng)求性能問題。連接的建?、關(guān)閉都是耗時(shí)操作。對(duì)于?個(gè)?頁(yè)來(lái)說(shuō),除了頁(yè)?本?的HTML請(qǐng)求,頁(yè)???的JS、CSS、img資源,都是?個(gè)個(gè)的HTTP請(qǐng)求?,F(xiàn)在的互聯(lián)?上的頁(yè)?,?個(gè)頁(yè)?上有??個(gè)資源?件是很常見的事。每來(lái)?個(gè)請(qǐng)求就開?個(gè)TCP連接是?常耗時(shí)的。雖然可以同時(shí)開多個(gè)連接,并發(fā)地發(fā)送請(qǐng)求,但連接數(shù)畢竟是有限的。服務(wù)器推送問題。不?持“?來(lái)多回”,服務(wù)器?法在客戶端沒有請(qǐng)求的情況下主動(dòng)向客戶端推送消息。但很多的應(yīng)?恰恰都需要服務(wù)器在某些事件完成后主動(dòng)通知客戶端。針對(duì)這兩個(gè)問題,來(lái)看HTTPKeep-Alive機(jī)制與Content-Length為了解決上?提及的第?個(gè)問題,HTTP1.0設(shè)計(jì)了?個(gè)Keep-Alive機(jī)制來(lái)實(shí)現(xiàn)TCP連接的復(fù)?。具體來(lái)說(shuō),就是客戶端在HTTP?個(gè)字段Connection:Keep-Alive理完請(qǐng)求之后不會(huì)關(guān)閉連接,同時(shí)在HTTP的Response??也會(huì)加上該字?個(gè)Keep-Alivetimeout參數(shù),過?段時(shí)間之后,如果該連接上沒有新的請(qǐng)連接復(fù)?之后又產(chǎn)?了?個(gè)新問題:以前?個(gè)連接就只發(fā)送?個(gè)請(qǐng)答案是在HTTPResponse的頭部,返回了?個(gè)Content-Length:xxx的字段,這個(gè)字段可以告訴客戶端HTTPResponse的Body共有多少個(gè)字節(jié),HTTP連接復(fù)?與Chunk從上?的分析可以看出,連接復(fù)??常有必要,所以到了HTTP1.1之地加上Connection:Close屬性,服務(wù)器才會(huì)在請(qǐng)求處理完畢之后主動(dòng)關(guān)在HTTP1.0??可以利?Content-Length字段,讓客戶端判斷?個(gè)請(qǐng)求的響應(yīng)成功是否接收完畢。但Content-Length有個(gè)問題,如果服務(wù)器返回為此,在HTTP1.1中引?了Chunk機(jī)制(HttpStreaming)。具體來(lái)的結(jié)尾也有個(gè)特殊標(biāo)記。這樣,即使沒有Content-Length字段,也能?便下?顯?了?個(gè)簡(jiǎn)單的具有Chunk機(jī)制的HTTP響應(yīng),頭部沒有Content-Length字段,?是Transfer-Encoding:chunked4個(gè)chunk,數(shù)字25(16進(jìn)制)表?第?個(gè)chunk的字節(jié)數(shù),1C(16進(jìn)制表?第?個(gè)chunk的字節(jié)數(shù)……最后的數(shù)字0表?整個(gè)響應(yīng)的末尾。HTTP/1.1200ThisisthedatainthefirstchunkandthisisthesecondonePipeline與Head-of-lineBlocking有了“連接復(fù)?”為此,HTTP1.1引?了Pipeline機(jī)制。在同?個(gè)TCP請(qǐng)求,這樣就提?了在同?個(gè)TCP連接上?的處理請(qǐng)求的效率。如圖5-1?,展?了在同?個(gè)TCP連接上?,串?和Pipeline圖5-1串?和Pipeline從圖中可以明顯看出,PipelinePipeline有個(gè)致命問題,就是Head-of-LineBlocking翻譯成中?叫作“隊(duì)頭阻塞”。什么客戶端發(fā)送的請(qǐng)求順序是1、2、3,雖然服務(wù)器是并發(fā)處理的,但客戶端接收響應(yīng)的順序必須是1、2、3,如此才能把響應(yīng)和請(qǐng)求成功配對(duì),跟隊(duì)列?樣,先進(jìn)先出。?旦隊(duì)列頭部請(qǐng)求1發(fā)?延遲,客戶端遲遲收不到請(qǐng)求1的響應(yīng),則請(qǐng)求2、請(qǐng)求3的響應(yīng)也會(huì)被阻塞,如圖5-2所?。如果請(qǐng)求2、請(qǐng)求3不和請(qǐng)求1在?個(gè)TCP連接上?,?是在其他的TCP連接上?發(fā)出去的話,說(shuō)不定響應(yīng)早返回了,現(xiàn)在因?yàn)檎?qǐng)求1處理得慢,也影響了請(qǐng)求2、請(qǐng)求3。圖5-2Pipeline也正因?yàn)槿绱耍瑸榱吮苊釶ipeline帶來(lái)的副作?,很多瀏覽器默認(rèn)把PipelineHTTP/2???,Pipeline不能?,在同?個(gè)TCP能要發(fā)??個(gè)HTTP請(qǐng)求,卻只有6~8個(gè)連接可?。如何提?并發(fā)度,或Spriting之前要發(fā)送很多個(gè)?圖?的HTTP請(qǐng)求,現(xiàn)在只要發(fā)送?個(gè)請(qǐng)求就可以了。內(nèi)聯(lián)內(nèi)聯(lián)是另外?種針對(duì)?圖?的技術(shù),它將圖?的原始數(shù)據(jù)嵌?在JS把?量?的JS?件合并成?個(gè)?件并壓縮(前端開發(fā)?具很容易實(shí)尤其是現(xiàn)在CDN?得?常?泛,?站的靜態(tài)資源(img,js,css)可能都在CDN上?,可以做?批CDN的域名,都建?6~8個(gè)連接,從?提?頁(yè)?加載的并發(fā)度。“?來(lái)多回”對(duì)于Web來(lái)說(shuō),?論HTTP1.0,還是HTTP1.1,都?法直接做到服務(wù)?如客戶端每5s向服務(wù)器發(fā)送?個(gè)HTTP請(qǐng)求,服務(wù)器如果有新消定期輪詢的?式既低效,又增加了服務(wù)器的壓?,現(xiàn)在已經(jīng)很少采HTTP客戶端發(fā)送?個(gè)HTTP請(qǐng)求,如果服務(wù)器有新消息,就?即返回;如果沒有,則服務(wù)器夯住此連接,客戶端?直等該請(qǐng)求返回。然后過?個(gè)約定的時(shí)間之后,如果服務(wù)器還沒有新消息,服務(wù)器就返回?個(gè)空消息(客戶端和服務(wù)器約定好的?個(gè)消息)。客戶端收到空消息之后關(guān)閉連接,再發(fā)起?個(gè)新的連接,重復(fù)此過程。HTTP與長(zhǎng)輪詢的差異在于,這?只有?個(gè)HTTP請(qǐng)求,不存在HTTP相?于HTTP1.0,HTTP1.1還有?個(gè)很實(shí)?的特性是“斷點(diǎn)續(xù)傳”。當(dāng)邊下載?邊記錄下載的數(shù)據(jù)量??,?旦連接中斷了,重新建?連接之后,在請(qǐng)求的頭部加上Rangefirstoffset-lastoffset字段,指定從某個(gè)offset下載到某個(gè)offset,服務(wù)器就可以只返回(firstoffset,lastoffset)之這?要補(bǔ)充說(shuō)明,HTTP1.1的這種特性只適?于斷點(diǎn)下載。要實(shí)現(xiàn)斷因?yàn)镠TTP1.1的Pipeline不夠完善,Web?HTTP1.1的效率。但這些?法都是從應(yīng)?層?去解決的,沒有普適性因此有?想到在協(xié)議層?去解決這問題,?這正是Google公司的SPDY協(xié)議的初衷。SPDY是Google公司開發(fā)的?個(gè)實(shí)驗(yàn)性協(xié)議,于2009年年中發(fā)布。2012年,該協(xié)議得到了Chrome、Firefox和Opera叫HTTP/2,沒有叫HTTP2.0?不會(huì)再有?版本。如果要有的話,下?個(gè)版本就是HTTP/3。因此,在HTTP/2標(biāo)準(zhǔn)出來(lái)之后,Google也棄?了SPDY,全?轉(zhuǎn)向HTTP/2。接下與HTTP1.1既然HTTP1.1已經(jīng)成了當(dāng)今互聯(lián)?的主流,因此HTTP/2在設(shè)計(jì)過程中,?先要考慮的就是和HTTP1.1的兼容問題,所謂兼容,意味著:不能改變http://、https://這樣的URL不能改變HTTPRequest/HttpResponse的報(bào)?結(jié)構(gòu)。HTTP如何能做到在不改變Reqeust/Response報(bào)?結(jié)構(gòu)的情況下,發(fā)明出?個(gè)新的HTTP/2協(xié)議呢?這點(diǎn)正是理解HTTP/2協(xié)議的關(guān)鍵所在。如圖5-3所?,HTTP/2和HTTP1.1并不是處于平級(jí)的位置,?是處在HTTP1.1和TCP之間。以前HTTP1.1直接構(gòu)建在TCP之上;現(xiàn)在相當(dāng)于在HTTP1.1和TCP之間多了?個(gè)轉(zhuǎn)換層,這個(gè)轉(zhuǎn)換層就是SPDY,也就是現(xiàn)圖5-3HTTP/2(SPDY)?進(jìn)制分幀是HTTP/2為了解決HTTP1.1的“隊(duì)頭阻塞”?特性,是圖5-3HTTP1.1本?是明?的字符格式,所謂的?進(jìn)制分幀,是指在把這個(gè)字符格式的報(bào)?給TCP之前轉(zhuǎn)換成?進(jìn)制,并且分成多個(gè)幀(多個(gè)數(shù)據(jù)如圖5-4TCP連接。因?yàn)門CP這?的請(qǐng)求1、2、3,響應(yīng)1、2、3是HTTP1.1的明?字符報(bào)?。每個(gè)圖5-4HTTP/2這?有?個(gè)關(guān)鍵問題:請(qǐng)求和響應(yīng)都是被打散后分成多個(gè)幀亂序地發(fā)出去的,請(qǐng)求和響應(yīng)都需要重新組裝起來(lái),同時(shí)請(qǐng)求和響應(yīng)還要??配對(duì)。那么組裝和配對(duì)應(yīng)如何實(shí)現(xiàn)呢?原理也很簡(jiǎn)單,每個(gè)請(qǐng)求和響應(yīng)實(shí)際上組成了?個(gè)邏輯上的“流”,為每條流分配?個(gè)流ID,把這個(gè)ID作為標(biāo)簽,打到每?個(gè)幀上。在圖5-4中,有三條流、三個(gè)流ID,分別打到三條流??每?個(gè)幀上。有了這個(gè)?進(jìn)制分幀之后,在TCP層?,雖然是串?的;但從HTTP?來(lái)看,請(qǐng)求就是并發(fā)地發(fā)出去、并發(fā)地接收的,沒有了HTTP1.1的圖5-5HTTP/2有了?進(jìn)制分幀,是不是就徹底解決了Pipeline的“隊(duì)頭阻塞”問題呢?其實(shí)還沒有。只是把隊(duì)頭阻塞問題從HTTPRequest粒度細(xì)化到只要?TCP協(xié)議,就繞不開“隊(duì)頭阻塞”問題,因?yàn)門CP協(xié)議是先進(jìn)先出的!如圖5-5所?,如果幀F(xiàn)3(隊(duì)頭的第?個(gè)幀)原因2:服務(wù)器對(duì)請(qǐng)求12,1的第?個(gè)幀又處在隊(duì)頭,則即使?進(jìn)制分幀也解決不了隊(duì)頭阻塞問題;但對(duì)于原因1,請(qǐng)求2、請(qǐng)求3的響應(yīng)分幀之后,是先于請(qǐng)求1的響應(yīng)發(fā)出去的,那么請(qǐng)求2和請(qǐng)求3的響應(yīng)就不會(huì)被請(qǐng)求1阻塞,從?就避免了隊(duì)頭阻塞問題。同時(shí),在HTTP/2??,還可以指定每個(gè)流的優(yōu)先級(jí),當(dāng)資源有限的除了?進(jìn)制分幀,HTTP/2另外?個(gè)提升效率的?法是頭部壓縮。在HTTP1.1?,對(duì)于報(bào)?的報(bào)?體,已經(jīng)有相應(yīng)的壓縮,尤其對(duì)于圖?,本為解決HTTP1.1SSL/TLS的歷史?乎和互聯(lián)?歷史?樣長(zhǎng):SSL(SecureSocketsLayer)的中?名稱為安全套接層,TLS(TransportLayerSecurity)的中?1994年,?景(NetScape)公司設(shè)計(jì)了SSL1995年,?景公司發(fā)布SSL2.01996年,SSL3.01999年,互聯(lián)?標(biāo)準(zhǔn)化組織IETF對(duì)SSL進(jìn)?標(biāo)準(zhǔn)化,發(fā)布了TLS2006年和2008年,TLS進(jìn)?了兩次升級(jí),分別為TLS1.1和TLS1.2。TLS1.0相當(dāng)于SSL3.1;TLS1.1、TLS1.2相當(dāng)于SSL3.2、SSL3.3。在應(yīng)?層?,習(xí)慣將兩者并稱為SSL/TLS如圖5-6所?,SSL/TLS處在TCP層的上?,它不僅可以?撐HTTP協(xié)圖5-6SSL/TLS對(duì)稱加密的想法很簡(jiǎn)單,如圖5-7所???蛻舳撕头?wù)器知道同?個(gè)圖5-7如何存儲(chǔ)密鑰?對(duì)于瀏覽器的?頁(yè)來(lái)說(shuō),都是明?的,肯定存儲(chǔ)不了密鑰;對(duì)于Android/iOS客戶端,即使能把密鑰藏在安裝包的某個(gè)位置,也很容易被破解。當(dāng)然,這兩個(gè)問題其實(shí)是?個(gè)問題。因?yàn)槿绻鉀Q了密鑰的傳輸問如圖5-8所?,客戶端為??準(zhǔn)備?對(duì)公私鑰(PubA,PriA),服務(wù)器為??準(zhǔn)備?對(duì)公私鑰(PubB,PriB)。公私鑰有個(gè)關(guān)鍵特性:公鑰PubA是通過私鑰PriA計(jì)算出來(lái)的,但反過來(lái)不?,不能根據(jù)PubAPriA圖5-8息時(shí),先???的私鑰PriB簽名,然后?PubA加密;客戶端接收到服務(wù)鑰和私鑰。如圖5-9所?,客戶端沒有公鑰和私鑰對(duì),只有服務(wù)器有。服服務(wù)器?私鑰解密。反過來(lái),服務(wù)器給客戶端發(fā)送的消息,采?明?發(fā)圖5-9假設(shè)PubB的傳輸過程是安全的,客戶端知道了服務(wù)器的公鑰??蛻舳司涂梢岳?加密通道給服務(wù)器發(fā)送?個(gè)對(duì)稱加密的密鑰,如圖5-10客戶端對(duì)服務(wù)器說(shuō):“Hixxx,接下來(lái)就?這個(gè)密鑰通信?!边@句話是通過PubB加密的,所以只有服務(wù)器能???的圖5-10單向?對(duì)稱加密+?個(gè)典型的“中間?攻擊”的案例為例,來(lái)看?下這個(gè)問題是如何被解決圖5-11反過來(lái),服務(wù)器本來(lái)是要把??的公鑰發(fā)給客戶端:“Hi,我是服務(wù)器,我的公鑰是PubB。”被C劫持之后,C???的公鑰替換服務(wù)器的公鑰,然后發(fā)給客戶端:“Hi,我是服務(wù)器,我的公鑰是PubC?!盋通信!接下來(lái),客戶端發(fā)給服務(wù)器的信息,會(huì)?PubC如圖5-12所?,引??個(gè)中間機(jī)構(gòu)CA。當(dāng)服務(wù)器把公鑰發(fā)給客戶端務(wù)器。服務(wù)器先把??的公鑰PubB發(fā)給CA,CA給服務(wù)器頒發(fā)?個(gè)數(shù)字證圖5-12服務(wù)器???的公鑰PubB通過CA反過來(lái)也同理,如圖5-13所?,客戶端???的公鑰PubA通過CA取?個(gè)證書,相當(dāng)于客戶端的?份證,客戶端把這個(gè)證書發(fā)給服務(wù)器,服務(wù)器就能驗(yàn)證整個(gè)證書是否為客戶端下發(fā)的。圖5-13客戶端???的公鑰PubA通過CACA有?對(duì)公鑰和私鑰對(duì),私鑰只有CA知道,公鑰在?絡(luò)上,誰(shuí)都可以知道。服務(wù)器把個(gè)?信息+CA,CA???的私鑰為服務(wù)器?成?個(gè)數(shù)字證書。通俗地講,服務(wù)器把??的公鑰發(fā)給CA,讓CA加蓋公章,之后別?就不能再偽造公鑰了。如果被中間?偽造了,客戶端拿著CA的公鑰去驗(yàn)證這個(gè)證書,驗(yàn)證將?法通過。根證書與CACA?臨和客戶端、服務(wù)器同樣的問題:客戶端和服務(wù)器需要證明公鑰的確是由??發(fā)出去的,不是被偽造的;CA同樣需要證明,??的公鑰是由??發(fā)出去的,不是被偽造的。答案是給CA頒發(fā)證書!CA的證書誰(shuí)來(lái)頒發(fā)呢?CA的上?級(jí)CA。最終形成圖5-14所?的證書信?鏈。關(guān)于證書信任鏈,有兩點(diǎn)說(shuō)明:圖5-14?CA0呢,只能?條件信任。怎么做到?條件信任呢?RootCA機(jī)構(gòu)覽器,也就信任了這些Root證書。頒發(fā)過程與驗(yàn)證過程剛好是逆向的,上?級(jí)CA給下?級(jí)CA頒發(fā)證安全領(lǐng)域經(jīng)常見到的?個(gè)詞,PKI(PublicKeyInfrastructure)。SymantecClass3SecureServer的機(jī)構(gòu),RootCA叫作VeriSign。圖5-15SSL/TLS圖5-16SSL/TLS當(dāng)然,為了協(xié)商出對(duì)稱加密的密鑰,SSL/TLS協(xié)議引?了?個(gè)隨機(jī)理解了SSL/TLS,再來(lái)看HTTPS就很簡(jiǎn)單了,HTTPS=HTTP+SSL/TLS。整個(gè)HTTPS的傳輸過程?致可以分成三個(gè)階圖5-17TCPSSL/TLS基于密鑰,在TCP連接上對(duì)所有的HTTPRequest/Response進(jìn)?其中階段(1)和階段(2)只在連接建?時(shí)做1次,之后只要連接不關(guān)閉,每個(gè)請(qǐng)求只需要經(jīng)過階段(3),因此相?HTTP,性能沒有太?損失。題,HTTPS主要解決安全問題。從理論上講,兩者沒有必然的關(guān)系,HTTP/2可以不依賴于HTTPS;反過來(lái)也如此。把兩者同時(shí)放在整個(gè)?絡(luò)為HTTP1.1;只去掉HTTP/2?層,變?yōu)镠TTPS;兩層都加上,變?yōu)榈趯?shí)踐層?,?前主流的瀏覽器都要求如果要?持HTTP/2?持HTTPS,這也是因?yàn)檎麄€(gè)互聯(lián)?都在推動(dòng)HTTPS圖5-18HTTP/2和HTTPS圖5-19UDP丟包:數(shù)據(jù)包2時(shí)序錯(cuò)亂:客戶端先發(fā)的是數(shù)據(jù)包1,后發(fā)的是數(shù)據(jù)包3;服務(wù)器卻先收到了數(shù)據(jù)包3,后收到的是數(shù)據(jù)包1。那TCP是如何做到“可靠”的呢?先看?下TCP數(shù)據(jù)包不重。?如服務(wù)器不會(huì)收到兩次數(shù)據(jù)包2,有且只會(huì)收到?圖5-20TCP但數(shù)據(jù)包1、2、3在?絡(luò)上?的是不同的路由鏈路,數(shù)據(jù)包既可能丟失,到達(dá)的先后順序也不?定正確。那么,客戶端和服務(wù)器之間通過什么機(jī)制可以保證“可靠”地傳輸這三個(gè)語(yǔ)義呢?或者說(shuō),如何在?個(gè)“不可靠”的?絡(luò)上實(shí)現(xiàn)?個(gè)“可靠”的?絡(luò)通道??這正是TCP的核?所在:解決不丟問題:ACK+送數(shù)據(jù)包22……這樣每個(gè)數(shù)據(jù)包都要??確?如服務(wù)器?乎同時(shí)收到了數(shù)據(jù)包1、2、3,它只?回復(fù)客戶端(ACK=3),3的數(shù)據(jù)包都已經(jīng)收到了;又過了?會(huì),服務(wù)器收到了數(shù)據(jù)包4、5、6,它只?回復(fù)客戶端(ACK=6),意思就會(huì)重發(fā)。但可能此時(shí)服務(wù)器的ACK已經(jīng)在?絡(luò)上了,只是還沒有到達(dá)然不現(xiàn)實(shí)。其實(shí)解決?法很簡(jiǎn)單,就是順序ACK。服務(wù)器給客戶端回復(fù)服務(wù)器收到了數(shù)據(jù)包4,回復(fù)ACK=7,同時(shí)數(shù)據(jù)包5、6、7?說(shuō)的判重的辦法,丟棄掉數(shù)據(jù)包5、6、7總之,服務(wù)器雖然接收數(shù)據(jù)包是并發(fā)的,但數(shù)據(jù)包的ACK是按照編端的數(shù)據(jù)包的發(fā)送與接收,?相同的原理來(lái)實(shí)現(xiàn),從?實(shí)現(xiàn)TCP的全雙TCP的“假”連接(狀態(tài)機(jī)可以看到,在物理層?,數(shù)據(jù)包1、2、3?的是不同的?絡(luò)鏈路,在客戶端和服務(wù)器之間并不存在?條可靠的“物理管道”。只是在邏輯層?,通過?定的機(jī)制,讓TCPPort)4Socket。其中有?個(gè)關(guān)鍵問題對(duì)這條連接維護(hù)不同的狀態(tài)變遷,在不同的狀態(tài)下執(zhí)?相應(yīng)的操作。圖5-21呈現(xiàn)了?個(gè)TCP連接在三個(gè)階段經(jīng)歷的11種狀態(tài)變遷。表5-1TCP圖5-21?個(gè)TCP到CLOSED狀態(tài)。為什么開始是處于ClOSED狀態(tài),?沒有?個(gè)INIT(初始)狀態(tài)呢?因?yàn)檫B接是復(fù)?的,每個(gè)連接?4元組唯?標(biāo)識(shí),關(guān)閉之三次握?(?絡(luò)2將軍問題圖5-22展?了TCP建?連接的三次握?過程,以及對(duì)應(yīng)的客戶端和圖5-22TCPseq=x表?發(fā)出去的包的編號(hào)是x。因?yàn)門CP把兩個(gè)包合在?起傳輸,所以就有了同?個(gè)包?,同時(shí)包含有seq=y,ACK=x+1。表?當(dāng)前這個(gè)包是發(fā)出去的第y個(gè)包,同時(shí)也是對(duì)對(duì)?的第x個(gè)CLOSEDSYN_SENTESTABLISHED;服務(wù)器的狀態(tài)轉(zhuǎn)移過程是服務(wù)器:“好的,可以。這就是經(jīng)典的?絡(luò)的2將軍問題:?論是兩次,還是三次,還是四到,只能讓對(duì)?為這個(gè)ACK再回復(fù)?個(gè)ACK?絡(luò)的2場(chǎng)景1場(chǎng)景2:該請(qǐng)求服務(wù)器收到了,服務(wù)器寫?成功了,但回復(fù)給客戶端時(shí),?絡(luò)有問題。場(chǎng)景3:?絡(luò)沒有問題,服務(wù)器接收到了請(qǐng)求,寫?成功了,但回復(fù)?論是兩次握?,還是三次握?、四次握?,都繞不開?絡(luò)的2將軍問題,那為什么是三次呢?做了?次確認(rèn)。第?次,客戶端給服務(wù)器發(fā)了seq=x,?法得到對(duì)?是否收到;第?次,對(duì)?回復(fù)了seq=y,ACK=x+1。這時(shí)客戶端知道??的發(fā)送和接收能?沒有問題,但服務(wù)器只知道??的接收能?沒問題;第三23所?,假設(shè)客戶端主動(dòng)發(fā)起關(guān)閉連接,客戶端的狀態(tài)轉(zhuǎn)移過程為: CLOSED;服務(wù)器的狀態(tài)轉(zhuǎn)移過程為:ESTABLISHED圖5-23TCP為什么是四次揮?呢?因?yàn)門CP是全雙?的,可以處于Half-Close狀如果只發(fā)?了第?次和第?次,意味著該連接處于Half-Close狀態(tài),F(xiàn)IN_WAIT_1狀態(tài),此時(shí)又收到了對(duì)?的ACK,這時(shí)雙?都會(huì)切到CLOSING狀態(tài),之后?起進(jìn)?TIME_WAIT狀態(tài),經(jīng)過?段時(shí)間后進(jìn)?做?個(gè)TIME_WAIT狀態(tài),?要等?段時(shí)間之后,才能進(jìn)?CLOSE狀態(tài)所謂的“連接”是假的,物理層?沒有連接。這意味著當(dāng)雙?都進(jìn)?CLOSE狀態(tài)后,仍可能有數(shù)據(jù)包還在?絡(luò)上“閑逛”,此時(shí)如果收到了這些閑逛的數(shù)據(jù)包,丟掉即可,但問題是連接可能重開。?個(gè)連接是由(客戶端IP、客戶端Port、服務(wù)器IP、服務(wù)器Port)4元組唯?標(biāo)識(shí)的,連接關(guān)閉之后再重開,應(yīng)該是?個(gè)新的連接,但?4元組后被當(dāng)作新的數(shù)據(jù)包,這樣?來(lái),?連接上的數(shù)據(jù)包會(huì)“串”到新連接上在整個(gè)TCP/IP?絡(luò)上,定義了?個(gè)值叫作MSL(MaximumSegmentLifetime),任何?個(gè)IP數(shù)據(jù)包在?絡(luò)上逗留的最長(zhǎng)時(shí)間是MSL,這個(gè)值默認(rèn)是120s。意味著?個(gè)數(shù)據(jù)包必須最多在MSL有了這個(gè)限定之后,?個(gè)連接保持在TIME_WAIT狀態(tài),再等待還有?個(gè)問題:客戶端處于TIME_WAIT狀態(tài),要等2×MSL時(shí)間進(jìn)?個(gè)連接都是?個(gè)4元組,同時(shí)關(guān)聯(lián)了客戶端和服務(wù)器,客戶端處于TIME_WAIT狀態(tài)后,意味著這個(gè)連接要到2×MSL時(shí)間之后才能重新啟通過分析會(huì)發(fā)現(xiàn),?個(gè)連接并不是想關(guān)就能?刻關(guān)的,關(guān)閉后還要等2×MSL時(shí)間才能重開。這就會(huì)造成?個(gè)問題,如果頻繁地創(chuàng)建連接,可能導(dǎo)致?量的連接處于TIME_AIT狀態(tài),最終耗光所有的連接資源。為了避免出現(xiàn)這種問題,可以采取如下措施:不要讓服務(wù)器主動(dòng)關(guān)閉連接。這樣服務(wù)器的連接就不會(huì)處于客戶端做連接池,復(fù)?連接,?不要頻繁地創(chuàng)建和關(guān)閉,這其實(shí)也是HTTP1.1和HTTP/2采?的思路。QUIC(QuickUDPInternetConnection)是由Google公司提出的基于UDP只要使?TCP,就沒有辦法完全解決隊(duì)頭阻塞問題,因?yàn)門CP是先發(fā)送先接收,?UDP沒有這個(gè)限制。正因?yàn)槿绱?,Google公司提出了圖5-24QUIC不丟包(Raid5算法和Raid6算法雖然針對(duì)UDP會(huì)丟包,TCP可以通過“ACK+重傳”來(lái)解決,但很多時(shí)候重傳的效率不夠?。?如服務(wù)器收到了數(shù)據(jù)包1、2、3,之后收到數(shù)據(jù)包5、6、7,其中數(shù)據(jù)包4?直沒有收到,客戶端會(huì)把數(shù)據(jù)包4、5、6、7全部重傳?遍。除了重傳,是否還有其他?法來(lái)解決丟包問題呢?這就要?到在磁盤存儲(chǔ)領(lǐng)域經(jīng)典的Raid5和Raid6算法。如圖5-25所?,每發(fā)送5個(gè)數(shù)據(jù)包,發(fā)送?個(gè)冗余包。冗余包是對(duì)5個(gè)數(shù)據(jù)包做異或運(yùn)算得到的。這樣?來(lái),服務(wù)器收到6個(gè)包,如果5個(gè)當(dāng)中,有?個(gè)丟失了,可以通過其他?個(gè)包計(jì)算出來(lái)。這就好?做最簡(jiǎn)單的數(shù)學(xué)運(yùn)算:A+B+C+D+E=R,假設(shè)數(shù)據(jù)包D丟失了,可以通過D=R-A-B-C-E計(jì)算出來(lái)。但這種丟包的恢復(fù)辦法有?個(gè)限制條件:每5個(gè)當(dāng)中只能丟失?個(gè),如果把它改成每發(fā)送10個(gè)數(shù)據(jù)包,再?成?個(gè)冗余包,就是每10個(gè)當(dāng)中只能丟失?個(gè)。圖5-25Raid5在Raid5基礎(chǔ)上,把可靠性向上提?個(gè)級(jí)別,?成兩個(gè)冗余塊,就是Raid6,如圖5-26所?。每5個(gè)數(shù)據(jù)塊?成兩個(gè)冗余塊,這就允許每5個(gè)塊A、B、C、D、E中丟失了任意的兩個(gè),可以通過解?元?次?程組反算回來(lái)。圖5-26Raid6對(duì)于QUIC來(lái)說(shuō),它采?了RAID5,?前是每發(fā)送10個(gè)數(shù)據(jù)包,構(gòu)建更少的加上SSL/TLS的四次握?,是三個(gè)RTT所以?論TCP,還是SSL/TLS,?數(shù)的前輩們都嘗試過優(yōu)化協(xié)議,減少TT的次數(shù),對(duì)于QUIC協(xié)議同樣如此?;赒UIC協(xié)議,可以把前?的七次握?(三個(gè)TT),減為0不再展開討論。TCP的連接由4元組組成,這在PC端上問題不?。?在移動(dòng)端上,戶端是i-Fi或者4G,客戶端的IP?直在變化,意味著頻繁地建?和關(guān)閉連接。有沒有可能,在客戶端的IP和端?浮動(dòng)的情況下,連接仍然可以維持呢?TCP的連接本來(lái)就是“假”的,?個(gè)邏輯上的概念?已,QUIC協(xié)議也可以創(chuàng)造?個(gè)邏輯上的連接。具體做法是,不再以4元組來(lái)標(biāo)識(shí)連接,?是讓客戶端?成?個(gè)64位的數(shù)字標(biāo)識(shí)連接,雖然客戶端的IP和Port在漂移,但64位的數(shù)字沒有變化,這條連接就會(huì)存在。這樣,對(duì)于上層應(yīng)?來(lái)說(shuō),就感覺連接?直存在,沒有中斷過。第6表6-1但在互聯(lián)?應(yīng)?中,為了性能或便于開發(fā),違背范式的設(shè)計(jì)??皆是,如字段冗余、字段存?個(gè)復(fù)雜的JSON串、分庫(kù)分表之后數(shù)據(jù)多維度冗余存儲(chǔ)、寬表等。JSON、存數(shù)組類型的字段。分布式ID同?張表?。當(dāng)查詢的時(shí)候,按?戶ID查,可以很容易地定位到某個(gè)庫(kù)的某個(gè)表;但如果按訂單ID或商戶ID維度查詢,就很難做。建?輔助維度和主維度之間的映射關(guān)系(IDID之間的映同?份數(shù)據(jù),兩套分庫(kù)分表。?套按?戶ID切分,?套按商戶ID切把訂單ID和?戶ID統(tǒng)?成?個(gè)維度,?如把?戶ID作為訂單ID?位,這樣訂單ID中就包含了?戶ID信息,然后按照?戶ID分庫(kù),當(dāng)按訂單ID查詢的時(shí)候,截取出?戶ID,再按?戶ID查詢;或者訂單ID和?Join把Join拆成多個(gè)單表查詢,不讓數(shù)據(jù)庫(kù)做Join,?是在代碼層對(duì)結(jié)很多時(shí)候會(huì)有這樣的情況:需要把Join的結(jié)果分頁(yè),這需要利?對(duì)于第?種?法當(dāng)中提到的場(chǎng)景,還可以利?類似ES的搜索引擎,B+關(guān)系型數(shù)據(jù)庫(kù)在查詢??有?些重要特性,是KV型的數(shù)據(jù)庫(kù)或者緩存所不具備的,?如:這些特性的?持,要?dú)w功于B+樹這種數(shù)據(jù)結(jié)構(gòu)。下?來(lái)分析B+如何?持這些查詢特性的。B+圖6-1展?了數(shù)據(jù)庫(kù)的主鍵對(duì)應(yīng)的B+樹的邏輯結(jié)構(gòu),這個(gè)結(jié)構(gòu)有?個(gè)關(guān)鍵特征:圖6-1數(shù)據(jù)庫(kù)的主鍵對(duì)應(yīng)的B+前綴匹配模糊查詢。假設(shè)主鍵是?個(gè)字符串類型,要查詢whereKeylikeabc%,其實(shí)可以轉(zhuǎn)化成?個(gè)范圍查詢Keyin[abc,abcz]。當(dāng)然,如果是后綴匹配模糊查詢,或者諸如whereKeylike%abc%這樣的中間匹配,則另外,基于B+樹的特性,會(huì)發(fā)現(xiàn)對(duì)于ofset這種特性,其實(shí)是?不到索引的。?如每頁(yè)顯?10條數(shù)據(jù),要展?第101頁(yè),通常會(huì)寫成selectxxxwherexxxlimit1000,10,從ofset=1000的位置開始取10條。101000條數(shù)據(jù)都遍offset=1000的位置在哪。對(duì)于這種情況,合理的辦法是不要?offset,?是把offset=1000的位置換算成某個(gè)max_id,然后?where語(yǔ)句實(shí)現(xiàn),就變成了selectxxxwherexxxandid>max_idlimit10,這樣就可以利B+這?所說(shuō)的“塊”,是?個(gè)邏輯單位,?不是指磁盤扇區(qū)的物理塊。塊是整數(shù)倍的數(shù)據(jù)。?論葉?節(jié)點(diǎn),還是?葉?節(jié)點(diǎn),都會(huì)裝在Page?。InnoDB為每個(gè)Page賦予?個(gè)全局的32位的編號(hào),所以InnoDB的存儲(chǔ)容量16KB是?個(gè)什么概念呢?如果?來(lái)裝?葉?節(jié)點(diǎn),?個(gè)Page裝1000個(gè)Key(16K,假設(shè)Key是64位整數(shù),8個(gè)字節(jié),再加上各種其他字段),意味著B+樹有1000個(gè)分叉;如果?來(lái)裝葉?節(jié)點(diǎn),?個(gè)Page?概可以裝200條記錄(記錄和索引放在?起存儲(chǔ),假設(shè)?條記錄?概100個(gè)字節(jié))?;谶@種估算,?個(gè)三層的B+樹可以存儲(chǔ)多少數(shù)據(jù)量呢?如圖6-2所?。第?層:?個(gè)節(jié)點(diǎn)是?個(gè)Page,??存放了1000個(gè)Key,對(duì)應(yīng)1000個(gè)分叉。第?層:1000個(gè)節(jié)點(diǎn),1000個(gè)Page,每個(gè)Page??裝1000個(gè)Key基于Page,最終整個(gè)B+樹的物理存儲(chǔ)類似圖6-3Page與Page之間組成雙向鏈表,每?個(gè)Page?個(gè)Page的編號(hào),后?個(gè)Page的編號(hào)。Page??存儲(chǔ)?條條的記錄,記為Page會(huì)?次性讀?內(nèi)存,同?個(gè)Page??的記錄可以在內(nèi)存中順序查圖6-2三層的磁盤B+圖6-3B+在InnoDB的實(shí)踐??,其中?個(gè)建議是按主鍵的?增順序插?記錄就是為了避免PageSplit問題。?如?個(gè)Page?依次裝?了Key為(1,3,5,9)四條記錄,并且假設(shè)這個(gè)Page滿了。接下來(lái)如果插??個(gè)Key=4的記錄,就不得不建?個(gè)新的Page,同時(shí)把(1,3,5,9)分成兩半,前?半(1,3,4)還在舊的Page中,后?半(5,9)拷貝到新的Page?,并且要調(diào)整Page前后的雙向鏈表的指針關(guān)系,這顯然會(huì)影響插?速度。但如果插?的是Key=10的記錄,就不需要做PageSplit,只需要建?個(gè)新的Page,把Key=10的記錄放進(jìn)去,然后讓整個(gè)鏈表的最后?個(gè)Page指向這個(gè)新的Page即可。對(duì)于?主鍵索引,同上?類似的結(jié)構(gòu),每?個(gè)?主鍵索引對(duì)應(yīng)?顆B+樹。在InnoDB中,?主鍵索引的葉?節(jié)點(diǎn)存儲(chǔ)的不是記錄的指針,?是主鍵的值。所以,對(duì)于?主鍵索引的查詢,會(huì)查詢兩棵B+樹,先在?主鍵索引的B+樹上定位主鍵,再?主鍵去主鍵索引的B+樹上找到最終記錄。6-2所?。假設(shè)對(duì)于字段1建?索引(字段1是?個(gè)字符類型),?個(gè)A應(yīng)1,5,7三條記錄,C對(duì)應(yīng)8、12兩條記錄。這反映在B+表6-2如圖6-4所?,?先,每個(gè)葉?節(jié)點(diǎn)存儲(chǔ)了主鍵的值;對(duì)于?葉?節(jié)圖6-4?主鍵索引B+表6-3據(jù)庫(kù)在事務(wù)隔離級(jí)別的定義和實(shí)現(xiàn)上會(huì)有差異,下?以MySQLInnoDB引表6-4InnoDB從表6-4中可以看出,隔離級(jí)別,?級(jí)??級(jí)嚴(yán)格。隔離級(jí)別4?化,所有事務(wù)串?執(zhí)?,雖然能解決上?的四個(gè)問題,但性能?法接受,所以?般不會(huì)采?;隔離級(jí)別1?的是隔離級(jí)別2和隔離級(jí)別3丟失更新在業(yè)務(wù)場(chǎng)景中?常常見,數(shù)據(jù)庫(kù)沒有幫?程師解決這個(gè)問題,只能靠我們??解決了。先看丟失更新出現(xiàn)的場(chǎng)景:假設(shè)DB中有張數(shù)據(jù)表,如表6-5所?。表6-5?戶余額表事務(wù)事務(wù)B:start如果正確地執(zhí)?了事務(wù)A和事務(wù)B(?論誰(shuí)先誰(shuí)后),執(zhí)?完成之后,user_id=1的?戶余額都是30;但現(xiàn)在事務(wù)A和事務(wù)B并?執(zhí)?,執(zhí)?結(jié)果可能是30(正確結(jié)果),也可能是80(事務(wù)A把事務(wù)B的結(jié)果覆蓋?法1事務(wù)事務(wù)balance先讀出來(lái),做各種邏輯計(jì)算之后再寫回去。如果不讀,直接修改balancebalance的值是多少。?法2悲觀鎖,就是認(rèn)為數(shù)據(jù)發(fā)?并發(fā)沖突的概率很?,所以讀之前就上鎖。利?selectxxxforupdate語(yǔ)句,偽代碼如下所?:事務(wù)悲觀鎖有潛在問題,假如事務(wù)A在拿到鎖之后、Commit之前出問題?法3常會(huì)講的CAS(ComapreAndSet)的思路。下?來(lái)看?下,如何實(shí)現(xiàn)在數(shù)表6-6事務(wù)CAS的核?思想是:數(shù)據(jù)讀出來(lái)的時(shí)候有?個(gè)版本v1?修改,當(dāng)再寫回去的時(shí)候,如果發(fā)現(xiàn)數(shù)據(jù)庫(kù)中的版本不是v1(?在實(shí)現(xiàn)層?,就是利?update語(yǔ)句的原?性實(shí)現(xiàn)了CAS,當(dāng)且僅當(dāng)version=v1時(shí),才能把balance更新成功。在更新balance的同時(shí),version也當(dāng)然,在實(shí)際場(chǎng)景中,不會(huì)讓客戶端?限循環(huán)地重試,可以重試三順便介紹Java是如何利?CAS來(lái)做樂觀鎖的。下?是JDK6的JUC?,AtomicInteger?法4樂觀鎖的?案可以很好地應(yīng)對(duì)上述場(chǎng)景,但有?個(gè)限制是select和發(fā)?死鎖。所以,作為數(shù)據(jù)庫(kù),必須有機(jī)制檢測(cè)出死鎖,并解決死鎖問1圖6-5把兩個(gè)事務(wù)的場(chǎng)景擴(kuò)展到多個(gè)事務(wù),如圖6-6圖6-6以事務(wù)為頂點(diǎn),以事務(wù)請(qǐng)求的鎖為邊,構(gòu)建?個(gè)有向圖,這個(gè)圖被稱為ait-forGraph。?如事務(wù)A要請(qǐng)求鎖1、鎖2,?鎖1、鎖2分別被事務(wù)B、事務(wù)C持有,因此事務(wù)A依賴事務(wù)B、事務(wù)C;事務(wù)B要請(qǐng)求鎖3,?鎖3被事務(wù)C持有,所以事務(wù)B依賴事務(wù)C;事務(wù)C要請(qǐng)求鎖4,?鎖4被事務(wù)A持有,所以事務(wù)C依賴事務(wù)A;依此類推。具體到MySQL位到具體的SQL語(yǔ)句,然后解決。死鎖發(fā)?的場(chǎng)景?常的多,與代碼有場(chǎng)景1:如表6-7所?,事務(wù)A操作了表T1、T2的兩條記錄,事務(wù)B也操作了表T1、T2中同樣的兩條記錄,順序剛好反過來(lái),可能發(fā)?死鎖。表6-7死鎖發(fā)?場(chǎng)景場(chǎng)景2:如表6-8所?,同?張表,在第三個(gè)隔離級(jí)別(RR)下,insert操作會(huì)增加Gap鎖,可能導(dǎo)致兩個(gè)事務(wù)死鎖。這個(gè)?較隱晦,不容表6-8死鎖發(fā)?場(chǎng)景事務(wù)實(shí)現(xiàn)原理之1:Redo介紹事務(wù)怎么?后,下?探討事務(wù)的實(shí)現(xiàn)原理。事務(wù)有ACIDI:隔離性。隔離性和并發(fā)性密切相關(guān),因?yàn)槿绻聞?wù)全是串?的(第四個(gè)隔離級(jí)別),在這四個(gè)屬性中,D?較容易,C主要是由上層的各種規(guī)則來(lái)約束,也相對(duì)簡(jiǎn)單。?A和I牽涉并發(fā)問題、崩潰恢復(fù)的問題,將是討論的重點(diǎn)。說(shuō)到事務(wù)的實(shí)現(xiàn)原理,會(huì)追溯到ARIES算法理論,ARIES(AlgorithmsforRecoveryAndIsolationExpolitingSemantics)是20世紀(jì)90年代由IBM?位研究員提出的?個(gè)算法集,主論?是ARIES:ATransactionMethodSupportingFine-GranularityLockingandPartialRollbacksWrite-AheadLoggging。ARIES的思想影響深遠(yuǎn),現(xiàn)代的關(guān)系型數(shù)據(jù)庫(kù)(DB2、MySQL、InnoDB、SQLServer、Oracle)在事務(wù)實(shí)現(xiàn)的很多??都吸收了該思想,在?學(xué)的教科書上如果講到事務(wù)的實(shí)現(xiàn),也都會(huì)介紹接下來(lái),就以InnoDB為背景,分析事務(wù)的ACID其中的三個(gè)屬性(A、I、D)是如何實(shí)現(xiàn)的。先從最簡(jiǎn)單的D開始(I/O問題),然后是A,最后討論I?個(gè)事務(wù)要修改多張表的多條記錄,多條記錄分布在不同的Page為此,就有了Write-aheadLog?志(所謂的Write-aheadLog),然后后臺(tái)任務(wù)把內(nèi)存中的數(shù)據(jù)異步刷到盤隨機(jī)I/O的問題。明明是先在內(nèi)存中提交事務(wù),后寫的?志,為什么叫因?yàn)槭窍葘懙?志,后把內(nèi)存數(shù)據(jù)刷到磁盤,所以叫Write-AheadLog。內(nèi)存操作數(shù)據(jù)+Write-AheadLog的這種思想?常普遍,后?講LSM樹具體到InnoDB中,Write-AheadLog是RedoLog。在InnoDB中,不光事務(wù)修改的數(shù)據(jù)庫(kù)表數(shù)據(jù)是異步刷盤的,連RedoLog的寫?本?也是異步的。如圖6-7所?,在事務(wù)提交之后,RedoLog先寫?到內(nèi)存中的RedoLogBuffer中,然后異步地刷到磁盤上的RedoLog。InnoDB有個(gè)關(guān)鍵的參數(shù)innodb_flush_log_at_trx_commit控制RedoLog0:每秒刷?次磁盤,把RedoLogBuffer中的數(shù)據(jù)刷到RedoLog(默認(rèn)1:每提交?個(gè)事務(wù),就刷?次磁盤(這個(gè)最安全)很顯然,該參數(shù)設(shè)置為0或者2都可能丟失數(shù)據(jù)。設(shè)置為1最安全,但性能最差。InnoDB設(shè)置此參數(shù),也是為了讓應(yīng)?在數(shù)據(jù)安全性和性能之間做?個(gè)權(quán)衡。圖6-7RedoLogRedoLog知道了RedoLog的基本設(shè)計(jì)思想,下?來(lái)看RedoLog的詳細(xì)結(jié)構(gòu)。(1)磁盤的讀取和寫?都不是按?個(gè)個(gè)字節(jié)來(lái)處理的,Log來(lái)說(shuō),就是RedoLogBlock,每個(gè)RedoLogBlock是512512字節(jié)呢?因?yàn)樵缙诘拇疟P,?個(gè)扇區(qū)(最細(xì)粒度的磁盤存儲(chǔ)單位)就是存儲(chǔ)512字節(jié)數(shù)據(jù)。(2)?志?件不可能?限制膨脹,過了?定時(shí)期,之前的歷史?志就不需要了,通俗地講叫“歸檔”,專業(yè)術(shù)語(yǔ)是Checkpoint。所以RedoLog其實(shí)是?個(gè)固定??的?件,循環(huán)使?,寫到尾部之后,回到頭部覆寫(實(shí)際RedoLog是?組?件,但這?就當(dāng)成?個(gè)??件,不影響對(duì)原理的理解)。之所以能覆寫,因?yàn)?旦Page數(shù)據(jù)刷到磁盤上,?志數(shù)據(jù)就沒圖6-8展?了RedoLog邏輯與物理結(jié)構(gòu)的差異,LSN(LogSequenceNumber)是邏輯上?志按照時(shí)間順序從?到?的編號(hào)。在InnoDB中,總的?志字節(jié)數(shù)。實(shí)際上LSN沒有從0開始,?是從8192開始,這個(gè)是圖6-8RedoLog物理上?,?個(gè)固定的?件??,每512個(gè)字節(jié)?個(gè)Block?。顯然,很容易通過LSN換算出所屬的Block。反過來(lái),給定RedoLog,也很容易算出第?條?志在什么位置。假設(shè)在RedoLog中,從頭到尾所記很顯然,第1條?志是30,最后1條?志是478,30以前的已經(jīng)被覆Physiological知道了RedoLog的整體結(jié)構(gòu),下?進(jìn)?步來(lái)看每個(gè)LogBlock??(1)1。類似Binlog的statement格式,記原始的SQL語(yǔ)句,insert/delete/update記法3。記錄修改的每個(gè)Page的字節(jié)數(shù)據(jù)。由于每個(gè)Page16KB,記錄這16KB?哪些部分被修改了。?個(gè)Page(PageID,offset1,len1,改之前的值,改之后的值(PageID,offset2,len2,改之前的值,改之后的值前兩種記法都是邏輯記法;第三種是物理記法。RedoLog采?了哪種記法有個(gè)專業(yè)術(shù)語(yǔ),叫PhysiologicalLogging。要搞清楚為什么要采?PhysiologicalLogging,就得知道邏輯?志和物插??條記錄,邏輯上是?條?志,但物理上可能會(huì)操作兩個(gè)以上的如果RedoLog采?邏輯?志的記法,?條記錄牽涉的多個(gè)Page寫到?志就可能對(duì)應(yīng)多條物理?志。PhysiologicalLogging綜合了兩種記法的優(yōu)I/O寫?的原?性(Double要實(shí)現(xiàn)事務(wù)的原?性,先得考慮磁盤I/O的原?性。?個(gè)LogBlock是512個(gè)字節(jié)。假設(shè)調(diào)?操作系統(tǒng)的?次Write,往磁盤上寫??個(gè)Log如果底層實(shí)現(xiàn)了512個(gè)字節(jié)寫?的原?性,上層就不需要做什么事情;否則,在上層就需要考慮這個(gè)問題。假設(shè)底層沒有保證512個(gè)字節(jié)的原?后重啟,?個(gè)LogBlock是否完整。如果不完整,就可以丟棄這個(gè)有16KB,往磁盤上刷盤,如果刷到?半系統(tǒng)宕機(jī)再重啟,請(qǐng)問這個(gè)Page是什么狀態(tài)?在這種情況下,Page既不是?個(gè)臟的Page,也不是?個(gè)?凈的Page,?是?個(gè)損壞的Page。既然已經(jīng)有RedoLog了,不能?RedoLog因?yàn)镽edoLog也恢復(fù)不了。因?yàn)镽edoLog是PhysiologicalLogging?只是?個(gè)對(duì)Page的修改的邏輯記錄,RedoLog記錄了哪個(gè)地?修改了,Doublewrite。把16KB寫?到?個(gè)臨時(shí)的磁盤位置,寫?成功后這樣,即使?標(biāo)磁盤位置的16KB因?yàn)殄礄C(jī)被損壞了,還可以?備份RedoLogBlockLogBlock還需要有Checksum的字段,另外還有?些頭部字段。事務(wù)圖6-9展?了?個(gè)RedoLogBlock的詳細(xì)結(jié)構(gòu),頭部有12字節(jié),尾部Checksum有4個(gè)字節(jié),所以實(shí)際?個(gè)Block能存的?志數(shù)據(jù)只有496圖6-9RedoLogBlock頭部4BlockNo:每個(gè)Block的唯?編號(hào),可以由LSNDateLen:該Block中實(shí)際?志數(shù)據(jù)的??,可能496字節(jié)沒有存滿。FirstRecGroupBlock?志很?,上?個(gè)Block沒有存下,?志的部分?jǐn)?shù)據(jù)到了當(dāng)前的Block果FirstRecGroup=DataLen,則說(shuō)明上?條?志太?,?到橫跨了上?個(gè)CheckpointNo:當(dāng)前Block進(jìn)?Checkpoint時(shí)對(duì)應(yīng)的LSN(下?會(huì)專門事務(wù)、LSN與LogBlock知道了RedoLog的結(jié)構(gòu),下?從?個(gè)事務(wù)的提交開始分析,看事務(wù)和對(duì)應(yīng)的RedoLog之間的關(guān)聯(lián)關(guān)系。假設(shè)有?個(gè)事務(wù),偽代碼如下:6-10所?。應(yīng)?層所說(shuō)的事務(wù)都是“邏輯事務(wù)”,具體到底層實(shí)現(xiàn),是“物理事務(wù)”,也叫作MiniTransaction(Mtr)。在邏輯個(gè)Page(當(dāng)然也可能是四個(gè)Page,五個(gè)Page……),每個(gè)Page?個(gè)Mtr。每個(gè)Mtr產(chǎn)??部分?志,?成?個(gè)LSN這個(gè)“邏輯事務(wù)”產(chǎn)?了兩段?志和兩個(gè)LSN。分別存儲(chǔ)到RedoLogBlock?,這兩段?志可能是連續(xù)的,也可能是不連續(xù)的(中間插?的有其他事務(wù)的?志)。所以,在實(shí)際磁盤上?,?個(gè)邏輯事務(wù)對(duì)應(yīng)的?志不是連續(xù)的,但?個(gè)物理事務(wù)(Mtr)對(duì)應(yīng)的?志?定是連續(xù)的(即使橫跨多個(gè)Block)。圖6-11展?了兩個(gè)邏輯事務(wù),其對(duì)應(yīng)的RedoLog在磁盤上的排列?意圖6-10事務(wù)與產(chǎn)?的RedoLog圖6-11兩個(gè)邏輯事務(wù)的RedoLog表6-9RedoLog與LSN事務(wù)Rollback與崩潰恢復(fù)(ARIES算法未提交事務(wù)的?志也在RedoLog通過上?的分析,可以看到不同事務(wù)的?志在RedoLog中是交叉存在的,這意味著未提交的事務(wù)也在RedoLog中!因?yàn)?志是交叉存在的,沒盤的RedoLog上?,后者不刷。?如圖6-11的場(chǎng)景,邏輯事務(wù)1提交了,要把邏輯事務(wù)1的RedoLog刷到磁盤上,但中間夾雜的有邏輯事務(wù)2的部分RedoLog,邏輯事務(wù)2此時(shí)還沒有提交,但其?志會(huì)被“連帶”地刷到磁盤會(huì)被記錄到RedoLog上。當(dāng)崩潰后再恢復(fù)的時(shí)候,會(huì)把RedoLog?遍,提交的事務(wù)和未提交的事務(wù),都被重放了,從?讓數(shù)據(jù)庫(kù)“原封不動(dòng)”地回到宕機(jī)之前的狀態(tài),這叫RepeatingHistory。么把宕機(jī)之前未完成的事務(wù)全部找出來(lái)?這點(diǎn)講Checkpoint時(shí)會(huì)詳細(xì)介把未完成的事務(wù)找出來(lái)后,逐?利?UndoLogRollback轉(zhuǎn)化為回滾是把未提交事務(wù)的RedoLog刪了嗎?顯然不是。在這??了?個(gè)如圖6-12所?,客戶端提交了Rollback,數(shù)據(jù)庫(kù)并沒有更改之前的數(shù)圖6-12?個(gè)Rollback事務(wù)被轉(zhuǎn)換為Commit不是刪除之前的部分,?是以相反的操作把這個(gè)事務(wù)補(bǔ)齊”,然后圖6-13宕機(jī)未完成的事務(wù)被轉(zhuǎn)換成Commit要改RedoLog。相當(dāng)于沒有了回滾,全部都是Commit。對(duì)于RedoLog來(lái)說(shuō),就是不斷地append。這種逆向操作的SQL語(yǔ)句對(duì)應(yīng)到RedoLog??,叫作CompensationLogRecord(CLR),會(huì)和正常操作的SQL的Log區(qū)分ARIESRedoLog中的起始和終?位置。發(fā)?宕機(jī)時(shí),T0、T1、T2已經(jīng)完成,圖6-14ARIESARIES(1)階段1第?,確定哪些數(shù)據(jù)頁(yè)是臟頁(yè),為階段2的Redo做準(zhǔn)備。發(fā)?宕機(jī)時(shí),雖然T0、T1、T2已經(jīng)提交了,但只是RedoLog在磁盤上,其對(duì)應(yīng)的數(shù)據(jù)Page是否已經(jīng)刷到磁盤上不得?知。如何找出從Checkpoint到Crash之第?,確定哪些事務(wù)未提交,為階段3的Undo?志也寫?了RedoLog。對(duì)應(yīng)到此圖,就是T3、T4、T5的部分?志也在RedoLog中。如何判斷出T3、T4、T5未提交,然后對(duì)其回滾呢?阻塞住,不再接受前端的請(qǐng)求,這時(shí)RedoLog也不再增長(zhǎng),然后?次性把所有的臟頁(yè)刷到磁盤中,叫作SharpCheckpoint。SharpCheckpoint的應(yīng)?場(chǎng)景很狹窄,因?yàn)橄到y(tǒng)不可能停下來(lái),所以FuzzyCheckpoint在內(nèi)存中,維護(hù)了兩個(gè)關(guān)鍵的表:活躍事務(wù)表(表6-10)和臟頁(yè)表表6-10和未提交的事務(wù)),recoveryLSN是導(dǎo)致該P(yáng)age為臟頁(yè)的最早的LSN?個(gè)Page本來(lái)是clean的(內(nèi)存和磁盤上數(shù)據(jù)?致),然后事務(wù)1修改了表6-11所謂的FuzzyCheckpoint,就是對(duì)這兩個(gè)關(guān)鍵表做了?個(gè)?不是對(duì)數(shù)據(jù)本?做Checkpoint。這點(diǎn)?常巧妙!因?yàn)镻age本?很多、數(shù)據(jù)量?,但這兩個(gè)表記錄的全是ID,數(shù)據(jù)量很?,很容易備份。所以,每?次FuzzyCheckpoint,就把兩個(gè)表的數(shù)據(jù)?成?個(gè)快照,形成?條Checkpoint?志,記?RedoLog。問題(1):求取Crash以圖6-14為例,在最近的?次Checkpoint2時(shí)候,未提交事務(wù)集合是{T2,T3},此時(shí)還沒有T4、T5。從此處開始,遍歷RedoLog之后遇到了事務(wù)T4的開始標(biāo)識(shí),把T4加?集合,集合變?yōu)閧T3之后遇到了事務(wù)T5的開始標(biāo)識(shí),把T5加?集合,集合變?yōu)門5}圖6-15展?了事務(wù)的開始標(biāo)識(shí)、結(jié)束標(biāo)識(shí)以及Checkpoint在Redo中的排列位置。其中的S表?Starttransaction,事務(wù)開始的?志記錄;C問題(2):求取Crash始,?直遍歷到RedoLog末尾,?旦遇到RedoLog操作的是新的Page,就這?有個(gè)關(guān)鍵點(diǎn):從Checkpoint2到Crash,這個(gè)集合會(huì)只增不減??赡躊1、P2在Checkpoint之后已經(jīng)不是臟頁(yè)了,但把它認(rèn)為是臟頁(yè)也沒關(guān)系,因?yàn)镽edoLog是冪等的。圖6-15事務(wù)在RedoLog階段2:進(jìn)?假設(shè)最后求出來(lái)的臟頁(yè)集合是{P1,P2,P3,P4,P5}。在這個(gè)集合中,可能都是真的臟頁(yè),也可能是已經(jīng)刷盤了。取集合中所有臟頁(yè)的recoveryLSN的最?值,得到firstLSN。從firstLSN遍歷RedoLog到末尾,把每條RedoLog對(duì)應(yīng)的Page全部重刷?次磁盤。關(guān)鍵是如何做冪等?磁盤上的每個(gè)Page有?個(gè)關(guān)鍵字段——pageLSN。這個(gè)LSNPage刷盤時(shí)最后?次修改它的?志對(duì)應(yīng)LSNLSN<=pageLSN,則不修改?志如圖6-16所?,Page1被多個(gè)事務(wù)先后修改了三次,
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 勤工培訓(xùn)機(jī)構(gòu)管理制度
- 制度培訓(xùn)視頻制作方法及流程
- 礦山安全培訓(xùn)管理制度及流程
- 社區(qū)監(jiān)委會(huì)培訓(xùn)制度
- 英語(yǔ)培訓(xùn)機(jī)構(gòu)學(xué)員制度
- 培訓(xùn)學(xué)校工作常規(guī)制度
- 培訓(xùn)學(xué)院學(xué)籍管理制度
- 人才交流培訓(xùn)工作制度
- 培訓(xùn)班學(xué)員檔案制度
- 《工程生理學(xué)》-第十二章 生殖
- 幫困基金管理辦法職代會(huì)
- 行吊安全操作規(guī)程及注意事項(xiàng)
- 艾歐史密斯熱水器CEWH-50P5說(shuō)明書
- ktv客遺物管理制度
- 制造業(yè)公司獎(jiǎng)懲管理制度
- 養(yǎng)老院公司年會(huì)策劃方案
- 司機(jī)入職心理測(cè)試題及答案
- 退休支部換屆工作報(bào)告
- T/CMES 37002-2022景區(qū)玻璃類游樂和觀景設(shè)施建造單位能力條件要求
- T/CATCM 029-2024中藥材產(chǎn)地加工(趁鮮切制)生產(chǎn)技術(shù)規(guī)范
- 網(wǎng)絡(luò)游戲代練團(tuán)隊(duì)服務(wù)合作協(xié)議
評(píng)論
0/150
提交評(píng)論