java高並發:
並發:當有多個線程在操作時,如果系統只有一個CPU,則它根本不可能真正同時進行一個以上的線程,它只能把CPU運行時間划分成若干個時間段,再將時間 段分配給各個線程執行,在一個時間段的線程代碼運行時,其它線程處於掛起狀。.這種方式我們稱之為並發(Concurrent)。
對於我們開發的網站,如果網站的訪問量非常大的話,那么我們就需要考慮相關的並發訪問問題了。而並發問題是絕大部分的程序員頭疼的問題,
、同步和異步的區別和聯系
所謂同步,可以理解為在執行完一個函數或方法之后,一直等待系統返回值或消息,這時程序是出於阻塞的,只有接收到
返回的值或消息后才往下執行其它的命令。
異步,執行完函數或方法后,不必阻塞性地等待返回值或消息,只需要向系統委托一個異步過程,那么當系統接收到返回
值或消息時,系統會自動觸發委托的異步過程,從而完成一個完整的流程。
同步在一定程度上可以看做是單線程,這個線程請求一個方法后就待這個方法給他回復,否則他不往下執行(死心眼)。
同步就是一件事,一件事情一件事的做。
異步就是,做一件事情,不引響做其他事情。異步在一定程度上可以看做是多線程的(廢話,一個線程怎么叫異步),請求一個方法后,就不管了,繼續執行其他的方法。
臟數據
不可重復讀
不可重復讀是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由於第二個事務的修改,那么第一個事務兩次讀到的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀
2、如何處理並發和同步
今天講的如何處理並發和同同步問題主要是通過鎖機制。
我們需要明白,鎖機制有兩個層面。
一種是代碼層次上的,如java中的同步鎖,典型的就是同步關鍵字synchronized,這里我不在做過多的講解,
另外一種是數據庫層次上的,比較典型的就是悲觀鎖和樂觀鎖。這里我們重點講解的就是悲觀鎖(傳統的物理鎖)和樂觀鎖。
悲觀鎖(Pessimistic Locking):
悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自 外部系統的事務處理)修改持保守態度,因此,
在整個數據處理過程中,將數據處於鎖定狀態。
悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能 真正保證數據訪問的排他性,否則,即使在本系統
中實現了加鎖機制,也無法保證外部系 統不會修改數據)。
一個典型的倚賴數據庫的悲觀鎖調用:
select * from account where name=”Erica” for update
這條 sql 語句鎖定了 account 表中所有符合檢索條件( name=”Erica” )的記錄。
本次事務提交之前(事務提交時會釋放事務過程中的鎖),外界無法修改這些記錄。
Hibernate 的悲觀鎖,也是基於數據庫的鎖機制實現。
下面的代碼實現了對查詢記錄的加鎖:
String hqlStr ="from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE); // 加鎖
List userList = query.list();// 執行查詢,獲取數據
query.setLockMode 對查詢語句中,特定別名所對應的記錄進行加鎖(我們為 TUser 類指定了一個別名 “user” ),這里也就是對
返回的所有 user 記錄進行加鎖。
觀察運行期 Hibernate 生成的 SQL 語句:
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
from t_user tuser0_ where (tuser0_.name='Erica' ) for update
這里 Hibernate 通過使用數據庫的 for update 子句實現了悲觀鎖機制。
Hibernate 的加鎖模式有:
Ø LockMode.NONE : 無鎖機制。
Ø LockMode.WRITE : Hibernate 在 Insert 和 Update 記錄的時候會自動獲取
Ø LockMode.READ : Hibernate 在讀取記錄的時候會自動獲取。
以上這三種鎖機制一般由 Hibernate 內部使用,如 Hibernate 為了保證 Update
過程中對象不會被外界修改,會在 save 方法實現中自動為目標對象加上 WRITE 鎖。
Ø LockMode.UPGRADE :利用數據庫的 for update 子句加鎖。
Ø LockMode. UPGRADE_NOWAIT : Oracle 的特定實現,利用 Oracle 的 for
update nowait 子句實現加鎖。
上面這兩種鎖機制是我們在應用層較為常用的,加鎖一般通過以下方法實現:
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查詢開始之前(也就是 Hiberate 生成 SQL 之前)設定加鎖,才會
真正通過數據庫的鎖機制進行加鎖處理,否則,數據已經通過不包含 for update
子句的 Select SQL 加載進來,所謂數據庫加鎖也就無從談起。
為了更好的理解select... for update的鎖表的過程,本人將要以mysql為例,進行相應的講解
1、要測試鎖定的狀況,可以利用MySQL的Command Mode ,開二個視窗來做測試。
需要注意的是for update要放到mysql的事務中,即begin和commit中,否者不起作用。
樂觀鎖(Optimistic Locking):
相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數情況下依 靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之
而來的就是數據庫 性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。 如一個金融系統,當某個操作員讀取用戶的數據,並在讀出的用戶數
據的基礎上進 行修改時(如更改用戶帳戶余額),如果采用悲觀鎖機制,也就意味着整個操作過 程中(從操作員讀出數據、開始修改直至提交修改結果的全
過程,甚至還包括操作 員中途去煮咖啡的時間),數據庫記錄始終處於加鎖狀態,可以想見,如果面對幾 百上千個並發,這樣的情況將導致怎樣的后果。 樂
觀鎖機制在一定程度上解決了這個問題。
樂觀鎖,大多是基於數據版本 Version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基於數據庫表的版本解決方案中,一般是通
過為數據庫表增加一個 “version” 字段來 實現。 讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提 交數據的版本數據與數據
庫表對應記錄的當前版本信息進行比對,如果提交的數據 版本號大於數據庫表當前版本號,則予以更新,否則認為是過期數據。對於上面修改用戶帳戶信息
的例子而言,假設數據庫中帳戶信息表中有一個 version 字段,當前值為 1 ;而當前帳戶余額字段( balance )為 $100 。操作員 A 此時將其讀出
( version=1 ),並從其帳戶余額中扣除 $50( $100-$50 )。 2 在操作員 A 操作的過程中,操作員 B 也讀入此用戶信息( version=1 ),並 從其帳
戶余額中扣除 $20 ( $100-$20 )。 3 操作員 A 完成了修改工作,將數據版本號加一( version=2 ),連同帳戶扣 除后余額( balance=$50 ),提交
至數據庫更新,此時由於提交數據版本大 於數據庫記錄當前版本,數據被更新,數據庫記錄 version 更新為 2 。 4 操作員 B 完成了操作,也將版本號加一
( version=2 )試圖向數據庫提交數 據( balance=$80 ),但此時比對數據庫記錄版本時發現,操作員 B 提交的 數據版本號為 2 ,數據庫記錄當前版
本也為 2 ,不滿足 “ 提交版本必須大於記 錄當前版本才能執行更新 “ 的樂觀鎖策略,因此,操作員 B 的提交被駁回。 這樣,就避免了操作員 B 用基於
version=1 的舊數據修改的結果覆蓋操作 員 A 的操作結果的可能。 從上面的例子可以看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷(操作員 A
和操作員 B 操作過程中,都沒有對數據庫數據加鎖),大大提升了大並發量下的系 統整體性能表現。 需要注意的是,樂觀鎖機制往往基於系統中的數據存儲
邏輯,因此也具備一定的局 限性,如在上例中,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的用戶 余額更新操作不受我們系統的控制,因此可能
會造成臟數據被更新到數據庫中。在 系統設計階段,我們應該充分考慮到這些情況出現的可能性,並進行相應調整(如 將樂觀鎖策略在數據庫存儲過程中實
現,對外只開放基於此存儲過程的數據更新途 徑,而不是將數據庫表直接對外公開)。 Hibernate 在其數據訪問引擎中內置了樂觀鎖實現。如果不用考慮外
部系統對數 據庫的更新操作,利用 Hibernate 提供的透明化樂觀鎖實現,將大大提升我們的 生產力。
一:高並發高負載類網站關注點之數據庫
沒錯,首先是數據庫,這是大多數應用所面臨的首個SPOF。尤其是Web2.0的應用,數據庫的響應是首先要解決的。
一般來說MySQL是最常用的,可能最初是一個mysql主機,當數據增加到100萬以上,那么,MySQL的效能急劇下降。常用的優化措施是M-S(主-從)方式進行同步復制,將查詢和操作和分別在不同的服務器上進行操作。我推薦的是M-M-Slaves方式,2個主Mysql,多個Slaves,需要注意的是,雖然有2個Master,但是同時只有1個是Active,我們可以在一定時候切換。之所以用2個M,是保證M不會又成為系統的SPOF。
Slaves可以進一步負載均衡,可以結合LVS,從而將select操作適當的平衡到不同的slaves上。
以上架構可以抗衡到一定量的負載,但是隨着用戶進一步增加,你的用戶表數據超過1千萬,這時那個M變成了SPOF。你不能任意擴充Slaves,否則復制同步的開銷將直線上升,怎么辦?我的方法是表分區,從業務層面上進行分區。最簡單的,以用戶數據為例。根據一定的切分方式,比如id,切分到不同的數據庫集群去。
全局數據庫用於meta數據的查詢。缺點是每次查詢,會增加一次,比如你要查一個用戶nightsailer,你首先要到全局數據庫群找到nightsailer對應的cluster id,然后再到指定的cluster找到nightsailer的實際數據。
每個cluster可以用m-m方式,或者m-m-slaves方式。這是一個可以擴展的結構,隨着負載的增加,你可以簡單的增加新的mysql cluster進去。
二:高並發高負載網站的系統架構之HTML靜態化
三:高並發高負載類網站關注點之緩存、負載均衡、存儲
緩存是另一個大問題,我一般用memcached來做緩存集群,一般來說部署10台左右就差不多(10g內存池)。需要注意一點,千萬不能用使用
swap,最好關閉linux的swap。
負載均衡/加速
可能上面說緩存的時候,有人第一想的是頁面靜態化,所謂的靜態html,我認為這是常識,不屬於要點了。頁面的靜態化隨之帶來的是靜態服務的
負載均衡和加速。我認為Lighttped+Squid是最好的方式了。
LVS <------->lighttped====>squid(s) ====lighttpd
上面是我經常用的。注意,我沒有用apache,除非特定的需求,否則我不部署apache,因為我一般用php-fastcgi配合lighttpd,
性能比apache+mod_php要強很多。
squid的使用可以解決文件的同步等等問題,但是需要注意,你要很好的監控緩存的命中率,盡可能的提高的90%以上。
存儲
存儲也是一個大問題,一種是小文件的存儲,比如圖片這類。另一種是大文件的存儲,比如搜索引擎的索引,一般單文件都超過2g以上。
小文件的存儲最簡單的方法是結合lighttpd來進行分布。或者干脆使用Redhat的GFS,優點是應用透明,缺點是費用較高。我是指
你購買盤陣的問題。我的項目中,存儲量是2-10Tb,我采用了分布式存儲。這里要解決文件的復制和冗余。
這樣每個文件有不同的冗余,這方面可以參考google的gfs的論文。
大文件的存儲,可以參考nutch的方案,現在已經獨立為hadoop子項目。(你可以google it)
四:高並發高負載網站的系統架構之圖片服務器分離
五:高並發高負載網站的系統架構之數據庫集群和庫表散列
在數據庫集群方面,很多數據庫都有自己的解決方案,Oracle、Sybase等都有很好的方案,常用的MySQL提供的Master/Slave也是類似的方案,您使用了什么樣的DB,就參考相應的解決方案來實施即可。
上面提到的數據庫集群由於在架構、成本、擴張性方面都會受到所采用DB類型的限制,於是我們需要從應用程序的角度來考慮改善系統架構,庫表散列是常用並 且最有效的解決方案。我們在應用程序中安裝業務和應用或者功能模塊將數據庫進行分離,不同的模塊對應不同的數據庫或者表,再按照一定的策略對某個頁面或者 功能進行更小的數據庫散列,比如用戶表,按照用戶ID進行表散列,這樣就能夠低成本的提升系統的性能並且有很好的擴展性。sohu的論壇就是采用了這樣的 架構,將論壇的用戶、設置、帖子等信息進行數據庫分離,然后對帖子、用戶按照板塊和ID進行散列數據庫和表,最終可以在配置文件中進行簡單的配置便能讓系 統隨時增加一台低成本的數據庫進來補充系統性能。
集群軟件的分類:
一般來講,集群軟件根據側重的方向和試圖解決的問題,分為三大類:高性能集群(High performance cluster,HPC)、負載均衡集群(Load balance cluster, LBC),高可用性集群(High availability cluster,HAC)。
高性能集群(High performance cluster,HPC),它是利用一個集群中的多台機器共同完成同一件任務,使得完成任務的速度和可靠性都遠遠高於單機運行的效果。彌補了單機性能上的不足。該集群在天氣預報、環境監控等數據量大,計算復雜的環境中應用比較多;
負載均衡集群(Load balance cluster, LBC),它是利用一個集群中的多台單機,完成許多並行的小的工作。一般情況下,如果一個應用使用的人多了,那么用戶請求的響應時間就會增大,機器的性能也會受到影響,如果使用負載均衡集群,那么集群中任意一台機器都能響應用戶的請求,這樣集群就會在用戶發出服務請求之后,選擇當時負載最小,能夠提供最好的服務的這台機器來接受請求並相應,這樣就可用用集群來增加系統的可用性和穩定性。這類集群在網站中使用較多;
高可用性集群(High availability cluster,HAC),它是利用集群中系統 的冗余,當系統中某台機器發生損壞的時候,其他后備的機器可以迅速的接替它來啟動服務,等待故障機的維修和返回。最大限度的保證集群中服務的可用性。這類系統一般在銀行,電信服務這類對系統可靠性有高的要求的領域有着廣泛的應用。
六:高並發高負載網站的系統架構之緩存
最基本的兩種緩存。高級和分布式的緩存在后面講述。
架構方面的緩存,對Apache比較熟悉的人都能知道Apache提供了自己的緩存模塊,也可以使用外加的Squid模塊進行緩存,這兩種方式均可以有效的提高Apache的訪問響應能力。
網站程序開發方面的緩存,Linux上提供的Memory Cache是常用的緩存接口,可以在web開發中使用,比如用Java開發的時候就可以調用MemoryCache對一些數據進行緩存和通訊共享,一些大 型社區使用了這樣的架構。另外,在使用web語言開發的時候,各種語言基本都有自己的緩存模塊和方法,PHP有Pear的Cache模塊,Java就更多 了,.net不是很熟悉,相信也肯定有。
Java開源緩存框架
JBossCache/TreeCache JBossCache是一個復制的事務處理緩存,它允許你緩存企業級應用數據來更好的改善性能。緩存數據被自動復制,讓你輕松進行Jboss服務器之間的集群工作。JBossCache能夠通過Jboss應用服務或其他J2EE容器來運行一個Mbean服務,當然,它也能獨立運行。 JBossCache包括兩個模塊:TreeCache和TreeCacheAOP。 TreeCache --是一個樹形結構復制的事務處理緩存。 TreeCacheAOP --是一個“面向對象”緩存,它使用AOP來動態管理POJO
OSCache OSCache標記庫由OpenSymphony設計,它是一種開創性的JSP定制標記應用,提供了在現有JSP頁面之內實現快速內存緩沖的功能。OSCache是個一個廣泛采用的高性能的J2EE緩存框架,OSCache能用於任何Java應用程序的普通的緩存解決方案。OSCache有以下特點:緩存任何對象,你可以不受限制的緩存部分jsp頁面或HTTP請求,任何java對象都可以緩存。 擁有全面的API--OSCache API給你全面的程序來控制所有的OSCache特性。 永久緩存--緩存能隨意的寫入硬盤,因此允許昂貴的創建(expensive-to-create)數據來保持緩存,甚至能讓應用重啟。 支持集群--集群緩存數據能被單個的進行參數配置,不需要修改代碼。 緩存記錄的過期--你可以有最大限度的控制緩存對象的過期,包括可插入式的刷新策略(如果默認性能不需要時)。
JCACHE JCACHE是一種即將公布的標准規范(JSR 107),說明了一種對Java對象臨時在內存中進行緩存的方法,包括對象的創建、共享訪問、假脫機(spooling)、失效、各JVM的一致性等。它可被用於緩存JSP內最經常讀取的數據,如產品目錄和價格列表。利用JCACHE,多數查詢的反應時間會因為有緩存的數據而加快(內部測試表明反應時間大約快15倍)。
Ehcache Ehcache出自Hibernate,在Hibernate中使用它作為數據緩存的解決方案。
Java Caching System JCS是Jakarta的項目Turbine的子項目。它是一個復合式的緩沖工具。可以將對象緩沖到內存、硬盤。具有緩沖對象時間過期設定。還可以通過JCS構建具有緩沖的分布式構架,以實現高性能的應用。 對於一些需要頻繁訪問而每訪問一次都非常消耗資源的對象,可以臨時存放在緩沖區中,這樣可以提高服務的性能。而JCS正是一個很好的緩沖工具。緩沖工具對於讀操作遠遠多於寫操作的應用性能提高非常顯著。
SwarmCache SwarmCache是一個簡單而功能強大的分布式緩存機制。它使用IP組播來有效地在緩存的實例之間進行通信。它是快速提高集群式Web應用程序的性能的理想選擇。
ShiftOne ShiftOne Object Cache這個Java庫提供了基本的對象緩存能力。實現的策略有先進先出(FIFO),最近使用(LRU),最不常使用(LFU)。所有的策略可以最大化元素的大小,最大化其生存時間。
WhirlyCache Whirlycache是一個快速的、可配置的、存在於內存中的對象的緩存。它能夠通過緩存對象來加快網站或應用程序的速度,否則就必須通過查詢數據庫或其他代價較高的處理程序來建立。
Jofti Jofti可對在緩存層中(支持EHCache,JBossCache和OSCache)的對象或在支持Map接口的存儲結構中的對象進行索引與搜索。這個框架還為對象在索引中的增刪改提供透明的功能同樣也為搜索提供易於使用的查詢功能。
cache4j cache4j是一個有簡單API與實現快速的Java對象緩存。它的特性包括:在內存中進行緩存,設計用於多線程環境,兩種實現:同步與阻塞,多種緩存清除策略:LFU, LRU, FIFO,可使用強引用(strong reference)與軟引用(soft reference)存儲對象。
Open Terracotta 一個JVM級的開源群集框架,提供:HTTP Session復制,分布式緩存,POJO群集,跨越群集的JVM來實現分布式應用程序協調(采用代碼注入的方式,所以你不需要修改任何)。
sccache SHOP.COM使用的對象緩存系統。sccache是一個in-process cache和二級、共享緩存。它將緩存對象存儲到磁盤上。支持關聯Key,任意大小的Key和任意大小的數據。能夠自動進行垃圾收集。
Shoal Shoal是一個基於Java可擴展的動態集群框架,能夠為構建容錯、可靠和可用的Java應用程序提供了基礎架構支持。這個框架還可以集成到不希望綁定到特定通信協議,但需要集群和分布式系統支持的任何Java產品中。Shoal是GlassFish和JonAS應用服務器的集群引擎。
Simple-Spring-Memcached Simple-Spring-Memcached,它封裝了對MemCached的調用,使MemCached的客戶端開發變得超乎尋常的簡單。