(六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题

news/2024/7/5 7:39:11

前言

本节内容是关于使用分布式锁解决并发访问“超卖”问题的最终篇,在前面的章节中我们介绍了使用mysql的行锁、乐观锁、悲观锁解决并发访问导致的超卖问题,存在的问题是行锁、乐观锁、悲观锁不太灵活,需要和具体的业务耦合到一起,不利于使用,本节内容我们通过mysql的唯一索引特性,数据不能重复插入,达到独占排它的基本要求,实现通用的分布式锁功能。

正文

  • 创建表app_lock,用于实现分布式锁

- app_lock表

CREATE TABLE `app_lock` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `lock_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '锁名称',
  `class_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '类名',
  `method_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '方法名称',
  `server_ip` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '服务器IP',
  `thread_name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '线程名称',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `expire_time` datetime DEFAULT NULL COMMENT '过期时间',
  `remark` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_lock_name` (`lock_name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='应用锁';

PS:lock_name为锁名称,建立唯一索引,保证锁的唯一性,server_ip主机信息和thread_name线程号,可以将分布式锁扩展为可重入锁,expire_time过期时间,可通过定时任务或者消息中间键,自动释放锁。

  • 表app_lock实体类创建 
package com.ht.atp.plat.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;


@Data
@TableName("app_lock")
@ApiModel(value = "AppLock对象", description = "应用锁")
public class AppLock implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @ApiModelProperty("锁名称")
    @TableField("lock_name")
    private String lockName;

    @ApiModelProperty("类名")
    @TableField("class_name")
    private String className;

    @ApiModelProperty("方法名称")
    @TableField("method_name")
    private String methodName;

    @ApiModelProperty("服务器IP")
    @TableField("server_ip")
    private String serverIp;

    @ApiModelProperty("线程名称")
    @TableField("thread_name")
    private String threadName;

    @ApiModelProperty("创建时间")
    @TableField("create_time")
    private LocalDateTime createTime;

    @ApiModelProperty("过期时间")
    @TableField("expire_time")
    private LocalDateTime expireTime;

    @ApiModelProperty("备注")
    @TableField("remark")
    private String remark;


}
  • 创建应用锁的加锁和解锁方法 

- 实现代码

package com.ht.atp.plat.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ht.atp.plat.entity.AppLock;
import com.ht.atp.plat.mapper.AppLockMapper;
import com.ht.atp.plat.service.AppLockService;
import org.springframework.stereotype.Service;


@Service
public class AppLockServiceImpl extends ServiceImpl<AppLockMapper, AppLock> implements AppLockService {

