JavaWeb 并发:FOR UPDATE 实战,监测并解决。
Writer:BYSocket(泥沙砖瓦浆木匠)
微博:BYSocket
豆瓣:BYSocket
一、前言
针对并发,老生常谈了。目前一个通用的做法有两种:锁机制:1.悲观锁;2.乐观锁。
但是这篇我主要用于记录我这次处理的经历,另外希望能看的大神,大牛,技师者,学长,兄长,大哥们能在评论中发表自己的看法和解决技巧等。
二、故事是这样的
一个表,暂且叫 wallet,其中3个字段是 金额。初始值为0,如下图所示:
然后我们写了一个极为简单的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应用压力测试工具:Boom。https://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:
可见后台程序报错了。什么错误呢?
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,控制台没报错。
数据库结果也是很不错。
五、加大压力
按着上面的测试连接数5000,并发数350,控制台还是没报错。
数据库结果却是很出错了!!!
少update了很多值。为什么呢?
六、jvisualvm 小工具检测,发现Tomcat线程连接数默认不够
然后我用jvisualvm 小工具检测。多测了几次,发现连接数5000,并发数350,并发数上升。有一个图的值始终不变。如图:
发现图中 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测试。轻松见图:
七、总结
感谢帮助我的人。希望有大牛在此讨论相关。小生感激不尽。
JavaWeb 并发:FOR UPDATE 实战,监测并解决。的更多相关文章
- sqlserver默认隔离级别下并发批量update同一张表引起的死锁
提到死锁,最最常规的场景之一是Session1 以排它锁的方式锁定A表,请求B表,session2以排它锁的方式锁定B表,请求A表之类的,访问顺序不一致导致死锁的情况本文通过简化,测试这样一种稍显特殊 ...
- Java生鲜电商平台-SpringCloud微服务架构高并发参数优化实战
Java生鲜电商平台-SpringCloud微服务架构高并发参数优化实战 一.写在前面 在Java生鲜电商平台平台中相信不少朋友都在自己公司使用Spring Cloud框架来构建微服务架构,毕竟现在这 ...
- tk.mybatis通用插件updateByPrimaryKeySelective无法自动更新ON UPDATE CURRENT_TIMESTAMP列的解决办法
tk.mybatis是一个很好用的通用插件,把CRUD这些基本的数据操作全都用动态SQL语句自动生成了,mapper和xml里十分清爽,但是昨天发现有一个小坑,记录在此: 有一张表,结构如下(已经简化 ...
- list的迭代器能解决并发问题,collection 的迭代器不能解决并发问题,for可以解决并发问题
list的迭代器能解决并发问题,collection 的迭代器不能解决并发问题 为什么list支持add,collection不支持 例如有两个人同时添加第三个元素 list的迭代器能锁定线程 只有等 ...
- win7系统 windows update 总是更新失败解决方法:
win7系统 windows update 总是更新失败解决方法: 右键单击桌面“计算机”选择“管理“. 进到“计算机管理“窗口后,展开”服务和应用程序“并双击”服务“,在窗口右侧按照名称找到”Win ...
- 去哪网实习总结:JavaWeb中文传參乱码问题的解决(JavaWeb)
本来是以做数据挖掘的目的进去哪网的.结构却成了系统开发... 只是还是比較认真的做了三个月.老师非常认同我的工作态度和成果... 实习立即就要结束了,总结一下几点之前没有注意过的变成习惯和问题,分享给 ...
- .net core (领域事件,并发 for update) 工作内容记录
这周工作,因为要对几个不同的表进行货币增加,锁定,所以 用了领域事件和并发 for update ,先记录一下 领域事件 ,Dapper 事务 ,sql for update 这几个点 头大,最近工 ...
- mysql You can't specify target table for update in FROM clause解决方法
mysql You can't specify target table for update in FROM clause解决方法出现这个错误的原因是不能在同一个sql语句中,先select同一个表 ...
- Java并发编程实战 02Java如何解决可见性和有序性问题
摘要 在上一篇文章当中,讲到了CPU缓存导致可见性.线程切换导致了原子性.编译优化导致了有序性问题.那么这篇文章就先解决其中的可见性和有序性问题,引出了今天的主角:Java内存模型(面试并发的时候会经 ...
随机推荐
- Python从入门到超神之文件处理
一.文件处理流程(python默认是utf-8编码) 打开文件函数:open(文件路径,encoding=‘utf-8’)注意:open会检索系统的编码,所以需要调整一致否则报错 例如:fi=open ...
- Codeforces Round #539 (Div. 2) C Sasha and a Bit of Relax
题中意思显而易见,即求满足al⊕al+1⊕…⊕amid=amid+1⊕amid+2⊕…⊕ar且l到r的区间长为偶数的这样的数对(l,r)的个数. 若al⊕al+1⊕…⊕amid=amid+1⊕amid ...
- 2019.03.25 bzoj4539: [Hnoi2016]树(主席树+倍增)
传送门 题意:给一棵大树,令一棵模板树与这棵树相同,然后进行mmm次操作,每次选择模板树中的一个节点aaa和大树中一个节点bbb,把aaa这棵子树接在bbb上面,节点编号顺序跟aaa中的编号顺序相同. ...
- lwip协议栈移植(1)
lwip移植分为两类: 1,只移植内核核心,用户应用程序编写只能基于raw/callback api进行 2,移植内核核心和上层API函数模块,用户可以使用所有三种API编程,即 raw/callba ...
- CSS3背景相关新增属性
background-clip border-box:充满边框和内边距,内容. padding-box:充满内边距,内容 content-box:只充满内容 background-origin bor ...
- 【pycharm】pycharm修改文件名快捷键
shift+F6 修改文件名 --------------------------------------------------
- poj3660 cow contest
这题主要是传递闭包 题意: n头牛,m次测试,假设a牛赢过b牛,那么说明a牛的能力比b牛强,问你根据输入的m次测试结果,最多能确定多少条牛的排名 大题的思路: 对于 k 牛,比他强的有x头牛,比他弱的 ...
- Maven和Gradle的区别
转自:http://www.infoq.com/cn/news/2011/04/xxb-maven-6-gradle Maven面临的挑战 软件行业新旧交替的速度之快往往令人咂舌,不用多少时间,你就会 ...
- 2018/9/6 spring框架的整理
spring知识的巩固整理AOP和ioc概念,以及了解到了为何要使用spring框架的目的,作用:变换资源获取的方向.更像是按需所求.配置bean的方式:利用XML的方式,基于注解的方式两种.1通过全 ...
- Android-获取Html元素
第一步导包: implementation 'org.jsoup:jsoup:1.10.3' 第二步:需获取解析的Html: <p> <myfont style="colo ...