Java Web concurrency: for update practices, monitoring and solving ., Javawebupdate
Writer: BYSocket)
Weibo: BYSocket
Douban: BYSocket
I. Preface
We have been talking about concurrency. At present, there are two common practices: Lock Mechanism: 1. pessimistic lock; 2. Optimistic lock.
However, this article is mainly used to record my processing experience. In addition, I hope to see the great gods, Daniel, technician, senior student, elder brother, you can post your views and solutions in comments.
2. The story is like this.
A table is currently called wallet. The three fields are the amount. The initial value is 0, as shown in:
Then we wrote an extremely simple Controller and wrote the following Service code:
?
123456789101112131415 |
@Override public void testLock(int lockId) { Wallet wallet = walletMapper.selectByPrimaryKey(4); BigDecimal one = new BigDecimal(1.00); BigDecimal two = new BigDecimal(2.00); BigDecimal three = new BigDecimal(3.00); wallet.setWalletAmount(wallet.getWalletAmount().add(one)); wallet.setWalletAvailableAmount(wallet.getWalletAvailableAmount().subtract(two)); wallet.setOldAmount(wallet.getOldAmount().add(three)); walletMapper.updateByPrimaryKeySelective(wallet); } |
Simply read an object through the primary key. Note that this object is not locked. That is to say, the corresponding SQL is as follows:
?
1234 |
SELECT < include refid = "Base_Column_List" /> FROM wallet WHERE wallet_id = #{walletId,jdbcType=INTEGER} |
I am using MyBiatis. you should understand it. Then, increase 1, decrease 2, and increase 3.
Iii. Test is like this
I used the Web application stress testing tool Boom. HTTP (S) load generator compiled by https://github.com/rakyll/boom Go, an alternative to ApacheBench (AB. Boom is a micro program that can perform load tests on Web applications. It is similar to Apache connector, but it has better availability on different platforms and is easy to install and use.
The simple usage is as follows:
?
123456789101112131415161718 |
boom -n 1000 -c 200 http://www.baidu.com Options: -n Number of requests to run. -c Number of requests to run concurrently. Total number of requests cannot be smaller than the concurency level. -q Rate limit, in seconds (QPS). -o Output type. If none provided, a summary is printed. "csv" is the only supported alternative. Dumps the response metrics in comma-seperated values format. -m HTTP method, one of GET, POST, PUT, DELETE, HEAD, OPTIONS. -h Custom HTTP headers, name1:value1;name2:value2. -d HTTP request body. -T Content-type, defaults to "text/html". -a Basic authentication, username:password. -allow-insecure Allow bad/expired TLS/SSL certificates. |
So I conducted a stress test. It is obvious that this small tool is quite beautiful. Here I have 1000 connections and 100 concurrent connections:
The background program reports an error. What are the errors?
?
1 |
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction |
The original concurrency caused the update dead table. It must be wrong to ignore reading the database data.
Iv. Use of FOR UPDATE
First, complete the knowledge: You can use select * for update to lock tables or rows. The pressure on the natural lock table is much greater than that on the lock row. So we adopt the lock row. When will the table be locked?
Assume that there is a form products, which contains two columns: id and name. id is the primary key.
Example 1: (specify the primary key and use this document, row lock)
SELECT * FROM wallet WHERE id = '3' for update;
Example 2: (specify a primary key. If no data is found, no lock is required)
SELECT * FROM wallet WHERE id = '-1' for update;
Example 2: (no primary key, table lock)
SELECT * FROM wallet WHERE name = 'mouse 'for update;
Example 3: (the primary key is not clear, table lock)
SELECT * FROM wallet WHERE id <> '3' for update;
Example 4: (the primary key is not clear, table lock)
SELECT * FROM wallet WHERE id LIKE '3' for update;
Therefore, we have updated the Service er method of the Service layer:
?
123456789101112131415 |
@Override public void testLock(int lockId) { Wallet wallet = walletMapper.selectForUpdate(4); BigDecimal one = new BigDecimal(1.00); BigDecimal two = new BigDecimal(2.00); BigDecimal three = new BigDecimal(3.00); wallet.setWalletAmount(wallet.getWalletAmount().add(one)); wallet.setWalletAvailableAmount(wallet.getWalletAvailableAmount().subtract(two)); wallet.setOldAmount(wallet.getOldAmount().add(three)); walletMapper.updateByPrimaryKeySelective(wallet); } |
The corresponding SQL statement is as follows:
?
1234567 |
< select id = "selectForUpdate" resultMap = "BaseResultMap" parameterType = "java.lang.Integer" > SELECT < include refid = "Base_Column_List" /> FROM wallet WHERE wallet_id = #{walletId,jdbcType=INTEGER} FOR UPDATE </ select > |
Naturally, we can see that the lock is applied to the primary key to lock the row.
According to the test connection count 1000 and concurrency 100, no error is reported on the console.
The database results are also very good.
5. Increasing Pressure
According to the test connection count of 5000 and the concurrency count of 350, no error is reported on the console.
Database results are very wrong !!!
Many values are updated less. Why?
6. Check the jvisualvm tool and find that the Tomcat thread connections are insufficient by default.
Then I used the jvisualvm tool for detection. After several tests, we found that the number of connections was 5000, the number of concurrent connections was 350, and the number of concurrent connections increased. The value of a graph remains unchanged.
It is found that the daemon thread of tomcat has been around 200 in the figure. Later, I found tomcat's server. xml and used the default value, which is about 200.
Therefore, the configuration is as follows:
1st Methods: Configure Connector
MaxThreads: the maximum number of threads that tomcat can use to process requests.
MinSpareThreads: the initial number of tomcat threads, that is, the minimum number of Idle threads.
MaxSpareThreads: the maximum number of Idle threads in tomcat. If it is exceeded, it will be disabled.
AcceptCount: when the number of threads that can be used to process requests is used, the number of requests that can be placed in the processing queue will not be processed.
?
1 |
< Connectorport = "8080" maxHttpHeaderSize = "8192" maxThreads = "150" minSpareThreads = "25" maxSpareThreads = "75" enableLookups = "false" redirectPort = "8443" acceptCount = "100" connectionTimeout = "20000" disableUploadTimeout = "true" /> |
2nd Methods: Configure Executor and Connector
Name: name of the thread pool
Class: The class Name of the thread pool.
NamePrefix: The name prefix of the thread in the thread pool.
MaxThreads: Maximum number of threads in the thread pool
MinSpareThreads: Minimum number of Idle threads in the thread pool
MaxIdleTime: when the minimum number of Idle threads is exceeded, multiple threads will wait for this length of time, and then close
ThreadPriority: thread priority
?
123 |
< Executorname = "tomcatThreadPool" namePrefix = "req-exec-" maxThreads = "1000" minSpareThreads = "50" maxIdleTime = "60000" /> < Connectorport = "8080" protocol = "HTTP/1.1" executor = "tomcatThreadPool" /> |
MaxThreads: the maximum number of threads in the thread pool. You can directly configure it to 1000 and then test it with 10000 connections and 800 concurrent connections. See the following figure for ease:
VII. Summary
Thank you for helping me. I hope you will discuss it here. John is grateful.