分布式锁之mysql实现

news2024/11/27 3:51:25

本地jvm锁

 搭建本地卖票案例

package com.test.lockservice.service.impl;

import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author sl
 */
@Service
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

    private  int count = 5000;

    @Override
    public void sellTicket() {
        count = count -1;
        System.out.println("count:"+ count);
    }
}

使用jmeter压测

5000个请求测试买票,查看是否出现超卖问题

出现了超卖问题

本地synchronized和ReentrantLock解决本地超卖问题

package com.test.lockservice.service.impl;

import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author sl
 */
@Service
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

    private  int count = 5000;
    
    @Override
    public synchronized void sellTicket() {
        count = count -1;
        System.out.println("count:"+ count);
    }
}

或者使用ReentrantLock

package com.test.lockservice.service.impl;

import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author sl
 */
@Service
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

    private  int count = 5000;

    private ReentrantLock lock = new ReentrantLock();
    @Override
    public  void sellTicket() {
        lock.lock();
        try {
            count = count -1;
            System.out.println("count:"+ count);
        }finally {
            lock.unlock();
        }
    }
}

jmeter压测结果显示,5000总票数,压测5000,都能够解决超卖的现象 

将共享资源放入mysql

 查库操作,演示超卖现象

package com.test.lockservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.model.Ticket;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author sl
 */
@Service
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

//    private ReentrantLock lock = new ReentrantLock();
    @Override
    public  void sellTicket() {
//        lock.lock();
        try {
            // 查询票数
            Ticket ticket = ticketMapper.selectOne(new QueryWrapper<Ticket>().eq("sell_company", "12306"));
            // 判断不为空和票数大于0
            if(ticket!=null&& ticket.getCount() > 0){
                ticket.setCount(ticket.getCount()-1);
                ticketMapper.updateById(ticket);
            }
        }finally {
//            lock.unlock();
        }
    }
}

5000总票数,压测5000,压测结果,显示超卖 

加锁,本地锁解决超卖现象

package com.test.lockservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.model.Ticket;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Service;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author sl
 */
@Service
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

    private ReentrantLock lock = new ReentrantLock();
    @Override
    public  void sellTicket() {
        lock.lock();
        try {
            // 查询票数
            Ticket ticket = ticketMapper.selectOne(new QueryWrapper<Ticket>().eq("sell_company", "12306"));
            // 判断不为空和票数大于0
            if(ticket!=null&& ticket.getCount() > 0){
                ticket.setCount(ticket.getCount()-1);
                ticketMapper.updateById(ticket);
            }
        }finally {
            lock.unlock();
        }
    }
}

5000总票数,压测5000,压测结果显示,可以解决超卖现象 

本地jvm锁失效的三种情况

1多例模式失效

2事务失效(@Transactional)

3集群部署失效(相当于多例模式,只不过是多个节点)

多例模式失效

@Scope(scopeName="prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)

package com.test.lockservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.model.Ticket;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author sl
 */
@Service
@Scope(scopeName="prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

    private ReentrantLock lock = new ReentrantLock();
    @Override
    public  void sellTicket() {
        lock.lock();
        try {
            // 查询票数
            Ticket ticket = ticketMapper.selectOne(new QueryWrapper<Ticket>().eq("sell_company", "12306"));
            // 判断不为空和票数大于0
            if(ticket!=null&& ticket.getCount() > 0){
                ticket.setCount(ticket.getCount()-1);
                ticketMapper.updateById(ticket);
            }
        }finally {
            lock.unlock();
        }
    }
}

5000总票数,压测1000,压测显示超卖现象

事务失效

@Transactional注解是aop开启的手动事务,代表一组操作,要么都成功,要么都失败,在代码中,释放锁过后,如果当前事务还未提交,其他线程获得了锁,在可重复读的隔离级别之下,会出现重复售卖的问题

a用户b用户
begin开启事务begin开启事务
获取锁
查询票数5000
扣减票数4999
释放锁
得到锁
查询票数5000
扣减票数4999
package com.test.lockservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.model.Ticket;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author sl
 */
@Service
@Transactional
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

    private ReentrantLock lock = new ReentrantLock();
    @Override
    public  void sellTicket() {
        lock.lock();
        try {
            // 查询票数
            Ticket ticket = ticketMapper.selectOne(new QueryWrapper<Ticket>().eq("sell_company", "12306"));
            // 判断不为空和票数大于0
            if(ticket!=null&& ticket.getCount() > 0){
                ticket.setCount(ticket.getCount()-1);
                ticketMapper.updateById(ticket);
            }
        }finally {
            lock.unlock();
        }
    }
}

