JavaWeb 并发:FOR UPDATE 实战,监测并解决【转】
一、前言
针对并发,老生常谈了。目前一个通用的做法有两种:锁机制:1.悲观锁;2.乐观锁。
但是这篇我主要用于记录我这次处理的经历,另外希望能看的大神,大牛,技师者,学长,兄长,大哥们能在评论中发表自己的看法和解决技巧等。
二、故事是这样的
一个表,暂且叫 wallet,其中3个字段是 金额。初始值为0,如下图所示:
然后我们写了一个极为简单的Controller,并写了下面的Service代码:
@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如下:
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 ,但在不同的平台上有更好的可用性,安装使用也比较简单。
简单使用方式如下:
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:
可见后台程序报错了。什么错误呢?
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方法:
@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如下:
< 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
自然大家可以看到,我这边加了锁,是通过主键锁行。
按着上面的测试连接数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:当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理
< 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:线程优先级
< Executorname = "tomcatThreadPool" namePrefix = "req-exec-" maxThreads = "1000"minSpareThreads = "50" maxIdleTime = "60000" /> < Connectorport = "8080" protocol = "HTTP/1.1" executor = "tomcatThreadPool" /> | |
maxThreads:线程池的最大线程数,直接配置1000,然后用连接数10000,并发数800测试。轻松见图:
七、总结
感谢帮助我的人。希望有大牛在此讨论相关。小生感激不尽。
Writer:BYSocket(泥沙砖瓦浆木匠)
微博:BYSocket
豆瓣:BYSocket

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Maven私有库和本地库的安装与配置 Sonatype Nexus + Maven
样例参考视频:http://www.roncoo.com/course/view/85d6008fe77c4199b0cdd2885eaeee53 环境:CentOS 6.6 Final、JDK7、Sonatype Nexus、Maven IP:192.168.4.221 root用户操作 前提:已安装JDK7并配置好了环境变量 1、下载最新版Nexus(本教程使用的是:nexus-2.11.2-03-bundle.tar.gz),下载地址:http://www.sonatype.org/nexus/go/ # wget https://sonatype-download.global.ssl.fastly.net/nexus/oss/nexus-2.11.2-03-bundle.tar.gz 2、解压 # mkdir nexus # tar -zxvf nexus-2.11.2-03-bundle.tar.gz -C nexus # cd nexus # ls nexus-2.11.2-03 sonatype-work (一个nexus服务,一个私有库目录) 3、编辑Nexu...
- 下一篇
Docker 基础 : Dockerfile
Dockerfile 是一个文本格式的配置文件,用户可以使用 Dockerfile 快速创建自定义的镜像。我们会先介绍 Dockerfile 的基本结构及其支持的众多指令,并具体讲解通过执行指令来编写定制镜像的 Dockerfile。 基本结构 Dockerfile 由一行行命令语句组成,并且支持已 # 开头的注释行。一般而言,Dockerfile 的内容分为四个部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。例如: #ThisdockerfileusestheUbuntuimage #VERSION2#Author:docker_user #Commandformat:Instruction[arguments/command]… #第一行必须指定基于的容器镜像 FROMubuntu #维护者信息 MAINTAINERdocker_userdocker_user@email.com #镜像的操作指令RUNecho“debhttp://archive.ubuntu.com/ubuntu/raringmainuniverse”>>/etc/apt/sourc...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7安装Docker,走上虚拟化容器引擎之路
- 2048小游戏-低调大师作品
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7设置SWAP分区,小内存服务器的救世主
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS6,7,8上安装Nginx,支持https2.0的开启