阿里巴巴高級Java面試題 續3

6.(七)-典型配置舉例1
以下配置主要針對分代垃圾回收算法而言。

堆大小設置
年輕代的設置很關鍵
JVM中最大堆大小有三方面限制:相關操作系統的數據模型(32-bt還是64-bit)限制;系統的可用虛擬內存限制;系統的可用物理內存限制。32位系統下,一般限制在1.5G~2G;64為操作系統對內存無限制。在Windows Server 2003 系統,3.5G物理內存,JDK5.0下測試,最大可設置為1478m。
典型設置:
java -Xmx3550m -Xms3550m -Xmn2g –Xss128k
-Xmx3550m:設置JVM最大可用內存為3550M。
-Xms3550m:設置JVM初始內存為3550m。此值可以設置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內存。
-Xmn2g:設置年輕代大小為2G。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代后,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8。
-Xss128k:設置每個線程的堆棧大小。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。

java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設置為4,則年輕代與年老代所占比值為1:4,年輕代占整個堆棧的1/5
-XX:SurvivorRatio=4:設置年輕代中Eden區與Survivor區的大小比值。設置為4,則兩個Survivor區與一個Eden區的比值為2:4,一個Survivor區占整個年輕代的1/6
-XX:MaxPermSize=16m:設置持久代大小為16m。
-XX:MaxTenuringThreshold=0:設置垃圾最大年齡。如果設置為0的話,則年輕代對象不經過Survivor區,直接進入年老代。對于年老代比較多的應用,可以提高效率。如果將此值設置為一個較大值,則年輕代對象會在Survivor區進行多次復制,這樣可以增加對象再年輕代的存活時間,增加在年輕代即被回收的概論。

回收器選擇
JVM給了三種選擇:串行收集器、并行收集器、并發收集器,但是串行收集器只適用于小數據量的情況,所以這里的選擇主要針對并行收集器和并發收集器。默認情況下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在啟動時加入相應參數。JDK5.0以后,JVM會根據當前系統配置進行判斷。
吞吐量優先的并行收集器
如上文所述,并行收集器主要以到達一定的吞吐量為目標,適用于科學技術和后臺處理等。
典型配置:
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC:選擇垃圾收集器為并行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用并發收集,而年老代仍舊使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的線程數,即:同時多少個線程一起進行垃圾回收。此值最好配置與處理器數目相等。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC:配置年老代垃圾收集方式為并行收集。JDK6.0支持對年老代并行收集。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100:設置每次年輕代垃圾回收的最長時間,如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此值。
n java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:設置此選項后,并行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用并行收集器時,一直打開。

響應時間優先的并發收集器
如上文所述,并發收集器主要是保證系統的響應時間,減少垃圾收集時的停頓時間。適用于應用服務器、電信領域等。
典型配置:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:設置年老代為并發收集。測試中配置這個以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此時年輕代大小最好用-Xmn設置。
-XX:+UseParNewGC: 設置年輕代為并行收集??膳cCMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設置,所以無需再設置此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并發收集器不對內存空間進行壓縮、整理,所以運行一段時間以后會產生“碎片”,使得運行效率降低。此值設置運行多少次GC以后對內存空間進行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮??赡軙绊懶阅?,但是可以消除碎片

輔助信息
JVM提供了大量命令行參數,打印信息,供調試使用。主要有以下一些:
-XX:+PrintGC:輸出形式:[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails:輸出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可與上面兩個混合使用
輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]

-XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中斷的執行時間。可與上面混合使用。輸出形式:Application time: 0.5291524 seconds
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期間程序暫停的時間??膳c上面混合使用。輸出形式:Total time for which application threads were stopped: 0.0468229 seconds
** -XX:PrintHeapAtGC: ** 打印GC前后的詳細堆棧信息。輸出形式:

34.702: [GC {Heap before gc invocations=7:
def new generation   total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K,  99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
from space 6144K,  55% used [0x221d0000, 0x22527e10, 0x227d0000)
to   space 6144K,   0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
tenured generation   total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K,   3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
   the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
def new generation   total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K,   0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
  from space 6144K,  55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
  to   space 6144K,   0% used [0x221d0000, 0x221d0000, 0x227d0000)
tenured generation   total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K,   4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
   the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
   ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
   rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
}
, 0.0757599 secs]

-Xloggc:filename:與上面幾個配合使用,把相關日志信息記錄到文件以便分析。

7.(八)-典型配置舉例2
常見配置匯總

堆設置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設置年輕代大小
-XX:NewRatio=n:設置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個年輕代年老代和的1/4
-XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區占整個年輕代的1/5
-XX:MaxPermSize=n:設置持久代大小

收集器設置
-XX:+UseSerialGC:設置串行收集器
-XX:+UseParallelGC:設置并行收集器
-XX:+UseParalledlOldGC:設置并行年老代收集器
-XX:+UseConcMarkSweepGC:設置并發收集器

垃圾回收統計信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename

并行收集器設置
-XX:ParallelGCThreads=n:設置并行收集器收集時使用的CPU數。并行收集線程數。
-XX:MaxGCPauseMillis=n:設置并行收集最大暫停時間
-XX:GCTimeRatio=n:設置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)

并發收集器設置
-XX:+CMSIncrementalMode:設置為增量模式。適用于單CPU情況。
-XX:ParallelGCThreads=n:設置并發收集器年輕代收集方式為并行收集時,使用的CPU數。并行收集線程數。

調優總結
年輕代大小選擇
響應時間優先的應用:盡可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇)。在此種情況下,年輕代收集發生的頻率也是最小的。同時,減少到達年老代的對象。
吞吐量優先的應用:盡可能的設置大,可能到達Gbit的程度。因為對響應時間沒有要求,垃圾收集可以并行進行,一般適合8CPU以上的應用。

