針對并發,老生常談了。目前一個通用的做法有兩種:鎖機制:1.悲觀鎖;2.樂觀鎖。
但是這篇我主要用于記錄我這次處理的經曆,另外希望能看的大神,大牛,技師者,學長,兄長,大哥們能在評論中發表自己的看法和解決技巧等。
一個表,暫且叫 wallet,其中3個字段是 金額。初始值為0,如下圖所示:

然後我們寫了一個極為簡單的controller,并寫了下面的service代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code>@override</code>
<code> </code><code>public void testlock(int lockid)</code>
<code> </code><code>{</code>
<code> </code><code>wallet wallet = walletmapper.selectbyprimarykey(4);</code>
<code> </code>
<code> </code><code>bigdecimal one = new bigdecimal(1.00);</code>
<code> </code><code>bigdecimal two = new bigdecimal(2.00);</code>
<code> </code><code>bigdecimal three = new bigdecimal(3.00);</code>
<code> </code><code>wallet.setwalletamount(wallet.getwalletamount().add(one));</code>
<code> </code><code>wallet.setwalletavailableamount(wallet.getwalletavailableamount().subtract(two));</code>
<code> </code><code>wallet.setoldamount(wallet.getoldamount().add(three)); </code>
<code> </code><code>walletmapper.updatebyprimarykeyselective(wallet);</code>
<code> </code><code>}</code>
就簡單的通過主鍵讀取到一個對象,注意這個對象是沒加鎖的。也就是說,所對應的sql如下:
<code>select</code>
<code> </code><code><</code><code>include</code> <code>refid</code><code>=</code><code>"base_column_list"</code> <code>/></code>
<code> </code><code>from wallet</code>
<code> </code><code>where wallet_id = #{walletid,jdbctype=integer}</code>
我這邊是mybiatis,大家應該看得懂的。然後一個增加1 一個減少2 一個增加 3。
簡單使用方式如下:
16
17
18
<code>options:</code>
<code> </code><code>-n number of requests to run.</code>
<code> </code><code>-c number of requests to run concurrently. total number of requests cannot</code>
<code> </code><code>be smaller than the concurency level.</code>
<code> </code><code>-q rate limit, in seconds (qps).</code>
<code> </code><code>-o output type. if none provided, a summary is printed.</code>
<code> </code><code>"csv" is the only supported alternative. dumps the response</code>
<code> </code><code>metrics in comma-seperated values format.</code>
<code> </code>
<code> </code><code>-m http method, one of get, post, put, delete, head, options.</code>
<code> </code><code>-h custom http headers, name1:value1;name2:value2.</code>
<code> </code><code>-d http request body.</code>
<code> </code><code>-t content-type, defaults to "text/html".</code>
<code> </code><code>-a basic authentication, username:password.</code>
<code> </code><code>-allow-insecure allow bad/expired tls/ssl certificates.</code>
是以我就如圖進行壓力測試,可見這個小工具還挺美的,這裡我連接配接數1000,并發數100:
可見背景程式報錯了。什麼錯誤呢?
<code>caused by: com.mysql.jdbc.exceptions.jdbc4.mysqltransactionrollbackexception: deadlock found when trying to get lock; try restarting transaction</code>
原來并發導緻update死表了。資料庫的資料不用看了肯定是錯誤的。
先補一下其知識:利用select * for update 可以鎖表/鎖行。自然鎖表的壓力遠大于鎖行。是以我們采用鎖行。什麼時候鎖表呢?
假設有個表單products ,裡面有id跟name二個欄位,id是主鍵。
例1: (明确指定主鍵,并且有此筆資料,row lock)
select * from wallet where id=’3′ for update;
例2: (明确指定主鍵,若查無此筆資料,無lock)
select * from wallet where id=’-1′ for update;
例2: (無主鍵,table lock)
select * from wallet where name=’mouse’ for update;
例3: (主鍵不明确,table lock)
select * from wallet where id<>’3′ for update;
例4: (主鍵不明确,table lock)
select * from wallet where id like ‘3’ for update;
是以我們更新了下service層的mapper方法:
<code> </code><code>wallet wallet = walletmapper.selectforupdate(4);</code>
所對應的sql如下:
<code><</code><code>select</code> <code>id</code><code>=</code><code>"selectforupdate"</code> <code>resultmap</code><code>=</code><code>"baseresultmap"</code> <code>parametertype</code><code>=</code><code>"java.lang.integer"</code> <code>></code>
<code> </code><code>select</code>
<code> </code><code><</code><code>include</code> <code>refid</code><code>=</code><code>"base_column_list"</code> <code>/></code>
<code> </code><code>from wallet</code>
<code> </code><code>where wallet_id = #{walletid,jdbctype=integer}</code>
<code> </code><code>for update</code>
<code></</code><code>select</code><code>></code>
自然大家可以看到,我這邊加了鎖,是通過主鍵鎖行。
按着上面的測試連接配接數1000,并發數100,控制台沒報錯。
資料庫結果也是很不錯。
按着上面的測試連接配接數5000,并發數350,控制台還是沒報錯。
資料庫結果卻是很出錯了!!!
少update了很多值。為什麼呢?
然後我用jvisualvm 小工具檢測。多測了幾次,發現連接配接數5000,并發數350,并發數上升。有一個圖的值始終不變。如圖:
發現圖中 tomcat的守護線程一直在200左右。後來我去找了下tomcat的server.xml發現了,使用了預設,大概就是200左右。
是以就配置了一下,大緻配置方法有兩種如下:
第1種方式:配置connector
maxthreads:tomcat可用于請求處理的最大線程數
minsparethreads:tomcat初始線程數,即最小空閑線程數
maxsparethreads:tomcat最大空閑線程數,超過的會被關閉
acceptcount:當所有可以使用的處理請求的線程數都被使用時,可以放到處理隊列中的請求數,超過這個數的請求将不予處理
<code><</code><code>connectorport</code><code>=</code><code>"8080"</code><code>maxhttpheadersize</code><code>=</code><code>"8192"</code><code>maxthreads</code><code>=</code><code>"150"</code><code>minsparethreads</code><code>=</code><code>"25"</code><code>maxsparethreads</code><code>=</code><code>"75"</code><code>enablelookups</code><code>=</code><code>"false"</code><code>redirectport</code><code>=</code><code>"8443"</code><code>acceptcount</code><code>=</code><code>"100"</code><code>connectiontimeout</code><code>=</code><code>"20000"</code><code>disableuploadtimeout</code><code>=</code><code>"true"</code><code>/></code>
第2種方式:配置executor和connector
name:線程池的名字
class:線程池的類名
nameprefix:線程池中線程的命名字首
maxthreads:線程池的最大線程數
minsparethreads:線程池的最小空閑線程數
maxidletime:超過最小空閑線程數時,多的線程會等待這個時間長度,然後關閉
threadpriority:線程優先級
<code><</code><code>executorname</code><code>=</code><code>"tomcatthreadpool"</code><code>nameprefix</code><code>=</code><code>"req-exec-"</code><code>maxthreads</code><code>=</code><code>"1000"</code><code>minsparethreads</code><code>=</code><code>"50"</code><code>maxidletime</code><code>=</code><code>"60000"</code><code>/></code>
<code><</code><code>connectorport</code><code>=</code><code>"8080"</code><code>protocol</code><code>=</code><code>"http/1.1"</code><code>executor</code><code>=</code><code>"tomcatthreadpool"</code><code>/></code>
maxthreads:線程池的最大線程數,直接配置1000,然後用連接配接數10000,并發數800測試。輕松見圖:
感謝幫助我的人。希望有大牛在此讨論相關。小生感激不盡。