分布式锁之mysql实现

这篇具有很好参考价值的文章主要介绍了分布式锁之mysql实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本地jvm锁

分布式锁之mysql实现,springCloud,分布式

 搭建本地卖票案例

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个请求测试买票,查看是否出现超卖问题

分布式锁之mysql实现,springCloud,分布式

出现了超卖问题分布式锁之mysql实现,springCloud,分布式

本地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实现,springCloud,分布式

将共享资源放入mysql

分布式锁之mysql实现,springCloud,分布式

 查库操作,演示超卖现象

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,压测结果,显示超卖 

分布式锁之mysql实现,springCloud,分布式

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

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,压测结果显示,可以解决超卖现象 

分布式锁之mysql实现,springCloud,分布式

本地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,压测显示超卖现象

分布式锁之mysql实现,springCloud,分布式

集群部署失效

下载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

分布式锁之mysql实现,springCloud,分布式

修改压测地址

分布式锁之mysql实现,springCloud,分布式

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

分布式锁之mysql实现,springCloud,分布式

一条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实现,springCloud,分布式

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实现,springCloud,分布式

mysql乐观锁解决失效问题

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

添加版本号列

分布式锁之mysql实现,springCloud,分布式

修改实体

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锁都避免不了三种失效的场景,根据实际情况选择即可文章来源地址https://www.toymoban.com/news/detail-678109.html

到了这里,关于分布式锁之mysql实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 最新版 !快速掌握 JDK17 + springboot3 + springcloud Alibaba : 10、Seata 整合实现分布式事务

    上一节成功启动了seata,传送门: https://blog.csdn.net/qq_16089135/article/details/133989446 1.1 官方文档 中文文档 Seata 是什么 1.2 模式分类 AT :基于支持本地 ACID 事务的关系型数据库。 Java 应用,通过 JDBC 访问数据库。 整体机制:二阶段提交。 一阶段:业务数据和回滚日志记录在同一

    2024年02月06日
    浏览(53)
  • 分布式springcloud

    微服务之间相互调用,因为调用链中的一个服务故障,引起整个链路都无法访问的情况。 微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。服务器支持的线程和并发数有限,到来的请求一直阻塞,会导致服务器资源耗尽, 从而导致所有其它服务

    2024年02月06日
    浏览(49)
  • 如何使用mysql实现分布式锁

    目录 什么是分布式锁? 如何实现分布式锁? 定义分布式表结构 定义锁统一接口 使用mysql来实现分布式锁 ① 生成线程标记ID ② 加锁 ③ 解锁 ④ 重置锁 写在最后 1. 什么是分布式锁? 百度百科:分布式锁是控制分布式系统之间同步访问共享资源的一种方式。 ㅤ如引用所述,

    2024年02月05日
    浏览(39)
  • 分布式锁实现(mysql,以及redis)以及分布式的概念(续)redsync包使用

    这张尽量结合上一章进行使用:上一章 这章主要是讲如何通过 redis 实现分布式锁的 这里我用 redis 去实现: 技术: golang , redis , 数据结构 这里是有一个大体的实现思路:主要是使用 redis 中这些语法 redis 命令说明: setnx 命令: set if not exists ,当且仅当 key 不存在时,将 ke

    2024年01月22日
    浏览(63)
  • 【Springcloud】分布式搜索elasticsearch

    先看下翻译: elasticsearch是一款非常强大的 开源搜索引擎 ,可以帮助我们 从海量数据中快速找到需要的内容 以下是生活中ES的使用场景: 项目在运行的时候会产生海量的日志信息,而elasticsearch结合kibana、Logstash、Beats,也就是elastic stack( ELK ),即ELK技术栈。被广泛应用在

    2024年02月08日
    浏览(41)
  • 【Springcloud】elk分布式日志

    (1)什么是分布式日志 在分布式应用中,日志被分散在储存不同的设备上。如果你管理数十上百台服务器,你还在使用依次登录每台机器的传统方法查阅日志。这样是不是感觉很繁琐和效率低下。所以我们使用集中化的日志管理,分布式日志就是对大规模日志数据进行采集

    2024年02月09日
    浏览(41)
  • SpringCloud(17~21章):Alibaba入门简介、Nacos服务注册和配置中心、Sentinel实现熔断与限流、Seata处理分布式事务

    Spring Cloud Netflix项目进入维护模式 https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now 说明 Spring Cloud Netflix Projects Entering Maintenance Mode 什么是维护模式 将模块置于维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能。我们将修复 block 级别的 bug 以及安全问题,我

    2024年01月19日
    浏览(58)
  • SpringCloud分布式配置中心——Config

    本专栏学习内容来自尚硅谷周阳老师的视频 有兴趣的小伙伴可以点击视频地址观看 由于微服务越来越多,项目越来越庞大,每一个项目都至少有两三个不同环境的application.properties文件,不易管理,假设我们数据库迁移,那么所有配置文件中有关数据库的地址都需要更改,不

    2023年04月22日
    浏览(37)
  • springcloud sleuth分布式请求链路跟踪

    在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败. Spring Cloud Sleuth提供了一套完

    2024年01月16日
    浏览(47)
  • SpringCloud——分布式请求链路跟踪Sleuth

    SpringCloud从F版已不需要自己构建Zipkin Server,只需要调用jar包即可 https://dl.bintray.com/oenzipkin/maven/io/zipkin/java/zipkin-server/ 下载:zipkin-server-2.12.9-exec.jar 运行:java -jar zipkin-server-2.12.9-exec.jar 浏览器访问: ================================================================================ 一条链路通过

    2024年02月16日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包