年老代大小選擇
響應時間優先的應用:年老代使用并發收集器,所以其大小需要小心設置,一般要考慮并發會話率和會話持續時間等一些參數。如果堆設置小了,可以會造成內存碎片、高回收頻率以及應用暫停而使用傳統的標記清除方式;如果堆大了,則需要較長的收集時間。最優化的方案,一般需要參考以下數據獲得:

  1. 并發垃圾收集信息
  2. 持久代并發收集次數
  3. 傳統GC信息
  4. 花在年輕代和年老代回收上的時間比例
    減少年輕代和年老代花費的時間,一般會提高應用的效率

吞吐量優先的應用
一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代。原因是,這樣可以盡可能回收掉大部分短期對象,減少中期的對象,而年老代盡存放長期存活對象。

較小堆引起的碎片問題
因為年老代的并發收集器使用標記、清除算法,所以不會對堆進行壓縮。當收集器回收時,他會把相鄰的空間進行合并,這樣可以分配給較大的對象。但是,當堆空間較小時,運行一段時間以后,就會出現“碎片”,如果并發收集器找不到足夠的空間,那么并發收集器將會停止,然后使用傳統的標記、清除方式進行回收。如果出現“碎片”,可能需要進行如下配置:

  1. -XX:+UseCMSCompactAtFullCollection:使用并發收集器時,開啟對年老代的壓縮。
    2.-XX:CMSFullGCsBeforeCompaction=0:上面配置開啟的情況下,這里設置多少次Full GC后,對年老代進行壓縮。

8.(九)-新一代的垃圾回收算法
垃圾回收的瓶頸
傳統分代垃圾回收方式,已經在一定程度上把垃圾回收給應用帶來的負擔降到了最小,把應用的吞吐量推到了一個極限。但是他無法解決的一個問題,就是Full GC所帶來的應用暫停。在一些對實時性要求很高的應用場景下,GC暫停所帶來的請求堆積和請求失敗是無法接受的。這類應用可能要求請求的返回時間在幾百甚至幾十毫秒以內,如果分代垃圾回收方式要達到這個指標,只能把最大堆的設置限制在一個相對較小范圍內,但是這樣有限制了應用本身的處理能力,同樣也是不可接收的。

分代垃圾回收方式確實也考慮了實時性要求而提供了并發回收器,支持最大暫停時間的設置,但是受限于分代垃圾回收的內存劃分模型,其效果也不是很理想。

為了達到實時性的要求(其實Java語言最初的設計也是在嵌入式系統上的),一種新垃圾回收方式呼之欲出,它既支持短的暫停時間,又支持大的內存空間分配。可以很好的解決傳統分代方式帶來的問題。

增量收集的演進
增量收集的方式在理論上可以解決傳統分代方式帶來的問題。增量收集把對堆空間劃分成一系列內存塊,使用時,先使用其中一部分(不會全部用完),垃圾收集時把之前用掉的部分中的存活對象再放到后面沒有用的空間中,這樣可以實現一直邊使用邊收集的效果,避免了傳統分代方式整個使用完了再暫停的回收的情況。

當然,傳統分代收集方式也提供了并發收集,但是他有一個很致命的地方,就是把整個堆做為一個內存塊,這樣一方面會造成碎片(無法壓縮),另一方面他的每次收集都是對整個堆的收集,無法進行選擇,在暫停時間的控制上還是很弱。而增量方式,通過內存空間的分塊,恰恰可以解決上面問題。

Garbage Firest(G1)
這部分的內容主要參考這里,這篇文章算是對G1算法論文的解讀。我也沒加什么東西了。

目標
從設計目標看G1完全是為了大型應用而準備的。
支持很大的堆
高吞吐量
--支持多CPU和垃圾回收線程
--在主線程暫停的情況下,使用并行收集
--在主線程運行的情況下,使用并發收集
實時目標:可配置在N毫秒內最多只占用M毫秒的時間進行垃圾回收
當然G1要達到實時性的要求,相對傳統的分代回收算法,在性能上會有一些損失。

算法詳解

image.png

G1可謂博采眾家之長,力求到達一種完美。他吸取了增量收集優點,把整個堆劃分為一個一個等大小的區域(region)。內存的回收和劃分都以region為單位;同時,他也吸取了CMS的特點,把這個垃圾回收過程分為幾個階段,分散一個垃圾回收過程;而且,G1也認同分代垃圾回收的思想,認為不同對象的生命周期不同,可以采取不同收集方式,因此,它也支持分代的垃圾回收。為了達到對回收時間的可預計性,G1在掃描了region以后,對其中的活躍對象的大小進行排序,首先會收集那些活躍對象小的region,以便快速回收空間(要復制的活躍對象少了),因為活躍對象小,里面可以認為多數都是垃圾,所以這種方式被稱為Garbage First(G1)的垃圾回收算法,即:垃圾優先的回收。

回收步驟:

初始標記(Initial Marking)
G1對于每個region都保存了兩個標識用的bitmap,一個為previous marking bitmap,一個為next marking bitmap,bitmap中包含了一個bit的地址信息來指向對象的起始點。

開始Initial Marking之前,首先并發的清空next marking bitmap,然后停止所有應用線程,并掃描標識出每個region中root可直接訪問到的對象,將region中top的值放入next top at mark start(TAMS)中,之后恢復所有應用線程。

觸發這個步驟執行的條件為:
G1定義了一個JVM Heap大小的百分比的閥值,稱為h,另外還有一個H,H的值為(1-h)*Heap Size,目前這個h的值是固定的,后續G1也許會將其改為動態的,根據jvm的運行情況來動態的調整,在分代方式下,G1還定義了一個u以及soft limit,soft limit的值為H-u*Heap Size,當Heap中使用的內存超過了soft limit值時,就會在一次clean up執行完畢后在應用允許的GC暫停時間范圍內盡快的執行此步驟;
在pure方式下,G1將marking與clean up組成一個環,以便clean up能充分的使用marking的信息,當clean up開始回收時,首先回收能夠帶來最多內存空間的regions,當經過多次的clean up,回收到沒多少空間的regions時,G1重新初始化一個新的marking與clean up構成的環。

并發標記(Concurrent Marking)
按照之前Initial Marking掃描到的對象進行遍歷,以識別這些對象的下層對象的活躍狀態,對于在此期間應用線程并發修改的對象的以來關系則記錄到remembered set logs中,新創建的對象則放入比top值更高的地址區間中,這些新創建的對象默認狀態即為活躍的,同時修改top值。

