天天看點

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

針對并發,老生常談了。目前一個通用的做法有兩種:鎖機制:1.悲觀鎖;2.樂觀鎖。

但是這篇我主要用于記錄我這次處理的經曆,另外希望能看的大神,大牛,技師者,學長,兄長,大哥們能在評論中發表自己的看法和解決技巧等。

一個表,暫且叫 wallet,其中3個字段是 金額。初始值為0,如下圖所示:

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

然後我們寫了一個極為簡單的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>&lt;</code><code>include</code> <code>refid</code><code>=</code><code>"base_column_list"</code> <code>/&gt;</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:

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

可見背景程式報錯了。什麼錯誤呢?

<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&lt;&gt;’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>&lt;</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>&gt;</code>

<code>  </code><code>select</code>

<code>  </code><code>&lt;</code><code>include</code> <code>refid</code><code>=</code><code>"base_column_list"</code> <code>/&gt;</code>

<code>  </code><code>from wallet</code>

<code>  </code><code>where wallet_id = #{walletid,jdbctype=integer}</code>

<code>  </code><code>for update</code>

<code>&lt;/</code><code>select</code><code>&gt;</code>

自然大家可以看到,我這邊加了鎖,是通過主鍵鎖行。

按着上面的測試連接配接數1000,并發數100,控制台沒報錯。

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

資料庫結果也是很不錯。

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

按着上面的測試連接配接數5000,并發數350,控制台還是沒報錯。

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

資料庫結果卻是很出錯了!!!

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

少update了很多值。為什麼呢?

然後我用jvisualvm 小工具檢測。多測了幾次,發現連接配接數5000,并發數350,并發數上升。有一個圖的值始終不變。如圖:

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

發現圖中 tomcat的守護線程一直在200左右。後來我去找了下tomcat的server.xml發現了,使用了預設,大概就是200左右。

是以就配置了一下,大緻配置方法有兩種如下:

第1種方式:配置connector

maxthreads:tomcat可用于請求處理的最大線程數

minsparethreads:tomcat初始線程數,即最小空閑線程數

maxsparethreads:tomcat最大空閑線程數,超過的會被關閉

acceptcount:當所有可以使用的處理請求的線程數都被使用時,可以放到處理隊列中的請求數,超過這個數的請求将不予處理

<code>&lt;</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>/&gt;</code>

第2種方式:配置executor和connector

name:線程池的名字

class:線程池的類名

nameprefix:線程池中線程的命名字首

maxthreads:線程池的最大線程數

minsparethreads:線程池的最小空閑線程數

maxidletime:超過最小空閑線程數時,多的線程會等待這個時間長度,然後關閉

threadpriority:線程優先級

<code>&lt;</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>/&gt;</code>

<code>&lt;</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>/&gt;</code>

maxthreads:線程池的最大線程數,直接配置1000,然後用連接配接數10000,并發數800測試。輕松見圖:

JavaWeb 并發:FOR UPDATE 實戰,監測并解決。
JavaWeb 并發:FOR UPDATE 實戰,監測并解決。

感謝幫助我的人。希望有大牛在此讨論相關。小生感激不盡。