使用Java開發需要關注的那些事兒

原文引自:

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)的傑作,如圖所示:
cache-speed.png
併發/多線程
在高併發環境下建議開發者使用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–

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章