最終標記暫停(Final Marking Pause)
當應用線程的remembered set logs未滿時,是不會放入filled RS buffers中的,在這樣的情況下,這些remebered set logs中記錄的card的修改就會被更新了,因此需要這一步,這一步要做的就是把應用線程中存在的remembered set logs的內容進行處理,并相應的修改remembered sets,這一步需要暫停應用,并行的運行。

存活對象計算及清除(Live Data Counting and Cleanup)
值得注意的是,在G1中,并不是說Final Marking Pause執行完了,就肯定執行Cleanup這步的,由于這步需要暫停應用,G1為了能夠達到準實時的要求,需要根據用戶指定的最大的GC造成的暫停時間來合理的規劃什么時候執行Cleanup,另外還有幾種情況也是會觸發這個步驟的執行的:

G1采用的是復制方法來進行收集,必須保證每次的”to space”的空間都是夠的,因此G1采取的策略是當已經使用的內存空間達到了H時,就執行Cleanup這個步驟;

對于full-young和partially-young的分代模式的G1而言,則還有情況會觸發Cleanup的執行,full-young模式下,G1根據應用可接受的暫停時間、回收young regions需要消耗的時間來估算出一個yound regions的數量值,當JVM中分配對象的young regions的數量達到此值時,Cleanup就會執行;partially-young模式下,則會盡量頻繁的在應用可接受的暫停時間范圍內執行Cleanup,并最大限度的去執行non-young regions的Cleanup。

展望
以后JVM的調優或許跟多需要針對G1算法進行調優了。

9.(十)-調優方法
Jconsole,jProfile,VisualVM
Jconsole : jdk自帶,功能簡單,但是可以在系統有一定負荷的情況下使用。對垃圾回收算法有很詳細的跟蹤。詳細說明參考這里

JProfiler:商業軟件,需要付費。功能強大。詳細說明參考這里

VisualVM:JDK自帶,功能強大,與JProfiler類似。推薦。

如何調優
觀察內存釋放情況、集合類檢查、對象樹
上面這些調優工具都提供了強大的功能,但是總的來說一般分為以下幾類功能

堆信息查看

image.png

可查看堆空間大小分配(年輕代、年老代、持久代分配)
提供即時的垃圾回收功能
垃圾監控(長時間監控回收情況)

image.png

查看堆內類、對象信息查看:數量、類型等

image.png

對象引用情況查看

有了堆信息查看方面的功能,我們一般可以順利解決以下問題:
--年老代年輕代大小劃分是否合理
--內存泄漏
--垃圾回收算法設置是否合理

線程監控

image.png

線程信息監控:系統線程數量。
線程狀態監控:各個線程都處在什么樣的狀態下

image.png

Dump線程詳細信息:查看線程內部運行情況
死鎖檢查

熱點分析

image.png

CPU熱點:檢查系統哪些方法占用的大量CPU時間
內存熱點:檢查哪些對象在系統中數量最大(一定時間內存活對象和銷毀對象一起統計)

這兩個東西對于系統優化很有幫助。我們可以根據找到的熱點,有針對性的進行系統的瓶頸查找和進行系統優化,而不是漫無目的的進行所有代碼的優化。

快照
快照是系統運行到某一時刻的一個定格。在我們進行調優的時候,不可能用眼睛去跟蹤所有系統變化,依賴快照功能,我們就可以進行系統兩個不同運行時刻,對象(或類、線程等)的不同,以便快速找到問題

舉例說,我要檢查系統進行垃圾回收以后,是否還有該收回的對象被遺漏下來的了。那么,我可以在進行垃圾回收前后,分別進行一次堆情況的快照,然后對比兩次快照的對象情況。

內存泄漏檢查
內存泄漏是比較常見的問題,而且解決方法也比較通用,這里可以重點說一下,而線程、熱點方面的問題則是具體問題具體分析了。

內存泄漏一般可以理解為系統資源(各方面的資源,堆、棧、線程等)在錯誤使用的情況下,導致使用完畢的資源無法回收(或沒有回收),從而導致新的資源分配請求無法完成,引起系統錯誤。

內存泄漏對系統危害比較大,因為他可以直接導致系統的崩潰。

需要區別一下,內存泄漏和系統超負荷兩者是有區別的,雖然可能導致的最終結果是一樣的。內存泄漏是用完的資源沒有回收引起錯誤,而系統超負荷則是系統確實沒有那么多資源可以分配了(其他的資源都在使用)。

年老代堆空間被占滿
異常: java.lang.OutOfMemoryError: Java heap space
說明:

image.png

這是最典型的內存泄漏方式,簡單說就是所有堆空間都被無法回收的垃圾對象占滿,虛擬機無法再在分配新空間。

如上圖所示,這是非常典型的內存泄漏的垃圾回收情況圖。所有峰值部分都是一次垃圾回收點,所有谷底部分表示是一次垃圾回收后剩余的內存。連接所有谷底的點,可以發現一條由底到高的線,這說明,隨時間的推移,系統的堆空間被不斷占滿,最終會占滿整個堆空間。因此可以初步認為系統內部可能有內存泄漏。(上面的圖僅供示例,在實際情況下收集數據的時間需要更長,比如幾個小時或者幾天)

解決:
這種方式解決起來也比較容易,一般就是根據垃圾回收前后情況對比,同時根據對象引用情況(常見的集合對象引用)分析,基本都可以找到泄漏點。

持久代被占滿
異常:java.lang.OutOfMemoryError: PermGen space
說明:
Perm空間被占滿。無法為新的class分配存儲空間而引發的異常。這個異常以前是沒有的,但是在Java反射大量使用的今天這個異常比較常見了。主要原因就是大量動態反射生成的類不斷被加載,最終導致Perm區被占滿。

更可怕的是,不同的classLoader即便使用了相同的類,但是都會對其進行加載,相當于同一個東西,如果有N個classLoader那么他將會被加載N次。因此,某些情況下,這個問題基本視為無解。當然,存在大量classLoader和大量反射類的情況其實也不多。