5000总票数,压测1000,压测显示超卖现象

集群部署失效

下载nginx

http://nginx.org/en/download.html

配置nginx.conf


    upstream test{
        server localhost:10010;
        server localhost:10086;
    }
    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_pass http://test;
        }

    }

启动两个程序

复制服务 -Dserver.port = 10086

修改压测地址

 5000总票数,压测1000,压测显示超卖现象

一条sql语句解决本地锁三种失效情况

优化所有操作为一条语句,因为数据库增删改自动加锁,保证了原子性问题

缺点

  • 注意下锁的范围(当更新条件或者查询条件没命中索引时,是表锁,命中索引为行锁)
  • 同一票数在多个售票点存在售卖记录
  • 无法记录票数变化前后的数据

修改mapper

package com.test.lockservice.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.test.lockservice.model.Ticket;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;

/**
 * @Author sl
 */
@Mapper
@Repository
public interface TicketMapper extends BaseMapper<Ticket> {

    @Update("update ticket set count = count - #{count} where sell_company=#{company} and count> 1")
    void updateByCompany(@Param("company") String company ,@Param("count") Integer count);
}

修改service

package com.test.lockservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.model.Ticket;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author sl
 */
@Service
public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;


    @Override
    public  void sellTicket() {

        ticketMapper.updateByCompany("12306",1);
    }
}

压测测试 

 5000总票数,压测1000,压测无超卖现象

mysql悲观锁解决失效问题

select ... for update,为语句加锁,解决失效问题

注意使用行级锁:

  • 锁的查询和更新条件必须是索引字段
  • 查询或者更新条件必须为具体值
  • 注意添加事务注解

修改mapper

package com.test.lockservice.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.test.lockservice.model.Ticket;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Author sl
 */
@Mapper
@Repository
public interface TicketMapper extends BaseMapper<Ticket> {


    @Select("select * from ticket where sell_company='12306' for update")
    List<Ticket> findList();
}

修改service添加事务

package com.test.lockservice.service;

import org.springframework.transaction.annotation.Transactional;

/**
 * @Author sl
 */

public interface TicketService {

  @Transactional
  public void sellTicket();
}

修改service

package com.test.lockservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.model.Ticket;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author sl
 */
@Service

public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

    @Override
    @Transactional(rollbackFor = {})
    public  void sellTicket() {
        // 查询所有结果
        List<Ticket> tickets = ticketMapper.findList();

        //取第一条记录,默认不为空指针,不做判空了哈
        Ticket ticket = tickets.get(0);

        if(ticket!= null && ticket.getCount()>0){
            ticket.setCount(ticket.getCount()-1);
            ticketMapper.updateById(ticket);
        }
    }
}

压测测试

 5000总票数,压测1000,压测无超卖现象,一定要用行级锁,否则性能太慢

mysql乐观锁解决失效问题

mysql乐观锁,采用加时间戳、版本号的方式采用cas的方式解决,mysql中没有提供cas的实现方式,需要在程序中手动实现,无需加事务注解,因为查询为for update,update本身也会加锁

添加版本号列

修改实体

package com.test.lockservice.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

/**
 * @Author sl
 */
@TableName(value = "ticket")
public class Ticket {

    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;

    private Integer count;

    private String sellCompany;
    
    private Integer version;

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public String getSellCompany() {
        return sellCompany;
    }

    public void setSellCompany(String sellCompany) {
        this.sellCompany = sellCompany;
    }
}

修改service

package com.test.lockservice.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.test.lockservice.mapper.TicketMapper;
import com.test.lockservice.model.Ticket;
import com.test.lockservice.service.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author sl
 */
@Service

public class TicketServiceImpl implements TicketService {

    @Autowired
    private TicketMapper ticketMapper;