    @Override
    public void lock(AppLock appLock) {
        try {
            this.baseMapper.insert(appLock);
        } catch (Exception ex) {
            // 获取锁失败,则重试
            try {
                Thread.sleep(50);
                this.lock(appLock);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    public void unlock(AppLock appLock) {
        this.baseMapper.deleteById(appLock.getId());
    }
}

- 通过自旋的方式加锁

- 解锁直接将锁删除

  • 实现扣减库存的业务代码 

- 实现代码

    @Autowired
    private AppLockService appLockService;

    @Override
    public void checkAndReduceStock() {
        AppLock appLock = new AppLock();
        appLock.setLockName("lock");
        appLock.setClassName(this.getClass().getName());
        appLock.setMethodName("checkAndReduceStock");
        appLock.setServerIp("127.0.0.1");
        appLock.setThreadName(Thread.currentThread().getName());
        appLock.setCreateTime(LocalDateTime.now());
        appLock.setExpireTime( LocalDateTime.now().plusSeconds(30));
        appLock.setRemark("这是一个测试用锁!");
        try {
            // 加锁
            appLockService.lock(appLock);

            // 查询库存
            WmsStock wmsStock = baseMapper.selectById(1L);
            // 验证库存大于0再扣减库存
            if (wmsStock != null && wmsStock.getStockQuantity() > 0) {
                wmsStock.setStockQuantity(wmsStock.getStockQuantity() - 1);
                baseMapper.updateById(wmsStock);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
            appLockService.unlock(appLock);
        }


    }

  • 启动7000、7001、7002服务器,并将库存设置为10000

 

  •  启动jmeter压测扣减库存接口

  • 最终压测结果

 - 库存扣减为:0

-  压测结果:平均响应时间3461ms,每秒钟的吞吐量为27,性能相比redis和zookeeper锁,性能相差不少。

  • 关于mysql分布式锁的几点总结 

- mysql数据库存在单点故障,一旦数据库宕机,将导致应用锁不可用,可通过搭建主备数据库保证高可用

- 没有实现自动释放锁:可通过定时任务扫描应用锁数据,如果过期时间小于当前时间,将锁删除掉;或者通过消息中间件去删除锁

- 锁是非重入的:可以通过使用主机信息、端口号、线程号等信息判断是否是同一个线程,如果是同一个线程,则锁可重入

- mysql数据库分布式锁性能一般,不及redis和zookeeper分布式锁,使用简单

结语

关于使用mysql分布式锁解决“超卖”问题的内容到这里就结束了,后会有期。。。。。。


http://lihuaxi.xjx100.cn/news/1766963.html

相关文章

【PTE-day05 宽字节注入】

1、函数 过滤输入的函数: addslashes mysql_real_escape_string mysql_escape_string当字符的大小为一个字节时,称之为窄字节 例如ascii编码 当字符的大小为两个字节时,称之为宽字节 例如GB2312、GBK、GB8030 mysql使用GBK编码时,默认的会认为两个字符为一个汉字,前一个字…

省钱兄短剧短视频小程序是如何运营的?

随着短视频的兴起&#xff0c;越来越多的人热衷于在闲暇时间刷短视频。在这种背景下&#xff0c;省钱兄短剧短视频小程序应运而生&#xff0c;以一种新颖、有趣的方式满足了用户的观看需求。本文将详细介绍这个小程序的成功运营策略。 产品特点 内容丰富&#xff1a;省钱兄短…

1212. 地宫取宝

题目&#xff1a; 1212. 地宫取宝 - AcWing题库 思路&#xff1a;dp&#xff08;最长上升子序列和摘花生的结合&#xff09; 代码&#xff1a; #include<iostream> using namespace std; const int N 55; const int MOD 1000000007;int n, m, k; int w[N][N];//每个坐…

图形界面应用案例——关灯游戏(以及扩展)(python)

7.8 图形界面应用案例——关灯游戏 题目: [案例]游戏初步——关灯游戏。 关灯游戏是很有意思的益智游戏,玩家通过单击关掉(或打开)一盏灯。如果关(掉(或打开)一个电灯,其周围(上下左右)的电灯也会触及开关,成功地关掉所有电灯即可过关。 图7-43 关灯游戏运行效…

Gerrit lfs安装及配置

Gerrit版本&#xff1a;3.1.4 lfs下载&#xff1a;Zuul Gerrit CI界面已经没有3.1.4对应版本的lfs.jar了&#xff0c;需要从上面的页面下载。 一、安装配置lfs 将上面下载的lfs.jar放到$GERRIT_SITE/plugins目录。 修改配置文件&#xff1a;$GERRIT_SITE/etc/gerrit.config …

2023/11/8JAVA学习

多个条件也可以&&放在一块,支持链式编程 map()数据加工,一个对象转化为另一个 不能这样写 不会去重报错

小程序制作(超详解!!!)第十五节 自动随机变化的三色旗

1.例题描述 设计一个小程序&#xff0c;开始时界面上显示一个三色旗和一个按钮&#xff0c;当点击按钮时&#xff0c;三色旗的颜色会发生随机变化&#xff0c;即使不点击按钮&#xff0c;三色旗的颜色也会每隔一定时间自动发生变化。 2.index.wxml <view class"box&…

Python算法例8 将整数A转换为B

1. 问题描述 给定整数A和B&#xff0c;求出将整数A转换为B&#xff0c;需要改变bit的位数。 2. 问题示例 把31转换为14&#xff0c;需要改变2个bit位&#xff0c;即&#xff1a;&#xff08;31&#xff09;10&#xff08;11111&#xff09;2&#xff0c;&#xff08;14&…