解決:
1. -XX:MaxPermSize=16m
2. 換用JDK。比如JRocket。

堆棧溢出
異常:java.lang.StackOverflowError
說明:這個就不多說了,一般就是遞歸沒返回,或者循環調用造成

線程堆棧滿
異常:Fatal: Stack size too small
說明:java中一個線程的空間大小是有限制的。JDK5.0以后這個值是1M。與這個線程相關的數據將會保存在其中。但是當線程空間滿了以后,將會出現上面異常。
解決:增加線程棧大小。-Xss2m。但這個配置無法解決根本問題,還要看代碼部分是否有造成泄漏的部分。

系統內存被占滿
異常:java.lang.OutOfMemoryError: unable to create new native thread
說明:
這個異常是由于操作系統沒有足夠的資源來產生這個線程造成的。系統創建線程時,除了要在Java堆中分配內存外,操作系統本身也需要分配資源來創建線程。因此,當線程數量大到一定程度以后,堆中或許還有空間,但是操作系統分配不出資源來了,就出現這個異常了。

分配給Java虛擬機的內存愈多,系統剩余的資源就越少,因此,當系統內存固定時,分配給Java虛擬機的內存越多,那么,系統總共能夠產生的線程也就越少,兩者成反比的關系。同時,可以通過修改-Xss來減少分配給單個線程的空間,也可以減少系統總共生產的線程數。

解決:
1.重新設計系統減少線程數量。
2.線程數量不能減少的情況下,通過-Xss減小單個線程大小。以便能生產更多的線程。

10.(十一)-反思
垃圾回收的悖論
所謂“成也蕭何敗蕭何”。Java的垃圾回收確實帶來了很多好處,為開發帶來了便利。但是在一些高性能、高并發的情況下,垃圾回收確成為了制約Java應用的瓶頸。目前JDK的垃圾回收算法,始終無法解決垃圾回收時的暫停問題,因為這個暫停嚴重影響了程序的相應時間,造成擁塞或堆積。這也是后續JDK增加G1算法的一個重要原因。

當然,上面是從技術角度出發解決垃圾回收帶來的問題,但是從系統設計方面我們就需要問一下了:
我們需要分配如此大的內存空間給應用嗎?
我們是否能夠通過有效使用內存而不是通過擴大內存的方式來設計我們的系統呢?
我們的內存中都放了什么內存中需要放什么呢?個人認為,內存中需要放的是你的應用需要在不久的將來再次用到到的東西。想想看,如果你在將來不用這些東西,何必放內存呢?放文件、數據庫不是更好?這些東西一般包括:

  1. 系統運行時業務相關的數據。比如web應用中的session、即時消息的session等。這些數據一般在一個用戶訪問周期或者一個使用過程中都需要存在。
  2. 緩存。緩存就比較多了,你所要快速訪問的都可以放這里面。其實上面的業務數據也可以理解為一種緩存。
  3. 線程。

因此,我們是不是可以這么認為,如果我們不把業務數據和緩存放在JVM中,或者把他們獨立出來,那么Java應用使用時所需的內存將會大大減少,同時垃圾回收時間也會相應減少。
我認為這是可能的。

解決之道

數據庫、文件系統
把所有數據都放入數據庫或者文件系統,這是一種最為簡單的方式。在這種方式下,Java應用的內存基本上等于處理一次峰值并發請求所需的內存。數據的獲取都在每次請求時從數據庫和文件系統中獲取。也可以理解為,一次業務訪問以后,所有對象都可以進行回收了。

這是一種內存使用最有效的方式,但是從應用角度來說,這種方式很低效。

內存-硬盤映射
上面的問題是因為我們使用了文件系統帶來了低效。但是如果我們不是讀寫硬盤,而是寫內存的話效率將會提高很多。

數據庫和文件系統都是實實在在進行了持久化,但是當我們并不需要這樣持久化的時候,我們可以做一些變通——把內存當硬盤使。

內存-硬盤映射很好很強大,既用了緩存又對Java應用的內存使用又沒有影響。Java應用還是Java應用,他只知道讀寫的還是文件,但是實際上是內存。

這種方式兼得的Java應用與緩存兩方面的好處。memcached的廣泛使用也正是這一類的代表。

同一機器部署多個JVM
這也是一種很好的方式,可以分為縱拆和橫拆??v拆可以理解為把Java應用劃分為不同模塊,各個模塊使用一個獨立的Java進程。而橫拆則是同樣功能的應用部署多個JVM。

通過部署多個JVM,可以把每個JVM的內存控制一個垃圾回收可以忍受的范圍內即可。但是這相當于進行了分布式的處理,其額外帶來的復雜性也是需要評估的。另外,也有支持分布式的這種JVM可以考慮,不要要錢哦:)

程序控制的對象生命周期
這種方式是理想當中的方式,目前的虛擬機還沒有,純屬假設。即:考慮由編程方式配置哪些對象在垃圾收集過程中可以直接跳過,減少垃圾回收線程遍歷標記的時間。

這種方式相當于在編程的時候告訴虛擬機某些對象你可以在*時間后在進行收集或者由代碼標識可以收集了(類似C、C++),在這之前你即便去遍歷他也是沒有效果的,他肯定是還在被引用的。

這種方式如果JVM可以實現,個人認為將是一個飛躍,Java即有了垃圾回收的優勢,又有了C、C++對內存的可控性。

線程分配
Java的阻塞式的線程模型基本上可以拋棄了,目前成熟的NIO框架也比較多了。阻塞式IO帶來的問題是線程數量的線性增長,而NIO則可以轉換成為常數線程。因此,對于服務端的應用而言,NIO還是唯一選擇。不過,JDK7中為我們帶來的AIO是否能讓人眼前一亮呢?我們拭目以待。

其他的JDK
本文說的都是Sun的JDK,目前常見的JDK還有JRocket和IBM的JDK。其中JRocket在IO方面比Sun的高很多,不過Sun JDK6.0以后提高也很大。而且JRocket在垃圾回收方面,也具有優勢,其可設置垃圾回收的最大暫停時間也是很吸引人的。不過,系統Sun的G1實現以后,在這方面會有一個質的飛躍。

