Mysql分布式锁(一)通过jvm本地锁解决mysql并发问题及可能的失效情况

强烈建议配合之前的JVM本地锁(一)简单实现阅读

mysql场景

将之前的场景修改为mysql场景,即在数据库中保存一条数据,多个线程并发处理该数据。

数据库建表如下

在这里插入图片描述

pom.xml中新增mybatis-plus和mysql

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.3.4</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

application.yml

配置文件中配置好mysql的地址和用户名密码

server:
  port: 10010
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://xxxx:3306/distributed_lock
    username: root
    password: 123

Stock

@TableName("db_stock")
@Data
public class Stock {
    private Long id;
    private String productCode;
    private String warehouse;
    private Integer count;
}

StockMapper

@Mapper
public interface StockMapper extends BaseMapper<Stock> {
}

StockService

@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;

    public void deduct(){
        Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("product_code","1001"));
        if(stock != null && stock.getCount() > 0){
            stock.setCount(stock.getCount()-1);
            stockMapper.updateById(stock);
        }
    }
}

StockController

@RestController
public class StockController {
    @Autowired
    private StockService stockService;

    @GetMapping("stock/deduct")
    public String deduct(){
        stockService.deduct();
        return "hello stock deduct";
    }
}

启动后,修改下Jmeter的http request地址
在这里插入图片描述
再进行测试
在这里插入图片描述

由于需要数据库连接访问,吞吐量明显降低,182。
再看数据库该条数据的count是否清0

在这里插入图片描述
并不是0,并发引起了超卖问题。

JVM本地锁解决mysql并发问题

synchronized

直接用JVM本地锁,加上synchronized

@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;

    public synchronized void deduct(){
        Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("product_code","1001"));
        if(stock != null && stock.getCount() > 0){
            stock.setCount(stock.getCount()-1);
            stockMapper.updateById(stock);
        }
    }
}

重启后,把数据库count修改为5000,再次测试
在这里插入图片描述
吞吐量惨不忍睹,16。
再看数据库,成功清0
在这里插入图片描述

ReentrantLock

简单修改StockService即可

@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;

    private ReentrantLock lock = new ReentrantLock();

    public void deduct(){
        lock.lock();
        try{
            Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("product_code","1001"));
            if(stock != null && stock.getCount() > 0){
                stock.setCount(stock.getCount()-1);
                stockMapper.updateById(stock);
            }
        }finally {
            lock.unlock();
        }
    }
}

重启后,把数据库count修改为5000,再次测试

在这里插入图片描述
吞吐量较synchronized略提升,22。
再看数据库
在这里插入图片描述

问题解决。

mysql中JVM本地锁不生效的三种情况

1. 多例模式

默认情况下,service controller等class都是单例模式,可以通过添加注解成为多例模式

@Service
@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StockService {

重启,修改count数据后再测试
在这里插入图片描述
无错误,再看数据库
在这里插入图片描述
count并没有清0。
因为多例模式,不同线程访问的可能是不同的service实例,那你锁的其实只是一部分,而不是所有的。

2.事务

在deduct方法上添加事务注解@Transactional

@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;

    private ReentrantLock lock = new ReentrantLock();

    @Transactional
    public void deduct(){
        lock.lock();
        try{
            Stock stock = stockMapper.selectOne(new QueryWrapper<Stock>().eq("product_code","1001"));
            if(stock != null && stock.getCount() > 0){
                stock.setCount(stock.getCount()-1);
                stockMapper.updateById(stock);
            }
        }finally {
            lock.unlock();
        }
    }
}

重新测试。
在这里插入图片描述
在这里插入图片描述
依然有问题,这是为什么呢?
举例,如果有两个线程A B同时请求,假设现在count为91

A访问B访问
A获取锁
A查询count:91
A扣减count:90
释放锁
B获取锁
B查询count:91
A提交事务
B扣减count:90
B释放锁
B提交事务

很明显,count为91,AB两个线程并发请求后,只变成了90

根本问题还是先释放锁再提交事务,不能保证原子性。

如果设置事务的隔离级别为 READ_UNCOMMITTED就可解决该问题,但是你读取别人未提交的事务,别人的事务很有可能还会回滚,就会造成脏读问题,所以不能使用READ_UNCOMMITTED。

3. 集群部署

类似多例模式,但还是有所不同。

1) 将服务新增一个端口启动。

在这里插入图片描述

在这里插入图片描述
两个服务都启动成功。

2) nginx负载均衡

修改nginx.conf

vi /usr/local/etc/nginx/nginx.conf

在这里插入图片描述

修改完后重启nginx

brew services restart nginx

3)修改jmeter测试端口为80
4)重新测试

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
很明显看见,有处理同一个库存数现象
数据库也未清0
在这里插入图片描述

总结一下:

jvm本地锁有三种情况后导致锁失效

  1. 多例模式
  2. 事务
  3. 集群部署
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值