
對于java多線程的應(yīng)用非常廣泛,現(xiàn)在的系統(tǒng)沒有多線程幾乎什么也做不了,很多時候我們在何種場合如何應(yīng)用多線程成為一種首先需要選擇的問題,另外關(guān)于java多線程的知識也是非常的多,本文中先介紹和說明一些常用的,在后續(xù)文章中如果有必要再說明更加復(fù)雜的吧,本文主要說明多線程的一下幾個內(nèi)容:
1、在應(yīng)用開發(fā)中什么時候選擇多線程?
2、多線程應(yīng)該注意些什么?
3、狀態(tài)轉(zhuǎn)換控制,如何解決死鎖?
4、如何設(shè)計一個具有可擴(kuò)展性的多線程處理器?
5、多線程聯(lián)想:在多主機(jī)下的擴(kuò)展-集群?
6、WEB應(yīng)用的多線程以及長連接原理。
1、在應(yīng)用開發(fā)中什么時候選擇多線程。
在前序的文章中已經(jīng)簡單提及到過一些關(guān)于多線程應(yīng)用的文章,通過對web的一些線程控制對下載流量的控制,其實(shí)那只是雕蟲小技,也存在很多的問題需要去解決,不過面對用戶量不大的人群一般問題不大而已。
多線程在生活中的體現(xiàn)就是將多個同樣很多事情交給多個人來并行的完成,而中間有一個主線程起到調(diào)度者的作用,運(yùn)行者可以強(qiáng)制依賴于主線程的存在而存在,也可以讓主線程依賴于自身;曾經(jīng)我聽很多人說過如果你的機(jī)器是單CPU,多線程沒有意義,其實(shí)我并不這么認(rèn)為,以為內(nèi)單個CPU只能證明在線程被調(diào)度的瞬間只能同時執(zhí)行一條最底層的命令,而并不代表不可以在CPU的征用上提高效率,一個是內(nèi)存級別的,而另一個是CPU級別的,效率上仍然存在很大差距的;(這個可以讓一個程序單線程去循環(huán)10億次(每次自增1),和讓十個線程獨(dú)立運(yùn)行1億次也是同樣的動作,記住這里不要將每條數(shù)據(jù)System.out.PRintln出來,一個是機(jī)器扛不住,另一個是這里會對測試數(shù)據(jù)產(chǎn)生影響,因?yàn)檫@個方法我前面的文章中已經(jīng)說明會產(chǎn)生阻塞,尤其是在并發(fā)情況下的阻塞,即使在單CPU下結(jié)果肯定也是有很大差距的,我這暫時沒有單核的PC機(jī)器,所以沒法得到一些測試結(jié)果數(shù)據(jù)給大家,請有條件的朋友自己測試一下)。
在現(xiàn)在的系統(tǒng)中無時無刻都離不開多線程的思想,包括集群、分布式都可以理解為多線程的一種原理,那么什么是多線程的原理呢?多線程和多進(jìn)程的是什么呢?
其實(shí)要實(shí)現(xiàn)分布最簡單的思想就是多進(jìn)程,其實(shí)類似于在系統(tǒng)分隔過程中的一種垂直分隔,將不同業(yè)務(wù)的系統(tǒng)分布在不同的節(jié)點(diǎn)上運(yùn)行,他們彼此互不干擾,而多進(jìn)程的申請、釋放資源各方面的開銷都很大,而且占用資源并非CPU級別的,而線程是屬于進(jìn)程內(nèi)部更細(xì)節(jié)的內(nèi)容,一個進(jìn)程內(nèi)部可以分配N個線程,這些線程會并行的征用CPU資源,如果你的機(jī)器是多核的處理器,并發(fā)將會帶來異常的性能提升,其實(shí)原理上就是在有限的資源下,如何發(fā)揮出最大的性能優(yōu)勢(但是一定是資源有一定余量的情況下,正所謂做事不能做得太絕)。
在java中常用于實(shí)現(xiàn)多線程的方法有3中:
1、繼承于Thread類,重寫run方法
2、實(shí)現(xiàn)Runable接口,實(shí)現(xiàn)run方法
3、實(shí)現(xiàn)Callable接口,實(shí)現(xiàn)call方法(具有返回值)
至于調(diào)用的方法多種多樣,可以直接用start啟動,也可以使用java.util.concurrent.Executors來創(chuàng)建線程池來完成,創(chuàng)建的線程池也主要分為:
1、Executors.newSingleThreadScheduledExecutor() 創(chuàng)建一個順序執(zhí)行的線程池,你在run方法內(nèi)部無需使用synchronized來同步,因?yàn)樗旧硎琼樞虻摹?/span>
2、Executors.newCachedThreadPool()創(chuàng)建一個線程池,線程會并行的去執(zhí)行它。
3、Executors.newFixedThreadPool(10)創(chuàng)建大小為10的一個線程池,這個線程池最多創(chuàng)建長度為10的隊列,如果超過10個,就最多有10個線程在執(zhí)行,即可以控制線程的數(shù)量,也可以讓其并行執(zhí)行。
如果你的系統(tǒng)是一個WEB應(yīng)用,建議盡量不要再web應(yīng)用中做多線程,因?yàn)檫@部分線程控制主要是由web容器控制的,如果在非得必要的情況下建立,盡量建立較少,或者盡量將可以不太頻繁調(diào)度的線程使用完后直接釋放掉,哪怕下次重建也無所謂。
如果你的多線程序是獨(dú)立運(yùn)行的,專門用于接受和和處理一些消息,那么我相信最少有一個線程是不斷探測的(有很多程序會先休眠一點(diǎn)時間,如:TimeUnit.MINUTES.sleep(SLEEP_TIME)此方法是按照毫秒級進(jìn)行休眠一段時間),這類程序,最好將線程設(shè)置為后臺線程(setDaemon(true),一定要在線程調(diào)用run之前調(diào)用該方法有效),后臺線程和非后臺線程最大的區(qū)別在于:后臺線程在所有非后臺線程死掉后,后臺線程自動會被殺死和回收;而正如你寫其他的多線程程序,即使你的main方法完成(主線程),但是在main中申請的子線程沒有完成,程序仍然不會結(jié)束。
總的來說,其實(shí)幾乎每時每刻寫的代碼都是多線程的,只是很多事情容器幫助我們完成了,即使編寫本地的AWT、SWING,也在很多控制處理中式異步的,只是這種異步相對較少,更多的異步可以由程序去編寫,自定義的多線程一般用于在獨(dú)立于前段容器應(yīng)用的后臺處理中。為什么類似web應(yīng)用的前端會把多線程早就處理好呢,一個是因?yàn)闉榱藴p少程序和bug,另外一個就是要寫好多線程的確不容易,這樣會使得程序員去關(guān)心更多沒有必要關(guān)心的東西,也需要程序員擁有很高的水準(zhǔn),但是如果要成為好的程序員就一定要懂多線程,我們接下來以幾個問題入手,再進(jìn)行說明:
如果一個系統(tǒng)專門用于時鐘處理、觸發(fā)器處理,這個系統(tǒng)可能是分布式的,那么在一個系統(tǒng)內(nèi)部應(yīng)該如何編寫呢?另外多線程中編寫的過程中我們最郁悶的事情、也是最難琢磨補(bǔ)丁的是什么:多線程現(xiàn)在的運(yùn)行狀況是怎樣的?我的這個線程不能死掉,如果死掉了我怎么發(fā)現(xiàn)?發(fā)現(xiàn)到了如何處理(自動、人工、難道重啟)?
帶著這些問題,我們引出了文章下面的一些話題。
2、多線程應(yīng)該注意些什么?
多線程用起來爽,出現(xiàn)問題你就不是那么爽了,簡單說來,多線程你最納悶的就是它的問題;但是不要害怕它,你害怕它就永遠(yuǎn)不能征服它,呵呵,只要摸清楚一些脾氣,我們總有辦法征服它的。
◆明白多線程有狀態(tài)信息,和之間的轉(zhuǎn)換規(guī)則?
◆多線程一般在什么情況下會出現(xiàn)焊住或者死掉的現(xiàn)象?
◆多線程焊住或者死掉如何捕獲和處理?
這里僅僅是提出問題,提出問題后,在說到問題之前,先提及一下擴(kuò)展知識點(diǎn),下面的章節(jié)來說明這些問題。
開源多線程調(diào)度任務(wù)框架中的一個很好選擇是:Quartz,有關(guān)它的文章可以到http://wenku.baidu.com/view/3220792eb4daa58da0114a01.html
下載這個文檔,這個文檔也講述了大部分該框架的使用方法,不過由于該框架本身的封裝層次較多,所以很多底層的實(shí)現(xiàn)內(nèi)容并不是那么明顯,而且對于線程池的管理基本是透明的,自己只能通過一些其他的手段得到這些內(nèi)容。
所以拿到這個框架首先學(xué)習(xí)好它的特性后,進(jìn)一步就是看如何進(jìn)一步封裝它得到最適合你項(xiàng)目的內(nèi)容。
另外多線程在數(shù)據(jù)結(jié)構(gòu)選項(xiàng)上也有很多技巧,關(guān)于多線程并發(fā)資源共享上的數(shù)據(jù)結(jié)構(gòu)選型專門來和大家探討,因?yàn)榧记傻拇_很多,尤其是jdk 1.6以后提出了很多的數(shù)據(jù)結(jié)構(gòu),它參考了類似于Oracle的版本號原理,在內(nèi)存中做了數(shù)據(jù)復(fù)制以及原子拷貝的方法,實(shí)現(xiàn)了即保證一致性讀寫又在很大程度上降低了并發(fā)的征用;另外還有對于樂觀鎖機(jī)制,也是高性能的多線程設(shè)計中非常重要知識體系。
3、狀態(tài)轉(zhuǎn)換控制,如何解決死鎖。
3.1.java默認(rèn)線程的狀態(tài)有哪些?(所謂默認(rèn)線程就是自己沒有重寫)
NEW :剛剛創(chuàng)建的線程,什么也沒有做,也就是還沒有使用start命令啟動的線程。
BLOCKED :阻塞或者叫梗阻,也就是線程此時由于鎖或者某些網(wǎng)絡(luò)原因造成阻塞,有焊住的跡象。
WAITING:等待鎖狀態(tài),它在等待對一個資源的notify,即資源的一個鎖機(jī)會,這個狀態(tài)一般和一個靜態(tài)資源綁定,并在使用中有synchronzed關(guān)鍵字的包裝,當(dāng)使用obj.wait()方法時,當(dāng)前線程就會等待obj對象上的一個notify方法,這個對象可能是this,如果是this的話那么在方法體上面一般就會有一個synchronized關(guān)鍵字。
TIME_WAITDE:基于時間的等待,當(dāng)線程使用了sleep命令后,就會處于時間等待狀態(tài),時間到的時候,恢復(fù)到running狀態(tài)。
RUNNING:運(yùn)行狀態(tài),即線程正在處于運(yùn)行之中(當(dāng)線程被梗阻)。
TERMINATED:線程已經(jīng)完成,此時線程的isAlive()返回為false。
一般默認(rèn)的線程狀態(tài)就是這些,部分容器或者框架會把線程的狀態(tài)等進(jìn)行進(jìn)一步的封裝操作,線程的名稱和狀態(tài)的內(nèi)容會有很多的變化,不過只要找好對應(yīng)的原理也不會脫離于這個本質(zhì)。
3.1.線程一般在什么情況下會死掉?
鎖,相互交叉派對,最終導(dǎo)致死鎖;可能是程序中自己導(dǎo)致,編寫共享緩存以及自定義的一部分脫離于容器的線程池管理這里就需要注意了;還有就是有可能是分布式的一些共享文件或者分布式數(shù)據(jù)庫的鎖導(dǎo)致。
網(wǎng)絡(luò)梗阻,網(wǎng)絡(luò)不怕沒有,也不怕太快,就怕時快時慢,現(xiàn)在的話叫太不給力了,傷不起啊!而國內(nèi)現(xiàn)在往往還就是這樣不給力;當(dāng)去網(wǎng)絡(luò)通信調(diào)用內(nèi)容的時候(包括數(shù)據(jù)庫交互一般也是通過網(wǎng)絡(luò)的),就很容易產(chǎn)生焊住的現(xiàn)象,也就是假死,此時很難判定線程到底是怎么了,除非有提前的監(jiān)控預(yù)案。
其他情況下線程還會死掉嗎?就我個人的經(jīng)驗(yàn)來說還沒遇到過,但并非絕不可能,我想在常規(guī)的同一個JVM內(nèi)部操作的線程會死掉的概率只有系統(tǒng)掛掉,不然SUN的java虛擬機(jī)也太不讓人信任了;至少從這一點(diǎn)上我們可以決定在絕大部分情況下線程阻塞的主要原因是上述兩個主要來源。
在明白絕大部分原因的基礎(chǔ)上,這里已經(jīng)提出了問題并初步分析了問題,那么繼續(xù)來如何解決這些問題,或者說將問題的概率降低到非常低的程度(因?yàn)闆]有百分百的高可用性環(huán)境,我們只是要盡量去做到它盡量完美,亞馬遜的云計算也有宕機(jī)的驚人時刻,呵呵)。
3.1. 多線程焊住或者死掉如何捕獲和處理?
說到捕獲,學(xué)習(xí)java朋友肯定第一想到的是try catch,但是線程假死根本不會拋異常,如何知道線程死掉了呢?
這需要從我們的設(shè)計層面下手,對于后來java提供的線程池也可以比較放心的使用,但是對于很多非常復(fù)雜的線程管理,需要我們自己來設(shè)計管理。如何捕獲我們用一個生活中的例子來舉例,下一長中將它反饋到實(shí)際的系統(tǒng)設(shè)計上。
首先多線程自己死掉了它肯定不知道,就想一個人自己喝醉了或者被被人打暈了一樣,呵呵,那么如何才能知道它的現(xiàn)狀了?提出兩種現(xiàn)實(shí)思路,一個是有一個跟班的人,而另一種是它上面有一個領(lǐng)導(dǎo)帶一群人出來玩,下面人丟了一個它肯定要去找。
先看看第一種思路,跟班那個我假如他平時什么也不做,就跟在前者后面,當(dāng)發(fā)現(xiàn)前者倒下,自己馬上跟上去頂替工作,這也是系統(tǒng)架構(gòu)上經(jīng)常采用的冗余主從切換,可能一主多從;而云計算也是在基礎(chǔ)上的進(jìn)一步做的異地分流切換和資源動態(tài)調(diào)度(也就是事情少了,這些人可以去做其他的事情或者睡覺養(yǎng)精神并且為國家節(jié)約糧食,當(dāng)這邊的事情忙不過來,會有做其它事情的人或者待命的人來幫著做這些事情;甚至于此地遭到地震洪水類天災(zāi)什么的,異地還有機(jī)構(gòu)可以頂替同樣的工作內(nèi)容,這樣讓對外的服務(wù)永遠(yuǎn)不斷間斷下來,也就是傳說中的24*7的高可用性服務(wù)),但是這樣冗余太大,成本將會非常巨大。
再看看第二種服務(wù),上面有一個老大,它過一小會看看這幫小弟在做什么,是不是遇到了困難,那里忙它在上面動態(tài)調(diào)配這資源;好像這種模式很好呢?小弟要是多了,它就忙不過來了,因?yàn)橘Y源的分配是需要提前明白下面資源的細(xì)節(jié)的,不然這個領(lǐng)導(dǎo)不是好領(lǐng)導(dǎo);那么再細(xì)想下去,我們可以用多個老大,每個老大帶領(lǐng)一個小團(tuán)隊,團(tuán)隊之間可以資源調(diào)配,但是團(tuán)隊內(nèi)部可以由老大自己掌控一切,老大的上面還有個老總它只關(guān)心于老大再做什么,而不需要關(guān)心小弟們的行為,這樣大家的事情就平均起來了;那么問題了又出來了,小弟的問題是可以透明的看到了,要是那個老大出事了甚至于老總出事了怎么辦?此時結(jié)合第一種思想,我們此時就只需要再老總下面掛一個跟班的,集合兩種模式的特征,也就是小弟不需要配跟班的,這樣就節(jié)約了很大的成本(因?yàn)槿~子節(jié)點(diǎn)的數(shù)量是最多的),而上面的節(jié)點(diǎn)我們需要有跟班的,如果想最大程度節(jié)約成本,只需要讓主節(jié)點(diǎn)配置一個或者多個跟班就可以,但是這樣恢復(fù)成本就上去了,因?yàn)榛謴?fù)信息需要逐層找到內(nèi)容才行,一般我們沒有必要在這個基礎(chǔ)上再進(jìn)一步去節(jié)約成本。
這些是現(xiàn)實(shí)的東西,如何結(jié)合到計算機(jī)系統(tǒng)架構(gòu)中,再回到本文的多線程設(shè)計上,第四章中一起來探討一下。
4、如何設(shè)計一個具有可擴(kuò)展性的多線程處理器。
其實(shí)在第三章中,已經(jīng)從生活的管理模式上找到了很多的解決方案,這也是我個人在解決問題上慣用的手段,因?yàn)閭€人認(rèn)為再復(fù)雜的數(shù)學(xué)算法也沒有人性本身的復(fù)雜,生活中的種種手段若用于計算機(jī)中可能會得到很多神奇的效果。
如果自己不使用任何開源技術(shù),要做一個多線程處理的框架應(yīng)該從何下手,在上面分析的基礎(chǔ)上,我們一般會將一個專門處理多線程的系統(tǒng)至少分解為主次二層,也就是主線程引導(dǎo)多個運(yùn)行線程去處理問題;好了,此時我們需要解決以下幾個問題:
a)多個線程處理的內(nèi)容是類似的,如何控制并發(fā)征用數(shù)據(jù)或者說降低并發(fā)熱點(diǎn)的粒度。
方法1:hash散列思想將會是優(yōu)秀的原則,按照數(shù)據(jù)特征進(jìn)行分解數(shù)據(jù)框,每個框的數(shù)據(jù)規(guī)則按照一種hash規(guī)則分布,hash散列對于編程容易遍歷,而且計算速度非常迅速,幾乎可以忽略掉定位分組的時間,但結(jié)構(gòu)擴(kuò)展過程比較麻煩,但在多線程設(shè)計中一般不需要考慮這個問題。
方法2:range分布,range范 圍分布數(shù)據(jù)是提前讓管理者知道數(shù)據(jù)的大致分布情況,并按照一種較為平均的規(guī)則交給下面的運(yùn)作線程去去處理自己范圍內(nèi)的數(shù)據(jù),相互之間的數(shù)據(jù)也是沒有任何交叉的,其擴(kuò)展性較好,可以任意擴(kuò)展,如果分解的數(shù)量不受控制的話,分解過多,會造成定位范圍比較慢一點(diǎn),但是多線程設(shè)計中也一般不用考慮這個問題,因?yàn)槌绦蚴怯勺约壕帉懙摹?/span>
方法3:位圖分布,即數(shù)據(jù)具有位圖規(guī)則,一般是狀態(tài),這種數(shù)據(jù)按照位圖分布后,線程可以設(shè)立為位圖個數(shù),找到自己的位圖段數(shù)據(jù)即可做操作,而不需要做進(jìn)一步的更新,但是往往位圖數(shù)量有限,而需要處理的數(shù)據(jù)量很大,一個線程處理一個位圖下的所有數(shù)據(jù)也往往力不從心,若多個線程處理一個位圖又會重蹈覆轍。
三種方法各自有優(yōu)缺點(diǎn),所以我們往往采用組合模式來講真?zhèn)€系統(tǒng)的架構(gòu)達(dá)到比較完美的狀態(tài),當(dāng)然沒有完美的東西,只有最適應(yīng)于當(dāng)前應(yīng)用環(huán)境的架構(gòu),所以設(shè)計前需要考慮很多預(yù)見性問題;關(guān)于這種數(shù)據(jù)分布更多的用于架構(gòu),但是架構(gòu)的基礎(chǔ)也來源于程序設(shè)計思想,兩者思想都是一致的,有關(guān)架構(gòu)和數(shù)據(jù)存儲分布,后面有機(jī)會單獨(dú)討論。
b)線程死掉如何發(fā)現(xiàn)(以及處理):
管理線程除有運(yùn)行動作的線程外,還有1~N跟班,個數(shù)根據(jù)實(shí)際情況決定,至少要有一個當(dāng)管理線程掛掉可以馬上頂替工作,另外還有應(yīng)當(dāng)有一個線兩程去定期檢測線程的運(yùn)行情況,由于它只負(fù)責(zé)這件事情,所以很簡單,而且這一組中的線程誰死掉都可以相互替換工作并重啟新的線程去替代,這個檢測的周期不用太快、也不用太慢,只要應(yīng)用可以接受就可以,因?yàn)閽斓粜〇|西,應(yīng)用阻塞一點(diǎn)時間是非常正常的事情。
發(fā)現(xiàn)線程有阻塞現(xiàn)象,在執(zhí)行中找到了某種以外而阻塞,導(dǎo)致的原因我們上面已經(jīng)分析過,解決的方法一般是在探測幾次(這個次數(shù)一般是基于配置的)后發(fā)現(xiàn)都是處于阻塞狀態(tài),就基本可以認(rèn)為它是錯誤的了;錯誤的情況此時需要給該線程執(zhí)行一個interrupt()方法,此時線程內(nèi)部的執(zhí)行會自動的拋出一個異常,也就是理解執(zhí)行線程的內(nèi)容的時候尤其是帶有網(wǎng)絡(luò)操作的時候需要帶上一個try catch,執(zhí)行部分都在try中,當(dāng)出現(xiàn)假死等現(xiàn)狀的時候,外部探測到使用一個interrupt()方法,運(yùn)行程序就會跳轉(zhuǎn)到catch之中,這個里面就不存在征用資源的問題,而快速的將自己的需要回滾的內(nèi)容執(zhí)行完,并認(rèn)為線程執(zhí)行結(jié)束,相應(yīng)的資源也會得到釋放,而使用stop方法之所以現(xiàn)在不推薦是因?yàn)樗粫尫刨Y源,會導(dǎo)致很多的問題。
另外寫代碼之前如果涉及到一些網(wǎng)絡(luò)操作,一定要對你所使用的網(wǎng)絡(luò)交互程序有很多的深入認(rèn)識,如socket交互時,一般情況下如果對方由于網(wǎng)絡(luò)原因(一般是有ip當(dāng)時端口不對或者網(wǎng)段的協(xié)議不通)導(dǎo)致在啟動連接對方時,socket連接對方好幾分鐘后才會顯示是超時連接,這是默認(rèn)的,所以你需要提前設(shè)置一個啟動連接超時保證網(wǎng)絡(luò)是可以通信的,再進(jìn)行執(zhí)行(注意socket里面還有一個超時是連接后不斷的時間,前者為連接之前設(shè)置的一個啟動連接超時時間,一般這個時間很短,一般是2秒就很長了,因?yàn)?秒都連接不上這個網(wǎng)絡(luò)就基本連接不上了,而后者是運(yùn)行,有些交互可能長達(dá)幾小時也有可能,但類似這種交互建議采用異步交互,以保證穩(wěn)定運(yùn)行)。
C)如果啟動和管理二級管理線程組:
上面有一個主線程來控制啟動和關(guān)閉,這里可以將這些線程在start前的setDaemon(true),那么該線程將會被設(shè)立為后臺線程,所謂后臺線程就是當(dāng)主線程執(zhí)行完畢釋放資源后,被主線程創(chuàng)建的這些線程將會自動釋放資源并死掉,如果一個線程被設(shè)置為后臺線程,若在其run方法內(nèi)部創(chuàng)建的其他子線程,將會自動被創(chuàng)建為后臺線程(如果在構(gòu)造方法中創(chuàng)建則不是這樣)。
管理線程也可以像二級線程一樣來管理子節(jié)點(diǎn),只要你的程序不怕寫得夠復(fù)雜,雖然需要使用非常好的代碼來編寫,并且需要通過很復(fù)雜的測試才會穩(wěn)定運(yùn)行,但是一旦成功,這個框架將會是非常漂亮和穩(wěn)定,而且也是高可用的。
5、多線程在多主機(jī)下的擴(kuò)展-集群
其實(shí)我們在上面以及提及了一些分布式的知識,也可以叫做數(shù)據(jù)的分區(qū)知識(在網(wǎng)絡(luò)環(huán)境利用PC實(shí)現(xiàn)類似于同一個主機(jī)上的分區(qū)模式,基本就可以稱為數(shù)據(jù)是分布式存儲的)。
但是這里提到的集群和這個有一些區(qū)別,可以說分布式中包含了集群的概念,但是一般集群的概念也有很多的區(qū)別,并且要分app集群和數(shù)據(jù)庫集群。
集群一般是指同一個機(jī)組下多個節(jié)點(diǎn)(同一臺機(jī)器也可以部署多個節(jié)點(diǎn)),這些節(jié)點(diǎn)幾乎去完成同樣的事情,或者說類似的事情,這就和多線程扯在一起了,多線程也正是如此,對比來看就是多線程調(diào)度在多主機(jī)群組下的實(shí)現(xiàn),所以參照app集群來說,一般有一個管理節(jié)點(diǎn),它幾乎干很少的事情,因?yàn)槲覀儾幌胱屗鼟斓簦驗(yàn)樗m然干的事情少,但是卻非常重要,一個是從它那里可以得到每一個節(jié)點(diǎn)的一些應(yīng)用部署和配置,以及狀態(tài)等等信息;另外是代理節(jié)點(diǎn)或者叫做分發(fā)節(jié)點(diǎn),它幾乎在管理節(jié)點(diǎn)的控制之下只做分發(fā)的,當(dāng)然要保證session一致性。
集群在多線程中的另一個體現(xiàn)就是掛掉一臺,其余的可以頂替,而不會導(dǎo)致全盤死掉;而集群組相當(dāng)于一個大的線程組,相關(guān)牽制管理,也相互可以失敗切換,而多個業(yè)務(wù)會或者多種工具項(xiàng)會劃分為不同的集群組,這就類似于我們設(shè)計線程中的三層線程模式的中多組線程組的模式,每組線程組內(nèi)部都有自己個性化的屬性和共享屬性。
而面對數(shù)據(jù)庫集群,就相對比app集群要復(fù)雜,app在垂直擴(kuò)展時幾乎只會受到分發(fā)節(jié)點(diǎn)能力的限制,而這部分是可以調(diào)整的,所以它在垂直擴(kuò)展的過程中非常方便,而數(shù)據(jù)庫集群則不一樣,它必須保證事務(wù)一致性,并實(shí)現(xiàn)事務(wù)級別切換和一定程度上的網(wǎng)格計算能力,中間比較復(fù)雜的也在內(nèi)存這塊,因?yàn)樗臄?shù)據(jù)讀入到內(nèi)存中要將多個主機(jī)的內(nèi)存配置得像一個內(nèi)存一樣(通過心跳完成),而且需要得到動態(tài)擴(kuò)展的能力,這也是數(shù)據(jù)庫集群下擴(kuò)展性收到限制發(fā)展的一個原因之一。
App難道沒有和數(shù)據(jù)庫一樣的困難嗎?有,但是粒度相對較小,app集群一般不需要考慮事務(wù),因?yàn)橐粋€用戶的session一般在不出現(xiàn)宕機(jī)的情況下,是不會出現(xiàn)復(fù)制要求的,而是一直會訪問指定的一臺機(jī)器,所以它們之間幾乎不需要通信;而耦合的粒度在于應(yīng)用本身的設(shè)計,有部分應(yīng)用系統(tǒng)會自己寫代碼將一些內(nèi)容初始化注入到內(nèi)存中,或者注入到app本地的一個文件中作為文件緩存;這樣當(dāng)這些數(shù)據(jù)發(fā)生改變時他們先改數(shù)據(jù)庫,再修改內(nèi)存或者通知內(nèi)存失效;數(shù)據(jù)庫由于集群使用心跳連接,所以保持一致性,而app這邊的數(shù)據(jù)由于只修改掉了自身的內(nèi)存相關(guān)信息,沒有修改掉其他機(jī)器的內(nèi)存信息,所以必然導(dǎo)致訪問其他數(shù)據(jù)的機(jī)器上的內(nèi)容是不一致的;至于這部分的解決方案,根據(jù)實(shí)際項(xiàng)目而定,有通過通信完成的,也有通過共享緩沖區(qū)完成(但這種方式又回到共享池資源征用產(chǎn)生的鎖了),也有通過其他方式完成。
大型系統(tǒng)架構(gòu)最終數(shù)據(jù)分布,集中式管理,分布式存儲計算,業(yè)務(wù)級別橫向切割,同業(yè)務(wù)下app垂直分隔,數(shù)據(jù)級別散列+range+位圖分布結(jié)構(gòu),異地分流容災(zāi),待命機(jī)組和資源調(diào)配的整合,這一切的基礎(chǔ)都來源于多線程的設(shè)計思想架構(gòu)在分布式機(jī)組上的實(shí)現(xiàn)。
6、WEB應(yīng)用的多線程以及長連接原理
WEB應(yīng)用中會對一些特殊的業(yè)務(wù)服務(wù)做特殊的服務(wù)器定制,類似一些高并發(fā)訪問系統(tǒng)甚至于專門用于瞬間高并發(fā)的系統(tǒng)(很多時候系統(tǒng)不怕高并發(fā),而是怕瞬間高并發(fā))但他們的訪問往往比較簡單,主要用于事務(wù)的處理以及數(shù)據(jù)的一致性保障,他們在數(shù)據(jù)的處理上要求在數(shù)據(jù)庫端也不允許有太大的計算量,計算一般在app中去完成,數(shù)據(jù)庫一般只是做存、取、事務(wù)一致性動作,這類一般屬于特殊的OLTP系統(tǒng);還有大分類一類是屬于并發(fā)量不算太大,但每次處理的數(shù)據(jù)和計算往往比較多,一把說的是OLAP類的系統(tǒng),而數(shù)據(jù)的來源一般是OLTP,OLAP每次處理的數(shù)據(jù)量可能會非常大,一般在類型收集和統(tǒng)計上進(jìn)行數(shù)據(jù)dump,需要將OLTP中的數(shù)據(jù)按照某種業(yè)務(wù)規(guī)則方面查詢和檢索的方法提取出來組織為有效信息存儲在另一個地方,這個地方有可能還是數(shù)據(jù)庫,但也有可能不是(數(shù)據(jù)庫的計算能力雖然是數(shù)據(jù)上最強(qiáng)的但是它在實(shí)際應(yīng)用中它是最慢的一種東西,因?yàn)閿?shù)據(jù)庫更多的是需要保證很多事務(wù)一致性和鎖機(jī)制問題,以及一些中間解析和優(yōu)化等等產(chǎn)生的開銷是非常大的,而且應(yīng)用程序與之交互過程是需要通過網(wǎng)絡(luò)完成,所以很多數(shù)據(jù)在實(shí)際的應(yīng)用中并不一定非要用數(shù)據(jù)庫);這兩類系統(tǒng)在設(shè)計和架構(gòu)上都有很大的區(qū)別,但普通系統(tǒng)兩者都有特征,但是都不是那么極端,所以不用考慮太多,這里需要提到的是一類非常特殊的系統(tǒng),是實(shí)時性推送數(shù)據(jù)并高并發(fā)的系統(tǒng),到目前為止我個人不知道將它歸并到哪一類系統(tǒng)中,這的確很特殊的一類系統(tǒng)。
這類系統(tǒng)如:高并發(fā)訪問中,而且需要將同一個平臺下的數(shù)據(jù)讓客戶端較為實(shí)時的得到內(nèi)容,這類網(wǎng)站不太可能一次獲取非常多的內(nèi)容到客戶端再訪問,而肯定是通過很多異步交互過程來完成的,下面簡單說下這個異步交互。
Web異步交互的所有框架基礎(chǔ)都是Ajax,其余的類似框架都是在這個基礎(chǔ)上完成的;那么此時ajax應(yīng)該如何來控制交互才能得到幾乎接近于實(shí)時的內(nèi)容呢?難道通過客戶端不斷去刷新相同的URL?那要是客戶端非常多,類似于一個大型網(wǎng)站,可能服務(wù)器端很快會宕機(jī),除非用比正常情況高出很多倍的服務(wù)器成本去做,而且更多的服務(wù)器可能在架構(gòu)上也需要改造才能發(fā)揮出他們的性能(因?yàn)樵诜?wù)器的架構(gòu)上,1 + 1永遠(yuǎn)是小于2的性能,更多的服務(wù)器在開銷)。
想到的另一種辦法就是從服務(wù)器端向客戶端推送數(shù)據(jù),那么問題是如何推送,這類操作是基于一種長連接機(jī)制完成,長連接即不斷開的連接,客戶端采用ajax與后端通信時,后端的反饋信息只要未曾斷開就可視為一種長連接的機(jī)制;很多是通過socket與服務(wù)器端通信,也可以使用ajax,不過ajax需要在其上面做很多的處理才行。
服務(wù)器端也是必須使用對應(yīng)的策略,現(xiàn)在較多的是javaNIO,相對BIO性能要低一點(diǎn),但是也是很不錯的,它在獲取到用戶請求時并不是馬上為用戶請求分配線程去處理,而是將請求進(jìn)行排隊,而排隊的過程可以自己去控制粒度,而線程也將作為線程池的隊列進(jìn)行分配處理,也就是服務(wù)器端對客戶端的請求是異步響應(yīng)(注意這里不是ajax單純的異步交互,而是服務(wù)器端對請求的異步響應(yīng)),它對很多請求的響應(yīng)并非及時,當(dāng)發(fā)生數(shù)據(jù)變化時,服務(wù)器第一時間通過請求列表獲取到客戶端session列表并與之輸出內(nèi)容,類似于服務(wù)器端主動推送數(shù)據(jù)向客戶端;而異步交互的好處是服務(wù)器端并不會為每一個客戶端分配或新申請一個線程,這樣會導(dǎo)致高并發(fā)時引起的資源分配不過來導(dǎo)致的內(nèi)存溢出現(xiàn)象;解決了上述兩個問題后,另外還有一個問題需要解決的是,當(dāng)一個線程在處理一個請求任務(wù)時,由于線程處理一個任務(wù)完成前除非死掉或者焊住,否則是不會斷開下來的,這個是肯定的(我們可以將一些大任務(wù)切割為一些小任務(wù),線程就處理的速度就會快很多了),但是有一個問題是,服務(wù)器端的這個線程可能很快處理好了需要處理的數(shù)據(jù)內(nèi)容并向客戶端推送,但是客戶端由于各類網(wǎng)絡(luò)通信問題,導(dǎo)致遲遲不能接受完成,此時該線程也會被占用些不必要的時間,那么是否在這個中間需要進(jìn)一步做一層斷點(diǎn)傳送的緩存呢?緩存不僅僅是屬于在斷點(diǎn)數(shù)據(jù)需要時取代應(yīng)用服務(wù)器的內(nèi)容,異步斷點(diǎn)向客戶端輸出信息,同時將應(yīng)用服務(wù)器處理的時間幾乎全部集中在數(shù)據(jù)和業(yè)務(wù)處理,而不是輸出網(wǎng)絡(luò)上的很多占用,有關(guān)網(wǎng)絡(luò)緩存有很多種做法,后續(xù)有機(jī)會和大家一起探討關(guān)于網(wǎng)絡(luò)緩存的知識吧。
新聞熱點(diǎn)
疑難解答