十、分布式系統設計原理與方案

一直在思考分布式系統設計的問題,業務對象原封不動的情況下部署在客戶端和服務器端,可以根據配置文件選擇是連接服務器還是連接本地的數據庫,這個問題讓我絞盡腦汁,我總是設想的客戶端與服務器端通信的方式是最低端的Socket。花了兩個晚上研究CSLA.NET框架關于數據門戶這塊代碼,才發現問題的關鍵所在:客戶端與服務器端通信不能采用最低端的Socket,而要用高端的WebService、.NET Remoting或者是自己定義一種協議等,只要它們支持客戶端直接根據服務器端的服務URL、類名、方法名和方法參數四個信息就可以調用服務器對應的類和方法就行。

說明:本文中所表達的思想與CSLA.NET有很大區別,不要看了本文就以為是CSLA.NET的設計思想,也不要以為本文錯誤的解釋了CSLA.NET,這不是一篇介紹CSLA.NET的文章,但純思想上它們是相同的。

分布式系統的部署
平常我們都說三層架構

接下來我要把三層變的更簡單點,兩層,數據訪問層合并到業務層,統稱為業務層,因為我們面對的問題不是分層的問題,而是分布式系統中各層應該怎么部署的問題。在CSLA.NET書中也說到業務層和數據訪問層放到同一臺機器上可以提高性能和容錯性。因此他們倆的合并不影響分布式系統的部署。

不過要解釋的是數據庫系統(CSLA.NET中說的數據存儲和管理層)并沒有考慮到三層中來,也就是它不包含在數據訪問層中,如果把它算進來,那么它是在數據訪問層之下單獨存在的。

綜上,在分布式系統部署角度考慮的分層實際是三層:界面層、業務層(包含數據訪問層的業務層)、數據存儲層。

下面舉例說明可能的部署情景,帶陰影的框框表示一臺機器,虛線框表示根據使用場合可有可無,虛橫線表示從此處劃開單獨出服務器。在B/S應用中,Web瀏覽器為客戶端,其他全部為服務器。在C/S應用中,處在最上層的界面層+業務層為客戶端,其他為服務器。

非分布式系統的部署

單機版
兩三臺機器

分布式系統的部署

分布式的Web系統
分布式的C/S系統

有幾點要說明:
1. 客戶端上的驗證等業務邏輯是不可信的,因此任何一種部署都需要服務器端包含業務層;
2. 為了開發、維護和部署中的高度可伸縮性,圖中的各業務層所包含的代碼都是一模一樣的;
3. 因為第2點,所以我遇到了業務層的同一個操作是與其他機器上的業務層通信還是訪問數據庫這個難題。

解決業務層的數據訪問問題
1.這個問題是關鍵問題,也就是上面幾點說明中的第3個問題,為了解決這個問題我們引入數據門戶的概念。
2.下面以WebService為例說明:界面層訪問本機的業務對象的增刪改查中的“查”方法時,跳過數據庫的查詢操作,訪問另一臺機器中的同一個業務對象類的“查”方法。

image.png

以上是向另一臺機器發送請求,該請求并不直接調用另一臺機器上的業務對象類的“查”方法,而是將要調用的業務對象和方法參數信息轉為一個“二進制包”,作為參數去調用另一臺機器上通用的“查”方法,另一臺機器上的“查”方法再解開這個包,然后去調用解開的包中所表示的業務對象類型,下面的靜態圖是另一臺機器接受到請求后的工作。

image.png

又有些說明:
1. 關于原理都已在圖中做了描述,不另寫大段文字解釋了;
2. 上面兩個圖中,除了“實際業務對象類”以外的部分全部屬于架構或者框架部分;
3. 如果用OO的思想去審查上面的兩個圖,你一定會為這糟糕的設計而抱怨,這里只是為了盡可能簡單的表述分布式系統的工作原理,你可以采用策略模式使數據門戶不改變的情況下適應各種請求響應場合,采用工廠模式實現不同的請求響應場合的切換。

關于數據庫的分布
  為了解決數據庫服務器的負擔,我們可能希望把數據分布存儲在多個服務器上,我設想的數據庫分布方案是,各服務器上的數據庫在結構上一模一樣,而表里的數據存儲到不同服務器上,這樣數據訪問層在查數據的時候分別向所有數據庫服務器發送同樣的sql命令,然后數據訪問層得到數據后整合,這樣減輕每臺服務器的工作量。亦或者根據表里的某個代表性的字段(如:省份)分布數據到不同服務器。

十一、分布式系統設計系列 -- 基本原理及高可用策略

【分布式系統中的概念】
三元組
其實,分布式系統說白了,就是很多機器組成的集群,靠彼此之間的網絡通信,擔當的角色可能不同,共同完成同一個事情的系統。如果按”實體“來劃分的話,就是如下這幾種:
1、節點 -- 系統中按照協議完成計算工作的一個邏輯實體,可能是執行某些工作的進程或機器
2、網絡 -- 系統的數據傳輸通道,用來彼此通信。通信是具有方向性的。
3、存儲 -- 系統中持久化數據的數據庫或者文件存儲。

image.png

狀態特性
各個節點的狀態可以是“無狀態”或者“有狀態的”.
一般認為,節點是偏計算和通信的模塊,一般是無狀態的。這類應用一般不會存儲自己的中間狀態信息,比如Nginx,一般情況下是轉發請求而已,不會存儲中間信息。另一種“有狀態”的,如mysql等數據庫,狀態和數據全部持久化到磁盤等介質。
“無狀態”的節點一般我們認為是可隨意重啟的,因為重啟后只需要立刻工作就好。“有狀態”的則不同,需要先讀取持久化的數據,才能開始服務。所以,“無狀態”的節點一般是可以隨意擴展的,“有狀態”的節點需要一些控制協議來保證擴展。

系統異常
異常,可認為是節點因為某種原因不能工作,此為節點異常。還有因為網絡原因,臨時、永久不能被其他節點所訪問,此為網絡異常。在分布式系統中,要有對異常的處理,保證集群的正常工作。

