系統架構-性能

      

      本文牽扯的面積可能會比較泛,或者說比較大,在這個層面不少人也有本身的看法,因此我這也僅僅是拋磚引玉,結合前面講述的一些基礎技術,從思想中闡述更爲深刻的架構思想基礎,由於最好的架構思想是架構師結合實際狀況思考出來最適合的架構,這裏僅僅說明下一些經常使用的原理和思想,主要包含的內容有(內容很泛,因此都是簡單闡述入門知識,具體後續深刻探討):php

一、app切分集羣組擴展html

二、app集羣組負載均衡前端

三、Memcached原理java

四、db cache應用node

五、db存儲類型以及存儲cache說明mysql

六、存儲條帶思想linux

七、數據庫集羣git

八、數據庫分佈式存儲程序員

九、數據庫容災備份以及監控web

十、nosql思想

十一、無鎖分析

 

 

 

 

一、app切分集羣組擴展

 

 

     應用系統架構隨着外部併發量的增長,必然致使的是app應用的壓力逐漸增長,而且絕大部分app對於線程的分配能力都是有限的,可是app應用在擴展上是很是容易的,最基礎的就是一種應用的垂直切割,其次是水平切割。

 

     在瞭解app切割原理的基礎上先來了解一個其餘的概念就是,就是Internate的路由器如何路由你請求的一個URL,你發送的URL並不知道路由器發送到哪裏去了,最終路由到遠端的服務器(互聯網自己就是一個雲計算爲基礎的平臺),到了遠程後,它如何找到本身的應用呢,以及應用下具體的服務內容呢?就是經過URL後面部分的標識符號。也就是在雲的最終技術也是各自管理各自的內容,而云之所謂稱之爲雲是由於你無須關心你發送的URL是如何路由到遠端的服務器的,又是如何經過哪些路由器返回回來的。這裏不深刻討論雲的概念,回到主題上是說遠端的服務器每個目標都有本身處理的對象,或者說不一樣的路徑下或者不一樣的端口下都會有本身的處理服務。因此app系統切割的基本思想就是路徑。

 

     當一個系統業務十分複雜,應用併發量很大的狀況下,必然致使的一個步驟就是業務分解,將一個大的系統拆分爲多個小系統,不管是軟件自己設計的可擴展性仍是軟件性能擴展性都會有很大的幫助,好比在各個子系統之間他們的業務的複雜性以及併發量都會有很大的區別,咱們能夠將這些小系統拆分到不一樣的集羣節點上去,這些集羣節點能夠是由同一個主機發布出來的不一樣端口或者URL,或者是不一樣的主機發布出來的內容,而且三者能夠根據實際狀況調整使得成本、軟件擴展性、性能擴展性達到較好的程度,總之將一個大系統拆分爲多個小系統是第一個須要作的,也就是app應用拆分,這種並不難,可是拆分的依據必定要把控好,並且還有一個整體架構,否則軟件最終會作的五花八門;在不少的應用下都會使用,只是在這種拆分,拆分後除上述的整體設計要作好外,還須要主意的一點就是系統通訊問題,子系統拆分後應該是高內聚的,可是免不了需呀通訊,不然就根本不算是一個系統的,並且即便不是同一個系統也有可能由於服務需求而須要去通訊,因此在通訊上須要多下功夫,在不一樣服務器以及語言之間通訊最麻煩的事情就是字符集,也是須要主意的,不過不是本文的重點。

 

     在上面拆分完成後,當某一個子系統的併發量很是大的時候,我就須要單獨對某一個子系統進行拆分了,這種沒的選,通常不太可能經過URL來控制(除非申請不一樣的VIP或者在同一個主機上用不一樣的虛擬目錄來作,不過是否是有點挫),這種通常是經過在不一樣的服務端口(也稱爲運行節點,在同一個主機上多個節點確定是不一樣的端口的),或者發佈到不一樣的主機上來完成;這部分拆分app應用不會受到太大的限制;這個地方須要主意的是,當你在app內部作靜態內存時,就沒法作到當一個機器的內存修改後同時修改到其餘內存中,除非你本身寫程序要麼定時刷新要麼相互之間傳送數據,可是這兩種都會付出巨大的成本,如何解決呢,咱們後面會說到的Memcached就是解決的方法。

 

     上述二者完成後,新的問題出現了,就是子系統之間的通訊,他們再也不是單機對單機的通訊,而是集羣組對集羣組的通訊,因此子系統中間件必然有一些非一對一的通訊機制就出現了,以及中途產生的同步通訊和異步通訊等機制,如IBM MQ、EJB、Webservice、HttpClient、HttpInvokie、RPC、Socket甚至於藉助於數據庫或者文件做爲中間層等等都是通訊機制的基礎,應用很是特殊的公司會本身寫本身的通訊機制。

 

     有了上述的集羣,URL能夠經過網絡路由到具體的服務器,可是服務器下每個集羣節點不可能都去申請一個URL吧,並且客戶也不會本身知道我第一次用URL1,下一次用URL二、再下一次用URL3來給你服務器作負載均衡吧,客戶只知道用一個URL訪問你,並且那麼多的IP在互聯網上也會佔用很是大的資源,因此不少時候,一個大網站後臺可能數萬的主機,前端暴露的IP可能只有幾個,這個能夠成爲VIP,他們之間有一個綁定關係,由這個VIP來負責域名的幫頂,而VIP通常會綁定在一個負載均衡器上面,由負載均衡器根據實際請求內容負載到具體的主機上面去,下面第二章就是咱們要寫的負載均衡基本原理。

 

二、app集羣組負載均衡

 

      所謂負載均衡就是負載均衡了,呵呵,也就是不讓某太機器單獨忙,也不讓某臺機器太閒,將請求進行分發,這就是負載均衡器設計的初衷了。

 

      隨着發展的變化,負載均衡器須要承擔更大的做用

     第一個須要作的就是請求解析,也就是不少不一樣的應可能由一個負載均衡器來完成;

     進一步,同一個應用發佈的不一樣的節點或者不一樣的端口,負載均衡器能夠識別出來並達到分發負載,將併發負載到不少不一樣的節點上去運行;

     再進一步,某個客戶端請求第一次訪問了某個節點後,當session未失效時,應當作到繼續訪問同一臺主機,這樣保證客戶在屢次交互中session內容是一致的,至少不會致使從新登錄等現象;

     再進一步,在節點失敗是,負載均衡器應當識別出來,並能夠將訪問切換到其餘主機,在有必要的狀況下須要作session複製。

 

     負載均衡最基本的須要作到以上幾點內容纔算負載均衡。

     負載均衡器通常須要的內容是全局的,可是它並不關注與細節,因此它主要作的事情是全局資源定位,監控,負載均衡,切換動做;通常會有一個單獨的管理節點和單獨的分發節點,可是每一門負載均衡的機制在設計層面都會有很大的區別,因此無需一律而論。

    

     由於負載均衡器在全部應用的最前端,因此咱們很是關注於它的性能,有不少基於高級語言編寫的負載均衡器,甚至於你能夠直接經過你的JSP、ASP、PHP等等作一個簡單的控制跳轉上的負載均衡,可是他們的性能就很低了,擴展性受到明顯的限制,Linux內核纔是負載均衡器的王道,終極方案,要深刻研究和負載均衡的方案,請你們多多參詳Linux內核。

 

     目前市面上很是經常使用的負載均衡器是apache,它自己也能夠做爲WEB服務器來應用(它的一些模塊就能夠直接用於php),另外weblogic自帶的proxy+domain+managed模式也是一種負載均衡方法,不過我作過幾個版本效果不理想,主要緣由仍是主要是由於實現的基礎是高級語言吧;而apache雖然性能不錯,並且你們廣受喜好的一種東西,不過隨着互聯網併發量的上升,apache在不少極爲高併發的系統中仍然受到擴展性的限制,因而乎ngnix出現了,它是目前高併發網站應用中最普遍或者說在大網站中用得最多的負載均衡器,國內的大網站基本都有它的影子,它是俄羅斯一位工程師編寫,並且是免費的,性能極高,佔用資源極少,而且支持cache以及代理,不少客戶端訪問的機制均可以配置化,安裝和使用都很是簡單(要深刻研究就沒那麼簡單),並且故障率很是低,爲何那麼好,由於它的基礎就是unux內核,沒有別的,固然寫代碼必定要寫得很好才行;固然國內並不是沒有這樣的人才存在,並且要看公司是否給這類人才一個機會去完成這樣一個東西,由於本身寫的可能會更加適合於本身,其實國內也有不少對Unix內核研究很深刻的頂尖高手。

 

三、Memcached原理

      這一章自己就想在數據庫後面說明的,不過因爲只是簡單介紹,並且後面應該幾乎都是技術,因此就這這裏說明了。

 

      通常應用程序除了處理業務邏輯和必定的計算後,就是訪問數據庫,作數據庫的存、取、事務操做,在OLAP會有更多的是在數據庫端的計算,OLAP不是本文的重點,由於OLAP不會涉及併發量的問題,因此更多偏重於OLTP,並且是極高併發的系統。

 

     當前端app併發達到必定程度,即將考慮的問題就是數據庫的壓力,數據庫面對的更多的數據,雖然它在各方面作了很是大的優化,不過它畢竟是存大量鎖機制和磁盤讀寫操做,來保證數據一致性和安全性,而這二者每每是影響性能的關鍵指標,可是咱們不少時候又不得不用數據庫,由於他能夠提供給咱們的東西實在是太多了。

 

     在互聯網應用中有個幾乎全部網站都會擁有的一個共同特徵那就是讀取次數很是多,而寫的次數相對比例較少(並不表明沒有寫操做),此時人們在設計上第一個想法是讓數據庫來完成主備或者鏡像方式上的讀寫分離,不過始終會與數據庫交互,並且擴展上會受到很是大的限制,在極高併發下,人們又對應用作了對頁面輸出html的方式,可是最終發如今實施過程當中會受到不少限制(尤爲是ajax交互),雖然有不少軟件能夠支持,此時不少人想到將數據載入到內存中,按照某種方式刷新內存便可,不過咱們上面已經討論,在集羣下它很難作到每一個被切割開的節點他們之間的靜態內存是一致的,因此Memcached出現了。

 

 

    Memcached我看網上寫它是分佈式的,這個你們最好不要亂理解,由於從基本的設計上講,它只是將app和靜態內存分開了,而並不是真正意義上作到分佈式(真正意義上的分佈式應當自動將多個Memcached節點的訪問如同訪問一個節點同樣簡單),而通常Memcached的訪問方式仍是經過程序去控制的,而多個不一樣節點劃分,也是經過人爲的完成的,你能夠認爲你訪問的Memcached是數據庫同樣的東西,由於它的訪問方式相似於數據庫,可是它若是命中確定比訪問數據庫要快不少,並且能夠大量減小讀的壓力,由於一個大網站百分之八九十以上的壓力來源於讀;一個好的Memcached設計會使得讀命中率達到95%以上,而其成本只須要大內存,並具備極大的擴展性;根據實際系統的場景講Memcached劃分數據的方法指定,當命中是獲取,當修改時先修改數據庫,而後讓對應的cached失效便可;主意解決若是它掛掉會產生什麼問題,它的基礎原理是一種Key-Value方式,可是通用的東西每每不是性能最佳的東西,因此你在有必要的狀況下能夠適當作下修改,淘寶網的tair開源技術就是一套本身完成的分佈式緩存技術,也是很不錯的選擇。

 

 

四、db cache應用

      上述已經描述到數據庫訪問會有大量的磁盤操做,這裏咱們說下Oracle是如何緩解這些問題的,以致於它一直在數據庫領域處於行業界得老大哥形象出現。

 

      它首先由一個SGA的全局區域,內部的其餘區域已經在前面的文章中說明,中間對於數據層面,最重要的就是databuffer了,這個databuffer是採用基於LRU算法爲基礎的方式來完成的因此只要有足夠大的內存,在讀遠大於寫的狀況下命中率也會很是高(其實oracle作寫操做也是寫內存的,即便你commit命令oracle也不會作磁盤寫,可是它會寫日誌,當達到必定併發量日誌寫也是不可估量的,並且髒塊列表也會很是頻繁的被刷新到磁盤上,也很容易出現瓶頸),data buffer這也是db cache最爲核心的部分,固然還有些其餘區域也有必定的cache思想。

 

     通常來講,對於極爲高併發的系統,數據庫的cached逐漸受到限制,雖然oracle rac能夠很是高效的擴展,可是其限制最可能是64節點的整列結構,並且在這個過程當中並不是沒有鎖,尤爲是在集羣下的全局鎖機制,這個開銷也是很大的,可是咱們不少時候訪問不少數據並不是須要鎖,也就是這些數據是在這段時間內咱們肯定不會被修改或者說根本不會被修改甚至於說修改了一個簡單髒數據的延遲讀也是無所謂的,這類數據咱們沒有必要讓他來和其餘須要作絕對一致性的事情套在一塊兒,該作事務的被阻塞了,能夠間接作事務的也被阻塞了,因此在更多的層面咱們但願的是app端作好cache,纔是更好的方案,一般app的性能會佔用整個系統性能指標的50%以上,而有20%在於數據庫端,另外還有設計方案、存儲等等其餘的,以及SQL了。

 

     在app設計cached後,數據庫更多的是作修改,讀顯得更加少,因此在app設計cached後,數據庫端的內存能夠保留,也能夠節約一些出來也能夠。

五、db存儲類型以及存儲cache說明

     存儲就是指最終數據存放的位置,有些地方也叫作整列(由於不少時候它是多個磁盤經過RAID完成的),存儲通常會有低端存儲、中端存儲、高端存儲。

 

    存儲設備中最挫的就是本地硬盤了,通常均可以不認爲他是獨立的存儲設備;可是最終你會發現它在是最好的,呵呵,在分佈式的架構上,咱們更加願意選擇廉價的成本設備,並本身架構主機來完成使得性能達到更高的程度;好比在一種順序寫很是多、隨機讀很是多的場景下,咱們就更加願意選擇SSD硬盤來作存儲,由於它的整體設計就很是適合這種狀況。

 

    低端存儲通常只有一個控制器,壞掉所有壞掉,沒有任何存儲cached,存磁盤操做。

 

     中端存儲通常有2個控制器,能夠作均衡負載,並且能夠冗餘保護,壞掉一個性能會下降50%,而且有必定的cache設備,有些時候也會分讀cache和寫cache,IBM DS 8000屬於一種中端存儲,不過它自稱是高端存儲設備,外部通常說他是僞高端設備。

 

    高端存儲,多個控制器相互冗餘,壞掉一兩個性能影響較小,具體影響要看存儲成本和具體需求;EMC高端存儲就是很是流行的選擇,DMX3中還有一種讀cache鏡像和寫cache鏡像,在某些應用下性能更加提高;不太高端存儲的成本極高,在必要的環境下才會使用,絕大部分企業會使用中端存儲設備。

 

    存儲成本並不是和性能或者說高可用性徹底成正比,尤爲是自己高可用性很好的狀況下;因此在選擇存儲的時候再考慮當前應用下須要考慮的就是成本,主要是:數據存儲容量、電費、網路帶寬;以及一個存儲在多少年後報廢等一塊兒計算。

 

    存儲的基本考量標準也是系統性能重點指標:IOPS、QPS、TPS、帶寬容量、單個請求響應時間。

 

    這些目前不作深刻探討,之後咱們再說(由於涉及內容很是多,並且和磁盤管理方式有關係,以下面的條帶就會對其影響),只作下簡單介紹:

 

    IOPS:磁盤陣列上每秒相應IO次數,這個IO次數不分讀寫,可是通常是OLTP系統中的小IO,通常用2K、4K這種來作測試(因此主意你在設計OLTP系統的數據庫block時爲何要小,由於提取一條數據並不想用屢次IO,而oracle提取數據的單位是block,MySQL和sqlserver是頁);通常單個硬盤的IOPS會根據設計有關係,一個15k rpm 的IOPS通常是150個,可是並不是絕對,可能會管理方式以及每一個IO的大小有關係。

 

    QPS和TPS是對IOPS的一個分解,其實自己沒這個概念,不過能夠作這個來看出一個系統的讀寫比例以及讓系統之後如何設計來更好的工做。這兩個分別表明的是每秒的查詢次數、事務次數;能夠經過一些內部SQL抓取等方法來實現。

 

   IO帶寬:當上述內容完成後,就須要考慮帶寬了,當你的IOPS能夠上去後,可是帶寬上不去就悲劇了,那剛纔的15k rpm來講,通常帶寬是13M/s,這裏單位注意是字節(B),這裏假設有120塊磁盤,那麼也就是1560M/s,此時就須要通訊上作一些支持,也就是要支持1G多的流量,須要光纖帶寬8Gb(這裏是網絡上的大小,也就是二進制大小),那麼最少使用4塊2Gb的光纖卡;這種考慮基本在OLAP中比較多,而在OLTP系統中IO都是小IO,帶寬按照小IO的大小乘以IOPS已經足夠。

 

   響應速度:這個因素就多了,除了上述的IOPS以及吞吐量之外,還和存儲cache有關係,甚至於和鎖都有關係,總之響應速度算是一個最終結果,影響因數上面每一種都會有,具體須要根據實際系統來協調,通常來講一個IO若是存磁盤操做最少須要10ms甚至於更多,而若是在cache中命中可能2ms左右就響應了,也就是從單個IO來講,cache命中比正常磁盤操做要快5倍,而平均IO通常保持在10ms是比較良好的,不少時候非cache的狀況下平均IO通常會達到20ms以上

 

六、存儲條帶思想

    你們不要被這個詞彙所嚇到,它就是RAID0的另外一種說法,其實RAID有不少種,從RAID0~RAID7每一種都有本身的特徵所在,並且還有組合的,企業經常使用的有:RAID 十、RAID五、RAID3這幾種,本文不對磁盤陣列作詳細闡述,而只是經過條帶給帶出來一些思想。

 

   RAID0,也就是條帶,它的思想源於負載均衡,和散列存儲,最終在磁盤上的統一實現,並將其做爲磁盤組爲中心,給外部調用,而無需關心磁盤的內部細節。

 

   它按照必定的數據順序,將數據分佈逐個分佈在多個磁盤上,因此看起來就像「條帶」同樣,同時不論在讀仍是寫的過程當中,它都將IO負載到了不一樣的磁盤上,使得IO的整體性能幾乎能夠與磁盤數成正比,極大提升IO性能。

 

    可是RAID0自己沒有保護,也就是當磁盤壞掉,數據就丟了,找不回來,因此後來出現各類各樣的RAID,根據不一樣的狀況每一種RAID都會有本身的方式來處理,實現補充程度的冗餘,仍是那句話,發展到必定的冗餘度將會致使成本直線上升,可是並不必定會帶來收益的直線上升;RAID10就是經過50%冗餘完成,也就是一對一冗餘完成,同一個整列下全部的數據壞掉也能夠找回來,除非兩塊磁盤是相互冗餘的磁盤同時壞掉;而RAID5屬於從RAID三、RAID4作一些算法改進和性能提高上來的,其原理都是奇、偶校驗碼原則,數據分佈式按照條帶思想,冗餘N+1塊磁盤,數據隨機存放在N塊磁盤上,剩餘一塊作校驗位,相對減小磁頭同步粒度,其中任意一塊磁盤壞掉,都可恢復,但同一個RAID5陣列同時壞掉2塊不行。

 

    順便說起下,ORACLE個只瘋狗什麼東西都想獨霸,他的ASM就是拿出來和RAID競爭的,它的管理能夠基於裸機,更加優於基於操做系統層的調用,而在裸設備的管理上又會有不少新的講究。

