1. 程式人生 > >提高程式併發量的幾個建議(不看保證後悔死你)

提高程式併發量的幾個建議(不看保證後悔死你)

有朋友和我說,他的程式遇到了瓶頸,程式中帶有演算法,但是沒有事務,僅是查詢情況下,應該如何提高併發數量呢??

首先,我要說的是,先糾正絕大多數人的思維。併發與並行不是一回事!!!並行,指同一時間多個事件同時發生。併發,是指在某個時間間隔中,有多個事件發生,不一定同時發生。

於是我仔細思考了一下,因為當時並沒有思緒,需要思考的時間!!!

應該先從瓶頸的地方開始,需要測試工具,來測試瓶頸的具體原因。到底是查詢慢問題,還是程式碼的問題。因為資料庫也有自身的優化策略。Java也有優化策略。

先說說,我們常規處理併發的解決方案:

1.動態頁面靜態化。

2.製作資料庫散列表,即分庫分表。

3.增加快取。

4.增加映象。

5.部署叢集。

6.負載均衡。

7.非同步讀取,非同步程式設計。

8.建立執行緒池和自定義連線池,將資料持久化。

9.把一件事,拆成若干件小事,啟用執行緒,為每個執行緒分配一定的事做,多個執行緒同時進行把該事件搞定再合併。

以上是我個人工作中常用的處理方案。這些都是不花錢的。條件允許,請選擇CDN,這是收費的,你要找服務商來商討。還有,可以採用GCDN的加速方式,網上有講解,但是這兩點都是要消費的。

如果以上覺得不可行,因為資料庫最優,程式碼最優,那麼怎麼辦????沒關係,還有一個至關重要的事情,就是你的Web容器!!!你是tomcat也好Jboss也罷,weblogic也沒關係。他們都有自己的調優方式。

接下來我要舉例項說明了。

你應該用過淘寶吧??也應該看過它裡面有個功能是定時搶寶貝的,有個倒計時,時間一到,大家瘋搶。這種功能必然會涉及高併發處理。

我在重複一遍,併發與並行是兩回事。它倆的區別請自行百度。回到問題中來,多個人同時搶一個東西,意味著只有一個人能拿到。有沒有一種可能,大家一起上前哄搶,然後把它撕碎了,一人分一點???肯定不行,事務的ACID原則不許與你那麼做。所以當某個人搶到的時候,就必然要加鎖。鎖,無非是兩種,一個是程式的鎖,Java中鎖有很多種,例如synchronized,ReenTrantLock等;另一種是資料庫中的鎖。

至於怎麼選擇,我來建議一下:如果你是簡單的程式,單機部署,建議用程式的鎖。如果是分散式部署,建議用資料庫的鎖。

為啥這麼說呢?因為單機情況下,使用者必然不多,即使用同步鎖,讓請求進行排隊等待,消耗的時間也不會太多,這種代價還是能承受的。當你使用分散式方案的時候,每一臺應用伺服器都會有自己的事務,每個請求都是相互獨立的,不在同一事務中,就無法保證資料的一致性,這種情況下,程式加鎖已經不起作用了。所以我們必須要用資料庫的加鎖機制,就是大家所說的悲觀鎖與樂觀鎖。

說到悲觀鎖,實際上就是for update,MyAsim只支援表級鎖,InnerDB支援行級鎖。  舉個例子吧:

select * from 表 where 某=某 for update。先不看for update,就看前面的查詢,顯然意思是根據某個欄位查詢當前表的所有資料,加上for update 後,意味著你查的這些資料都會被鎖上,那麼這時候有人更改這些資料的某一條可以嗎??不可以。那什麼時候可以呢?必須要等到你鎖定的那個表的事務完全提交完成後。如果你那個事務是個執行緒而且要等待另一個執行緒完成後才能執行,一旦出現死鎖,那麼就會永久等待,那可就完蛋了,這就是重大事故啊!所以,實際開發中很少使用悲觀鎖。

那麼是什麼又是樂觀鎖呢?相比之下也是很簡單。

在資料庫增加一個欄位‘version’預設值為1,每次涉及事務的時候,都給它加1,舉個例子:

資料中的某條資料的version值為5,說明被更改過4次。現在我再次更改,那我就要先獲得當前的的version值,需要先查詢,發現是5,Ok,然後我提交,把它變成6。注意,場景變了,高併發情況下。我又去更改,先獲取值為6,當我提交更改時應該變成7對不對?但是我卻發現它已經變成10了,說明了什麼,是不是說,在我正要更改時,已經有4個人提前完成了更改,那是不是就不符合我的要求了,此時怎麼辦,是不是應該回滾啊。那這個SQL應該怎麼寫啊?

首先要把自動提交設為手動,setAutoCommit(false),注意啊,這是在同一個事務裡執行的,

begin:

int currentVersion = select version from 表 where id = 某;此時查出當前版本號。

int result = update 表 set a=某,b=某,c=某,version=currentVersion +1 where id = 某 and version < currentVersion + 1。執行後返回結果。

if(result > 0){

大於零,說明成功了對不對。是不是就應該提交啊。

commit();

}else{

否則小於零,說明失敗了對不對,那是不是就應該回滾啊。

rollback();

}

end;

這就是樂觀鎖,即使在高併發的情況下,也是可以互不妨礙的。有人說:他提交了一次失敗了,就需要再次提交,假如很多人同時訪問同一條資料,同時更改,那麼總是會有最後的人拿到舊的版本號,必然他會失敗,這樣使用者體驗性是不是會很差?這種問題確實存在,但也要分情況而定。如果是定時搶,那麼就不涉及這問題,全憑運氣了,難不成你拍在最後了,還指望能搶到?所以業務的不同,解決方案也會有所不同,但是,這個方案絕對能解決現有的絕大部分需求。而且這種方案現實生活中也是絕對能承受的。

現在有人和我說,那麼春運購買火車票呢?全國各地好多的視窗售賣點,假如A視窗的賣票人,點一下開票,失敗,又點,又失敗,再點,發現票賣沒了,那是不是就尷尬了?

那我們怎麼解決這個問題?這就需要進行程式碼優化了,還是拿上面的例子舉例:

begin:

boolean isSuccess = false;

while(!isSuccess){

int currentVersion = select version from 表 where id = 某;此時查出當前版本號。

isSuccess= update 表 set a=某,b=某,c=某,version=currentVersion +1 where id = 某 and version < currentVersion + 1。執行後返回結果。

注:居然有人和我說,更新應該返回int型別,怎麼會返回布林呢?我回答:三目運算。statement.executeUpdate() > 0 ? true : false

都怨我,我真的沒想過這種問題還有人問。

if(isSuccess){

說明成功了對不對。是不是就應該提交啊。

commit();

}else{

否則,說明失敗了對不對,那是不是就應該回滾啊。

rollback();

}

}

end;

看到了吧,是不是不同的場景應對方法不同啊,但是都能解決問題。以上就是賣票人只點擊一次,就不斷去迴圈更改,只要有一次能成功就退出更改。解決了問題。