【分布式系統與單節點的不同】
1、從linux write()系統調用說起
眾所周知,在unix/linux/mac(類Unix)環境下,兩個機器通信,最常用的就是通過socket連接對方。傳輸數據的話,無非就是調用write()這個系統調用,把一段內存緩沖區發出去。但是可以進一步想一下,write()之后能確認對方收到了這些數據嗎?

答案肯定是不能,原因就是發送數據需要走內核->網卡->鏈路->對端網卡->內核,這一路徑太長了,所以只能是異步操作。write()把數據寫入內核緩沖區之后就返回到應用層了,具體后面何時發送、怎么發送、TCP怎么做滑動窗口、流控都是tcp/ip協議棧內核的事情了。

所以在應用層,能確認對方受到了消息只能是對方應用返回數據,邏輯確認了這次發送才認為是成功的。這就區別與單系統編程,大部分系統調用、庫調用只要返回了就說明已經確認完成了。

2、TCP/IP協議是“不可靠”的
教科書上明確寫明了互聯網是不可靠的,TCP實現了可靠傳輸。何來“不可靠”呢?先來看一下網絡交互的例子,有A、B兩個節點,之間通過TCP連接,現在A、B都想確認自己發出的任何一條消息都能被對方接收并反饋,于是開始了如下操作:
A->B發送數據,然后A需要等待B收到數據的確認,B收到數據后發送確認消息給A,然后B需要等待A收到數據的確認,A收到B的數據確認消息后再次發送確認消息給B,然后A又去需要等待B收到的確認。。。死循環了!!

其實,這就是著名的“拜占庭將軍”問題:http://baike.baidu.com/link?url=6iPrbRxHLOo9an1hT-s6DvM5kAoq7RxclIrzgrS34W1fRq1h507RDWJOxfhkDOcihVFRZ2c7ybCkUosWQeUoS_

所以,通信雙方是“不可能”同時確認對方受到了自己的信息。而教科書上定義的其實是指“單向”通信是成立的,比如A向B發起Http調用,收到了HttpCode 200的響應包,這只能確認,A確認B收到了自己的請求,并且B正常處理了,不能確認的是B確認A受到了它的成功的消息。

3、不可控的狀態
在單系統編程中,我們對系統狀態是非??煽氐摹1热绾瘮嫡{用、邏輯運算,要么成功,要么失敗,因為這些操作被框在一個機器內部,cpu/總線/內存都是可以快速得到反饋的。開發者可以針對這兩個狀態很明確的做出程序上的判斷和后續的操作。

而在分布式的網絡環境下,這就變得微妙了。比如一次rpc、http調用,可能成功、失敗,還有可能是“超時”,這就比前者的狀態多了一個不可控因素,導致后面的代碼不是很容易做出判斷。試想一下,用A用支付寶向B轉了一大筆錢,當他按下“確認”后,界面上有個圈在轉啊轉,然后顯示請求超時了,然后A就抓狂了,不知道到底錢轉沒轉過去,開始確認自己的賬戶、確認B的賬戶、打電話找客服等等。

所以分布式環境下,我們的其實要時時刻刻考慮面對這種不可控的“第三狀態”設計開發,這也是挑戰之一。

4、視”異常“為”正常“
單系統下,進程/機器的異常概率十分小。即使出現了問題,可以通過人工干預重啟、遷移等手段恢復。但在分布式環境下,機器上千臺,每幾分鐘都可能出現宕機、死機、網絡斷網等異常,出現的概率很大。所以,這種環境下,進程core掉、機器掛掉都是需要我們在編程中認為隨時可能出現的,這樣才能使我們整個系統健壯起來,所以”容錯“是基本需求。

異??梢苑譃槿缦聨最悾?br> 節點錯誤:
一般是由于應用導致,一些coredump和系統錯誤觸發,一般重新服務后可恢復。
硬件錯誤:
由于磁盤或者內存等硬件設備導致某節點不能服務,需要人工干預恢復。
網絡錯誤:
由于點對點的網絡抖動,暫時的訪問錯誤,一般拓撲穩定后或流量減小可以恢復。
網絡分化:
網絡中路由器、交換機錯誤導致網絡不可達,但是網絡兩邊都正常,這類錯誤比較難恢復,并且需要在開發時特別處理?!具@種情況也會比較前面的問題較難處理】

image.png

【分布式系統特性】
CAP是分布式系統里最著名的理論,wiki百科如下
Consistency (all nodes see the same data at the same time)
Availability (a guarantee that every request receives a response about whether it was successful or failed)
Partition tolerance (the system continues to operate despite arbitrary message loss or failure of part of the system)
早些時候,國外的大牛已經證明了CAP三者是不能兼得,很多實踐也證明了。
本人就不挑戰權威了,感興趣的同學可以自己Google。本人以自己的觀點總結了一下:

一致性
描述當前所有節點存儲數據的統一模型,分為強一致性和弱一致性:
強一致性描述了所有節點的數據高度一致,無論從哪個節點讀取,都是一樣的。無需擔心同一時刻會獲得不同的數據。是級別最高的,實現的代價比較高

image.png

弱一致性又分為單調一致性和最終一致性:
1、單調一致性強調數據是按照時間的新舊,單調向最新的數據靠近,不會回退,如:數據存在三個版本v1->v2->v3,獲取只能向v3靠近(如取到的是v2,就不可能再次獲得v1)
2、最終一致性強調數據經過一個時間窗口之后,只要多嘗試幾次,最終的狀態是一致的,是最新的數據

image.png

強一致性的場景,就好像交易系統,存取錢的+/-操作必須是馬上一致的,否則會令很多人誤解。
弱一致性的場景,大部分就像web互聯網的模式,比如發了一條微博,改了某些配置,可能不會馬上生效,但刷新幾次后就可以看到了,其實弱一致性就是在系統上通過業務可接受的方式換取了一些系統的低復雜度和可用性。

可用性
保證系統的正常可運行性,在請求方看來,只要發送了一個請求,就可以得到恢復無論成功還是失?。ú粫瑫r)!