七、數據庫集羣

     數據庫集羣上,最初是經過一種操做系統機制HA完成,可是它在數據庫層面存在不少缺陷,相對管理數據庫來講還存在不少專業上的個性化,因此ORACLE在10g推出了ORACLE RAC(實際上是9i,可是9i的集羣作得很爛,因此能夠認爲是10g纔有的);另外10g以前的集羣須要第三方的cluster軟件完成,10g後就有了oracle本身的CRS軟件,而且是免費的,能夠到官方下載。

 

    數據庫集羣除正常的app擁有的(load banlance)負載均衡、(failover)失敗切換,還有不少機制在內,包含主從關係、切換機制、以及分佈式計算(網格計算(Grid)在ORACLE RAC中是一種最簡單的實現方法,真正的網格計算是指在實際的網格環境下去管理網格下多個應用的數據庫包括集羣,他們是同一的,甚至於你無須關心網格下集羣組之間的關係,就能很是清晰得去作操做了),這裏的網格計算是指在一些大的統計下,在配置數據庫參數時,將相應的INSTANCE參數設置爲集羣分組,並開啓並行,在作一些大操做時就會實現多實例配合完成,也是經過心跳完成的。

 

   數據庫集羣的負載均衡通常是經過app端完成,這部分多是client端的TNS配置(此時前提是經過cluster完成使用同一個service_name對應多個SID),或者相似TNS配置在連接數據庫的URL中,它內部一個重要參數就是LOAD_BALANCE等等,它能夠設置爲:(yes、on、true是等價的,不區分大小寫,即開啓負載均衡),相反,設置爲(no、off、false)則爲取消負載均衡,此時按照配置的遠程主機IP或者域名的順序逐個訪問到一個可用的便可,此時通常會致使一臺機器忙一臺機器閒的狀況,不過另外一臺機器若是隻是用來作備機器,當一臺掛掉後切換過去也是能夠的,通常用RAC咱們也會將該參數開啓。

 

   failover就是將數據庫的SQL切換到另外一個機器上,可是事務會被回滾,具體是否切換或者如何切換要看其它參數配置,首先FAILOVER參數和上面參數的參數值同樣都是那樣設置,當設置爲開啓狀態就會進行失敗切換,不然這個鏈接池的請求就會失敗;而其它幾個參數通常是在開啓狀態下有默認值的,本身也能夠設置的哦,在FAILOVER_MODE配置中不少:

   首先是TYPE參數的配置中通常有:session(失敗時候,全部內容被停止,已經操做的事務被回滾,建立新的session到另外一個可用實例上)、select(設置爲該參數和上面差很少,不過切換時,開始被操做的事務雖然被回滾,可是若是是select語句不會被中斷,會繼續執行),none(不作任何操做,直接回滾,也不接管,用於測試,客戶端會直接報錯)

   其次METHOD參數,這個參數通常是有:basic(在發生失敗時候再在另外一個實例上建立session回話節點)、preconnect(預先設立回話節點,有必定開銷,可是切換速度很快速,在主從模式下推薦)而RETRIES分別表明重試次數(默認5)、DELAY表明每次重試時間片信息(默認1秒)、BACKUP(備份節點的網路服務名)

 

   集羣RAC因爲設計更加專業於數據庫應用,因此他比起HA更加適用於數據庫,也是衆多企業的選擇,它配合data guard(有些是extend rac是包含了這兩種功能)來完成備份,也有oracle的一直以來的終極備份方案rman來完成,不過前者更加偏重於容災,還有些關於複製以及遷移等功能不是本文重點,不便多說起。

 

   ORACLE RAC和相關的東西都是燒錢的東西,價格不菲,對各項硬件要求很是高,因此注意成本預算,如高速網絡以及各個INSTANCE鏈接共享存儲陣列的SAN交換機通常須要多個來冗餘,心跳的交換機也須要冗餘等等。

 

   ORACLE RAC依賴於一個共享存儲,作相應INSTANCE和數據庫級別的管理,這也是數據庫和實例的區別了,那麼它的瓶頸就在後端了,因此後端不少時候會選擇高端存儲來完成;另外它還有不少全局資源管理使得它的不少發展在這些瓶頸上出現問題,如它的節點通常最多支持64節點,而隨着節點數量的增長,成本會直線上升,至於性能是否能直線上升呢,你應該能夠考慮下當前的各類瓶頸在哪裏,也須要和實際狀況結合纔好說。

八、數據庫容災備份以及監控

     接下來一個系統設計應該如何?須要作的就是容災以及監控運行情況是否良好,對於app端通常不須要容災,只須要監控,而其通常是經過監控內存、CPU、磁盤使用量(主要是日誌和本地緩存文件);若是監控系統作得很差,那麼我想不少DBA晚上睡不着(至於夜間作生產變動這類能夠經過其餘的自動化程序完成),系統的發展也會受到限制,咱們須要一個伸縮性很強的系統就必然會走這一步。

 

     而數據庫容災如今又不少方案,上面已經說了,如今比較多的就是使用dataguard備份到一個或多個備份機器上,dataguard上有多種配置機制,來實現各類經常使用的要求,關於磁盤管理能夠使用ASM來管理,數據庫也能夠負責制過去,也能夠異步經過程序度過去,也能夠經過觸發器+dblink過去等等均可以實現。關鍵看實際需求。

 

     數據庫的監控,這個oracle也提供了系列的監控軟件(Statspace、AWR、logmgr等等系列),不過不少時候咱們須要更加精確的參數須要本身去編碼,不然就仍是須要本身去查詢不少本身作報表什麼的,並且很不直觀;長期須要監控的除了經常使用的IOPS、TPS、QPS之外,還須要關心不少如latch徵用、sql parser(硬解析和軟解析的各方面指標)、cache命中率、鎖等待、內存指標變化、CPU指標變化、索引、磁盤碎片等等都須要獲得全方位的監控

 

    數據庫的管理應當自動化,首先從監控下手,徹底自動化管理和資源調配方面是一個理想,不過半自動化也是很容易的,就是在有問題或者在必定狀況下有某種方式的通知,如短信息吧。這樣DBA就不用整天盯着監控或者後臺的某個字典表一直看了。

九、CDN思想基礎

    後面幾個章節不是本文重點,簡單闡述下便可,在高可用性網站設計中,即便前端應用增長了Memcached這類東西,不過始終不能很好的效果,要達到極佳的效果,由於不少時候跨網段的開銷是很是大的,通過的路由器越多開銷越大;其次不少時候,不肯意由於大文件輸出(如視頻下載)致使應用服務器宕機的事情,這是沒有必要的,由於應用服務器更多關心的應該是業務處理。

 

    CDN的出現就是爲了解決這個問題,也就是網站加速器,他須要運營商的配合(具體細節請本身查閱資料),在不少地方創建站點,它須要作的事情就是託管DNS,一般DNS是解析域名成IP並訪問對應IP內容,而CDN作了一層重寫,就是經過域名解析獲得的是一個CNAME,它按照提供CNAME會按照最短路徑找到對應的CDN網點,接受數據,客戶端的數據接受更加快速,而且能夠實現冗餘保護,另外它只是緩存在這裏,能夠認爲是本地的一個私服,也就是須要跨網段的流量都切換到本地了,這裏作一個極端的假設,就是跨網段的開銷是2,本網段拖數據是1,有100個請求時,跨網段須要200的開銷,而本地網段就只須要101個開銷。

 

    大文件下載,是經過緩存到本地的私服上,如視頻下載就不少時候這段時間你們看的都是熱播電影,就能夠經過CDN來進行網站加速。

十、nosql思想

     根據上面的描述,咱們不少時候就不想作到百分百的數據安全,或者一致性吧,好比作一個網站的留言板,數據有一點誤差也無所謂,並且數據庫的sql parser通常是很慢的,很容易達到極限,因此nosql的誕生就出現了,如今不少開源的nosql平臺,它也是現有云存儲的基礎,apache的Hadoop以及谷歌的mapreduce後來作了一個Percolator,還有RedisMongoDB等等,其實所謂nosql基礎的原理就是沒有sql,就想剛纔說的Memcache同樣,只是它有存儲以及根據設計不一樣,會有一些會存在一些鎖機制,而且只是面向對象;有基於行存儲的、有基於列存儲的他們是根據實際應用場景設計的一種相似於數據庫的東西,它具備極高的擴展性和伸縮性,由於控制徹底在於你自己的架構和設計,也是咱們一直所崇尚的:最好的東西確定是最優秀的人根據實際的場景所架構出來的。

 

      不管是哪一門,nosql它首先拋開的是sql parser的一種,可是它沒有了SQL的支持,在一些複雜操做上顯得比較困難(這些就要看具體場景和nosql的設計了);咱們在結合上述幾種技術的基礎上如何不將Cached、nosql、RDBMS、app幾個結合起來,向後端移動,實現app調用徹底無需關心不少調用的細節,那麼這就是真正的雲存儲了,由於是在分佈式存儲基礎上以及cache管理的基礎上實現了對應用的透明調用。

 

     如何設計待之後專門有文章來闡述,今天只是一個開頭而已。

十一、無鎖分析

    經過上面的文章內容,咱們在不少時候不少沒必要要的信息沒有必要使用RDBMS同樣的鎖和同步等等動做,因此所謂真正意義上的無鎖或者幾乎無鎖,就是將不少內容抽象出來利用間接的方法來實現。

 

    通常來講下降鎖的粒度有如下幾種方法:

     a.使用hash、range、位圖對數據進行提早分佈,讓其分框,根據實際狀況而定,若是一個框只有一個線程在處理那麼就幾乎能夠算是無鎖了。

     b.在一些特殊必要的應用中,使用特殊的方法來控制,變通的方法來控制,如隊列中的對頭和隊尾算法,若是隻有一個生產者和一個消費者可讓他們在一個定長數組下跑圈圈便可,後者永遠追不上前者,而多生產者多消費者模式又該如何呢?好比多個線程作push操做,那麼你只須要在多個線程以當前隊頭下標開始位置分配到不一樣的下標,幾個線程就能夠無鎖操做了,那麼如何分配到不一樣的下標呢?用Java的volatile,你能夠認爲它是鎖的,不過它很是輕量級的鎖,只是在對使用volatile變量修改和讀取過程當中強制從重新內存中獲取,而不是寄存器,因此在計數器使用中,多個線程去同時修改這個變量並獲取到的值都是不一樣的;pop也是如此,這些有必定的應用場景,棧也能夠用變通的手段獲得解決。

     c.還有一些經過版本號碼、向量複製、髒塊列表等等思想來實現,都有一些應用場景和方法;以及java提供的樂觀鎖機制(適用於很是多線程調用同一段代碼,而不是循環很是屢次去調用同一段代碼)。

 

     還有不少其餘的知識能夠借鑑,曾經看到過很是複雜的圖形算法,並且是多維度的,太複雜了,因此這裏就不說明了。

 

     根據上述N多知識能夠看出不少知識都是相通的,無非就是分解、根據實際狀況命中與解鎖,讓更快的地方替換最慢的地方,讓複雜的管理變得更加簡單。

 

     另外一種無鎖是一種變通的手段,就是單線程寫操做了,也就是徹底無鎖的一種機制,其實你會以爲它很慢,通過測試發現,若是你的操做全是或者基本是OLTP中的小IO單個線程的寫已經能夠達到很是快速度,這種很是適合於寫很少,但讀很是多的系統,也就是讀寫分離,寫所有在內存中完成,可是須要寫日誌,讀是從多個散列主機上獲取,可是也會從這個內存中獲取相應數據,內存中爲最新修改後得數據列,他們之間會在對應字段上之內存爲主進行返回,這個機器只要內存足夠大(如今稍微好點的PC SERVER幾十G的內存很是容易),就能夠承受很是大的修改,這個數據只須要在業務量較小的時候合併到靜態數據中便可;那麼當業務進行擴大,單線程沒法承受的時候應該如何呢?內存也寫不下了,那麼此時又須要對其進行切割分離了,在業務和邏輯表上作必定的標識符號,相似於上述說到的volatile同樣的東西,而寫操做也能夠相似於讀操做同樣的分層,這就愈來愈像Memcache+app+RDBMS這種結構了,只是它在Memcached有日誌記錄和恢復,並對於應用來講透明化了這種分佈式的調用,它將整個體系向後端移動和抽象出來使得app的編程更加簡單和方便,也就是app無需關心數據的具體位置在哪裏,以及寫到哪裏去了,緩存在哪裏,他們如何同步的,這就逐步能夠認爲是雲存儲和計算了,另外其精巧的設計不得不說是很是優秀的。

 

       一個web應用絕大部分請求的整個過程:client發出請求->server開始響應並建立請求對象及反饋對象->若是沒有用戶對象就建立session信息->調用業務代碼->業務代碼分層組織數據->調用數據(從某個遠程或數據庫或文件等)->開始組織輸出數據->反饋數據開始經過模板引擎進行渲染->渲染完成未靜態文件向客戶端進行輸出->待客戶端接收完成結束掉請求對象(這種請求針對短鏈接,長鏈接有所區別)。

就從前端提及吧,說下一下幾個內容:

一、線程數量

二、內容輸出

三、線程上下文切換

四、內存

 

1.首先說下線程數量,線程數量不少人認爲在配置服務器的線程數量時認爲越多越好,各大網站上不少人也給出了本身的測試數據,也有人說了每一個CPU配置多少線程爲合適(好比有人說過每一個CPU給25個線程較爲合適),可是沒有一個明確的爲何,其實這個要和CPU自己的運行效率和上來講明,並不是一律而論的,也須要考慮每一個請求所持有的CPU開銷大小以及其處於非Running狀態的時間來講明,線程配置得過多,其實每每會造成CPU的徵用調度問題,要比較恰當將CPU用滿纔是性能的最佳狀態(說到線程就不得不說下CPU,由於線程就是消耗CPU的,其自己持有的內存片斷很是小,前面文章已經說明了它的內存使用狀況,因此咱們主要是討論它與CPU之間的關係)。

首先內存到CPU的延遲在幾十納秒,雖然CPU內部的三級緩存比這個更加小,可是幾乎對於咱們所能識別的時間來說能夠被忽略;另外內存與CPU之間的帶寬也是以最少幾百M每秒的速度通訊,因此對於內存與CPU交互數據的時間開銷對於常規的高併發小請求的應用客戶忽略掉,咱們只計算自己的計算延遲開銷以及非計算的等待開銷,這些都通常會用毫秒來計算,相互之間是用10e6的級別來衡量,因此前者能夠忽略,咱們能夠認爲處於running的時間就是CPU實際執行的時間,由於這種短暫的時間也很難監控出來到底用了多久。

那麼首先能夠將線程的運行狀態劃分爲兩大類,就是:運行與等待,咱們不考慮被釋放的狀況,由於線程池通常不會釋放線程,至於等待有不少種,咱們都認爲它是等待就能夠了;爲何是這兩種呢,這兩種正好對應了CPU是否在被使用,running狀態的線程就在持有CPU的佔用,等待的就處於沒有使用CPU。

再明確一個概念,一個常規的web請求,後臺對應一個線程對它的請求進行處理,同一個線程在同一個時間片上只能請求一個CPU爲他進行處理,也就是說咱們能夠認爲它不論請求過多少次CPU、不論請求了多少個CPU,只要這些CPU的型號是同樣的,咱們就能夠認爲它是請求的一個CPU(注意這裏的CPU不包含多個core的狀況,由於多個core的CPU只能說明這個CPU的處理速度能夠接近於多個CPU的速度,而真正對線程的請求來說,它認爲這是一個CPU,在主板上也是一個插槽,因此計算CPU的時候不考慮多核心)。

最後明確線程在什麼狀況下會發生等待,好比讀取數據庫時,數據庫還沒有反饋內容以前,該線程是不會佔用CPU的,只會處理等待;相似的是向客戶端輸出、線程爲了去持有鎖的等待一系列的狀況。

此時一個線程過來若是一個線程毫無等待(這種狀況不存在,只是一種假設),不論它處理多久,處理時間長度多長,此時若是隻有一個CPU,那麼這個應用服務器只須要一個1個線程就足以支撐,由於線程沒有等待,那麼CPU就沒有中止運行,1個線程處理完這個請求後,接着就處理下一個請求,CPU一直是滿的,也幾乎沒有太大的徵用,此時1個線程就是最佳的,若是是多個同型號的CPU,那麼就是CPU數量的線程是最佳的;不過這個例子比較極端,在不少相似的狀況下,你們喜歡用CPU+1或CPU-1來完成對相似狀況的線程設置,爲了保證一些特殊狀況的發生。

那麼考慮下實際的狀況,若是有等待,這個等待不是鎖等待的(由於鎖等待有瓶頸,瓶頸在於CPU的個數對於他們無效),應該如何考慮呢?咱們此時來考慮下這個等待的時間長度應該如何去考慮,假如等待的時間長度爲100ms,而運行的時間長度爲10ms,那麼在等待的這100ms中,就能夠有另外10個線程進來,對CPU進行佔用,也就是說對於單個CPU來講,11個線程就能夠佔滿整個CPU的使用,若是是多個CPU固然在理論上能夠乘以CPU的個數,這裏再次強調,這裏的CPU個數是物理的,而不算多核,多核在這裏的意義好比之前一個CPU處理一個線程須要30ms,如今採用4個core,只須要處理10ms了,在這裏體現了速度,因此計算是不要用它來計算。

那麼對於鎖等待呢?這個有點麻煩了,由於這個和模塊有關係,這裏也只能說明某個有鎖等待的模塊要達到最佳狀態的訪問效率能夠配置的線程數,首先要明確鎖等待已經沒有CPU個數的概念,不論多少個CPU,只要運行到這段代碼,他們就是一個CPU,否則鎖就沒有存在的意義了;另外,假如訪問是很是密集的,那麼當某個線程持有鎖並訪問的時候,其餘沒有獲得的運行到這個位置都會處於等待,咱們將一個模塊的全部有鎖等待的時間集中在一塊兒,只有當前一個線程將具備鎖的這段代碼運行完成後,下一個線程才能夠繼續運行,因此它其餘地方都沒有瓶頸,或者說其餘地方理論配置的線程數都會很高,惟獨遇到這個地方就會很慢,假如一個線程從運行代碼時長爲20ms,等待事件爲100ms,鎖等待爲20ms,此時假如該線程沒有受到任何等待就是140ms便可運行完成,而當多個線程同時併發到這裏的時候,後續每一個線程將會等待20*N的時間長度,當有7個線程的時候,剛好排滿運行的隊列,也就是當又7個線程訪問這個模塊的時候,理論上恰好達到每一個線程順序執行並且成流水線狀態,可是這裏不能乘以CPU的個數了,爲何,你懂的。

 

2.內容輸出,其實內容輸出有不少種方法,在Java方面,你能夠本身編寫OutputStream或者PrintWriter去輸出,也能夠用渲染模板去渲染輸出,渲染的模板也有不少,最多見的就是JSP模板來渲染,也有velocity等各類各樣的渲染模板,固然對於頁面來說只能用渲染模板去作,不過異步請求你能夠選擇,在選擇時要對應選擇才能將效果作得比較好。

說到這裏不得不說下velocity這個東西,也就是常常看到的vm的文件,這種文件和JSP同樣都是渲染模板的方法,只是語法格式有所區別,velocity是新出來的東西,不少人認爲新的東西確定很好,其實velocity是渲染效率很低的,在內容較小的輸出上對性能進行壓力測試,其單位時間內所能承受的訪問量,比JSP渲染模板要低好幾倍,不過對較大的數據輸出和JSP差很少,也就是頁面輸出使用velocity無所謂的,並且效果比JSP要好,可是相似ajax交互中的小數據輸出建議不要使用vm模板引擎,使用JSP模板引擎甚至於直接輸出是最佳的方式。

說到這裏JSP模板引擎在輸出時是會被預先編譯爲java的class文件,VM是解釋執行的,因此小文件二者性能差距很大,當遇到大數據輸出時,其實大部分時間在輸出文件的過程當中,解釋時間幾乎就能夠被忽略掉了。

那麼JSP輸出小文件是否是最快的呢?未必,JSP的輸出實際上是將JSP頁面的內容組成字符串,最終使用PrintWriter流取完成,中間跳轉交互其實仍是蠻多的,並且有部分容器在組裝字符串的時候居然用+,這個讓我非常鬱悶啊,因此不少時候小數據的輸出,我仍是喜歡本身寫,通過測試獲得的結果是使用OutputStream的性能將會比PrintWriter高一些,(至於高多少,你們能夠本身用工具或寫代碼測試下就知道了,這裏可能單個處理速度幾乎看不出區別,要併發訪問看下平均每秒能處理的請求數就會有區別了),字符集方面,在獲取要輸出內容的時候,指定byte的字符集,如:String.getByte(「字符集」),通常這類輸出也不會有表頭,只須要和接收方或者叫瀏覽器一致就能夠了(有些接收方多是請求方);其實OutputStream比PrintWriter快速的緣由很簡單,在底層運行和傳輸的過程當中,始終採用二進制流來完成,即便是字符也須要轉換成byte格式,在轉換前,它須要去尋找不少的字符集關係,最終定位到應該如何去轉換,內部代碼看過一下就明白,內部的方法調用很是多,一層套一層,相應占用的CPU開銷也會升高。