    @Override
    public  void sellTicket() throws InterruptedException {
        // 查询所有结果
        List<Ticket> tickets = ticketMapper.findList();

        //取第一条记录,默认不为空指针,不做判空了哈
        Ticket ticket = tickets.get(0);

        if(ticket!= null && ticket.getCount()>0){
            ticket.setCount(ticket.getCount()-1);
            // 更新版本号
            Integer version = ticket.getVersion();
            ticket.setVersion(version+1);
            // 如果影响条件为0的话证明更新失败
            if(ticketMapper.update(ticket,new QueryWrapper<Ticket>().eq("id",ticket.getId()).eq("version",version))==0){
                Thread.sleep(20);
                this.sellTicket();
            }

        }
    }
}

压测测试 

 5000总票数,压测1000,压测无超卖现象

小结

性能: 一个sql > 悲观锁 > JVM锁 > 乐观锁

在解决分布式锁的问题中,不要使用JVM锁,因为基本分布式问题,jvm锁都避免不了三种失效的场景,根据实际情况选择即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/940705.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

定时任务库的详解与魅力应用:探索schedule的无尽可能性

文章目录 摘要一些示例其他的示例向任务传递参数取消任务运行一次任务获取所有任务取消所有任务按标签获取多个任务以随机间隔运行作业运行一项作业&#xff0c;直到某个时间距离下次执行的时间立即运行所有作业&#xff0c;无论其计划如何在后台运行多个调度器 记录日志自定义…

海康机器人工业相机 Win10+Qt+Cmake 开发环境搭建

文章目录 一. Qt搭建海康机器人工业相机开发环境 一. Qt搭建海康机器人工业相机开发环境 参考这个链接安装好MVS客户端 Qt新建一个c项目 cmakeList中添加海康机器人的库&#xff0c;如下&#xff1a; cmake_minimum_required(VERSION 3.5)project(HIKRobotCameraTest LANG…

微信小程序隐私协议接入

自2023年9月15日起&#xff0c;对于涉及处理用户个人信息的小程序开发者&#xff0c;微信要求&#xff0c;仅当开发者主动向平台同步用户已阅读并同意了小程序的隐私保护指引等信息处理规则后&#xff0c;方可调用微信提供的隐私接口。 相关公告见&#xff1a;关于小程序隐私保…

计算机竞赛 基于机器视觉的二维码识别检测 - opencv 二维码 识别检测 机器视觉

文章目录 0 简介1 二维码检测2 算法实现流程3 特征提取4 特征分类5 后处理6 代码实现5 最后 0 简介 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器学习的二维码识别检测 - opencv 二维码 识别检测 机器视觉 该项目较为新颖&#xff0c;适合作为竞赛课…

格创校园跑腿小程序独立版v2.0.7+前端

应用介绍 格创校园跑腿SAAS管理系统小程序独立版v2.0.7前端 格创校园跑腿小程序系统独立版是一款基于ThinkPHP6框架开发的多校园专业跑腿平台&#xff0c;可应用至小区、街区、园区、厂区等。 格创校园跑腿小程序系统是校园创业版块热门应用&#xff0c;全新UI界面&#xff0c…

(202308)科研论文配图 task5 安装LaTex + 书籍第二章SciencePlots部

SciencePlots 序言阅读笔记绘图包介绍Windows下安装Windows下的安装MikTexWindows下的安装ghostscript加入系统环境变量安装scienceplots 序言 有幸在这次的组队学习活动中&#xff0c;拜读宁海涛先生的《科研论文配图绘制指南——基于python》一书&#xff0c;这本书文辞亲切…

【网络安全带你练爬虫-100练】第19练:使用python打开exe文件

目录 一、目标1&#xff1a;调用exe文件 二、目标2&#xff1a;调用exe打开文件 一、目标1&#xff1a;调用exe文件 1、subprocess 模块允许在 Python 中启动一个新的进程&#xff0c;并与其进行交互 2、subprocess.run() 函数来启动exe文件 3、subprocess.run(["文件路…

【百草阁送书-第二期】一名阿里服务端开发工程师的进阶之路

文章目录 一、前言二、AI 时代&#xff0c;服务端开发面临新挑战三、服务端开发会被 AI 取代吗&#xff1f;四、知识体系化&#xff0c;构建核心竞争力五、业界首本体系化、全景式解读服务端开发的著作六、参与抽奖方式 一、前言 目前&#xff0c;资讯、社交、游戏、消费、出行…

c++(8.28)菱形继承,虚继承,多态,抽象类,模板+Xmind