分區容忍性
在系統某些節點或網絡有異常的情況下,系統依舊可以繼續服務。
這通常是有負載均衡和副本來支撐的。例如計算模塊異常可通過負載均衡引流到其他平行節點,存儲模塊通過其他幾點上的副本來對外提供服務。

擴展性
擴展性是融合在CAP里面的特性,我覺得此處可以單獨講一下。擴展性直接影響了分布式系統的好壞,系統開發初期不可能把系統的容量、峰值都考慮到,后期肯定牽扯到擴容,而如何做到快而不太影響業務的擴容策略,也是需要考慮的。(后面在介紹數據分布時會著重討論這個問題)

【分布式系統設計策略】
1、重試機制
一般情況下,寫一段網絡交互的代碼,發起rpc或者http,都會遇到請求超時而失敗情況。可能是網絡抖動(暫時的網絡變更導致包不可達,比如拓撲變更)或者對端掛掉。這時一般處理邏輯是將請求包在一個重試循環塊里,如下:

int retry = 3;  
while(!request() && retry--)  
    sched_yield();   // or usleep(100) 

此種模式可以防止網絡暫時的抖動,一般停頓時間很短,并重試多次后,請求成功!但不能防止對端長時間不能連接(網絡問題或進程問題)

2、心跳機制
心跳顧名思義,就是以固定的頻率向其他節點匯報當前節點狀態的方式。收到心跳,一般可以認為一個節點和現在的網絡拓撲是良好的。當然,心跳匯報時,一般也會攜帶一些附加的狀態、元數據信息,以便管理。如下圖:

image.png

但心跳不是萬能的,收到心跳可以確認ok,但是收不到心跳卻不能確認節點不存在或者掛掉了,因為可能是網絡原因倒是鏈路不通但是節點依舊在工作。
所以切記,”心跳“只能告訴你正常的狀態是ok,它不能發現節點是否真的死亡,有可能還在繼續服務。(后面會介紹一種可靠的方式 -- Lease機制)

3、副本
副本指的是針對一份數據的多份冗余拷貝,在不同的節點上持久化同一份數據,當某一個節點的數據丟失時,可以從副本上獲取數據。數據副本是分布式系統解決數據丟失異常的僅有的唯一途徑。當然對多份副本的寫入會帶來一致性和可用性的問題,比如規定副本數為3,同步寫3份,會帶來3次IO的性能問題。還是同步寫1份,然后異步寫2份,會帶來一致性問題,比如后面2份未寫成功其他模塊就去讀了(下個小結會詳細討論如果在副本一致性中間做取舍)。

4、中心化/無中心化
系統模型這方面,無非就是兩種:
中心節點,例如mysql的MSS單主雙從、MongDB Master、HDFS NameNode、MapReduce JobTracker等,有1個或幾個節點充當整個系統的核心元數據及節點管理工作,其他節點都和中心節點交互。這種方式的好處顯而易見,數據和管理高度統一集中在一個地方,容易聚合,就像領導者一樣,其他人都服從就好。簡單可行。
但是缺點是模塊高度集中,容易形成性能瓶頸,并且如果出現異常,就像群龍無首一樣。

無中心化的設計,例如cassandra、zookeeper,系統中不存在一個領導者,節點彼此通信并且彼此合作完成任務。好處在于如果出現異常,不會影響整體系統,局部不可用。缺點是比較協議復雜,而且需要各個節點間同步信息。

【分布式系統設計實踐】
基本的理論和策略簡單介紹這么多,后面本人會從工程的角度,細化說一下”數據分布“、"副本控制"和"高可用協議"

在分布式系統中,無論是計算還是存儲,處理的對象都是數據,數據不存在于一臺機器或進程中,這就牽扯到如何多機均勻分發數據的問題,此小結主要討論"哈希取模",”一致性哈希“,”范圍表劃分“,”數據塊劃分“

1、哈希取模:
哈希方式是最常見的數據分布方式,實現方式是通過可以描述記錄的業務的id或key(比如用戶 id),通過Hash函數的計算求余。余數作為處理該數據的服務器索引編號處理。如圖:

image.png

這樣的好處是只需要通過計算就可以映射出數據和處理節點的關系,不需要存儲映射。難點就是如果id分布不均勻可能出現計算、存儲傾斜的問題,在某個節點上分布過重。并且當處理節點宕機時,這種”硬哈?!暗姆绞綍苯訉е虏糠謹祿惓?,還有擴容非常困難,原來的映射關系全部發生變更。

此處,如果是”無狀態“型的節點,影響比較小,但遇到”有狀態“的存儲節點時,會發生大量數據位置需要變更,發生大量數據遷移的問題。這個問題在實際生產中,可以通過按2的冪的機器數,成倍擴容的方式來緩解,如圖:

image.png

不過擴容的數量和方式后收到很大限制。下面介紹一種”自適應“的方式解決擴容和容災的問題。

2、一致性哈希:
一致性哈希 -- Consistent Hash 是使用一個哈希函數計算數據或數據特征的哈希值,令該哈希函數的輸出值域為一個封閉的環,最大值+1=最小值。將節點隨機分布到這個環上,每個節點負責處理從自己開始順時針至下一個節點的全部哈希值域上的數據,如圖:

image.png

一致性哈希的優點在于可以任意動態添加、刪除節點,每次添加、刪除一個節點僅影響一致性哈希環上相鄰的節點。 為了盡可能均勻的分布節點和數據,一種常見的改進算法是引入虛節點的概念,系統會創建許多虛擬節點,個數遠大于當前節點的個數,均勻分布到一致性哈希值域環上。讀寫數據時,首先通過數據的哈希值在環上找到對應的虛節點,然后查找到對應的real節點。這樣在擴容和容錯時,大量讀寫的壓力會再次被其他部分節點分攤,主要解決了壓力集中的問題。如圖:

image.png