總結起來講,若是你有vm模板引擎,在頁面請求時建議使用vm模板引擎來作,由於代碼要規範一些,並且也很好用;另外若是在簡單的ajax請求,返回數據較小的狀況下,建議使用OutputStream直接輸出,這個輸出能夠放在你的BaseAction的中,對實現類中是透明的,實現類只須要將處理的反饋結果數據放在一個地方,由父類完成統一的輸出便可,此處將Ajax類的調用能夠獨立一個父親類出來,這樣繼承後就不用關心細節了。

輸出中文件和大數據將是一個問題,對於文件來講,尤爲是大文件,在前面文章已經說明,輸出時壓縮只能節省服務器輸出時和客戶端的流量,從而提升下載速度,可是絕對不會提升服務器端的性能,由於服務器端是經過消耗CPU去作動做,並且壓縮的這個過程是須要時間的,這種只會下降速度,而絕對不會提升;那麼大文件的方法就是一種是將大文件提早壓縮好存放,若是實在太大,須要考慮採用斷點傳送,並將文件分解。

對大數據來說,和文件相似,不過數據可能對咱們要好處理一點,須要控制訪問頻率甚至於直接在超過訪問頻率下拒絕訪問請求,每次請求的量也須要控制,若是對特殊大的數據量,建議採用異步方式輸出到文件並壓縮後,再由客戶端下載,這樣不管是客戶端仍是服務器端都是有好處的。

 

三、線程上下文切換,對於線程的上下文切換,在通常的系統中基本遇不到,不過一些特殊應用會遇到,好比剛纔的異步導出的功能,請求的線程只是將事情提交上去,可是不是由它去下載,而是由其餘線程再去處理這個問題,處理完成後再回寫某個狀態便可;在javaNIO中是很是的多,NIO是一種高性能服務器的解決方案,在有限的線程資源狀況下,對極高併發的小請求,並存在不少推拉數據的狀況下是頗有效的,最大的要求就是服務器要有較好的鏈接支撐能力,NIO細節不用多說,理解上就是異步IO,把事情交給異步的一個線程去作,可是它也未必立刻作,它作完再反饋,這段時間交給你的這個線程不是等待而是去作其餘的事情,充分利用線程的資源,處理完反饋結果的線程也未必是開始請求的線程,幾個來來回回是有不少的開銷的,整體其實效率上未必有單個請求好,可是對服務器的性能發揮是很是有效的。

線程之間的開銷大小也要看具體應用狀況以及配置狀況決定,此時將任務和線程沒有作一個一對一的綁定,而是放一堆事情在隊列中,處理線程也有不少,誰有時間處理誰就處理它,每一個線程都作本身這一類的事情,甚至於將一些內容交給遠程去作,交互後就無論了,結果反饋的時候,這邊再由一個線程去處理結果請求便可。

在整個過程當中會涉及到一次或屢次的線程切換,這個過程當中的開銷在某些時候也是不小的,關鍵仍是要看應用場景,不能一律而論。

四、內存,最後仍是內存,其實這裏我就不想多說了,由於前面幾篇文章說得太多了,不管是理論上仍是實現上,以及經驗上都說了很是多,不過能夠說明的一點就是內存的問題絕大部分來源於代碼,而代碼有很大一部分可能性來源於工程的程序員編寫或者框架,第三方包的內存問題相對較少,通常被開源出來的包內存溢出的可能性不大,可是不排除有寫得比較爛的代碼;二方包呢,通常指代公司內部人員封裝的包,若是在通過不少項目的驗證能夠比較放心使用,要絕對放心的話仍是須要看看源碼才行,至於JVM自己的BUG通常不要找到這個上面來,雖然也有這種可能性,不過這種問題除了升級JVM外也沒有太多的辦法,修改它的源碼的可能性不大,除非你真的太厲害了(這裏在內存上通常是指C或C++語言的源碼,java部分的基礎類包這些代碼若是真的有問題,仍是比較容易修改的,但仍是不建議本身刻意去修改,除非你能確定有你更好的解決方案並且是穩定有效的);在編寫代碼的時候將那些能夠提早作的事情作了(好比這個事情之後會反覆作,重複作,並且都是同樣的,那麼能夠提早作一次,之後就不用作了),那些邏輯是能夠省掉的,最後是若是你的應用很特殊是否是更好的解決方案和算法來完成。

 

總結下,從今天提到的系統設計的角度來講,影響QPS的最關鍵的東西就是模板渲染,它會佔據請求的很大一部分時間,並且這個東西能夠作很是大的改進,好比:壓縮空白字符、重複對象的簡化和模板化、大數據和重複信息的CSS化、儘可能將輸出轉化爲網絡能夠直接接受的內容;而其次就是如何配置線程,配置得太少,CPU的開銷一直處於一種比較閒的狀態,而配置得太多,CPU的徵用狀況比較嚴重,沒有建議值,只要最適合應用場景的值,不過你的代碼若是沒有太多的同步,線程最少應該設置爲CPU的格式+1或-1個;上下文切換對常規應用通常不要使用,對特殊的應用要注意中間的切換開銷應該如何下降;文件輸出上講提早作的壓縮提早作掉,注意控制訪問頻率和單次輸出量;最後內存上多多注意代碼,配置上只須要控制好常規的幾個參數,其他的在沒有特殊狀況不要修改默認的配置。

 

擴展,那麼關於一個系統的架構中是否是就這麼一點就完了呢,固然不是,這應該說說出了一個常見的OLTP系統的一些常見的性能指標,可是還有很內容,好比:緩存、宕機類異常處理、session切換、IO、數據庫、分佈式、集羣等都是這方面的關鍵內容,尤爲是IO也是當今系統中性能瓶頸的最主要緣由之一;在後續的文章中會逐步說明一些相關的解決方案。

 

下面說下OOM的常見狀況(本文基於jdk 1.6系列版原本編寫,其他的版本未必徹底適用):

 

第一類內存溢出,也是你們認爲最多,第一反應認爲是的內存溢出,就是堆棧溢出:

那什麼樣的狀況就是堆棧溢出呢?當你看到下面的關鍵字的時候它就是堆棧溢出了:

Java.lang.OutOfMemoryError: ......Java heap space.....

也就是當你看到heap相關的時候就確定是堆棧溢出了,此時若是代碼沒有問題的狀況下,適當調整-Xmx和-Xms是能夠避免的,不過必定是代碼沒有問題的前提,爲何會溢出呢,要麼代碼有問題,要麼訪問量太多而且每一個訪問的時間太長或者數據太多,致使數據釋放不掉,由於垃圾回收器是要找到那些是垃圾才能回收,這裏它不會認爲這些東西是垃圾,天然不會去回收了;主意這個溢出以前,可能系統會提早先報錯關鍵字爲:

java.lang.OutOfMemoryError:GC over head limit exceeded

這種狀況是當系統處於高頻的GC狀態,並且回收的效果依然不佳的狀況,就會開始報這個錯誤,這種狀況通常是產生了不少不能夠被釋放的對象,有多是引用使用不當致使,或申請大對象致使,可是java heap space的內存溢出有可能提早不會報這個錯誤,也就是可能內存就直接不夠致使,而不是高頻GC.

 

第二類內存溢出,PermGen的溢出,或者PermGen 滿了的提示,你會看到這樣的關鍵字:

關鍵信息爲:

java.lang.OutOfMemoryError: PermGen space

緣由:系統的代碼很是多或引用的第三方包很是多、或代碼中使用了大量的常量、或經過intern注入常量、或者經過動態代碼加載等方法,致使常量池的膨脹,雖然JDK 1.5之後能夠經過設置對永久帶進行回收,可是咱們但願的是這個地方是不作GC的,它夠用就行,因此通常狀況下今年少作相似的操做,因此在面對這種狀況經常使用的手段是:增長-XX:PermSize和-XX:MaxPermSize的大小。

 

第三類內存溢出:在使用ByteBuffer中的allocateDirect()的時候會用到,不少javaNIO的框架中被封裝爲其餘的方法

溢出關鍵字:

java.lang.OutOfMemoryError: Direct buffer memory
若是你在直接或間接使用了ByteBuffer中的allocateDirect方法的時候,而不作clear的時候就會出現相似的問題,常規的引用程序IO輸出存在一個內核態與用戶態的轉換過程,也就是對應直接內存與非直接內存,若是常規的應用程序你要將一個文件的內容輸出到客戶端須要經過OS的直接內存轉換拷貝到程序的非直接內存(也就是heap中),而後再輸出到直接內存由操做系統發送出去,而直接內存就是由OS和應用程序共同管理的,而非直接內存能夠直接由應用程序本身控制的內存,jvm垃圾回收不會回收掉直接內存這部分的內存,因此要注意了哦。

若是常常有相似的操做,能夠考慮設置參數:-XX:MaxDirectMemorySize

 

第四類內存溢出錯誤:

溢出關鍵字:

java.lang.StackOverflowError  

這個參數直接說明一個內容,就是-Xss過小了,咱們申請不少局部調用的棧針等內容是存放在用戶當前所持有的線程中的,線程在jdk 1.4之前默認是256K,1.5之後是1M,若是報這個錯,只能說明-Xss設置得過小,固然有些廠商的JVM不是這個參數,本文僅僅針對Hotspot VM而已;不過在有必要的狀況下能夠對系統作一些優化,使得-Xss的值是可用的。

 

第五類內存溢出錯誤:

溢出關鍵字:

java.lang.OutOfMemoryError: unable to create new native thread 

上面第四種溢出錯誤,已經說明了線程的內存空間,其實線程基本只佔用heap之外的內存區域,也就是這個錯誤說明除了heap之外的區域,沒法爲線程分配一塊內存區域了,這個要麼是內存自己就不夠,要麼heap的空間設置得太大了,致使了剩餘的內存已經很少了,而因爲線程自己要佔用內存,因此就不夠用了,說明了緣由,如何去修改,不用我多說,你懂的。

 

第六類內存溢出:

溢出關鍵字

java.lang.OutOfMemoryError: request {} byte for {}out of swap

這類錯誤通常是因爲地址空間不夠而致使。 

 

六大類常見溢出已經說明JVM中99%的溢出狀況,要逃出這些溢出狀況很是困難,除非一些很怪異的故障問題會發生,好比因爲物理內存的硬件問題,致使了code cache的錯誤(在由byte code轉換爲native code的過程當中出現,可是機率極低),這種狀況內存 會被直接crash掉,相似還有swap的頻繁交互在部分系統中會致使系統直接被crash掉,OS地址空間不夠的話,系統根本沒法啓動,呵呵;JNI的濫用也會致使一些本地內存沒法釋放的問題,因此儘可能避開JNI;socket鏈接數據打開過多的socket也會報相似:IOException: Too many open files等錯誤信息。

 

JNI就不用多說了,儘可能少用,除非你的代碼太牛B了,我無話可說,呵呵,這種內存若是沒有在被調用的語言內部將內存釋放掉(如C語言),那麼在進程結束前這些內存永遠釋放不掉,解決辦法只有一個就是將進程kill掉。

 

另外GC自己是須要內存空間的,由於在運算和中間數據轉換過程當中都須要有內存,因此你要保證GC的時候有足夠的內存哦,若是沒有的話GC的過程將會很是的緩慢。

 

順便這裏就說起一些新的CMS GC的內容和策略(有點亂,每次寫都很亂,可是能看多少看多少吧):

首先我再寫一次一前博客中的已經寫過的內容,就是不少參數沒啥建議值,建議值是本身在現場根據實際狀況科學計算和測試獲得的綜合效果,建議值沒有絕對好的,並且默認值不少也是有問題的,由於不一樣的版本和廠商都有很大的區別,默認值沒有永久都是同樣的,就像-Xss參數的變化同樣,要看到你當前的java程序heap的大體狀況能夠這樣看看(如下參數是隨便設置的,並非什麼默認值):

