zoukankan      html  css  js  c++  java
  • 监测并解决

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

     

    Writer:BYSocket(泥沙砖瓦浆木匠)

    微博:BYSocket

    豆瓣:BYSocket

    一、前言

    针对并发,老生常谈了。目前一个通用的做法有两种:锁机制: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

    七、总结

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

     
  • 相关阅读:
    C# 文件类的操作---删除
    C#实现Zip压缩解压实例
    UVALIVE 2431 Binary Stirling Numbers
    UVA 10570 meeting with aliens
    UVA 306 Cipher
    UVA 10994 Simple Addition
    UVA 696 How Many Knights
    UVA 10205 Stack 'em Up
    UVA 11125 Arrange Some Marbles
    UVA 10912 Simple Minded Hashing
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/4358081.html
Copyright © 2011-2022 走看看