3、數據范圍劃分:
有些時候業務的數據id或key分布不是很均勻,并且讀寫也會呈現聚集的方式。比如某些id的數據量特別大,這時候可以將數據按Group劃分,從業務角度劃分比如id為010000,已知8000以上的id可能訪問量特別大,那么分布可以劃分為[[08000],[80009000],[90001000]]。將小訪問量的聚集在一起。

這樣可以根據真實場景按需劃分,缺點是由于這些信息不能通過計算獲取,需要引入一個模塊存儲這些映射信息。這就增加了模塊依賴,可能會有性能和可用性的額外代價。

4、數據塊劃分:
許多文件系統經常采用類似設計,將數據按固定塊大小(比如HDFS的64MB),將數據分為一個個大小固定的塊,然后這些塊均勻的分布在各個節點,這種做法也需要外部節點來存儲映射關系。

由于與具體的數據內容無關,按數據量分布數據的方式一般沒有數據傾斜的問題,數據總是被均勻切分并分布到集群中。當集群需要重新負載均衡時,只需通過遷移數據塊即可完成。
如圖:

image.png

大概說了一下數據分布的具體實施,后面根據這些分布,看看工程中各個節點間如何相互配合、管理,一起對外服務。

1、paxos
paxos很多人都聽說過了,這是唯一一個被認可的在工程中證實的強一致性、高可用的去中心化分布式協議。

雖然論文里提到的概念比較復雜,但基本流程不難理解。本人能力有限,這里只簡單的闡述一下基本原理:
Paxos 協議中,有三類角色:
Proposer:Proposer 可以有多個,Proposer 提出議案,此處定義為value。不同的 Proposer 可以提出不同的甚至矛盾的 value,例如某個 Proposer 提議“將變量a設置為x1” ,另一個 Proposer 提議“將變量a設置為x2” ,但對同一輪 Paxos過程,最多只有一個 value 被批準。
Acceptor: 批準者。 Acceptor 有 N 個, Proposer 提出的 value 必須獲得超過半數(N/2+1)的 Acceptor批準后才能通過。Acceptor 之間對等獨立。
Learner:學習者。Learner 學習被批準的 value。所謂學習就是通過讀取各個 Proposer 對 value的選擇結果, 如果某個 value 被超過半數 Proposer 通過, 則 Learner 學習到了這個 value。從而學習者需要至少讀取 N/2+1 個 Accpetor,至多讀取 N 個 Acceptor 的結果后,能學習到一個通過的 value。

paxos在開源界里比較好的實現就是zookeeper(類似Google chubby),zookeeper犧牲了分區容忍性,在一半節點宕機情況下,zookeeper就不可用了??梢蕴峁┲行幕渲霉芾硐掳l、分布式鎖、選主等消息隊列等功能。其中前兩者依靠了Lease機制來實現節點存活感知和網絡異常檢測。

2、Lease機制
Lease英文含義是”租期“、”承諾“。在分布式環境中,此機制描述為:
Lease 是由授權者授予的在一段時間內的承諾。授權者一旦發出 lease,則無論接受方是否收到,也無論后續接收方處于何種狀態,只要 lease 不過期,授權者一定遵守承諾,按承諾的時間、內容執行。接收方在有效期內可以使用頒發者的承諾,只要 lease 過期,接收方放棄授權,不再繼續執行,要重新申請Lease。

image.png
image.png

Lease用法舉例1:
現有一個類似DNS服務的系統,數據的規律是改動很少,大量的讀操作。客戶端從服務端獲取數據,如果每次都去服務器查詢,則量比較大??梢园褦祿彺嬖诒镜兀敂祿凶儎拥臅r候重新拉取?,F在服務器以lease的形式,把數據和lease一同推送給客戶端,在lease中存放承諾該數據的不變的時間,然后客戶端就可以一直放心的使用這些數據(因為這些數據在服務器不會發生變更)。如果有客戶端修改了數據,則把這些數據推送給服務器,服務器會阻塞一直到已發布的所有lease都已經超時用完,然后后面發送數據和lease時,更新現在的數據。

這里有個優化可以做,當服務器收到數據更新需要等所有已經下發的lease超時的這段時間,可以直接發送讓數據和lease失效的指令到客戶端,減小服務器等待時間,如果不是所有的lease都失效成功,則退化為前面的等待方案(概率小)。

Lease用法舉例2:
現有一個系統,有三個角色,選主模塊Manager,唯一的Master,和其他salver節點。slaver都向Maganer注冊自己,并由manager選出唯一的Master節點并告知其他slaver節點。當網絡出現異常時,可能是Master和Manager之間的鏈路斷了,Master認為Master已經死掉了,則會再選出一個Master,但是原來的Master對其他網絡鏈路可能都還是正常的,原來的Master認為自己還是主節點,繼續服務。這時候系統中就出現了”雙主“,俗稱”腦裂“。
解決這個問題的方式可以通過Lease,來規定節點可以當Master的時間,如果沒有可用的Lease,則自動退化為Slaver。如果出現”雙主“,原Master會因為Lease到期而放棄當Master,退化為Slaver,恢復了一個Master的情況。

3、選主算法
有些時候出于系統某些特性,可以在有取舍的情況下,實現一些類似Lease的選主的方案,可見本人另一篇文章:http://blog.csdn.net/gugemichael/article/details/8964834

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容

  • 八、深入理解java異常處理機制 引子try…catch…finally恐怕是大家再熟悉不過的語句了, 你的答案是...
    壹點零閱讀 1,598評論 0 0
  • 1.一些概念 1.1.數據類型 Java虛擬機中,數據類型可以分為兩類:基本類型和引用類型?;绢愋偷淖兞勘4嬖?..
    落落落落大大方方閱讀 4,553評論 4 86
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,314評論 11 349
  • Java SE 基礎: 封裝、繼承、多態 封裝: 概念:就是把對象的屬性和操作(或服務)結合為一個獨立的整體,并盡...
    Jayden_Cao閱讀 2,123評論 0 8
  • 吃過晚飯,全家人在一起閑聊。聊著聊著,便聊到了弟弟的學習上。 弟弟馬上就要參加中考了。他的學業屬于中游,如果再努力...
    漪漪麻麻417閱讀 214評論 0 0