$sudo jmap -heap `pgrep java`
Attaching to process ID 4280, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 19.1-b02

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 1073741824 (1024.0MB)
   NewSize          = 134217728 (128.0MB)
   MaxNewSize       = 134217728 (128.0MB)
   OldSize          = 5439488 (5.1875MB)
   NewRatio         = 2
   SurvivorRatio    = 8
   PermSize         = 134217728 (128.0MB)
   MaxPermSize      = 268435456 (256.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 85721088 (81.75MB)
   used     = 22481312 (21.439849853515625MB)
   free     = 63239776 (60.310150146484375MB)
   26.22611602876529% used
From Space:
   capacity = 24051712 (22.9375MB)
   used     = 478488 (0.45632171630859375MB)
   free     = 23573224 (22.481178283691406MB)
   1.9894134770946867% used
To Space:
   capacity = 24248320 (23.125MB)
   used     = 0 (0.0MB)
   free     = 24248320 (23.125MB)
   0.0% used
PS Old Generation
   capacity = 939524096 (896.0MB)
   used     = 16343864 (15.586723327636719MB)
   free     = 923180232 (880.4132766723633MB)
   1.7395896571023124% used
PS Perm Generation
   capacity = 134217728 (128.0MB)
   used     = 48021344 (45.796722412109375MB)
   free     = 86196384 (82.20327758789062MB)
   35.77868938446045% used

 

付:sudo是須要拿到管理員權限,若是你的系統權限很大那麼就不須要了,最後的grep java那個內容若是不對,能夠直接經過jps或者ps命令將和java相關的進程號直接寫進去,如:java -map 4280,這個參數其實徹底能夠經過jstat工具來替代,並且看到的效果更加好,這個參數在線上應用中,儘可能少用(尤爲是高併發的應用中),可能會觸發JVM的bug,致使應用掛起;在jvm 1.6u14後能夠編寫任意一段程序,而後在運行程序的時候,增長參數爲:-XX:+PrintFlagsFinal來輸出當前JVM中運行時的參數值,或者經過jinfo來查看,jinfo是很是強大的工具,能夠對部分參數進行動態修改,固然內存相關的東西是不能修改的,只能增長一些不是很相關的參數,有關JVM的工具使用,後續文章中若是有機會咱們再來探討,不是本文的重點;補充:關於參數的默認值對不一樣的JVM版本、不一樣的廠商、運行於不一樣的環境(通常和位數有關係)默認值會有區別。

 

OK,再說下反覆的一句,沒有必要的話就不要亂設置參數,參數不是拿來玩的,默認的參數對於這門JDK都是有好處的,關鍵是否適合你的應用場景,通常來說你常規的只須要設置如下幾個參數就能夠了:

-server 表示爲服務器端,會提供不少服務器端默認的配置,如並行回收,而服務器上通常這個參數都是默認的,因此都是能夠省掉,與之對應的還有一個-client參數,通常在64位機器上,JVM是默認啓動-server參數,也就是默認啓動並行GC的,可是是ParallelGC而不是ParallelOldGC,二者算法不一樣(後面會簡單說明下),而比較特殊的是windows 32位上默認是-client,這兩個的區別不只僅是默認的參數不同,在jdk包下的jre包下通常會包含client和server包,下面分別對應啓動的動態連接庫,而真正看到的java、javac等相關命令指示一個啓動導向,它只是根據命令找到對應的JVM並傳入jvm中進行啓動,也就是看到的java.exe這些文件並非jvm;說了這麼多,最終總結一下就是,-server和-client就是徹底不一樣的兩套VM,一個用於桌面應用,一個用於服務器的。

-Xmx 爲Heap區域的最大值

-Xms 爲Heap區域的初始值,線上環境須要與-Xmx設置爲一致,不然capacity的值會來回飄動,飄得你心曠神怡,你懂的。

-Xss(或-ss) 這個其實也是能夠默認的,若是你真的以爲有設置的必要,你就改下吧,1.5之後是1M的默認大小(指一個線程的native空間),若是代碼很少,能夠設置小點來讓系統能夠接受更大的內存。注意,還有一個參數是-XX:ThreadStackSize,這兩個參數在設置的過程當中若是都設置是有衝突的,通常按照JVM常理來講,誰設置在後面,就以誰爲主,可是最後發現若是是在1.6以上的版本,-Xss設置在後面的確都是以-Xss爲主,可是要是-XX:ThreadStackSize設置在後面,主線程仍是爲-Xss爲主,而其它線程以-XX:ThreadStackSize爲主,主線程作了一個特殊斷定處理;單獨設置都是以自己爲主,-Xss不設置也不會採用其默認值,除非兩個都不設置會採用-Xss的默認值。另外這個參數針對於hotspot的vm,在IBM的jvm中,還有一個參數爲-Xoss,主要緣由是IBM在對棧的處理上有操做數棧和方法棧等各類不一樣的棧種類,而hotspot無論是什麼棧都放在一個私有的線程內部的,不區分是什麼棧,因此只須要設置一個參數,而IBM的J9不是這樣的;有關棧上的細節,後續咱們有機會專門寫文章來講明。

 

-XX:PermSize-XX:MaxPermSize兩個包含了class的裝載的位置,或者說是方法區(但不是本地方法區),在Hotspot默認狀況下爲64M,主意全世界的JVM只有hostpot的VM纔有Perm的區域,或者說只有hotspot纔有對用戶能夠設置的這塊區域,其餘的JVM都沒有,其實並非沒有這塊區域,而是這塊區域沒有讓用戶來設置,其實這塊區域自己也不該該讓用戶來設置,咱們也沒有一個明確的說法這塊空間必需要設置多大,都是拍腦殼設置一個數字,若是發佈到線上看下若是用得比較多,就再多點,若是用的少,就減小點,而這塊區域和性能關鍵沒有多大關係,只要能裝下就OK,而且時不時會由於Perm不夠而致使Full GC,因此交給開發者來調節這個參數不知道是怎麼想的;因此Oracle將在新一代JVM中將這個區域完全刪掉,也就是對用戶透明,G1的若是真正穩定起來,之後JVM的啓動參數將會很是簡單,並且理論上管理再大的內存也是沒有問題的,其實G1(garbage first,一種基於region的垃圾收集回收器)已經在hotspot中開始有所試用,不過目前效果很差,還不如CMS呢,因此只是試用,G1已經做爲ORACLE對JVM研發的最高重點,CMS自如今最高版本後也再也不有新功能(能夠修改bug),該項目已經進行5年,還沒有發佈正式版,CMS是四五年前發佈的正式版,可是是最近一兩年纔開始穩定,而G1的複雜性將會遠遠超越CMS,因此要真正使用上G1還有待考察,全世界目前只有IBM J9真正實現了G1論文中提到的思想(論文於05年左右發表),IBM已經將J9應用於websphere中,可是並不表明這是全世界最好的jvm,全世界最好的jvm是Azul(無停頓垃圾回收算法和一個零開銷的診斷/監控工具),幾乎能夠說這個jvm是沒有暫停的,在全世界不少頂尖級的公司使用,不過價格很是貴,不能直接使用,目前這個jvm的主導者在研究JRockit,而目前hotspot和JRockit都是Oracle的,因此他們可能會合並,因此咱們應該對JVM的性能充滿信心。

 

也就是說你經常使用的狀況下只須要設置4個參數就OK了,除非你的應用有些特殊,不然不要亂改,那麼來看看一些其餘狀況的參數吧:

 

先來看個不大經常使用的,就是你們都知道JVM新的對象應該說幾乎百分百的在Eden裏面,除非Eden真的裝不下,咱們不考慮這種變態的問題,由於線上環境Eden區域都是不小的,來下降GC的次數以及全局 GC的機率;而JVM習慣將內存按照較爲連續的位置進行分配,這樣使得有足夠的內存能夠被分配,減小碎片,那麼對於內存最後一個位置必然就有大量的徵用問題,JVM在高一點的版本里面提出了爲每一個線程分配一些私有的區域來作來解決這個問題,而1.5後的版本還能夠動態管理這些區域,那麼如何本身設置和查看這些區域呢,看下英文全稱爲:Thread Local Allocation Buffer,簡稱就是:TLAB,即內存本地的持有的buffer,設置參數有:

-XX:+UseTLAB                          啓用這種機制的意思
-XX:TLABSize=<size in kb>    設置大小,也就是本地線程中的私有區域大小(只有這個區域放不下才會到Eden中去申請)。
-XX:+ResizeTLAB                     是否啓動動態修改

這幾個參數在多CPU下很是有用。

-XX:+PrintTLAB                        能夠輸出TLAB的內容。

 

下面再閒扯些其它的參數:

 

若是你須要對Yong區域進行並行回收應該如何修改呢?在jdk1.5之後能夠使用參數:

-XX:+UseParNewGC

注意: 與它衝突的參數是:-XX:+UseParallelOldGC-XX:+UseSerialGC,若是須要用這個參數,又想讓整個區域是並行回收的,那麼就使用-XX:+UseConcMarkSweepGC參數來配合,其實這個參數在使用了CMS後,默認就會啓動該參數,也就是這個參數在CMS GC下是無需設置的,後面會說起到這些參數。

 

 

默認服務器上的對Full並行GC策略爲(這個時候Yong空間回收的時候啓動PSYong算法,也是並行回收的):

-XX:+UseParallelGC

另外,在jdk1.5後出現一個新的參數以下,這個對Yong的回收算法和上面同樣,對Old區域會有所區別,上面對Old回收的過程當中會作一個全局的Compact,也就是全局的壓縮操做,而下面的算法是局部壓縮,爲何要局部壓縮呢?是由於JVM發現每次壓縮後再邏輯上數據都在Old區域的左邊位置,申請的時候從左向右申請,那麼生命力越長的對象就通常是靠左的,因此它認爲左邊的對象就是生命力很強,並且較爲密集的,因此它針對這種狀況進行部分密集,可是這兩種算法mark階段都是會暫停的,並且存活的對象越多活着的越多;而ParallelOldGC會進行部分壓縮算法(主意一點,最原始的copy算法是不須要通過mark階段,由於只須要找到一個或活着的就只須要作拷貝就能夠,而Yong區域借用了Copy算法,只是惟一的區別就是傳統的copy算法是採用兩個相同大小的內存來拷貝,浪費空間爲50%,因此分代的目標就是想要實現不少優點所在,認爲新生代85%以上的對象都應該是死掉的,因此S0和S1通常並非很大),該算法爲jdk 1.5之後對於絕大部分應用的最佳選擇。

-XX:+UseParallelOldGC

 

-XX:ParallelGCThread=12:並行回收的線程數,最好根據實際狀況而定,由於線程多每每存在徵用調度和上下文切換的開銷;並且也並不是CPU越多線程數也能夠設置越大,通常設置爲12就再增長用處也不大,主要是算法自己內部的徵用會致使其線程的極限就是這樣。

 

設置Yong區域大小:

-Xmn  Yong區域的初始值和最大值同樣大

-XX:NewSize-XX:MaxNewSize若是設置覺得同樣大就是和-Xmn,在JRockit中會動態變化這些參數,根據實際狀況有可能會變化出兩個Yong區域,或者沒有Yong區域,有些時候會生出來一個半長命對象區域;這裏除了這幾個參數外,還有一個參數是NewRatio是設置Old/Yong的倍數的,這幾個參數都是有衝突的,服務器端建議是設置-Xmn就能夠了,若是幾個參數所有都有設置,-Xmn和-XX:NewSize與-XX:MaxNewSize將是誰設置在後面,以誰的爲準,而-XX:NewSize -XX:MaxNewSize與-XX:NewRatio時,那麼參數設置的結果可能會如下這樣的(jdk 1.4.1後):

min(MaxNewSize,max(NewSize, heap/(NewRatio+1)))

-XX:NewRatio爲Old區域爲Yong的多少倍,間接設置Yong的大小,1.6中若是使用此參數,則默認會在適當時候被動態調整,具體請看下面參數UseAdaptiveSizepollcy 的說明。

三個參數不要同時設置,由於都是設置Yong的大小的。

 

-XX:SurvivorRatio:該參數爲Eden與兩個求助空間之一的比例,注意Yong的大小等價於Eden + S0 + S1,S0和S1的大小是等價的,這個參數爲Eden與其中一個S區域的大小比例,如參數爲8,那麼Eden就佔用Yong的80%,而S0和S1分別佔用10%。

之前的老版本有一個參數爲:-XX:InitialSurivivorRatio,若是不作任何設置,就會以這個參數爲準,這個參數的默認值就是8,不過這個參數並非Eden/Survivor的大小,而是Yong/Survivor,因此因此默認值8,表明每個S區域的空間大小爲Yong區域的12.5%而不是10%。另外順便說起一下,每次你們看到GC日誌的時候,GC日誌中的每一個區域的最大值,其中Yong的空間最大值,始終比設置的Yong空間的大小要小一點,大概是小12.5%左右,那是由於每次可用空間爲Eden加上一個Survivor區域的大小,而不是整個Yong的大小,由於可用空間每次最可能是這樣大,兩個Survivor區域始終有一塊是空的,因此不會加上兩個來計算。

 

-XX:MaxTenuringThreshold=15:在正常狀況下,新申請的對象在Yong區域發生多少次GC後就會被移動到Old(非正常就是S0或S1放不下或者不太可能出現的Eden都放不下的對象),這個參數通常不會超過16(由於計數器從0開始計數,因此設置爲15的時候至關於生命週期爲16)。

要查看如今的這個值的具體狀況,能夠使用參數:-XX:+PrintTenuringDistribution

  

經過上面的jmap應該能夠看出個人機器上的MinHeapFreeRatio和MaxHeapFreeRatio分別爲40個70,也就是你們常常說的在GC後剩餘空間小於40%時capacity開始增大,而大於70%時減少,因爲咱們不但願讓它移動,因此這兩個參數幾乎沒有意義,若是你須要設置就設置參數爲:

-XX:MinHeapFreeRatio=40
-XX:MaxHeapFreeRatio=70

 

JDK 1.6後有一個動態調節板塊的,固然若是你的每個板塊都是設置固定值,這個參數也沒有用,不過若是是非固定的,建議仍是不要動態調整,默認是開啓的,建議將其關掉,參數爲:

-XX:+UseAdaptiveSizepollcy 建議使用-XX:-UseAdaptiveSizepollcy關掉,爲何當你的參數設置了NewRatio、Survivor、MaxTenuringThreshold這幾個參數若是在啓動了動態更新狀況下,是無效的,固然若是你設置-Xmn是有效的,可是若是設置的比例的話,初始化可能會按照你的參數去運行,不過運行過程當中會經過必定的算法動態修改,監控中你可能會發現這些參數會發生改變,甚至於S0和S1的大小不同。

若是啓動了這個參數,又想要跟蹤變化,那麼就使用參數:-XX:+PrintAdaptiveSizePolicy

 

上面已經提到,javaNIO中經過Direct內存來提升性能,這個區域的大小默認是64M,在適當的場景能夠設置大一些。

-XX:MaxDirectMemorySize

 

一個不太經常使用的參數:

-XX:+ScavengeBeforeFullGC 默認是開啓狀態,在full GC前先進行minor GC。

 

對於java堆中若是要設置大頁內存,能夠經過設置參數:

付:此參數必須在操做系統的內核支持的基礎上,須要在OS級別作操做爲:

echo 1024 > /proc/sys/vm/nr_hugepages

echo 2147483647 > /proc/sys/kernel/shmmax

-XX:+UseLargePages

-XX:LargePageSizeInBytes

此時整個JVM都將在這塊內存中,不然所有不在這塊內存中。 

 

javaIO的臨時目錄設置

-Djava.io.tmpdir

jstack會去尋找/tmp/hsperfdata_admin下去尋找與進程號相同的文件,32位機器上是沒有問題的,64爲機器的是有BUG的,在jdk 1.6u23版本中已經修復了這個bug,若是你遇到這個問題,就須要升級JDK了。

 

還記得上次說的平均晉升大小嗎,在並行GC時,若是平均晉升大小大於old剩餘空間,則發生full GC,那麼當小於剩餘空間時,也就是平均晉升小於剩餘空間,可是剩餘空間小於eden + 一個survivor的空間時,此時就依賴於參數:

-XX:-HandlePromotionFailure

啓動該參數時,上述狀況成立就發生minor gc(YGC),大於則發生full gc(major gc)。

 

通常默認直接分配的對象若是大於Eden的一半就會直接晉升到old區域,可是也能夠經過參數來指定:

-XX:PretenureSizeThreshold=2m  我我的不建議使用這個參數

也就是當申請對象大於這個值就會晉升到old區域。

 

傳說中GC時間的限制,一個是經過比例限制,一個是經過最大暫停時間限制,可是GC時間能限制麼,呵呵,在增量中貌似能夠限制,不過不能限制住GC整體的時間,因此這個參數也不是那麼關鍵。

-XX:GCTimeRatio=

-XX:MaxGCPauseMillis

-XX:GCTimeLimit

要看到真正暫停的時間就一個是看GCDetail的日誌,另外一個是設置參數看:

-XX:+PrintGCApplicationStoppedTime 

 

有些人,有些人就是喜歡在代碼裏面裏頭寫System.gc(),耍酷,這個不是測試程序是線上業務,這樣將會致使N多的問題,很少說了,你應該懂的,不懂的話看下書吧,而RMI是很不聽話的一個鳥玩意,EJB的框架也是基於RMI寫的,RMI爲何不聽話呢,就是它本身在裏面非要搞個System.gc(),哎,爲了放置頻繁的作,頻繁的作,你就將這個命令的執行禁用掉吧,固然程序不用改,否則那些EJB都跑步起來了,呵呵:

-XX:+DisableExplicitGC 默認是沒有禁用掉,寫成+就是禁用掉的了,可是有些時候在使用allocateDirect的時候,不少時候還真須要System.gc來強制回收這塊資源。

 

內存溢出時導出溢出的錯誤信息:
-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/home/xieyu/logs/   這個參數指定導出時的路徑,否則導出的路徑就是虛擬機的目標位置,很差找了,默認的文件名是:java_pid<進程號>.hprof,這個文件能夠相似使用jmap -dump:file=....,format=b <pid>來dump相似的內容,文件後綴都是hprof,而後下載mat工具進行分析便可(不過內存有多大dump文件就多大,而本地分析的時候內存也須要那麼大,因此不少時候下載到本地都沒法啓動是很正常的),後續文章有機會咱們來講明這些工具,另外jmap -dump參數也不要常常用,會致使應用掛起哦;另外此參數只會在第一次輸出OOM的時候纔會進行堆的dump操做(java heap的溢出是能夠繼續運行再運行的程序的,至於web應用是否服務要看應用服務器自身如何處理,而c heap區域的溢出就根本沒有dump的機會,由於直接就宕機了,目前系統沒法看到c heap的大小以及內部變化,要看大小隻能間接經過看JVM進程的內存大小(top或相似參數),這個大小通常會大於heap+perm的大小,多餘的部分基本就能夠認爲是c heap的大小了,而看內部變化呢只有google perftools能夠達到這個目的),若是內存過大這個dump操做將會很是長,因此hotspot若是之後想管理大內存,這塊必須有新的辦法出來。

最後,用dump出來的文件,經過mat分析出來的結果每每有些時候難以直接肯定到底哪裏有問題,能夠看到的維度大概有:那個類使用的內存最多,以及每個線程使用的內存,以及線程內部每個調用的類和方法所使用的內存,可是不少時候沒法斷定究竟是程序什麼地方調用了這個類或者方法,由於這裏只能看到最終消耗內存的類,可是不知道誰使用了它,一個辦法是掃描代碼,可是太笨重,並且若是是jar包中調用了就很差弄了,另外一種方法是寫agent,那麼就須要相應的配合了,可是有一個很是好的工具就是btrace工具(jdk 1.7貌似還不支持),能夠跟蹤到某個類的某個方法被那些類中的方法調用過,那這個問題就好說了,只要知道開銷內存的是哪個類,就能知道誰調用過它,OK,關於btrace的不是本文重點,網上都有,後續文章有機會再探討,
原理:
No performance impact during runtime(無性能影響)
Dumping a –Xmx512m heap
Create a 512MB .hprof file(512M內存就dump出512M的空間大小)
JVM is 「dead」 during dumping(死掉時dump)
Restarting JVM during this dump will cause unusable .hprof file(重啓致使文件不可用) 

 

註明的NUMA架構,在JVM中開始支持,固然也須要CPU和OS的支持才能夠,須要設置參數爲:

-XX:+UseNUMA  必須在並行GC的基礎上纔有的

老年代沒法分配區域的最大等待時間爲(默認值爲0,可是也不要去動它):

-XX:GCExpandToAllocateDelayMillis 

讓JVM中全部的set和get方法轉換爲本地代碼:

-XX:+UseFastAccessorMethods

以時間戳輸出Heap的利用率

-XX:+PrintHeapUsageOverTime 

 在64bit的OS上面(其實通常達不到57位左右),因爲指針會放大爲8個byte,因此會致使空間使用增長,固然,若是內存夠大,就沒有問題,可是若是升級到64bit系統後,只是想讓內存達到4G或者8G,那麼就徹底能夠經過不少指針壓縮爲4byte就OK了,因此在提供如下參數(本參數於jdk 1.6u23後使用,並自動開啓,因此也不須要你設置,知道就OK):

-XX:+UseCompressedOops   請注意:這個參數默認在64bit的環境下默認啓動,可是若是JVM的內存達到32G後,這個參數就會默認爲不啓動,由於32G內存後,壓縮就沒有多大必要了,要管理那麼大的內存指針也須要很大的寬度了。

後臺JIT編譯優化啓動

-XX:+BackgroundCompilation

若是你要輸出GC的日誌以及時間戳,相關的參數有:

-XX:+PrintGCDetails  輸出GC的日誌詳情,包含了時間戳

-XX:+PrintGCTimeStamps 輸出GC的時間戳信息,按照啓動JVM後相對時間的每次GC的相對秒值(毫秒在小數點後面),也就是每次GC相對啓動JVM啓動了多少秒後發生了此次GC

-XX:+PrintGCDateStamps輸出GC的時間信息,會按照系統格式的日期輸出每次GC的時間

-XX:+PrintGCTaskTimeStamps輸出任務的時間戳信息,這個細節上比較複雜,後續有文章來探討。

-XX:-TraceClassLoading  跟蹤類的裝載

-XX:-TraceClassUnloading 跟蹤類的卸載

-XX:+PrintHeapAtGC  輸出GC後各個堆板塊的大小。

將常量信息GC信息輸出到日誌文件:

-Xloggc:/home/xieyu/logs/gc.log

 

 

如今面對大內存比較流行是是CMS GC(最少1.5才支持),首先明白CMS的全稱是什麼,不是傳統意義上的內容管理系統(Content Management System)哈,第一次我也沒看懂,它的全稱是:Concurrent Mark Sweep,三個單詞分別表明併發、標記、清掃(主意這裏沒有compact操做,其實CMS GC的確沒有compact操做),也就是在程序運行的同時進行標記和清掃工做,至於它的原理前面有說起過,只是有不一樣的廠商在上面作了一些特殊的優化,好比一些廠商在標記根節點的過程當中,標記完當前的根,那麼這個根下面的內容就不會被暫停恢復運行了,而移動過程當中,經過讀屏障來看這個內存是否是發生移動,若是在移動稍微停一下,移動過去後再使用,hotspot還沒這麼厲害,暫停時間仍是挺長的,只是相對其餘的GC策略在面對大內存來說是不錯的選擇。

 

下面看一些CMS的策略(併發GC總時間會比常規的並行GC長,由於它是在運行時去作GC,不少資源徵用都會影響其GC的效率,而整體的暫停時間會短暫不少不少,其並行線程數默認爲:(上面設置的並行線程數 + 3)/ 4

 

付:CMS是目前Hotspot管理大內存最好的JVM,若是是常規的JVM,最佳選擇爲ParallelOldGC,若是必需要以響應時間爲準,則選擇CMS,不過CMS有兩個隱藏的隱患:

一、CMS GC雖然是併發且並行運行的GC,可是初始化的時候若是採用默認值92%JVM 1.5的白皮書上描述爲68%實際上是錯誤的,1.6是正確的),就很容易出現問題,由於CMS GC僅僅針對Old區域,Yong區域使用ParNew算法,也就是Old的CMS回收和Yong的回收能夠同時進行,也就是回收過程當中Yong有可能會晉升對象Old,而且業務也能夠同時運行,因此92%基本開始啓動CMS GC頗有可能old的內存就不夠用了,當內存不夠用的時候,就啓動Full GC,而且這個Full GC是串行的,因此若是弄的很差,CMS會比並行GC更加慢,爲何要啓用串行是由於CMS GC、並行GC、串行GC的繼承關係決定的,簡單說就是它沒辦法去調用並行GC的代碼,細節說後續有文章來細節說明),建議這個值設置爲70%左右吧,不過具體時間仍是本身決定。

二、CMS GC另外一個大的隱患,其實不看也差很少應該清楚,看名字就知道,就是不會作Compact操做,它最噁心的地方也在這裏,因此上面才說通常的應用都不使用它,它只有內存垃圾很是多,多得沒法分配晉升的空間的時候纔會出現一次compact,可是這個是Full GC,也就是上面的串行,很恐怖的,因此內存不是很大的,不要考慮使用它,並且它的算法十分複雜。

 

還有一些小的隱患是:和應用一塊兒徵用CPU(不過這個不是大問題,增長CPU便可)、整個運行過程當中時間比並行GC長(這個也不是大問題,由於咱們更加關心暫停時間而不是運行時間,由於暫停會影響很是多的業務)。

