原文引自:
http://www.javabloger.com/article/java-development-concern-those-things.html
近期各家IT媒體舉辦的業內技術大會讓很多網站都在披露自己的技術內幕與同行們分享,大到facebook,百度,小到剛起步的網站。facebook,百度之類的大型網站採用的技術和超凡的處理能力的確給人耳目一新的感覺,但並不是每個網站都是像facebook,百度 有上億的使用者訪問流量,有海量的資料需要儲存,需要使用到mapreduce/並行計算,HBase/列儲存這些技術不可。技術手段始終是運營的支撐,對於當前的運營環境適用就好,沒有必要非要趕個時髦,一定要和某項流行的技術產生點關係才善罷甘休。
在最近的技術大會中我們更多的目光都聚焦在這些大型網站,其實中小型門戶網站的技術體系也是值得去探討和關注。全天下的攻城師們並不是都在為這些大型門戶網站服務,更多的攻城師們正在默默無聞的為一些剛剛起步的中小型網站服務,而且佔據了攻城師隊伍中的60%以上的人群。在關注大型門戶網站的時候,中小型門戶網站的技術發展和實戰經驗更值得去分享。
無論大型門戶網站還是中小型垂直類型網站都會對穩定性、效能和延展性有所追求。大型網站的技術經驗分享值得我們去學習和借用,但落實到更具體的實踐上並不是對所有網站可以適用,其他語言開發的網站我還不敢多說,但Java開發的系統,我還是能您給插上幾句話:
JVM
JEE容器中啟動並執行JVM參數配置參數的正確使用直接關係到整個系統的效能和處理能力,JVM的調優主要是對記憶體管理方面的調優,最佳化的方向分為以下4點:
1.HeapSize 堆的大小,也可以說Java虛擬機器使用記憶體的策略,這點是非常關鍵的。
2.GarbageCollector 通過配置相關的參數進行Java中的垃圾收集器的4個演算法(策略)進行使用。
3.StackSize 棧是JVM的記憶體指令區,每個線程都有他自己的Stack,Stack的大小限制著線程的數量。
4.DeBug/Log 在JVM中還可以設定對JVM運行時的日誌和JVM掛掉後的日誌輸出,這點非常的關鍵,根據各類JVM的日誌輸出才能配置合適的參數。
網上隨處可見JVM的配置技巧,但是我還是推薦閱讀Sun官方的2篇文章,可以對配置參數的其所依然有一個瞭解
1.Java HotSpot VM Options http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
2.Troubleshooting Guide for Java SE 6 with HotSpot VM http://www.oracle.com/technetwork/java/javase/index-137495.html
另外,我相信不是每個人攻城師都是天天對著這些JVM參數的,如果你忘記了那些關鍵的參數你可以輸入Java -X(大寫X)進行提示。
JDBC
針對MySQL的JDBC的參數在之前的文章中也有介紹過,在單台機器或者叢集的環境下合理的使用JDBC中的配置參數對操作資料庫也有很大的影響。
一些所謂高效能的 Java ORM開源架構也就是開啟了很多JDBC中的預設參數:
1.例如:autoReconnect、prepStmtCacheSize、cachePrepStmts、useNewIO、blobSendChunkSize 等,
2.例如叢集環境下:roundRobinLoadBalance、failOverReadOnly、autoReconnectForPools、secondsBeforeRetryMaster。
具體內容可以參閱MySQL的JDBC官方使用手冊:
http://dev.mysql.com/doc/refman/5.1/zh/connectors.html#cj-jdbc-reference
資料庫連接池(DataSource)
應用程式與資料庫連接頻繁的互動會給系統帶來瓶頸和大量的開銷會影響到系統的效能,JDBC串連池負責分配、管理和釋放資料庫連接,它允許應用程式重複使用一個現有的資料庫連接,而再不是重建立立一個串連,因此應用程式不需要頻繁的與資料庫開關串連,並且可以釋放空閑時間超過最大空閑時間的資料庫連接來避免因為沒有釋放資料庫連接而引起的資料庫連接遺漏。這項技術能明顯提高對資料庫操作的效能。
在此我認為有一點需要說明:
串連池的使用也是需要關閉,因為在資料庫連接池啟動的時候就預先和資料庫獲得了相應的串連,之後不再需要應用程式直接的和資料庫打交道,因為應用程式使用資料庫連接池是一個“借”的概念,應用程式從資料庫連接池中獲得資源是“借出”,還需要還回去,就好比有20個水桶放在這裡,需要拿水的人都可以使用這些木桶從水池裡面拿水,如果20個人都拿完水,不將水桶還回原地,那麼後面來的人再需要拿水,只能在旁邊等待有人將木桶還回去,之前的人用完後需要放回去,不然後面的人就會一直等待,造成資源堵塞,同理,應用程式擷取資料庫連接的時候Connection連線物件的時候是從“池”中分配一個資料庫連接出去,在使用完畢後,歸還這個資料庫連接,這樣才能保持資料庫的串連“有借有還”準則。
參考資料:
http://dev.mysql.com/doc/refman/5.1/zh/connectors.html#cj-connection-pooling
資料存取
資料庫伺服器的最佳化和資料的存取,什麼類型的資料放在什麼地方更好是值得去思考的問題,將來的儲存很可能是混用的,Cache,NOSQL,DFS,DataBase 在一個系統中都會有,生活的餐具和平日裡穿衣服需要擺放在家裡,但是不會用同一種類型的傢具存放,貌似沒有那個人家把餐具和衣服放在同一個柜子裡面的。這就像是系統中不同類型的資料一樣,對不同類型的資料需要使用合適的儲存環境。檔案和圖片的儲存,首先按照訪問的熱度分類,或者按照檔案的大小。強關聯類型並且需要事務支援的採用傳統的資料庫,弱關係型不需要事務支援的可以考慮NOSQL,海量檔案儲存體可以考慮一下支援網路儲存的DFS,至於緩衝要看你單個資料存放區的大小和讀寫的比例。
還有一點值得注意就是資料讀寫分離,無論在DataBase還是NOSQL的環境中大部分都是讀大於寫,因此在設計時還需考慮 不僅僅需要讓資料的讀分散在多台機器上,還需要考慮多台機器之間的資料一致性,MySQL的一主多從,在加上MySQL-Proxy或者借用JDBC中的一些參數(roundRobinLoadBalance、failOverReadOnly、autoReconnectForPools、secondsBeforeRetryMaster)對後續應用程式開發,可以將讀和寫分離,將大量讀的壓力分散在多台機器上,並且還保證了資料的一致性。
緩衝
在宏觀上看緩衝一般分為2種:本機快取和分布式緩衝
1.本機快取,對於Java的本機快取而言就是講資料放入靜態(static)的資料結合中,然後需要用的時候就從待用資料結合中拿出來,對於高並發的環境建議使用 ConcurrentHashMap或者CopyOnWriteArrayList作為本機快取。緩衝的使用更具體點說就是對系統記憶體的使用,使用多少記憶體的資源需要有一個適當比例,如果超過適當的使用儲存訪問,將會適得其反,導致整個系統的運行效率低下。
2. 分布式緩衝,一般用於分布式的環境,將每台機器上的緩衝進行集中化的儲存,並且不僅僅用於緩衝的使用範疇,還可以作為分布式系統資料同步/傳輸的一種手段,一般被使用最多的就是Memcached和Redis。
資料存放區在不同的介質上讀/寫得到的效率是不同的,在系統中如何善用緩衝,讓你的資料更靠近cpu,下面有一張圖你需要永遠牢記在心裡,來自Google的技術大牛Jeff Dean(Ref)的傑作,:
並發/多線程
在高並發環境下建議開發人員使用JDK中內建的並發包(java.util.concurrent),在JDK1.5以後使用java.util.concurrent下的工具類可以簡化多線程開發,在java.util.concurrent的工具中主要分為以下幾個主要部分:
1.線程池,線程池的介面(Executor、ExecutorService)與實作類別(ThreadPoolExecutor、 ScheduledThreadPoolExecutor),利用jdk內建的線程池架構可以管理工作的排隊和安排,並允許受控制的關閉。因為運行一個線程需要消耗系統CPU資源,而建立、結束一個線程也對系統CPU資源有開銷,使用線程池不僅僅可以有效管理多線程的使用,還是可以提高線程的運行效率。
2.本地隊列,提供了高效的、可伸縮的、安全執行緒的非阻塞 FIFO 隊列。java.util.concurrent 中的五個實現都支援擴充的 BlockingQueue 介面,該介面定義了 put 和 take 的阻塞版本:LinkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue、PriorityBlockingQueue 和 DelayQueue。這些不同的類覆蓋了生產者-使用者、訊息傳遞、並行任務執行和相關並發設計的大多數常見使用的上下文。
3.同步器,四個類可協助實現常見的專用同步語句。Semaphore 是一個經典的並發工具。CountDownLatch 是一個極其簡單但又極其常用的工具 + 生產力,用於在保持給定數目的訊號、事件或條件前阻塞執行。CyclicBarrier 是一個可重設的多路同步點,在某些並行編程風格中很有用。Exchanger 允許兩個線程在 collection 點交換對象,它在多流水線設計中是有用的。
4.並發包 Collection,此包還提供了設計用於多線程上下文中的 Collection 實現:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。當期望許多線程訪問一個給定 collection 時,ConcurrentHashMap 通常優於同步的 HashMap,ConcurrentSkipListMap 通常優於同步的 TreeMap。當期望的讀數和遍曆遠遠大於列表的更新數時,CopyOnWriteArrayList 優於同步的 ArrayList。
隊列
關於隊列可以分為:本地的隊列 和 分布式隊列 2類
本地隊列:一般常見的用於非及時性的資料批量寫入,可以將擷取的資料緩衝在一個數組中等達到一定數量的時候在進行批量的一次寫入,可以使用BlockingQueue或者List/Map來實現。
相關資料:Sun Java API.
分布式隊列:一般作為訊息中介軟體,構建分布式環境下子系統與子系統之間通訊的橋樑,JEE環境中使用最多的就是Apache的AvtiveMQ和Sun公司的OpenMQ。
輕量級的MQ中介軟體之前也向大家介紹過一些例如:Kestrel和Redis(Ref http://www.javabloger.com/article/mq-kestrel-redis-for-java.html),最近又聽說LinkedIn的搜尋技術團隊推出了一個MQ產品-kaukaf(Ref http://sna-projects.com/kafka ),對此保持關注。
相關資料:
1.ActiveMQ http://activemq.apache.org/getting-started.html
2.OpenMQ http://mq.java.net/about.html
3.Kafka http://sna-projects.com/kafka
4.JMS文章 http://www.javabloger.com/article/category/jms
NIO
NIO是在JDK1.4後的版本中出現的,在Java 1.4之前,Jdk提供的都是面向流的I/O系統,例如讀/寫檔案則是一次一個位元組地處理資料,一個輸入資料流產生一個位元組的資料,一個輸出資料流消費一個位元組的資料, 面向流的I/O速度非常慢,並且一個資料包要麼整個資料報已經收到,要麼還沒有。Java NIO非堵塞技術實際是採取Reactor模式,有內容進來會自動通知,不必死等、死迴圈,大大的提升了系統效能。在現實情境中NIO技術多數運用兩個方面,1是檔案的讀寫操作,2是網路上資料流的操作。在NIO中有幾個核心對象需要掌握:1選取器(Selector)、2通道(Channel)、3緩衝區(Buffer)。
我的廢話:
1.在Java NIO的技術範疇中記憶體對應檔是一種高效的做法,可以用於緩衝中儲存的冷/熱資料分離,將緩衝中的一部分冷資料進行這樣的處理,這種做法上比常規的基於流或者基於通道的I/O快的多,通過使檔案中的資料出現為記憶體數組的內容來完成的,將檔案中實際讀取或者寫入的部分才會映射到記憶體中,並不是將整個檔案讀到記憶體中。
2.在Mysql的jdbc驅動中也可以使用NIO技術對資料庫進行操作來提升系統的效能。
長串連/Servlet3.0
這裡說的長串連就是長輪詢,以前瀏覽器(用戶端)需要關注伺服器端發生的資料變化需要不斷的訪問伺服器,這樣用戶端的數量一多必然會給伺服器端造成很大的壓力,例如:論壇中的站內訊息。現在Servlet3.0規範中提供了一個新的特性:非同步IO通訊;該特性會保持一個長串連。利用Servlet3非同步請求的這項技術可以大大的緩解伺服器端的壓力。
Servlet3.0的原理就是將request的請求開啟一個線程掛起,中間設定等待逾時的時間,如果後台事件觸發request請求,得到的結果返回給用戶端的request請求,如果在設定等待逾時的時間內沒有任何事件發生也將請求返回給用戶端,用戶端將再次發起request請求,用戶端與伺服器端的互動可以與此往複。
就好比,你先過來跟我說如果有人找你,我就立馬通知你你來見他,原先你需要不斷的問我有沒有要找你,而不管有沒有人找你,你都需要不斷的問我有沒有人找你,這樣的話不論問的人還是被問的人都會累死。
日誌
Log4J是通常被人們使用的工具,系統在剛剛上線的時候日誌一般都設定在INFO的級,真正上線後一般設定在ERROR級,但無論在任何時候,日誌的輸入內容都是需要關注的,開發人員一般可以依靠輸出的日誌尋找出現的問題或者依靠輸出的日誌對系統的效能進行最佳化,日誌也是系統運行狀態的報告和排錯的依據。
簡單來說日誌按照定義的不同策略和等級輸出到不同的環境,那樣便於我們分析和管理。相反你沒有策略的輸出,那麼機器一多,時間一長,會有一大推亂糟糟的日誌,會讓你排錯的時候無從下手,所以日誌的輸出策略是使用日誌的關鍵點。
參考資料:http://logging.apache.org/log4j/1.2/manual.html
打包/部署
在代碼設計的時候最好能將不同類型的功能模組在IDE環境中粗粒度的分為不同的工程,便於打成不同jar包部署在不同的環境中。有這樣的一個應用情境:需要每天定時遠程從SP那邊獲得當天100條新聞和部分城市的天氣預報,雖然每天的資料量不多,但是前端訪問的並發量很大,顯然需要在系統架構上做到讀寫分離。
如果把web工程和定時抓取的功能模組完全集中在一個工程裡打包,將導致需要擴充的時候每台機器上既有web應用也有定時器,因為功能模組沒有分開,每台機器上都有定時器工作將會造成資料庫裡面的資料重複。
如果開發的時候就將web和定時器分為2個工程,打包的時候就可以分開部署,10台web對應一台定期器,分解了前端請求的壓力,資料的寫入也不會重複。
這樣做的另一個好處就是可以共用,在上述的情境中web和定時器都需要對資料庫進行讀取,那麼web和定時器的工程裡都有操作資料庫的代碼,在代碼的邏輯上還是感覺亂亂的。如果再抽出一個DAL層的jar,web和定時器的應用模組開發人員只需要引用DAL層的jar,開發相關的商務邏輯,面向介面編程,無需考慮具體的資料庫操作,具體的對資料庫操作由其他開發人員完成,可以在開發工作單位分工上很明確,並且互不干涉。
架構
所謂流行的SSH(Struts/Spring/Hiberanet)輕量級架構,對於很多中小型項目而言一點都不輕量級,開發人員不僅需要維護代碼,還需要維護繁瑣的xml設定檔,而且說不定某個設定檔寫的不對就讓整個都工程無法運行。無設定檔可以取代SSH(struts/Spring/Hiberanet)架構的產品真的太多了,我之前就向大家介紹過一些個產品(Ref)。
這個我並不是一味的反對使用SSH(Struts/Spring/Hiberanet)架構,在我眼裡SSH架構真的作用是做到了規範開發,而並不使用了SSH(Struts/Spring/Hiberanet)架構能提高多少效能。
SSH架構只是對於非常大的項目人數上百人的團隊,還需要、繼續增加團隊規模的公司而言,是需要選擇一些市面上大家都認可,並且熟悉的技術,SSH(Struts/Spring/Hiberanet)架構比較成熟所以是首先產品。
但是對於一些小團隊中間有個把技術高人的團隊而言完全可以選擇更加簡潔的架構,真正的做到提速你的開發效率,早日拋棄SSH架構選擇更簡潔的技術在小團隊開發中是一種比較明知的選擇。
–end–