JavaWeb 并发:FOR UPDATE 实战,监测并解决。

简介:

一、前言

针对并发,老生常谈了。目前一个通用的做法有两种:锁机制:1.悲观锁;2.乐观锁。

但是这篇我主要用于记录我这次处理的经历,另外希望能看的大神,大牛,技师者,学长,兄长,大哥们能在评论中发表自己的看法和解决技巧等。

 

二、故事是这样的

一个表,暂且叫 wallet,其中3个字段是 金额。初始值为0,如下图所示:
image

 

然后我们写了一个极为简单的Controller,并写了下面的Service代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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);
     }

就简单的通过主键读取到一个对象,注意这个对象是没加锁的。也就是说,所对应的SQL如下:

1
2
3
4
SELECT
     < include refid = "Base_Column_List" />
     FROM wallet
     WHERE wallet_id = #{walletId,jdbcType=INTEGER}

我这边是MyBiatis,大家应该看得懂的。然后一个增加1 一个减少2 一个增加 3。

 

三、测试是这样

我用了Web应用压力测试工具:Boomhttps://github.com/rakyll/boom Go编写的HTTP(S)负载生成器,ApacheBench(AB)的替代工具。Boom是一个微型程序,能够对Web应用程序进行负载测试。它类似于 Apache Bench ,但在不同的平台上有更好的可用性,安装使用也比较简单。

简单使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.

 

所以我就如图进行压力测试,可见这个小工具还挺美的,这里我连接数1000,并发数100
image
可见后台程序报错了。什么错误呢?

1
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

 

原来并发导致update死表了。数据库的数据不用看了肯定是错误的。

 

四、FOR 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方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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);
     }

所对应的SQL如下:

1
2
3
4
5
6
7
< 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 >

自然大家可以看到,我这边加了锁,是通过主键锁行。

 

按着上面的测试连接数1000,并发数100,控制台没报错。

image

数据库结果也是很不错。

image

 

五、加大压力

按着上面的测试连接数5000,并发数350,控制台还是没报错。

image

数据库结果却是很出错了!!!
image

少update了很多值。为什么呢?

 

六、jvisualvm 小工具检测,发现Tomcat线程连接数默认不够

然后我用jvisualvm 小工具检测。多测了几次,发现连接数5000,并发数350,并发数上升。有一个图的值始终不变。如图:

QQ截图20150322124739

发现图中 tomcat的守护线程一直在200左右。后来我去找了下tomcat的server.xml发现了,使用了默认,大概就是200左右。

 

所以就配置了一下,大致配置方法有两种如下:

第1种方式:配置Connector
maxThreads:tomcat可用于请求处理的最大线程数
minSpareThreads:tomcat初始线程数,即最小空闲线程数
maxSpareThreads:tomcat最大空闲线程数,超过的会被关闭
acceptCount:当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理

1
< Connectorport = "8080" maxHttpHeaderSize = "8192" maxThreads = "150" minSpareThreads = "25" maxSpareThreads = "75" enableLookups = "false" redirectPort = "8443" acceptCount = "100" connectionTimeout = "20000" disableUploadTimeout = "true" />

 

第2种方式:配置Executor和Connector

name:线程池的名字
class:线程池的类名
namePrefix:线程池中线程的命名前缀
maxThreads:线程池的最大线程数
minSpareThreads:线程池的最小空闲线程数
maxIdleTime:超过最小空闲线程数时,多的线程会等待这个时间长度,然后关闭
threadPriority:线程优先级

1
2
3
< Executorname = "tomcatThreadPool" namePrefix = "req-exec-" maxThreads = "1000" minSpareThreads = "50" maxIdleTime = "60000" />
 
< Connectorport = "8080" protocol = "HTTP/1.1" executor = "tomcatThreadPool" />

 

maxThreads:线程池的最大线程数,直接配置1000,然后用连接数10000,并发数800测试。轻松见图:

UFRJFLLO6@F1)LWQ6EQJ8P8

image

七、总结

感谢帮助我的人。希望有大牛在此讨论相关。小生感激不尽。

相关文章
|
8天前
|
存储 SQL 安全
Java 安全性编程:基本概念与实战指南
【4月更文挑战第27天】在当今的软件开发领域,安全性编程是一个至关重要的方面。Java,作为广泛使用的编程语言之一,提供了多种机制来保护应用免受常见的安全威胁。本博客将探讨 Java 安全性编程的基本概念,并通过实际示例来展示如何实现这些安全措施。
16 3
|
3天前
|
存储 Java 数据格式
Java实战:轻松掌握文件重命名与路径提取技巧
Java实战:轻松掌握文件重命名与路径提取技巧
10 0
|
5天前
|
设计模式 算法 安全
Java多线程编程实战:从入门到精通
【4月更文挑战第30天】本文介绍了Java多线程编程的基础,包括线程概念、创建线程(继承`Thread`或实现`Runnable`)、线程生命周期。还讨论了线程同步与锁(同步代码块、`ReentrantLock`)、线程间通信(等待/通知、并发集合)以及实战技巧,如使用线程池、线程安全设计模式和避免死锁。性能优化方面,建议减少锁粒度和使用非阻塞算法。理解这些概念和技术对于编写高效、可靠的多线程程序至关重要。
|
6天前
|
XML Java 测试技术
Java异常处理神器:Guava Throwables类概念与实战
【4月更文挑战第29天】在Java开发中,异常处理是保证程序稳定性和可靠性的关键。Google的Guava库提供了一个强大的工具类Throwables,用于简化和增强异常处理。本篇博客将探讨Throwables类的核心功能及其在实战中的应用。
16 2
|
6天前
|
安全 Java 测试技术
利用Java反射机制提高Spring Boot的代码质量:概念与实战
【4月更文挑战第29天】Java反射机制提供了一种强大的方法来在运行时检查或修改类和对象的行为。在Spring Boot应用中,合理利用反射可以提高代码的灵活性和可维护性。本篇博客将探讨Java反射的核心概念,并展示如何通过反射提高Spring Boot项目的代码质量。
22 0
|
6天前
|
存储 Java 大数据
JAVA:编程的艺术与实战解析
JAVA:编程的艺术与实战解析
15 2
|
8天前
|
监控 Java API
Java 模块化设计:概念与实战应用
【4月更文挑战第27天】模块化设计是现代软件开发的关键,它帮助开发者构建可管理、可维护的大型系统。Java 平台的模块化支持始于 Java 9,引入了一种全新的模块系统。
16 3
|
8天前
|
设计模式 算法 Java
Java 设计模式:探索策略模式的概念和实战应用
【4月更文挑战第27天】策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在 Java 中,策略模式通过定义一系列的算法,并将每一个算法封装起来,并使它们可以互换,这样算法的变化不会影响到使用算法的客户。
16 1
|
8天前
|
Java
Java 事件驱动编程:概念、优势与实战示例
【4月更文挑战第27天】事件驱动编程是一种编程范式,其中程序的执行流程由外部事件的发生而触发或驱动。
15 0
|
8天前
|
Java 程序员
Java 异步编程:概念、优势与实战示例
【4月更文挑战第27天】在现代软件开发中,异步编程是一种重要的编程范式,特别适用于处理长时间运行的任务,如网络通信、文件操作等。
17 0