啓動CMS爲全局GC方法(注意這個參數也不能上面的並行GC進行混淆,Yong默認是並行的,上面已經說過

-XX:+UseConcMarkSweepGC

在併發GC下啓動增量模式,只能在CMS GC下這個參數纔有效。

-XX:+CMSIncrementalMode

啓動自動調節duty cycle,即在CMS GC中發生的時間比率設置,也就是說這段時間內最大容許發生多長時間的GC工做是能夠調整的。

-XX:+CMSIncrementalPacing

在上面這個參數設定後能夠分別設置如下兩個參數(參數設置的比率,範圍爲0-100):

-XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10

增量GC上還有一個保護因子(CMSIncrementalSafetyFactor),不太經常使用;CMSIncrementalOffset提供增量GC連續時間比率的設置;CMSExpAvgFactor爲增量併發的GC增長權重計算。

-XX:CMSIncrementalSafetyFactor=
-XX:CMSIncrementalOffset= 
-XX:CMSExpAvgFactor=

 

是否啓動並行CMS GC(默認也是開啓的)

-XX:+CMSParallelRemarkEnabled

要單獨對CMS GC設置並行線程數就設置(默認也不須要設置):

-XX:ParallelCMSThreads

 

對PernGen進行垃圾回收:

JDK 1.5在CMS GC基礎上須要設置參數(也就是前提是CMS GC纔有):

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

1.6之後的版本無需設置:-XX:+CMSPermGenSweepingEnabled,注意,其實一直以來Full GC都會觸發對Perm的回收過程,CMS GC須要有一些特殊照顧,雖然VM會對這塊區域回收,可是Perm回收的條件幾乎不太可能實現,首先須要這個類的classloader必須死掉,才能夠將該classloader下全部的class幹掉,也就是要麼所有死掉,要麼所有活着;另外,這個classloader下的class沒有任何object在使用,這個也太苛刻了吧,由於常規的對象申請都是經過系統默認的,應用服務器也有本身默認的classloader,要讓它死掉可能性不大,若是這都死掉了,系統也應該快掛了。

 

CMS GC由於是在程序運行時進行GC,不會暫停,因此不能等到不夠用的時候纔去開啓GC,官方說法是他們的默認值是68%,可是惋惜的是文檔寫錯了,通過不少測試和源碼驗證這個參數應該是在92%的時候被啓動,雖然還有8%的空間,可是仍是很可憐了,當CMS發現內存實在不夠的時候又回到常規的並行GC,因此不少人在沒有設置這個參數的時候發現CMS GC並無神馬優點嘛,和並行GC一個鳥樣子甚至於更加慢,因此這個時候須要設置參數(這個參數在上面已經說過,啓動CMS必定要設置這個參數):

-XX:CMSInitiatingOccupancyFraction=70

這樣保證Old的內存在使用到70%的時候,就開始啓動CMS了;若是你真的想看看默認值,那麼就使用參數:-XX:+PrintCMSInitiationStatistics 這個變量只有JDK 1.6能夠使用 1.5不能夠,查看實際值-XX:+PrintCMSStatistics;另外,還能夠設置參數-XX:CMSInitiatingPermOccupancyFraction來設置Perm空間達到多少時啓動CMS GC,不過意義不大。

JDK 1.6之後有些時候啓動CMS GC是根據計算代價進行啓動,也就是不必定按照你指定的參數來設置的,若是你不想讓它按照所謂的成原本計算GC的話,那麼你就使用一個參數:-XX:+UseCMSInitiatingOccupancyOnly,默認是false,它就只會按照你設置的比率來啓動CMS GC了。若是你的程序中有System.gc以及設置了ExplicitGCInvokesConcurrent在jdk 1.6中,這種狀況使用NIO是有可能產生問題的。

 

啓動CMS GC的compation操做,也就是發生多少次後作一次全局的compaction:

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction:發生多少次CMS Full GC,這個參數最好不要設置,由於要作compaction的話,也就是真正的Full GC是串行的,很是慢,讓它本身去決定何時須要作compaction。

 

-XX:CMSMaxAbortablePrecleanTime=5000 設置preclean步驟的超時時間,單位爲毫秒,preclean爲cms gc其中一個步驟,關於cms gc步驟比較多,本文就不細節探討了。

 

並行GC在mark階段,可能會同時發生minor GC,old區域也可能發生改變,因而併發GC會對發生了改變的內容進行remark操做,這個觸發的條件是:

-XX:CMSScheduleRemarkEdenSizeThreshold

-XX:CMSScheduleRemarkEdenPenetration

即Eden區域多大的時候開始觸發,和eden使用量超過百分比多少的時候觸發,前者默認是2M,後者默認是50%。 

可是若是長期不作remark致使old作不了,能夠設置超時,這個超時默認是5秒,能夠經過參數:

-XX:CMSMaxAbortablePrecleanTime

-XX:+ExplicitGCInvokesConcurrent 在顯示發生GC的時候,容許進行並行GC。

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 幾乎和上面同樣,只不過多一個對Perm區域的回收而已。

 

補充:

其實JVM還有不少的版本,不少的廠商,與其優化的原則,隨便舉兩個例子hotspot在GC中作的一些優化(這裏不說代碼的編譯時優化或運行時優化):

Eden申請的空間對象由Old區域的某個對象的一個屬性指向(也就是Old區域的這個空間不回收,Eden這塊就沒有必要考慮回收),因此Hotspot在CPU寫上面,作了一個屏障,當發生賦值語句的時候(對內存來說賦值就是一種寫操做),若是發現是一個新的對象由Old指向Eden,那麼就會將這個對象記錄在一個卡片機裏面,這個卡片機是有不少512字節的卡片組成,當在YGC過程當中,就基本不會去移動或者管理這塊對象(付:這種卡片機會在CMS GC的算法中使用,不過和這個卡片不是放在同一個地方的,也是CMS GC的關鍵,對於CMS GC的算法細節描述,後續文章咱們單獨說明)。

Old區域對於一些比較大的對象,JVM就不會去管理個對象,也就是compact過程當中不會去移動這塊對象的區域等等吧。

 

以上大部分參數爲hotspot的自帶關於性能的參數,參考版本爲JDK 1.5和1.6的版本,不少爲我的經驗說明,不足以說明全部問題,若是有問題,歡迎探討;另外,JDK的參數是否是就只有這些呢,確定並非,我知道的也不止這些,可是有些以爲不必說出來的參數和一些數學運算的參數我就不想給出來了,好比像禁用掉GC的參數有神馬意義,咱們的服務器要是把這個禁用掉幹個屁啊,呵呵,作測試還能夠用這玩玩,讓它不作GC直接溢出;還有一些什麼計算因子啥的,還有不少複雜的數學運算規則,要是把這個配置明白了,就太那個了,並且通常狀況下也沒那個必要,JDK到如今的配置參數多達上500個以上,要知道完的話慢慢看吧,不過意義不大,並且要知道默認值最靠譜的是看源碼而不是看文檔,官方文檔也只能保證絕大部是正確的,不能保證全部的是正確的。

 

 

本文最後追加在jdk 1.6u 24後經過上面說明的-XX:+PrintFlagsFinal輸出的參數以及默認值(仍是那句話,在不一樣的平臺上是不同的),輸出的參數以下,能夠看看JVM的參數是至關的多,參數如此之多,你只須要掌握關鍵便可,參數還有不少有衝突的,不要糾結於每個參數的細節:

$java -XX:+PrintFlagsFinal

uintx AdaptivePermSizeWeight               = 20               {product}
uintx AdaptiveSizeDecrementScaleFactor     = 4                {product}
uintx AdaptiveSizeMajorGCDecayTimeScale    = 10               {product}
uintx AdaptiveSizePausePolicy              = 0                {product}
uintx AdaptiveSizePolicyCollectionCostMargin  = 50               {product}
uintx AdaptiveSizePolicyInitializingSteps  = 20               {product}
uintx AdaptiveSizePolicyOutputInterval     = 0                {product}
uintx AdaptiveSizePolicyWeight             = 10               {product}
uintx AdaptiveSizeThroughPutPolicy         = 0                {product}
uintx AdaptiveTimeWeight                   = 25               {product}
 bool AdjustConcurrency                    = false            {product}
 bool AggressiveOpts                       = false            {product}
 intx AliasLevel                           = 3                {product}
 intx AllocatePrefetchDistance             = -1               {product}
 intx AllocatePrefetchInstr                = 0                {product}
 intx AllocatePrefetchLines                = 1                {product}
 intx AllocatePrefetchStepSize             = 16               {product}
 intx AllocatePrefetchStyle                = 1                {product}
 bool AllowJNIEnvProxy                     = false            {product}
 bool AllowParallelDefineClass             = false            {product}
 bool AllowUserSignalHandlers              = false            {product}
 bool AlwaysActAsServerClassMachine        = false            {product}
 bool AlwaysCompileLoopMethods             = false            {product}
 intx AlwaysInflate                        = 0                {product}
 bool AlwaysLockClassLoader                = false            {product}
 bool AlwaysPreTouch                       = false            {product}
 bool AlwaysRestoreFPU                     = false            {product}
 bool AlwaysTenure                         = false            {product}
 bool AnonymousClasses                     = false            {product}
 bool AssertOnSuspendWaitFailure           = false            {product}
 intx Atomics                              = 0                {product}
uintx AutoGCSelectPauseMillis              = 5000             {product}
 intx BCEATraceLevel                       = 0                {product}
 intx BackEdgeThreshold                    = 100000           {pd product}
 bool BackgroundCompilation                = true             {pd product}
uintx BaseFootPrintEstimate                = 268435456        {product}
 intx BiasedLockingBulkRebiasThreshold     = 20               {product}
 intx BiasedLockingBulkRevokeThreshold     = 40               {product}
 intx BiasedLockingDecayTime               = 25000            {product}
 intx BiasedLockingStartupDelay            = 4000             {product}
 bool BindGCTaskThreadsToCPUs              = false            {product}
 bool BlockOffsetArrayUseUnallocatedBlock  = false            {product}
 bool BytecodeVerificationLocal            = false            {product}
 bool BytecodeVerificationRemote           = true             {product}
 intx CICompilerCount                      = 1                {product}
 bool CICompilerCountPerCPU                = false            {product}
 bool CITime                               = false            {product}
 bool CMSAbortSemantics                    = false            {product}
uintx CMSAbortablePrecleanMinWorkPerIteration  = 100              {product}
 intx CMSAbortablePrecleanWaitMillis       = 100              {product}
uintx CMSBitMapYieldQuantum                = 10485760         {product}
uintx CMSBootstrapOccupancy                = 50               {product}
 bool CMSClassUnloadingEnabled             = false            {product}
uintx CMSClassUnloadingMaxInterval         = 0                {product}
 bool CMSCleanOnEnter                      = true             {product}
 bool CMSCompactWhenClearAllSoftRefs       = true             {product}
uintx CMSConcMarkMultiple                  = 32               {product}
 bool CMSConcurrentMTEnabled               = true             {product}
uintx CMSCoordinatorYieldSleepCount        = 10               {product}
 bool CMSDumpAtPromotionFailure            = false            {product}
uintx CMSExpAvgFactor                      = 50               {product}
 bool CMSExtrapolateSweep                  = false            {product}
uintx CMSFullGCsBeforeCompaction           = 0                {product}
uintx CMSIncrementalDutyCycle              = 10               {product}
uintx CMSIncrementalDutyCycleMin           = 0                {product}
 bool CMSIncrementalMode                   = false            {product}
uintx CMSIncrementalOffset                 = 0                {product}
 bool CMSIncrementalPacing                 = true             {product}
uintx CMSIncrementalSafetyFactor           = 10               {product}
uintx CMSIndexedFreeListReplenish          = 4                {product}
 intx CMSInitiatingOccupancyFraction       = -1               {product}
 intx CMSInitiatingPermOccupancyFraction   = -1               {product}
 intx CMSIsTooFullPercentage               = 98               {product}
double CMSLargeCoalSurplusPercent           =  {product}
double CMSLargeSplitSurplusPercent          =  {product}
 bool CMSLoopWarn                          = false            {product}
uintx CMSMaxAbortablePrecleanLoops         = 0                {product}
 intx CMSMaxAbortablePrecleanTime          = 5000             {product}
uintx CMSOldPLABMax                        = 1024             {product}
uintx CMSOldPLABMin                        = 16               {product}
uintx CMSOldPLABNumRefills                 = 4                {product}
uintx CMSOldPLABReactivityCeiling          = 10               {product}
uintx CMSOldPLABReactivityFactor           = 2                {product}
 bool CMSOldPLABResizeQuicker              = false            {product}
uintx CMSOldPLABToleranceFactor            = 4                {product}
 bool CMSPLABRecordAlways                  = true             {product}
uintx CMSParPromoteBlocksToClaim           = 16               {product}
 bool CMSParallelRemarkEnabled             = true             {product}
 bool CMSParallelSurvivorRemarkEnabled     = true             {product}
 bool CMSPermGenPrecleaningEnabled         = true             {product}
uintx CMSPrecleanDenominator               = 3                {product}
uintx CMSPrecleanIter                      = 3                {product}
uintx CMSPrecleanNumerator                 = 2                {product}
 bool CMSPrecleanRefLists1                 = true             {product}
 bool CMSPrecleanRefLists2                 = false            {product}
 bool CMSPrecleanSurvivors1                = false            {product}
 bool CMSPrecleanSurvivors2                = true             {product}
uintx CMSPrecleanThreshold                 = 1000             {product}
 bool CMSPrecleaningEnabled                = true             {product}
 bool CMSPrintChunksInDump                 = false            {product}
 bool CMSPrintObjectsInDump                = false            {product}
uintx CMSRemarkVerifyVariant               = 1                {product}
 bool CMSReplenishIntermediate             = true             {product}
uintx CMSRescanMultiple                    = 32               {product}
uintx CMSRevisitStackSize                  = 1048576          {product}
uintx CMSSamplingGrain                     = 16384            {product}
 bool CMSScavengeBeforeRemark              = false            {product}
uintx CMSScheduleRemarkEdenPenetration     = 50               {product}
uintx CMSScheduleRemarkEdenSizeThreshold   = 2097152          {product}
uintx CMSScheduleRemarkSamplingRatio       = 5                {product}
double CMSSmallCoalSurplusPercent           =  {product}
double CMSSmallSplitSurplusPercent          =  {product}
 bool CMSSplitIndexedFreeListBlocks        = true             {product}
 intx CMSTriggerPermRatio                  = 80               {product}
 intx CMSTriggerRatio                      = 80               {product}
 bool CMSUseOldDefaults                    = false            {product}
 intx CMSWaitDuration                      = 2000             {product}
uintx CMSWorkQueueDrainThreshold           = 10               {product}
 bool CMSYield                             = true             {product}
uintx CMSYieldSleepCount                   = 0                {product}
 intx CMSYoungGenPerWorker                 = 16777216         {product}
uintx CMS_FLSPadding                       = 1                {product}
uintx CMS_FLSWeight                        = 75               {product}
uintx CMS_SweepPadding                     = 1                {product}
uintx CMS_SweepTimerThresholdMillis        = 10               {product}
uintx CMS_SweepWeight                      = 75               {product}
 bool CheckJNICalls                        = false            {product}
 bool ClassUnloading                       = true             {product}
 intx ClearFPUAtPark                       = 0                {product}
 bool ClipInlining                         = true             {product}
uintx CodeCacheExpansionSize               = 32768            {pd product}
uintx CodeCacheFlushingMinimumFreeSpace    = 1536000          {product}
uintx CodeCacheMinimumFreeSpace            = 512000           {product}
 bool CollectGen0First                     = false            {product}
 bool CompactFields                        = true             {product}
 intx CompilationPolicyChoice              = 0                {product}
 intx CompilationRepeat                    = 0                {C1 product}
ccstrlist CompileCommand                       =                  {product}
ccstr CompileCommandFile                   =  {product}
ccstrlist CompileOnly                          =                  {product}
 intx CompileThreshold                     = 1500             {pd product}
 bool CompilerThreadHintNoPreempt          = true             {product}
 intx CompilerThreadPriority               = -1               {product}
 intx CompilerThreadStackSize              = 0                {pd product}
uintx ConcGCThreads                        = 0                {product}
 bool ConvertSleepToYield                  = true             {pd product}
 bool ConvertYieldToSleep                  = false            {product}
 bool DTraceAllocProbes                    = false            {product}
 bool DTraceMethodProbes                   = false            {product}
 bool DTraceMonitorProbes                  = false            {product}
uintx DefaultMaxRAMFraction                = 4                {product}
 intx DefaultThreadPriority                = -1               {product}
 intx DeferPollingPageLoopCount            = -1               {product}
 intx DeferThrSuspendLoopCount             = 4000             {product}
 bool DeoptimizeRandom                     = false            {product}
 bool DisableAttachMechanism               = false            {product}
 bool DisableExplicitGC                    = false            {product}
 bool DisplayVMOutputToStderr              = false            {product}
 bool DisplayVMOutputToStdout              = false            {product}
 bool DontCompileHugeMethods               = true             {product}
 bool DontYieldALot                        = false            {pd product}
 bool DumpSharedSpaces                     = false            {product}
 bool EagerXrunInit                        = false            {product}
 intx EmitSync                             = 0                {product}
uintx ErgoHeapSizeLimit                    = 0                {product}
ccstr ErrorFile                            =  {product}
 bool EstimateArgEscape                    = true             {product}
 intx EventLogLength                       = 2000             {product}
 bool ExplicitGCInvokesConcurrent          = false            {product}
 bool ExplicitGCInvokesConcurrentAndUnloadsClasses  = false            {produ
 bool ExtendedDTraceProbes                 = false            {product}
 bool FLSAlwaysCoalesceLarge               = false            {product}
uintx FLSCoalescePolicy                    = 2                {product}
double FLSLargestBlockCoalesceProximity     =  {product}
 bool FailOverToOldVerifier                = true             {product}
 bool FastTLABRefill                       = true             {product}
 intx FenceInstruction                     = 0                {product}
 intx FieldsAllocationStyle                = 1                {product}
 bool FilterSpuriousWakeups                = true             {product}
 bool ForceFullGCJVMTIEpilogues            = false            {product}
 bool ForceNUMA                            = false            {product}
 bool ForceSharedSpaces                    = false            {product}
 bool ForceTimeHighResolution              = false            {product}
 intx FreqInlineSize                       = 325              {pd product}
 intx G1ConcRefinementGreenZone            = 0                {product}
 intx G1ConcRefinementRedZone              = 0                {product}
 intx G1ConcRefinementServiceIntervalMillis  = 300              {product}
uintx G1ConcRefinementThreads              = 0                {product}
 intx G1ConcRefinementThresholdStep        = 0                {product}
 intx G1ConcRefinementYellowZone           = 0                {product}
 intx G1ConfidencePercent                  = 50               {product}
uintx G1HeapRegionSize                     = 0                {product}
 intx G1MarkRegionStackSize                = 1048576          {product}
 intx G1RSetRegionEntries                  = 0                {product}
uintx G1RSetScanBlockSize                  = 64               {product}
 intx G1RSetSparseRegionEntries            = 0                {product}
 intx G1RSetUpdatingPauseTimePercent       = 10               {product}
 intx G1ReservePercent                     = 10               {product}
 intx G1SATBBufferSize                     = 1024             {product}
 intx G1UpdateBufferSize                   = 256              {product}
 bool G1UseAdaptiveConcRefinement          = true             {product}
 bool G1UseFixedWindowMMUTracker           = false            {product}
uintx GCDrainStackTargetSize               = 64               {product}
uintx GCHeapFreeLimit                      = 2                {product}
 bool GCLockerInvokesConcurrent            = false            {product}
 bool GCOverheadReporting                  = false            {product}
 intx GCOverheadReportingPeriodMS          = 100              {product}
 intx GCPauseIntervalMillis                = 500              {product}
uintx GCTaskTimeStampEntries               = 200              {product}
uintx GCTimeLimit                          = 98               {product}
uintx GCTimeRatio                          = 99               {product}
ccstr HPILibPath                           =  {product}
 bool HandlePromotionFailure               = true             {product}
uintx HeapBaseMinAddress                   = 2147483648       {pd product}
 bool HeapDumpAfterFullGC                  = false            {manageable}
 bool HeapDumpBeforeFullGC                 = false            {manageable}
 bool HeapDumpOnOutOfMemoryError           = false            {manageable}
ccstr HeapDumpPath                         =  {manageable}
uintx HeapFirstMaximumCompactionCount      = 3                {product}
uintx HeapMaximumCompactionInterval        = 20               {product}
 bool IgnoreUnrecognizedVMOptions          = false            {product}
uintx InitialCodeCacheSize                 = 163840           {pd product}
uintx InitialHeapSize                     := 16777216         {product}
uintx InitialRAMFraction                   = 64               {product}
uintx InitialSurvivorRatio                 = 8                {product}
 intx InitialTenuringThreshold             = 7                {product}
uintx InitiatingHeapOccupancyPercent       = 45               {product}
 bool Inline                               = true             {product}
 intx InlineSmallCode                      = 1000             {pd product}
 intx InterpreterProfilePercentage         = 33               {product}
 bool JNIDetachReleasesMonitors            = true             {product}
 bool JavaMonitorsInStackTrace             = true             {product}
 intx JavaPriority10_To_OSPriority         = -1               {product}
 intx JavaPriority1_To_OSPriority          = -1               {product}
 intx JavaPriority2_To_OSPriority          = -1               {product}
 intx JavaPriority3_To_OSPriority          = -1               {product}
 intx JavaPriority4_To_OSPriority          = -1               {product}
 intx JavaPriority5_To_OSPriority          = -1               {product}
 intx JavaPriority6_To_OSPriority          = -1               {product}
 intx JavaPriority7_To_OSPriority          = -1               {product}
 intx JavaPriority8_To_OSPriority          = -1               {product}
 intx JavaPriority9_To_OSPriority          = -1               {product}
 bool LIRFillDelaySlots                    = false            {C1 pd product}
uintx LargePageHeapSizeThreshold           = 134217728        {product}
uintx LargePageSizeInBytes                 = 0                {product}
 bool LazyBootClassLoader                  = true             {product}
 bool ManagementServer                     = false            {product}
uintx MarkStackSize                        = 32768            {product}
uintx MarkStackSizeMax                     = 4194304          {product}
 intx MarkSweepAlwaysCompactCount          = 4                {product}
uintx MarkSweepDeadRatio                   = 5                {product}
 intx MaxBCEAEstimateLevel                 = 5                {product}
 intx MaxBCEAEstimateSize                  = 150              {product}
 intx MaxDirectMemorySize                  = -1               {product}
 bool MaxFDLimit                           = true             {product}
uintx MaxGCMinorPauseMillis                = 4294967295       {product}
uintx MaxGCPauseMillis                     = 4294967295       {product}
uintx MaxHeapFreeRatio                     = 70               {product}
uintx MaxHeapSize                         := 268435456        {product}
 intx MaxInlineLevel                       = 9                {product}
 intx MaxInlineSize                        = 35               {product}
 intx MaxJavaStackTraceDepth               = 1024             {product}
uintx MaxLiveObjectEvacuationRatio         = 100              {product}
uintx MaxNewSize                           = 4294967295       {product}
uintx MaxPermHeapExpansion                 = 4194304          {product}
uintx MaxPermSize                          = 67108864         {pd product}
uint64_t MaxRAM                               = 1073741824       {pd product}
uintx MaxRAMFraction                       = 4                {product}
 intx MaxRecursiveInlineLevel              = 1                {product}
 intx MaxTenuringThreshold                 = 15               {product}
 intx MaxTrivialSize                       = 6                {product}
 bool MethodFlushing                       = true             {product}
 intx MinCodeCacheFlushingInterval         = 30               {product}
uintx MinHeapDeltaBytes                    = 131072           {product}
uintx MinHeapFreeRatio                     = 40               {product}
 intx MinInliningThreshold                 = 250              {product}
uintx MinPermHeapExpansion                 = 262144           {product}
uintx MinRAMFraction                       = 2                {product}
uintx MinSurvivorRatio                     = 3                {product}
uintx MinTLABSize                          = 2048             {product}
 intx MonitorBound                         = 0                {product}
 bool MonitorInUseLists                    = false            {product}
 bool MustCallLoadClassInternal            = false            {product}
 intx NUMAChunkResizeWeight                = 20               {product}
 intx NUMAPageScanRate                     = 256              {product}
 intx NUMASpaceResizeRate                  = 1073741824       {product}
 bool NUMAStats                            = false            {product}
 intx NativeMonitorFlags                   = 0                {product}
 intx NativeMonitorSpinLimit               = 20               {product}
 intx NativeMonitorTimeout                 = -1               {product}
 bool NeedsDeoptSuspend                    = false            {pd product}
 bool NeverActAsServerClassMachine         = true             {pd product}
 bool NeverTenure                          = false            {product}
 intx NewRatio                             = 2                {product}
uintx NewSize                              = 1048576          {product}
uintx NewSizeThreadIncrease                = 4096             {pd product}
 intx NmethodSweepFraction                 = 4                {product}
uintx OldPLABSize                          = 1024             {product}
uintx OldPLABWeight                        = 50               {product}
uintx OldSize                              = 4194304          {product}
 bool OmitStackTraceInFastThrow            = true             {product}
ccstrlist OnError                              =                  {product}
ccstrlist OnOutOfMemoryError                   =                  {product}
 intx OnStackReplacePercentage             = 933              {pd product}
uintx PLABWeight                           = 75               {product}
 bool PSChunkLargeArrays                   = true             {product}
 intx ParGCArrayScanChunk                  = 50               {product}
uintx ParGCDesiredObjsFromOverflowList     = 20               {product}
 bool ParGCTrimOverflow                    = true             {product}
 bool ParGCUseLocalOverflow                = false            {product}
 intx ParallelGCBufferWastePct             = 10               {product}
 bool ParallelGCRetainPLAB                 = true             {product}
uintx ParallelGCThreads                    = 0                {product}
 bool ParallelGCVerbose                    = false            {product}
uintx ParallelOldDeadWoodLimiterMean       = 50               {product}
uintx ParallelOldDeadWoodLimiterStdDev     = 80               {product}
 bool ParallelRefProcBalancingEnabled      = true             {product}
 bool ParallelRefProcEnabled               = false            {product}
uintx PausePadding                         = 1                {product}
 intx PerBytecodeRecompilationCutoff       = 200              {product}
 intx PerBytecodeTrapLimit                 = 4                {product}
 intx PerMethodRecompilationCutoff         = 400              {product}
 intx PerMethodTrapLimit                   = 100              {product}
 bool PerfAllowAtExitRegistration          = false            {product}
 bool PerfBypassFileSystemCheck            = false            {product}
 intx PerfDataMemorySize                   = 32768            {product}
 intx PerfDataSamplingInterval             = 50               {product}
ccstr PerfDataSaveFile                     =  {product}
 bool PerfDataSaveToFile                   = false            {product}
 bool PerfDisableSharedMem                 = false            {product}
 intx PerfMaxStringConstLength             = 1024             {product}
uintx PermGenPadding                       = 3                {product}
uintx PermMarkSweepDeadRatio               = 20               {product}
uintx PermSize                             = 12582912         {pd product}
 bool PostSpinYield                        = true             {product}
 intx PreBlockSpin                         = 10               {product}
 intx PreInflateSpin                       = 10               {pd product}
 bool PreSpinYield                         = false            {product}
 bool PreferInterpreterNativeStubs         = false            {pd product}
 intx PrefetchCopyIntervalInBytes          = -1               {product}
 intx PrefetchFieldsAhead                  = -1               {product}
 intx PrefetchScanIntervalInBytes          = -1               {product}
 bool PreserveAllAnnotations               = false            {product}
uintx PreserveMarkStackSize                = 1024             {product}
uintx PretenureSizeThreshold               = 0                {product}
 bool PrintAdaptiveSizePolicy              = false            {product}
 bool PrintCMSInitiationStatistics         = false            {product}
 intx PrintCMSStatistics                   = 0                {product}
 bool PrintClassHistogram                  = false            {manageable}
 bool PrintClassHistogramAfterFullGC       = false            {manageable}
 bool PrintClassHistogramBeforeFullGC      = false            {manageable}
 bool PrintCommandLineFlags                = false            {product}
 bool PrintCompilation                     = false            {product}
 bool PrintConcurrentLocks                 = false            {manageable}
 intx PrintFLSCensus                       = 0                {product}
 intx PrintFLSStatistics                   = 0                {product}
 bool PrintFlagsFinal                     := true             {product}
 bool PrintFlagsInitial                    = false            {product}
 bool PrintGC                              = false            {manageable}
 bool PrintGCApplicationConcurrentTime     = false            {product}
 bool PrintGCApplicationStoppedTime        = false            {product}
 bool PrintGCDateStamps                    = false            {manageable}
 bool PrintGCDetails                       = false            {manageable}
 bool PrintGCTaskTimeStamps                = false            {product}
 bool PrintGCTimeStamps                    = false            {manageable}
 bool PrintHeapAtGC                        = false            {product rw}
 bool PrintHeapAtGCExtended                = false            {product rw}
 bool PrintHeapAtSIGBREAK                  = true             {product}
 bool PrintJNIGCStalls                     = false            {product}
 bool PrintJNIResolving                    = false            {product}
 bool PrintOldPLAB                         = false            {product}
 bool PrintPLAB                            = false            {product}
 bool PrintParallelOldGCPhaseTimes         = false            {product}
 bool PrintPromotionFailure                = false            {product}
 bool PrintReferenceGC                     = false            {product}
 bool PrintRevisitStats                    = false            {product}
 bool PrintSafepointStatistics             = false            {product}
 intx PrintSafepointStatisticsCount        = 300              {product}
 intx PrintSafepointStatisticsTimeout      = -1               {product}
 bool PrintSharedSpaces                    = false            {product}
 bool PrintTLAB                            = false            {product}
 bool PrintTenuringDistribution            = false            {product}
 bool PrintVMOptions                       = false            {product}
 bool PrintVMQWaitTime                     = false            {product}
uintx ProcessDistributionStride            = 4                {product}
 bool ProfileInterpreter                   = false            {pd product}
 bool ProfileIntervals                     = false            {product}
 intx ProfileIntervalsTicks                = 100              {product}
 intx ProfileMaturityPercentage            = 20               {product}
 bool ProfileVM                            = false            {product}
 bool ProfilerPrintByteCodeStatistics      = false            {product}
 bool ProfilerRecordPC                     = false            {product}
uintx PromotedPadding                      = 3                {product}
 intx QueuedAllocationWarningCount         = 0                {product}
 bool RangeCheckElimination                = true             {product}
 intx ReadPrefetchInstr                    = 0                {product}
 intx ReadSpinIterations                   = 100              {product}
 bool ReduceSignalUsage                    = false            {product}
 intx RefDiscoveryPolicy                   = 0                {product}
 bool ReflectionWrapResolutionErrors       = true             {product}
 bool RegisterFinalizersAtInit             = true             {product}
 bool RelaxAccessControlCheck              = false            {product}
 bool RequireSharedSpaces                  = false            {product}
uintx ReservedCodeCacheSize                = 33554432         {pd product}
 bool ResizeOldPLAB                        = true             {product}
 bool ResizePLAB                           = true             {product}
 bool ResizeTLAB                           = true             {pd product}
 bool RestoreMXCSROnJNICalls               = false            {product}
 bool RewriteBytecodes                     = false            {pd product}
 bool RewriteFrequentPairs                 = false            {pd product}
 intx SafepointPollOffset                  = 256              {C1 pd product}
 intx SafepointSpinBeforeYield             = 2000             {product}
 bool SafepointTimeout                     = false            {product}
 intx SafepointTimeoutDelay                = 10000            {product}
 bool ScavengeBeforeFullGC                 = true             {product}
 intx SelfDestructTimer                    = 0                {product}
uintx SharedDummyBlockSize                 = 536870912        {product}
uintx SharedMiscCodeSize                   = 4194304          {product}
uintx SharedMiscDataSize                   = 4194304          {product}
uintx SharedReadOnlySize                   = 10485760         {product}
uintx SharedReadWriteSize                  = 12582912         {product}
 bool ShowMessageBoxOnError                = false            {product}
 intx SoftRefLRUPolicyMSPerMB              = 1000             {product}
 bool SplitIfBlocks                        = true             {product}
 intx StackRedPages                        = 1                {pd product}
 intx StackShadowPages                     = 3                {pd product}
 bool StackTraceInThrowable                = true             {product}
 intx StackYellowPages                     = 2                {pd product}
 bool StartAttachListener                  = false            {product}
 intx StarvationMonitorInterval            = 200              {product}
 bool StressLdcRewrite                     = false            {product}
 bool StressTieredRuntime                  = false            {product}
 bool SuppressFatalErrorMessage            = false            {product}
uintx SurvivorPadding                      = 3                {product}
 intx SurvivorRatio                        = 8                {product}
 intx SuspendRetryCount                    = 50               {product}
 intx SuspendRetryDelay                    = 5                {product}
 intx SyncFlags                            = 0                {product}
ccstr SyncKnobs                            =  {product}
 intx SyncVerbose                          = 0                {product}
uintx TLABAllocationWeight                 = 35               {product}
uintx TLABRefillWasteFraction              = 64               {product}
uintx TLABSize                             = 0                {product}
 bool TLABStats                            = true             {product}
uintx TLABWasteIncrement                   = 4                {product}
uintx TLABWasteTargetPercent               = 1                {product}
 bool TaggedStackInterpreter               = false            {product}
 intx TargetPLABWastePct                   = 10               {product}
 intx TargetSurvivorRatio                  = 50               {product}
uintx TenuredGenerationSizeIncrement       = 20               {product}
uintx TenuredGenerationSizeSupplement      = 80               {product}
uintx TenuredGenerationSizeSupplementDecay  = 2                {product}
 intx ThreadPriorityPolicy                 = 0                {product}
 bool ThreadPriorityVerbose                = false            {product}
uintx ThreadSafetyMargin                   = 52428800         {product}
 intx ThreadStackSize                      = 0                {pd product}
uintx ThresholdTolerance                   = 10               {product}
 intx Tier1BytecodeLimit                   = 10               {product}
 bool Tier1OptimizeVirtualCallProfiling    = true             {C1 product}
 bool Tier1ProfileBranches                 = true             {C1 product}
 bool Tier1ProfileCalls                    = true             {C1 product}
 bool Tier1ProfileCheckcasts               = true             {C1 product}
 bool Tier1ProfileInlinedCalls             = true             {C1 product}
 bool Tier1ProfileVirtualCalls             = true             {C1 product}
 bool Tier1UpdateMethodData                = false            {product}
 intx Tier2BackEdgeThreshold               = 100000           {pd product}
 intx Tier2CompileThreshold                = 1500             {pd product}
 intx Tier3BackEdgeThreshold               = 100000           {pd product}
 intx Tier3CompileThreshold                = 2500             {pd product}
 intx Tier4BackEdgeThreshold               = 100000           {pd product}
 intx Tier4CompileThreshold                = 4500             {pd product}
 bool TieredCompilation                    = false            {pd product}
 bool TimeLinearScan                       = false            {C1 product}
 bool TraceBiasedLocking                   = false            {product}
 bool TraceClassLoading                    = false            {product rw}
 bool TraceClassLoadingPreorder            = false            {product}
 bool TraceClassResolution                 = false            {product}
 bool TraceClassUnloading                  = false            {product rw}
 bool TraceGen0Time                        = false            {product}
 bool TraceGen1Time                        = false            {product}
ccstr TraceJVMTI                           =  {product}
 bool TraceLoaderConstraints               = false            {product rw}
 bool TraceMonitorInflation                = false            {product}
 bool TraceParallelOldGCTasks              = false            {product}
 intx TraceRedefineClasses                 = 0                {product}
 bool TraceSafepointCleanupTime            = false            {product}
 bool TraceSuspendWaitFailures             = false            {product}
 intx TypeProfileMajorReceiverPercent      = 90               {product}
 intx TypeProfileWidth                     = 2                {product}
 intx UnguardOnExecutionViolation          = 0                {product}
 bool Use486InstrsOnly                     = false            {product}
 bool UseAdaptiveGCBoundary                = false            {product}
 bool UseAdaptiveGenerationSizePolicyAtMajorCollection  = true             {p
 bool UseAdaptiveGenerationSizePolicyAtMinorCollection  = true             {p
 bool UseAdaptiveNUMAChunkSizing           = true             {product}
 bool UseAdaptiveSizeDecayMajorGCCost      = true             {product}
 bool UseAdaptiveSizePolicy                = true             {product}
 bool UseAdaptiveSizePolicyFootprintGoal   = true             {product}
 bool UseAdaptiveSizePolicyWithSystemGC    = false            {product}
 bool UseAddressNop                        = false            {product}
 bool UseAltSigs                           = false            {product}
 bool UseAutoGCSelectPolicy                = false            {product}
 bool UseBiasedLocking                     = true             {product}
 bool UseBoundThreads                      = true             {product}
 bool UseCMSBestFit                        = true             {product}
 bool UseCMSCollectionPassing              = true             {product}
 bool UseCMSCompactAtFullCollection        = true             {product}
 bool UseCMSInitiatingOccupancyOnly        = false            {product}
 bool UseCodeCacheFlushing                 = false            {product}
 bool UseCompiler                          = true             {product}
 bool UseCompilerSafepoints                = true             {product}
 bool UseConcMarkSweepGC                   = false            {product}
 bool UseCountLeadingZerosInstruction      = false            {product}
 bool UseCounterDecay                      = true             {product}
 bool UseDepthFirstScavengeOrder           = true             {product}
 bool UseFastAccessorMethods               = true             {product}
 bool UseFastEmptyMethods                  = true             {product}
 bool UseFastJNIAccessors                  = true             {product}
 bool UseG1GC                              = false            {product}
 bool UseGCOverheadLimit                   = true             {product}
 bool UseGCTaskAffinity                    = false            {product}
 bool UseHeavyMonitors                     = false            {product}
 bool UseInlineCaches                      = true             {product}
 bool UseInterpreter                       = true             {product}
 bool UseLWPSynchronization                = true             {product}
 bool UseLargePages                        = false            {pd product}
 bool UseLargePagesIndividualAllocation   := false            {pd product}
 bool UseLoopCounter                       = true             {product}
 bool UseMaximumCompactionOnSystemGC       = true             {product}
 bool UseMembar                            = false            {product}
 bool UseNUMA                              = false            {product}
 bool UseNewFeature1                       = false            {C1 product}
 bool UseNewFeature2                       = false            {C1 product}
 bool UseNewFeature3                       = false            {C1 product}
 bool UseNewFeature4                       = false            {C1 product}
 bool UseNewLongLShift                     = false            {product}
 bool UseNiagaraInstrs                     = false            {product}
 bool UseOSErrorReporting                  = false            {pd product}
 bool UseOnStackReplacement                = true             {pd product}
 bool UsePSAdaptiveSurvivorSizePolicy      = true             {product}
 bool UseParNewGC                          = false            {product}
 bool UseParallelDensePrefixUpdate         = true             {product}
 bool UseParallelGC                        = false            {product}
 bool UseParallelOldGC                     = false            {product}
 bool UseParallelOldGCCompacting           = true             {product}
 bool UseParallelOldGCDensePrefix          = true             {product}
 bool UsePerfData                          = true             {product}
 bool UsePopCountInstruction               = false            {product}
 intx UseSSE                               = 99               {product}
 bool UseSSE42Intrinsics                   = false            {product}
 bool UseSerialGC                          = false            {product}
 bool UseSharedSpaces                      = true             {product}
 bool UseSignalChaining                    = true             {product}
 bool UseSpinning                          = false            {product}
 bool UseSplitVerifier                     = true             {product}
 bool UseStoreImmI16                       = true             {product}
 bool UseStringCache                       = false            {product}
 bool UseTLAB                              = true             {pd product}
 bool UseThreadPriorities                  = true             {pd product}
 bool UseTypeProfile                       = true             {product}
 bool UseUTCFileTimestamp                  = true             {product}
 bool UseUnalignedLoadStores               = false            {product}
 bool UseVMInterruptibleIO                 = true             {product}
 bool UseVectoredExceptions                = false            {pd product}
 bool UseXMMForArrayCopy                   = false            {product}
 bool UseXmmI2D                            = false            {product}
 bool UseXmmI2F                            = false            {product}
 bool UseXmmLoadAndClearUpper              = true             {product}
 bool UseXmmRegToRegMoveAll                = false            {product}
 bool VMThreadHintNoPreempt                = false            {product}
 intx VMThreadPriority                     = -1               {product}
 intx VMThreadStackSize                    = 0                {pd product}
 intx ValueMapInitialSize                  = 11               {C1 product}
 intx ValueMapMaxLoopSize                  = 8                {C1 product}
 bool VerifyMergedCPBytecodes              = true             {product}
 intx WorkAroundNPTLTimedWaitHang          = 1                {product}
uintx YoungGenerationSizeIncrement         = 20               {product}
uintx YoungGenerationSizeSupplement        = 80               {product}
uintx YoungGenerationSizeSupplementDecay   = 8                {product}
uintx YoungPLABSize                        = 4096             {product}
 bool ZeroTLAB                             = false            {product}
 intx hashCode                             = 0                {product}

 

系統爲何拆分?

系統作大了,併發量沒法扛得住,如何作?

業務作複雜了,單個應用中不能個性化,如何作?

模塊和邏輯對各種資源開銷很是特殊,如何作?

。。。。。。

拆分、拆分、再拆分。

由 全世界用一個系統表達全世界全部的企業和公司的業務開始,註定系統作大後必然拆分的走向,也就是一個大力士沒法完成成千上萬羣衆所能作到的一件大事,高集 成度的硬件和軟件解決方案,爲傳統企業提供較爲完善的解決方案,並在這種程度上是能夠節約成本,高端機和高端存儲的解決方案,當達到一個成本的交叉點後,隨着數據量以及併發量的不斷上升,其解決方案的成本也會隨之直線上漲。

 

如何拆分?拆分後有什麼後果,是其中一個問題?

 

首先咱們看看應用通常是如何拆分的:

一、應用通常的企業內部都是按照業務增加方式比較多,因此隨着業務的增長,將系統進行拆分的是比較多的,也就是將一個較大的系統拆分爲多個小的系統。

 

二、在一些企業中,不肯意將系統拆分爲小系統(緣由後面說明),而是將全部的內容部署在一塊兒,依賴於集羣分發到多個節點上去作負載均衡,這樣來完成一種切割,前序兩種也就是應用系統級別的縱橫向切割。

 

三、 獨立工具、模塊、服務的獨立化和集羣化,基於SOA服務的企業級應用,不少模塊通過抽象後,並不是子系統,而是一個獨立的服務系統,不參與業務,只參與一個技術級別的功能服務,如MQ、JMS、MemCached等,咱們常常也管這一類叫作中間件,也就是平臺沒有提供本身來作或第三方提供的中間件(固然中間 件也包含應用服務器)。

 

四、數據庫拆分,數據庫拆分是也是由於壓力上升,以及存儲容量的需求,最終在成本上認爲拆分是必然的走勢;數據庫拆分有多重規則存在。

 

五、因爲上述各種拆分致使的運維的困難,在數以萬計的計算機集羣下,如何動態資源分配和拆分以及拋開分佈式的內部細節來編程,如何自動化運維繫統就是大型計算機集羣下須要考慮的問題-雲存儲與雲計算

 

咱們拆分中面臨哪些問題?(這些內容在後面的文章中說明,本文再也不闡述)

一、負載均衡器的問題。

二、不一樣系統之間的通訊問題。

三、數據寫入和查找的問題。

四、跨數據庫事務問題。

五、跨數據庫序列問題。

六、不一樣應用的本地緩存問題。

七、系統之間的直接依賴和間接依賴問題。

八、獨立模塊面臨的單點問題。

九、各種批量分組、切換、擴展的問題。

十、統一監控和恢復問題。

 

本 文咱們暫時不討論關於雲存儲方面的問題,先引入話題,不過每項技術的產生都是爲了解決某些特定的問題而存在,因此雲也並不是萬能的,後面的文章咱們會介紹一 些基於純Java開發的Hadoop相關架構和模塊(如:MapReduce、HbaseHive、HDFS、Pig等子系統,說明當今海量信息的互聯 網中大象的崛起)。

 

一、系統按照業務拆分

首先看下企業中拆分爲小系統的過程當中的過程和遇到的問題,在大多數企業中,選擇高端企業的解決方案,由於一臺兩臺小型機通常的企業都沒有問題,除非是作的項目的確過小了,這類系統的訪問量大概天天保持在幾十萬左右高的有一百多萬的,不過爲何要拆分,通常有如下緣由

   a.隨着業務的發展,模塊之間的耦合性愈來愈強

   b.開發人員愈來愈多,相互之間代碼版本也難以管理

   c.系統啓動加載PermGen的時間也會很長而且須要很大的PermGen,更加劇要的緣由是JVM在CMSGC出來以前管理大內存是有問題的

   d.尤爲是發生Full GC時候在大內存的JVM上暫停時間是至關的長,長得讓人沒法接受.

   e.在單個機器上硬件廠商作得集成度越高,算法就愈來愈複雜,尤爲是CPU的個數始終有限,這樣就致使的是單位時間內處理的請求數也就受到限制,拆分水平擴展是很是容易的.

   f.一個大系統由多個開發商完成,多個開發商都有本身的主打產品,爲本身節約成本,將各個產品以集成的方法完成一個大系統的業務過程。

  等等緣由。

 

那 麼系統拆分這樣的系統拆分有什麼技巧嗎,能夠說緣由就算是技巧,也就是在何時再拆分,通常系統咱們能不拆分就不拆分,由於拆分有有不少麻煩要去面對, 面臨的第一個困難就是之前一個工程內部的系統,相互之間的調用就能夠直接調用到,如今很麻煩,要兩邊來作接口,接口還得聯調,聯調是一件比較噁心的事情,尤爲是兩個廠商之間來聯調。

 

因此拆分應當具備的最基本條件是高內聚、 低耦合的條件,也就是說,這個系統和外部系統的調用模塊對於整個系統的模塊來說是比較少的,而不是大部分模塊都是在和外部系統交互,除了專門用於處理系統交互的系統外,這樣的拆分設計是確定不合理的,由於通訊的代價遠遠大於本地JVM的代價。

 

開 發人員愈來愈多,從最初的一我的,幾我的,到幾十人,幾百人甚至上千人,在一個工程中來寫代碼是很恐怖的事情,誰改了無法查出來,沒法定位,很亂,因此拆 分在必定程度上能夠將版本控制的粒度細化一下,可是並不表明拆分後就沒有版本問題;隨着產品不斷模塊化和抽象化,在大多數的應用中,獨立的子系統就會成爲一個獨立的行業產品,能夠基於配置模式的適用於大部分的地區工廠或者企業的應用,也能夠經過一個頂層版本分發出來的多個地區化個性化版本(可能有兩層結 構);也就是在節約大部分共享勞動力的基礎上如何作到個性化平臺,這也是行業軟件中很是盛行的,不過這樣將絕大部分程序員控制在一個小匣子裏面了,幾乎沒有發揮的空間。

 

上面也說了,系統可能由幾十人、幾百人甚至於上千人去 寫,若是你們都寫一個工程,代碼量可想而知,系統初始化須要加載代碼段到PermGen,這塊空間將不可預知的大小發展,而且隨着業務的複雜性,須要的引 入的第三方技術愈來愈多,第三發包的class一樣會佔用PermGen的空間,不用多說,這塊空間的大小是不可預知的。

 

當 發生Full GC的時候,遍歷整個內存,在沒有CMS GC出來以前,或者如今G 1的出現,Full GC對於幾十G上百G的大內存是一件很是痛苦的事情,延遲時間能夠打到十幾秒甚至於上百秒(這裏在16個4 core的CPU使用了並行GC,時間是應用暫停時間),這是不能夠接受的,雖然CMS GC已經能夠在較短的暫停時間內回收掉大內存(只是暫停時間減小,可是回收時間可能會更加長),不過在它目前解決的主要問題是這個,同時因爲大內存部署邏輯節點的個數減小,使得負載均衡器的負載目標成倍減小,這樣可讓一樣的負載均衡器支撐起更加龐大的後臺訪問集羣;不過大部分早期的系統尚未看到CMS GC的誕生,更加沒有想到G1會出現(其實早在N多年前,論文就出來了,只是一直沒有實現而已),因此一直都仍是在沿用比較老的拆分方法,不過拆分始終是 有它的好處的,不只僅是由於GC的問題,在傳統企業中通常的負載均衡器也足以支撐,不會面臨更大的問題。

 

對 於集成度較高的,經過芯片等方式來完成高性能的服務方法,對於傳統軟件來說是很是好的,由於經過硬件完成的,通常狀況下比軟件完成的速度要快(所謂經過硬 件完成除了經過集成電路增長各種特殊指令外,還有就是基於芯片或底層語言實現使之效能更高並且封裝操做),不過遇到的問題就是隨着集成度的高度集中,算法愈來愈複雜,致使了內部的諸多衝突,水平擴展性受到了嚴重的限制,因此幾乎沒有多少算法的拆分,是一個必然的發展趨勢。

 

多 個開發商完成了一個本身的系統,開發商爲了產品化系統,而且因爲系統的複雜性,以及提高開發商在行業內部的積澱,因此就須要不斷完善產品,不斷版本化,以 及本地化的不斷改善;這個目的是好的,不過必定要作好版本的事情,以及一個大型的行業軟件的頂層架構以及繼承關係,不然不如不作,部分軟件廠商可能只考慮到前者,也就是產品化,不過代碼頂層架構幾乎沒有,只有業務架構,產品化和本地化代碼更加是爲所欲爲,軟件五花八門,就像貼補丁同樣,誰要作本地就加一個 else if,甚至於有直接對地區斷定的硬代碼,很無語的作法,我我的認爲這樣作不如直接拿一個模板來改出來一個系統,就不要作什麼版本,由於這樣的版本的代碼是愈來愈爛,面對這種代碼惟一的辦法就是重構,若是不想重構就永遠下去吧,不面臨改變終究會被淘汰掉;這種狀況也面臨在系統底層版本升級上,包括JDK的升 級,若是隻是考慮到成本和風險的話應該說真的永遠都沒法升級,沒有作不到的升級,關鍵是否願意去作,越晚去升級,所帶來的成本代價是越高的,相似國際上有多少大型軟件的底層版本也是在不斷的升級中,而上層的代碼因爲繁雜而不斷的重構,雖說不必定要時時刻刻重構,這樣程序員也會很累,而且也體現不出他們的 價值,由於整天就是改代碼,可是該重構就應該要去重構。

負載均衡,首先負載均衡能夠是硬件也能夠是軟件,硬件在必定程度上支撐不上去的時候就要考慮經過軟件的負載均衡來解決了(硬件通常狀況下比軟件要快速,可是它自己設計的複雜性致使了在必定場景下不如軟件的擴展性好),系統在拆分後不管是分佈到各個機器上仍是在一個機器上虛擬出來多個節點都是須要,將其負載均衡的,按照URL和端口+虛擬目錄名稱進行負載,負載均衡器須要 知道這些內容,也須要知道那個session訪問了那臺機器,中間負載均衡器會採用一些特殊的算法來解決一些問題,這裏簡單介紹到這裏,在下一篇文章中會 介紹下負載均衡的大體原理和做用。

負載均衡器並不簡單承擔這個問題,在負載均衡器中通常還會有不少算法存在,如負載均衡器比較經典的算法就是一致性hash算法,或者輪訓檢測;而在有限的線程下,爲了獲得更大的鏈接訪問,異步IO的訪問策略應運而生,著名的Nginx到目前爲止都是全世界大型互聯網前端負載均衡的設計藍圖的標準,其QPS極限狀況能夠打到30000-50000左右,內部還存在各類模式來支持不一樣的狀況(NAT、DR、RUN),固然還有不少相似的負載均衡設備(設計上有些差異)。

二、系統水平拆分:系統水平拆分即同一個子系統,或者整個系統部署在多個node上,這些node能夠是多個主機或同一個主機上的多個軟件節點;可是這些節點目前來說即便應用拆分得再細,在分佈式系統上的這種低端機器也不可能扛得住高併發的訪問,通常這類低端服務器代碼調解得較好等各類狀況下,服務器的QPS通常都是保持在200之內的(這是以16個CPU來處理,一個請求在80ms內處理完成請求分派,業務處理和數據請求,反饋結果等過程已是很是快速的了,不少時候一個SQL都會超過這個時間),固然單用幾個字節去作壓力測試,反饋幾個字節,而且中間幾乎沒有IO方面的額請求(如數據庫、文件、遠程方法調用等),那麼這個QPS你可能會上千,甚至於在好的機器上能夠上萬也有可能。

也就是系統真正運行的時候,前端的用的併發量都是有限的,並且不少時候代碼很差的時候,通常應用的QPS還會更低;面對高併發,在這種狀況下,咱們惟一能夠作的就是加機器,也就是水平擴展,它的分發也是依賴於負載均衡設備,加機器的過程就比如是工廠裏面的請不少工人來作同一件事情同樣,相對來說第一種拆分就是請不一樣的人來作不一樣的事情,不要讓一我的從頭作到尾部,那樣會搞得很累,並且對人的要求也很高。

這種拆分沒有什麼過高的要求,只要負載均衡設備能夠支撐就能夠,爲了讓負載均衡能夠支撐更大的壓力,那麼就儘可能讓節點數量減小,那麼就但願在同一臺實體機器上儘可能一個節點(經過對實體機器進行虛擬化能夠在某些狀況下節約成本,並將物理機自己的性能發揮到一個極限,並能夠將一個比較好一點的機器分攤給多個訪問量較低的系統,不過虛擬化自己也會產生不少開銷,在這些方面須要綜合權衡一下好壞),惋惜目前來說Oracle的Hotspot VM還不足以支撐大型的很是好的實時系統(咱們不少時候不得不在同一個大內存機器上部署多個小的JVM節點),尤爲面對幾種場景顯得力不從心:

一、大內存的管理(包括GC、內存分析、跟蹤等都沒有完善的體系和解決方案)。

二、作實時應用不適合,實時應用的延遲通常是毫秒級別(如2ms響應,最慢也不能有十多毫秒的響應,固然這種不包含IO操做,只是作一些內存處理和反饋,而且數據量不大),而java在正常狀況下,若是一旦發生GC,即便並行GC,並且僅僅只針對Yong空間作GC,也須要一段延遲(在一個16CPU的機器上,配置了並行GC,發生YGC的時候(Yong的大小大概爲330M左右),延遲大概爲10ms-15ms左右,發生Full GC的時候(Heap大小爲1.5G),延遲大概爲30ms-40ms左右),若是是更大的內存,就更加蠻了,由於回收的時間不少時候取決於內存的大小,增長一倍的內存,並不表明回收時間只增長一倍,由於隨着內存的增長,回收過程當中產生的開銷和衝突也變化,因此內存增長一倍,時間不必定只增長一倍,曾經在96G的JVM內存上,採用16CPU進行全局GC,大概須要3分多鐘,也就是說這3分多鐘外部是沒法訪問的,在實時應用面前這就是垃圾。

三、作緩存不適合,分代垃圾回收考慮的是絕大部分對象都應該死掉,而Old會採用全局GC,即便是CMS也會有各類問題;不少時候咱們在緩存的時候,數據初始化就會裝載進去,而不多甚至於不用去作GC,至少能夠說99%的內存是不須要考慮GC的;並且作緩存的服務器內存都是大內存,也就是沒有地方讓本身來操控可存放不作GC的內容,可是程序員發現這部份內容佔據了絕大部份內存而本身卻沒法控制它。

四、目前不支持半長命對象,也就是要麼是長命鬼、要麼是短命鬼,可是不少很是規應用中,有不少半長命對象,採用不一樣的算法,會提升更好的性能,如一些page cache數據,在內存中啓用LRU策略,這些隊列的數據,不能說他們的壽命很長,也不能說他們的壽命很短,可是LRU自己在使用的過程當中,不想受到相似Yong和Old之間的這種晉升策略,由於放在Eden中以爲命過短,來回倒騰,有不少仍是會到Old中(具體有多少進入old要看應用場景),進入old它又並非什麼太長命的東西,隨時可能就掛掉了,真是無奈啊。

其實還有不少JVM不方便去作的服務器方面的特殊應用,不過隨着JVM的發展已經比之前有了很大的飛躍,並且愈來愈多的硬件廠商和學術界的頂尖高手在爲java的發展而努力,因此我很期待java能解決掉這些問題。

三、 獨立工具、模塊、服務的獨立化和集羣化

其實這種拆分和第一種拆分有類似之處,幾乎能夠算是同樣的拆分模式,不過說到工具化、模塊化、服務化、集羣化,這種屬於更爲專業的拆分,第一種拆分的依據是系統各項壓力上來,爲考慮擴展性,而不得不拆分系統,而將不少高內聚、低耦合的系統拆分出來,也就是模塊成爲了子系統。

而這種拆分是一種技術獨立性拆分,將不少較爲複雜,很差解決的技術以及工具特徵獨立出來,虛擬化爲一種服務模式,爲外部提供服務,你能夠將它理解爲一個傳統的子系統,不過它是屬於不少系統裏面都須要的一個公共子系統,而前者僅僅通常只爲本身的兄弟模塊提供相應的服務以及一些本身的對外用戶服務;好比:將郵件系統獨立、短信系統獨立就是爲不少應用服務,你們均可以使用,將通訊技術獨立、將分佈式緩存獨立、將配置管理獨立、將版本管理獨立就是屬於技術上的獨立進而逐步個性化成爲一種服務。

第一種和這種拆分方法沒有明顯的區別,能夠說這種拆分的思想是受第一種拆分的影響或者說基於它來作的,它的目的是以一個個體或者集羣爲外部提供一種公共服務;當一個企業或者一個大的互聯網公司,將這些公共服務開放出來後,造成一種全局的數據、技術的服務平臺。

四、數據庫拆分

這個話題扯得有點大了,由於數據庫拆分這個拆分方法的確太多,本文也不能徹底說明數據庫的拆分方法,只是概要性的說起一些內容。

首先,前端有壓力,數據庫天然也有,並且數據庫壓力確定比前端壓力會更多(固然前端能夠採用不少緩存技術來環節數據庫的壓力),數據庫的複雜性比前端要更多,由於它是數據的核心,須要對數據庫的安全、一致性等作保障,在不少處理上它都是採用磁盤IO操做,而普通的sata盤是很爛的,sas盤可能會稍微好一些,這些盤上作幾個KB的IOPS測試,通常只能達到180的IOPS就很不錯了,固然根據磁盤自己的尺寸和轉速會有所區別;在早期的技術上,咱們大部分的都是採用高端存儲,如:EMC、IBM這類公司就是專門作高端存儲的,其IOPS能夠達到萬級別,其實其原理也是在普通存儲級別上作了不少相似多存儲控制器、鏡像、cache等技術來解決不少問題,可是其價格很是昂貴,小型機+EMC的解決方案相信是諸多企業的絕佳解決方案,由於根本不用擔憂性能、穩定性和存儲空間,可是在數據量達到很是大的時候,他們也會顯得力不從心,此時在這種解決方案下也不得不去拆分,拆分過程當中出現的問題就須要技術人員來解決,付出的成本將是指數級的上升,而不是平衡上升的;SSD的出現雖然顛覆了傳統的磁盤存取效率(主要是隨機存取效率,順序讀寫優點並不大),不過目前還有不少問題存在,最近Intel也稱其發生過丟失數據的問題,並且SSD目前的成本很是高,不過咱們能夠看到它的來臨是傳統磁盤開始被取代的標誌。

好,OK,隨着磁盤性能提升,可是容量仍是和之前差很少,並且更加貴,因此就當前來說咱們絕大部分仍是用傳統磁盤來解決,在這種一塊磁盤作一百多的IOPS的狀況下(注意一個SQL並不表明只作一次IO,具體作多少次IO和實際的應用場景、實際的優化方案、以及SQL的寫法所決定;而一個業務請求也通常會作多個SQL操做),咱們如何提升數據庫的性能呢?和上面同樣,在不少時候咱們先選擇的是小機+高端存儲的解決方案;可是隨着複雜性的增長,成本開始補課預測,因此爲了接觸這種耦合性,咱們須要一種高可擴展的分佈式技術來解決,在多個分佈式的機器上來解決這些問題。

首先,這種能夠認爲是一種分區技術在分佈式上的一種實現,也就是將原有分區的技術應用在多臺機器上,按照一種規則拆分到多臺計算機上,若是跨機器查找也就是原來的跨分區查找,顯然性能不如在單個機器上查找快速,因此如何設計分區成爲一個性能關鍵,而不是僅僅爲了拆分而拆分;另外拆分以前要有預算,計算所須要的TPS、QPS等負載狀況,拆分到多少個機器上能夠承受得起這樣的訪問量,通常最少須要預留一半的餘量才能夠預防突發性事件,若是須要將來幾年都不受到拆分上的干擾,那麼就能夠預留更多;由於這種數據庫拆分的代價是很高的。

在早期的數據庫拆分中,有主備讀寫分離,ORACLE RAC多實例運算,不過面對愈來愈龐大的系統,他們都顯得力不從心了,固然讀寫分離仍是能夠和現有的人工拆分所兼容,人工拆分主要是爲了獲得更好的水平擴展。

首先咱們來看看傳統應用中的range分區,若是用在分佈式上,就是放在多個主機上的多個庫上的多個表,這種用於自動增加列或時間上比較多,如剛開始能夠將1-1億的數據通常能夠用多久,選擇多少個機器來作,每臺機器能夠存放多少數據,而這種拆分Insert操做始終落在最後一臺機器的最後一個表的最後一個block上,並且在剛開始使用的時候,後面全部的機器的全部的表都是空的,沒有任何用處,顯得很是的浪費,也就是沒有數據的機器一直都是閒着的;這個問題比較好解決,你能夠用一個無窮大來表明最後一臺機器,當以爲應該加機器的時候,再將最後前一臺機器的上限控制住,不過前一個問題是沒辦法搞定的,因此這種方法是用在insert壓力並非很大的狀況,每秒要是有幾千個insert這樣作確定是不行的,其他的update、delete等若是有最近訪問熱點,那麼最後一臺機器也必將成爲熱點訪問區域,通常最近訪問的都是熱點,不過這種思路最容易讓人接受,並且最容易作出來。

那麼在大部分應用中咱們爲了考慮負載較爲均衡,因此咱們選擇hash算法,可是絕對不是一致性hash算法,由於一致性hash在擴展時會致使數據不一致,一些數學模型能夠解決,可是很是複雜並且也會存在數據版本的問題;hash算法最簡單的就是求模,如將一個表拆分爲100個表,那麼按照絕大部分狀況按照某個編號去求模獲得的是0-99之間的數據,這一百個表編號爲0-99,分別對應存儲便可,不管是自動增加仍是非自動增加也不太可能落在同一個表上面去;而對於某些熱點用戶,若是按照用戶拆分,這些熱點用戶的訪問就會被反覆訪問到同一個表,好比相似微博這種東西,也許某個熱門人物的他的好友個數就會很是多,可能會致使某個表很是大,因此爲了緩解這種問題,咱們會再作二次hash;而對於一些非數字類的數據,咱們通常會採起對其前幾個字符的ascii或者hash值等取出來作操做,具體看實際狀況而定;那麼hash算法就是完美的嗎?固然不是,它最痛苦的就是拆分,一旦拆分將會面臨各類問題,應用要重啓,配置要修改,數據要遷移;雖然用了一些手段來解決,可是這些手段通常都是須要提早預估出來的,好比hash算法通常都是2的次方拆分法則,由於數據庫都會有備庫,並且不少時候會有多個備庫,因此若是作2倍數拆分的時候,能夠直接將一個備份庫上的數據拿上來用,如原來拆分規則爲100,如今變成200,按照100求模=1的機器的主庫數據和200求模等於1的都還在這個上面,只是有一部分和200求模會變成101(理論上能夠認爲是一半),備庫也是這樣,因此在乘以2之後這個備庫變成主庫後,數據是徹底能夠路由到的,剩餘的工做就只須要將原有主庫上和200求模等於101的數據刪掉,以將這部分空間節約出來,而原有備庫替換成的主庫上和200求模等於1的數據刪掉,刪掉的這個過程是痛苦的,時間長,資源多,全是IO操做,並且還有各類鎖,性能影響極大;試圖想到我能夠直接把他幹掉,幾乎不影響性能就除非須要刪掉的數據是一個獨立的邏輯單位,在同一個表上能想到的就是分區,也就是它若是是一個分區你就能夠直接把他很快的drop掉或truncate掉,這種必需要有提早的預案才能夠,不然這些都是空想;因此這種基於hash的拆分通常不要隨便拆分,代價是很大的,由於這個上面的每一個節點都須要作切割,甚至於只有一個備庫的須要作遷移,要儘可能少作,壓力來了也得作,因此須要預估將來幾年幾乎不太可能作這樣的事情,這個估算是根據業務的發展速度和趨勢來決定的。

剩下是一種很常規可是不多用到的拆分,就是基於位圖的拆分,也就是認爲的講某個字段(這個字段的值是能夠被列舉的),某些值放在某個放在某個表裏面,也就是表的個數是被定義好的,拆分的個數收到值的個數的限制,除非和其餘字段再進行二次組合;雖然它自己用途很少,可是若是以range或hash做爲前提它也有多是有用途的。

上面闡述了幾種基本的拆分方法,都有各自有優缺點,爲了更好的解決問題,咱們考慮得失,會考慮使用他們進行組合,組合的方法根據實際狀況而定,如咱們在一些數字列上,既想考慮擴展性,又想考慮負載均衡,那麼在可接收的條件下,那麼咱們將range-hash或hash-range,至因而那一種要看具體狀況,咱們簡單說下range-hash,它在作range的時候,每一個hash值就面對多個主機目標,在這部分主機目標內部作相應的hash負載均衡,若是出現熱點,在這個range內部作二次拆分,其餘的range是不須要的,若是負載較低,能夠合併一些數據,range拆分的條件只是負責某個數據段的數據太多,較爲均衡的分佈數據,多個range若是之後不是怎麼用了,能夠將多個range的數據進行再次合併(這個代價相對較大,由於每一個range下面的hash規則能夠是不同的,可是若是隻要2的多少次方來完成這個動做,就不會出現太大的問題);而面對字符串的數據,或者不是自動增加類的數據,range沒有辦法,由於範圍不可預知,雖然能夠經過ascii來取,咱們的範圍也能夠用正無窮和負無窮來表明,可是咱們沒法保證數據的均衡的,因此建議仍是先作hash,而在拆分的過程當中,爲了使得應用不停須要設置一個版本號,也就是拆分過程當中,的時間戳標記,全部在這個時間點之後的數據都在新的分佈式規則中,老的數據讀取的時候在老的規則中,而後能夠遷移數據,可是遷移過程當中性能是很低的,遷移完成後就將中間規則去掉就完成了整個的拆分過程,這個拆分過程就不侷限於必須是2倍拆分了。

有關組合條件有不少,能夠根據本身的應用場景去選取不一樣的組合方法,使得它的性能最佳,儘可能少出現跨庫跨表的操做,若是是按照非拆分字段進行查詢,要麼作二級拆分,要麼就是作索引表,索引表也能夠是拆分的表,也就是先查索引表而後再從索引表獲得的主表的分表字段去找主表內容(可是因爲索引表的結構徹底又開發人員本身定義,因此索引表的維護徹底是程序來控制,一致性須要開發人員來保證)。

如上,拆分解決了不少問題,也帶來了不少新問題,如維護成本極度上升,須要大量外圍軟件來支持,不然發生任何問題將無從下手;其二,開發人員要編寫不少的代碼來處理路由規則信息和分佈式的一致性數據的問題;切分和數據庫切換過程當中,一次要切換一大堆機器,應用重啓時間很長;動態擴展要實現就須要很是複雜的代價。

爲了解決第一個問題,公司須要較好的基層架構的人員,來編寫不少外圍的相似分佈式一致性監控、問題跟蹤處理等工具軟件,而且這些軟件要可持續的,不然常常換成本永遠沒法控制,只要基層作好了,之後這些成本就會愈來愈少了,或者這些成本在同等的業務水平下會愈來愈少。

爲了解決第二個問題,讓開發來編寫路由等信息確定是不合理的,一個是不少開發人員水平有限,數據是業務關鍵,路由更加保護這數據存儲在哪裏,因此要是代碼寫得很差就死定了;因而咱們須要獨立中間件,這個中間件能夠只是保留在應用中的一個算法,也能夠是一個獨立的服務模式,服務模式爲了保證其不是單點問題以及訪問壓力過大,須要優化的是提供服務應當是一個集羣,而全部訪問它的應用系統應當作必定算法的本地緩存來處理;這又回到咱們上一章節說的應用系統的拆分了。

爲了解決第三個問題,切換要讓應用不知道,那麼就要讓應用感受不到IP、PORT、庫名稱的變化,那麼就必須將這些東西抽象出來,抽象爲如上所示的獨立服務模式或本地配置,當一個統一的配置被修改後,將會通知相關的應用系統進行修改,並一致性將多套機器所有一次性切換完成。

爲了解決第四個問題,咱們想了不少拆分的動態擴展性,可是算法十分複雜,就增長第二個中間件的複雜性,而且面臨各類風險,因此傳統RDBMS的拆分再次受到水平擴展的限制,人爲介入太多,主要緣由就是先獨立作數據庫,再作上層管理,是一個從下到上的過程,也就是有問題貼補丁的過程,而並不是從一個站在高處把控一切的思想;因而爲了解決更多的特殊的問題,如數據量超級大,並且增量也不少,讀的訪問很是多的狀況,nosql這種低耦合的拆分技術出現了,也是雲計算來臨的基礎(如今雲計算這個詞彙完全被用亂了,尤爲是中國,太過度了,這麼好歌東西,在國內就被處處使用,用着當招牌,對此我表示無語),關於這部分不是本文的重點,最後一章節會簡單說起一些bigtable的思路和原理,以及其開源版本的實現HBase的大概的架構模式。

Nosql技術概述:

谷歌是一家偉大的互聯網公司,其引領着互聯網時代的發展,bigtable的經典一直在世界各大互聯網公司所效仿,後來還有多個升級版本,可是你們仍是喜歡叫他bigtable;谷歌取名字很奇怪,就是直截了當,bigtable就是大表,什麼樣的表的是大表,每一個幾十億、幾百億、幾千億什麼的,不是大表,谷歌的架構能夠承受萬億級別的數量,它的MapReduce也是一個很是簡單的名詞,也就是先作Map處理(也就是將須要分析的目標中將須要分析的有效數據提取出來並組織爲K-V結構),而Reduce就負責將這些K-V數據進行處理;Apache也是一家偉大的公司,開源社區的大拿,在java界更加孕育了很是多的經典,它的Hadoop架構就是仿照谷歌的架構來完成的,這套架構徹底是java編寫的,這個公司也有本身的特色,就是不少名字都是動物的名字,hadoop號稱就是大象的崛起,呵呵,這個hadoop架構裏頭有什麼:pig、zookeeper就是什麼豬、公園什麼的意思,整個就是動物園,他們不須要多麼玄的名字,就是純屬喜歡什麼就用用什麼,甚至於某些食物的名字或某個親人的名字。

Hadoop雖然還不能夠和谷歌的架構抗衡(基本差一個到兩個數量級),可是對於絕大部分的應用是絕對沒有問題,在分佈式運算上,它的MapReduce架構能夠支撐4000臺(在雅虎)的機器同時進行分佈式運算;對於線上應用就是其子模塊的HBase架構,全世界最大的是960臺(也是在雅虎)。

HBase算是nosql中的一種,每一種nosql都是爲了解決某些特殊的問題而存在,由於前面看到分佈式的拆分方法有不少種,nosql也不可能解決全部的問題,HBase整體來說須要配合的子系統有多個Region Server、Zookeeper、Master、HDFS來配合,其中HDFS爲存儲引擎,全部和hadoop相關的內容不管是不是HBase都基本是存在這個上面的,少部份內容是存儲在本地文件(如MapReduce中Map後的中間結果可能會用本地文件來存儲,由於用完後中間數據就沒有用了,沒有必要放在HDFS上面);Zookeeper就是公園,管理這些動物,哪裏出事或者門票都是它說了算,在這裏若是誰宕機了(那個Region server宕機了),它要知道,而後告訴Master(管理者)去處理這些問題,Master宕機了就會啓動備用的Master,客戶端要請求首先就就是從Zookeeper請求,Zookeeper從Master哪裏獲得數據分佈信息後,反饋給客戶端,客戶端會緩存主分佈信息,而後開始訪問,若是訪問不到就再次請求Zookeeper,因此Zookeeper必須是多臺機器才能保證穩定性。

也就是客戶端最終訪問的Region Server(其實這裏能夠看得出來它是基於範圍,可是這個範圍絕對不是基於某個自動增加列什麼的,而是基於數據的字節碼匹配,能夠放中文、數字什麼的均可以,可是放進去前都須要轉換爲二進制,因此轉換的過程徹底是業務層本身定義的),這個東西你就能夠理解爲一個JVM節點,數據cache在內存的是memstore,內部存儲不少storefile,以hfile爲單位,對文件進行讀寫;memstore通常都是64M兩個之間來回寫,一個寫滿就flush,另外一個也在寫,若是另外一個寫滿了,這個flush還未完成,就要鎖住兩個部分。

Region Server負責和HDFS通訊,其另外一個須要作的就是HLog的編寫,Hlog通常是數據實時寫的,可是也能夠不是實時寫的;HBase數據的版本個數等方面都是能夠設置的,而且能夠保證單個操做的一致性;Master在初始化的時候,就會從HDFS上去獲取字典Meta信息,因此這些內容都是存儲在HDFS上的。

OK,這部分並非本文的重點,本文重點在於拆分,這裏攜帶闡述了下HBase,可是它也有不少問題,相信問題最多的就是他是用java作的,對於後端的實時應用一旦發生GC就有不少的問題,尤爲是咱們前面也簡單說了下GC對於這種半長命的鬼東西是頗有問題的;其次是雖然ZK能夠發現宕機,可是時間很長,這個心跳時間設置過短可能會是一種假死,心跳時間太長就宕機不少也不會被人發現,總之還有不少問題,可是在JVM進步的同時,咱們相信這些問題均可以獲得解決

 

:以業務考慮數據庫確實是,拆分的規則引擎自己就是以業務爲立足點,業務上也會作相應的改造來完成這樣的事情,在互聯網公司一般是與業務配合作這個事情,使用最爲頻繁的是hash,而離散規則是考慮數據庫自己的性能容量。在目前這些規則逐步開始自動化,對使用者逐步開始隔離,業務規則理解分佈式規則,根據規則本身的業務特徵來選擇劃分方式。

讀大於寫在大部分場景適用,咱們有不少歷史庫,基本只有寫,一年下來沒幾個讀請求(用戶只有翻歷史帳的時候會用下),這就是根據業務嘛,我拿評論這個案例出來講也是說業務背景。另外,讀寫比不大的狀況下,還算不上讀多寫少,通常是超過好幾倍以上纔算的能算得上讀多寫少,例如大部分前端網站的讀寫比通常能夠達到10~18倍。

評論這個案例的寫大不大是看規模,還有相似聊天的一些信息,在阿里裏面的這種規模仍是很恐怖的,具體數字我倒不能說,只能說比較變態。我可沒說啥場景,一直但是說的都是拆分的作法而已,並且是一種算法上的參考,我可沒有下什麼結論怎麼作是正確的,業務應用自己拆分是另外一個維度的事情,在前面也有提到過。換句話說,若是沒遇到的一些併發場景,也不須要怎麼拆分,除非是一些業務上解耦合,若是須要拆分確定是遇到一些併發的問題,在這邊有大概上萬個應用的數據庫應用拆分規則,業務特徵各有奇葩之事,但總歸是在這裏面繞圈子,不少業界其它公司的拆分也差很少是拿這一套過去本身改的。傳統行業去年提到過拆分,也想這麼拆,只是很麻煩,爲啥不想多說,由於我之前也幹傳統行業的,很清楚裏頭的規則。

你說本身不要一律而論,但確先下結論是說對數據庫很小(峯值不峯值也得看應用自己是幹啥的,有的應用歷來就沒閒過),這不是自相矛盾嗎,對數據庫小不小天然是根據場景來講,你本身首先下定論說下,怎麼還說不要一律而論呢,我雖然嘴巴上沒說不要一律而論,但話語之中一直就沒有下過某個說法確定要怎麼作,每每你在說必須怎麼作纔是對的,怎麼作纔是錯的,還有我可不止考慮本身的場景,我在公司某個關鍵時間專門給各個應用幹這個事情,外部不少認識的朋友的場景和作法也大概清楚咋回事(固然不是說多權威,只是說不是瞎扯出來的東西而已),而這是從技術角度去分析一個場景,由於在互聯網公司技術會做爲產品最終自動化給人爲來選擇。對於讀遠遠大於寫,的看數據庫抗不扛得住,扛不住除優化外還有其它的方案而定。

 

回覆xieyuooo:我以爲仍是得以業務爲核心來考慮數據庫,而不是隻看數據庫去考慮數據庫。
寫的需求永遠小於讀,這是毋庸置疑的。
評論什麼的,只是寫接近讀,而後讀有緩存,對數據庫來講就變成寫大於讀了。可是這種狀況下,寫的IOPS也不會很高,只有特殊時候會產生很高的峯值,這個峯值可能會超出平時好幾倍,甚至更多。
也就是說,從現實狀況來看,折騰了那麼多,只是在峯值的時候有效果。
並且,還有一點很重要。在峯值的時候,產生高IOPS的寫,其實只會針對某些特定的數據,這些數據對於整個數據庫來講,佔比是很小的。徹底能夠用別的方法去應對峯值,而不是一律而論。
這只是一個思路,我也正在閒暇時間進行完善。

 

回覆xiceblue:首先,拆分的方法與讀寫比確實有關係,讀寫比是另外一個看待拆分的方式,不少讀寫比很高的,會把不少讀剝離到數據庫之外的組件去作,單純數據庫的拆分,讀寫分離的效果與數據庫自己的性能有關係,這自己是從數據庫管理者的角度在思考這個問題。
IO有讀也有寫,不少場景看來是讀遠遠大於寫,但並不表明全部的場景都是這樣的,讀的話緩存命中還不會產生過多IO,而寫是最終確定會產生的,至於比重依然會看場景,因此極端多是你只考慮本身的場景,我並無說必需要怎麼作,雖然這個帖子是幾年前寫的,當時見解比較片面這個是真的。
不過說是否是這麼玩的,我能夠說的是,在目前國內的大型互聯網公司裏面的大部分業務在數據庫這個位置基本都這麼玩的,只是在其中會變通出不少細節出來,至少到目前位置基本都是。
這個拆分已經不是單純的數據庫級別,而是有中間件配合來完成。我提到的片面是指內容比較泛泛而談,沒有談及其中的細節問題和詳細場景。而Insert較多的業務確實是存在的,並且很多,例如評價、蒐藏、日誌等,在這些選擇上,會考慮其它的存儲,而並不是RDBMS,提到Range狀況,它的弱點就是最後一個庫的最後一個位置,天然會去評估最後一個機器的負載狀況,在HBase的時候,咱們依然會考慮各類hash頭部規則將數據離散到不一樣的服務器上去。

 

拆分不是這麼玩的吧,太極端了。
數據庫最大的壓力是在讀上,而不是寫。由於寫能夠簡單的拆分後解決,而形成的就是讀的複雜。另外,對於寫來講IOPS通常是不會很高的,主要是讀的狀況IOPS會很高。因此我的認爲數據庫拆分應該圍繞讀來思考。

 

回覆zkq1989:其實拆分也不是隨便拆的,我這篇文章寫了有幾年了,當時寫得給人感受有點通用化。 其實拆分一個要有:業務背景,一個要有將來預期。所謂業務背景就是你的場景爲啥要拆分,這就牽涉到目的。 拆分的目無非2個:一個是爲了系統的耦合小,能夠作本身的事情能夠本身優化,局部化,一個是爲了讓性能提高總體擴展性。 不過拆分也會帶來不少問題,就是之前的代碼調用,都須要經過網絡來完成,須要考慮更多的技術細節。 其實你要說用什麼技術實現,其實就是基本的網絡交互,細化下去就是網絡的交互方式,從使用上來說,例若有Socket、HttpInvoker、RMI、EJB等,若是從技術上,一般說協議、同步異步、阻塞非阻塞、長鏈接短鏈接等。 拆分自己是很自由的,沒有什麼拆分是絕對合理和不合理的,有一些通用的拆分方案,也只是60%常見場景的抽象,最爲關鍵的是利用拆分的思想,合理地去作出一些拆分。 啥爲合理,就回到預期,你預期將來的系統是什麼樣子的,若是從拆分的角度來說,就是服務化的板塊足夠強,本身內部的事情大部分能夠內部解決,只有牽涉到其它的服務纔會去調用其它的模塊。例如你看到的圖形中,產品、會員、訂單、支付,若是在小規模使用的時候,作在一塊兒也無所謂,只要將其模塊化就好,大規模使用的時候,業務複雜性開始加強,業務上會相互依賴、發佈相互依賴、宕機也相互依賴,各自對系統的壓力評估很差作,拆分後業務相對單一。經過網絡交互去完成,其實自己也有開銷,只不過擴展性會更好。由於每一個模塊拆分爲子系統後能夠更好的評估本身的性能趨勢(由於代碼相對更加單一),能夠更好的去作局部化優化,能夠更好地去作本模塊的業務擴展和服務優化,它能夠一用一組集羣去單獨提供這樣的服務,就像協做式分工同樣的道理。 其實若是規模作得足夠大,這裏面也有可能還會繼續拆分的,只是繼續拆分可能又會把系統變得更加複雜,關鍵看是否有必要。總之拆分是自定義的,目的是清晰、業務擴展性、性能擴展,技術是網絡,思想是高內聚、低耦合。

相關文章
相關標籤/搜索