xmind: 作业&#xff1a; 1.编程题&#xff1a; 以下是一个简单的比喻&#xff0c;将多态概念与生活中的实际情况相联系&#xff1a; 比喻&#xff1a;动物园的讲解员和动物表演 想象一下你去了一家动物园&#xff0c;看到了许多不同种类的动物&#xff0c;如狮子、大象、猴…

OLED透明屏是什么?什么叫做OLED透明屏的原屏?

OLED透明屏是一种新型的显示技术&#xff0c;具有高对比度、高亮度和能耗低等优势&#xff0c;正被越来越广泛地应用于各个领域中。 在OLED透明屏中&#xff0c;原屏是至关重要的元件之一。本文将深入探讨OLED透明屏原屏的意义、制造过程、品质要求、应用案例和发展趋势&#…

2023-8-28 排列数字(DFS)

题目链接&#xff1a;排列数字 #include <iostream>using namespace std;const int N 10;int n;int path[N];bool st[N];// u 看第几个位置 void dfs(int u) {if(u n){for(int i 0; i < n; i ) cout << path[i] << ;cout << endl;return ;}//…

初试Eureka注册中心

Eureka是spring cloud中的一个负责服务注册与发现的组件。遵循着CAP理论中的A(可用性)P(分区容错性)。一个Eureka中分为eureka server和eureka client。其中eureka server是作为服务的注册与发现中心。 搭建eureka服务 引入eureka依赖 引入SpringCloud为eureka提供的starter依…

0828|C++day6 菱形继承+虚继承+多态+抽象类+模板

一、思维导图 二、今日知识回顾 1&#xff09;多态 父类的指针或者引用&#xff0c;指向或初始化子类的对象&#xff0c;调用子类对父类重写的函数&#xff0c;进而展开子类的功能。 #include <iostream> using namespace std;class Zhou { private:string name;int age…

论文阅读_扩散模型_LDM

英文名称: High-Resolution Image Synthesis with Latent Diffusion Models 中文名称: 使用潜空间扩散模型合成高分辨率图像 地址: https://ieeexplore.ieee.org/document/9878449/ 代码: https://github.com/CompVis/latent-diffusion 作者&#xff1a;Robin Rombach 日期: 20…

Spring补充

一.Spring JDB 配置两个jar包 <!-- spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.2.RELEASE</version> </dependency> <!-- 阿里数据…

Python实现T检验

今天来分享一下T检验的python实现方法。 01 先来上一波概念。 1.单样本t检验&#xff0c;又称单样本均数t检验&#xff0c;适用于来自正态分布的某个样本均数与已知总体均数的比较&#xff0c;其比较目的是检验样本均数所代表的总体均数是否与已知总体均数有差别。已知总体均数…

权限提升-数据库提权-MSF-UDF提权

权限提升基础信息 1、具体有哪些权限需要我们了解掌握的&#xff1f; 后台权限&#xff0c;网站权限&#xff0c;数据库权限&#xff0c;接口权限&#xff0c;系统权限&#xff0c;域控权限等 2、以上常见权限获取方法简要归类说明&#xff1f; 后台权限&#xff1a;SQL注入,数…

PVE 8.0.4 配置记录

前言 七夕收到了媳妇送的礼物 Beelink SER 5 PRO (Ryzen 5700U), 记录打造成私人服务器的过程. 下载安装 Proxmox 8.0.4 https://www.proxmox.com/en/downloads 安装过程中修改磁盘设置: swap 分区设置为物理内存的 2 倍, 防止虚机太多内存不足 root 最大设置为 32 GB, 多了…

SpringCloud入门——微服务调用的方式 RestTemplate的使用 使用nacos的服务名初步(Ribbon负载均衡)

目录 引出微服务之间的调用几种调用方法spring提供的组件 RestTemplate的使用导入依赖生产者模块单个配置的情况多个配置的情况没加.yaml的报错【报错】两个同名配置【细节】 完整代码config配置主启动类controller层 消费者模块进行配置restTemplate配置类controller层 使用na…

云渲染对本地电脑要求高不高?对配置有要求吗?

自己本地电脑渲不动&#xff0c;又没有用过云渲染的朋友们一般都会有这样的疑问&#xff1a;云渲染对电脑要求高不高&#xff1f;需要什么样的配置才能用上云渲染&#xff1f; 其实云渲染对本地电脑的配置是完全没有要求的&#xff0c;相反它还能减轻你本地电脑的运行负担&…