【Java并发编程】java高并发的解决方案(一)
對于我們開發的網站,如果網站的訪問量非常大的話,我們就需要考慮相關的并發訪問問題了。而且并發問題也是中高級工程師面試中必問的問題,今天我們就來系統學習一下。
為了更好的理解并發和同步,我們先學習兩個重要的概念:同步和異步。
1、同步和異步的區別和聯系
所謂同步,可以理解為在執行完一個函數或方法之后,一直等待系統返回值或消息,這時程序是處于阻塞狀態的,只有接受到返回值或消息之后才往下執行其他的命令。
所謂異步,執行外函數或方法之后,不必阻塞性地等待返回值或消息,只需要向系統委托一個異步過程,那么當系統接收到返回值或消息時,系統會自動觸發委托的異步過程,從而完成一個完整的流程。
同步在一定程度上可以看作是單線程,這個線程請求一個方法之后就等待這個方法給他回復,否則就不往下執行。
異步在一定程度上可以看作多線程,請求一個方法之后就不管了,繼續執行其他的方法。
同步就是一件事,一件事一件事的做。
異步就是,做一件事,不影響做其他的事情。
對于java程序而言,我們會經常聽到同步關鍵字synchronized,假如這個同步的監事對象是類,那么當一個對象訪問這個類里面的同步方法的時候,其他的對象也想訪問這個同步方法就會進入阻塞狀態,只有等前一個對象執行完該同步方法,當前對象才能繼續執行該方法,這就是同步。相反,如果方法前沒有同步關鍵字修飾的話,那么不同的對象就可以在同一時間訪問同一個方法,這就是異步。再補充一下臟數據和不可重復讀的概念。
2、臟數據和不可重復讀
臟數據:
臟讀就是指,當一個事務正在訪問數據,并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務訪問并使用了這個數據。因為這個數據還沒有提交,那么另外一個事務讀到的數據就是臟數據(dirty data),依據臟數據所做的操作可能是不正確的。
不可重復讀:
指在數據庫訪問中,一個事務范圍內兩個相同的查詢卻返回了不同的數據。這是由于查詢時系統中其它事務修改的提交引起的。一種更容易理解的說法是:在一個事務內,多次讀取同一數據。在這個事務還沒結束時,另一個事務也訪問該數據。那么,在第一個事務的兩次讀數據之間,由于第二個事務的修改,導致第一個事務兩次讀到的數據可能不同,因此成為不可重復讀,即原始讀取不可重復。
3、如何處理并發和同步
今天學習的處理并發和同步問題主要是通過鎖機制。
鎖機制有兩個層面:
一種是代碼層面上的,如java中的同步鎖,典型的就是同步關鍵字synchronized和lock。
http://www.cnblogs.com/xiohao/p/4151408.html
http://www.cnblogs.com/xiohao/p/4151924.html
http://blog.csdn.net/lmb55/article/details/46279155
另一種是數據庫層面上的,比較典型的就是悲觀鎖和樂觀鎖。
有關悲觀鎖和樂觀鎖的內容請參看:http://blog.csdn.net/lmb55/article/details/78266667
4、常見java并發同步案例分析
案例一:訂票系統案例,某航班只有一張機票,假定有1W個人打開你的網站來訂票,問你如何解決并發問題(可擴展到任何高并發網站要考慮的并發讀寫問題)
問題,1W個人來訪問,要求票沒出去前必須保證大家都能看到票,不可能一個人在看到票的時候別人就不能看到。但是最后到底誰能搶到,那得要看這個人的“運氣”了(網絡快慢等)
首先我們容易想到和并發相關的幾個方案:
鎖同步更多指的是應用程序的層面,多個線程進來,只能一個一個的訪問。除了Java中的對象鎖,還有另外一個層面—數據庫鎖。如果是分布式系統,顯然只能使用數據庫端的鎖來實現。
假定我們采用了同步機制或者數據庫物理鎖機制,如果保證1W個人還能同時看到有票,這顯然會犧牲性能,在高并發網站中是不可取的。
由此來看,采用樂觀鎖即可解決此問題。樂觀鎖是在不鎖表的情況下,利用業務的控制來解決并發問題,這樣既保證數據的并發可讀性又保證數據的排他性,保證性能的同時也解決了并發帶來的臟數據問題。
hibernate如何實現樂觀鎖:
前提:在現有表中增加一個冗余字段,long類型的version版本號。
原理:
1)只有當前版本號>=數據庫表的版本號才能提交
2)提價成功后,版本號version++
實現很簡單:在orgmapping增加一個屬性optimistic-lock=”version”即可,以下是樣例片段:
<hibernate-mapping> <class name="com.stock.ABC" optimistic-lock="version" table="T_Stock" schema="STOCK"> </hibernate-mapping>案例二:股票交易系統、銀行系統,大數據量你是如何考慮的
首先,股票交易系統的行情表,每幾秒鐘就有一個行情記錄產生,一天下來就有(假定行情3s一個)股票數量*20*60*6條記錄,一個月下來這個表的記錄數量多大?Oracle中一張表的記錄數超過100W之后查詢性能就很差了,如何保證系統性能?
再比如,中國移動有上億的用戶量,表如何設計?把所有數據存到一張表?
所以,大數據量的系統,必須考慮表拆分(表名不一樣,但是結構完全一樣),通常用到的有以下幾種方式(視情況而定):
1)按業務分。比如手機號的表,我們可以考慮130開頭的一個表,131開頭的另一個表,以此類推。
2)利用Oracle的表拆分機制做分表
3)如果是交易系統,我們可以考慮按時間軸拆分,當日數據一個表,歷史數據弄到其它表。這里歷史數據的報表和查詢不會影響當日交易。
當然,表拆分后我們的應用要做相應的適配。單純的orgmapping就得改動了。比如部分業務要通過存儲過程等。
此外,我們還要考慮緩存:
這里的緩存,指的不僅是hibernate,hibernate本身提供了一級二級緩存。這里的緩存獨立于應用,依然是內存的讀取。假如我們能減少數據庫頻繁的訪問,那對系統肯定大大有利。比如一個電子商務系統的商品搜索,如果某個關鍵字的商品經常被搜,那就可以考慮把這部分商品列表存放到緩存(內存)中去,這樣不用每次訪問數據庫,性能大大增加。
簡單的緩存我們可以理解為自己做一個HashMap,把經常訪問的數據做一個key,value是第一次從數據庫搜索出來的值,下次訪問就可以從map中讀取,而不需要讀數據庫。目前專業些的有獨立緩存框架比如memcached等,可獨立部署成一個緩存服務器。
redis實現分布式鎖:
http://blog.csdn.net/lmb55/article/details/78235768
Redis實現秒殺:
http://blog.csdn.net/lmb55/article/details/78266905
5、常見的提高高并發下訪問效率的手段
首先要了解高并發的瓶頸在哪里?
1)可能是服務器網絡帶寬不夠
2)可能是web線程連接數不夠
3)可能是數據庫連接查詢上不去
根據不同的情況,解決思路也不同
1)像第一種情況可以增加網絡帶寬,DNS域名解析分發多臺服務器。
2)負載均衡,前置代理服務器nginx、apache等等。
3)數據庫查詢優化,讀寫分離,分表等等。
最后復制一些在高并發下面常常需要處理的內容:
1)盡量使用緩存,包括用戶緩存、信息緩存等,多花點內存來做緩存??梢源罅繙p少與數據庫的交互,提高性能。
2)用jprofiler等工具找出性能瓶頸,減少額外開銷。
3)優化數據庫查詢語句,減少直接使用hibernate等工具的直接生成語句(僅耗時較長的查詢做優化)
4)優化數據庫結構,多做索引,提高查詢效率
5)統計的功能盡量做緩存,或按每天統計或者定時統計相關報表,避免需要時進行統計的功能。
6)能使用靜態頁面的地方盡量使用靜態頁面,減少容器的解析(盡量將動態內容內容生成靜態html顯示)。
7)解決以上問題后,使用服務器集群來解決單臺的瓶頸問題。
總結
以上是生活随笔為你收集整理的【Java并发编程】java高并发的解决方案(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java提高—对象克隆(复制)/对象属性
- 下一篇: 基于数据库的分布式锁实现