分布式锁问题_演示问题

news2024/11/17 19:34:52

 通过idea创建两个服务

 启动Nginx服务

下载Nginx windows服务,官网nginx: download

当然我这里提供了:

我们打开nginx的conf目录,然后打开配置文件nginx.conf进行配置

upstream test{

      server localhost:9090 ;

      server localhost:9091 ;

    }

    server {

        listen       80;

        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {

            proxy_pass http://test;

        }

当然我已经帮你配置好了含义就是当我请求80端口服务的时候nginx帮我代理上面的两个服务,负载均衡类型为轮询

启动nginx即可

然后我们把数据库的库存修改为2,其他表的数据清空,一会我要进行测试

启动9090和9091服务

打开Jmeter进行测试,把端口进行修改成80

启动测试,它有可能会发生问题

你会发现数据库创建了3个订单,说明出现了并发问题【其实就是分布式下锁出现的问题】

分布式锁解决方案

分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一 致性(Consistency)、可用性(Availability)和分区容错性 (Partition tolerance),最多只能同时满足两项。”所以,很多系 统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的 场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只 需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范 围内即可。

分布式锁实现方案

基于数据库实现的分布式锁

基于数据库实现分布式锁主要是利用数据库的唯一索引来实现,唯一索引天然具有排他性,这刚好符合我们对锁的要求:同一时刻只能允许一个竞争者获取锁。【我们这里会通过两种操作来实现1悲观锁,2乐观锁】

基于 Redis 实现的分布式锁

 使用Redis来实现分布式锁的效率最高,加锁速度最快,因为Redis几乎都是纯内存操作,而基于数据库的方案和基于Zookeeper的方案都会涉及到磁盘文件IO,效率相对低下。一般使用Redis来实现分布式锁都是利用Redis的SETNX key value这个命令,只有当key不存在时才会执行成功,如果key已经存在则命令执行失败。

基于 Zookeeper 实现的分布式锁

 Zookeeper一般用作配置中心,其实现分布式锁的原理和Redis类似,我们在Zookeeper中创建临时顺序节点,利用节点不能重复创建的特性来保证排他性。

分布式锁解决方案_数据库悲观锁实现的分布式锁

 什么是悲观锁

顾名思义,就是比较悲观的锁,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁, 这样别人想拿这个数据就会阻塞直到它拿到锁。

通过for upate进行上锁操作

在Mysql中开启2个命令行界面

 

 

 现第二个客户端什么都没查询出来,因为第一个客户端查询数据的使用因为for update 语句持有了1001这条数据的锁,如果我不提交事物【commit命令】,客户端二是不能查询出来数据的这就是我们所说的悲观锁

如果我么commit

 

我们客户端一commit后释放了锁,那么客户端二才能查询出来数据

下面我们在程序中通过悲观锁来解决问题

修改mapper包下的接口ProductMapper,创建查询方法:

package com.ss.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ss.demo.domain.Product;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 *  Mapper 接口
 * </p>
 */
public interface ProductMapper extends BaseMapper<Product> {
    Product findById(@Param("id") Integer id);
}

修改映射文件ProductMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ss.demo.mapper.ProductMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ss.demo.domain.Product">
        <id column="id" property="id" />
        <result column="product_name" property="productName" />
        <result column="price" property="price" />
        <result column="count" property="count" />
        <result column="product_desc" property="productDesc" />
        <result column="version" property="version" />
    </resultMap>

    <!--根据Id查询数据-->
    <select id="findById" resultType="com.ss.demo.domain.Product">
        SELECT * FROM product WHERE id = #{id} FOR UPDATE
    </select>

</mapper>

修改service在接口ITOrderService中添加悲观锁的方法

package com.ss.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.ss.demo.domain.TOrder;

/**
 * <p>
 *  服务类
 * </p>
 */
public interface ITOrderService extends IService<TOrder> {
    /**
     * 创建订单方法
     * @param productId
     * @param count
     * @return
     */
    String createOrder(Integer productId, Integer count);

    /**
     * 使用悲观锁进行实现
     * @param productId
     * @param count
     * @return
     */
    String createOrderPessimisticlock(Integer productId, Integer count);
}

实现类:TOrderServiceImpl

/**
 * 悲观锁操作
 * 创建订单操作
 * @param productId
 * @param count
 * @return
 */

@Transactional
@Override
public String createOrderPessimisticlock(Integer productId, Integer count) {
    //根据商品id获取商品信息
    Product product = productMapper.findById(productId);
    if(product == null) {
        throw new RuntimeException("购买商品不存在");
    }
    log.info(Thread.currentThread().getName() +"库存数量" + product.getCount());
    //校验库存
    if(count > product.getCount()) {
        throw new RuntimeException("库存不足");
    }
    //更新库存
    Integer iCount = product.getCount() - count;
    product.setCount(iCount);
    //更新操作
    productMapper.updateById(product);
    //创建订单操作
    TOrder order = new TOrder();
    order.setOrderStatus(1);
    order.setReceiverName("张三");
    order.setReceiverMobile("12345678765");
    //设置订单价格【商品单价*商品数量】
    order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));
    orderMapper.insert(order);  //插入订单操作
    //创建订单商品表的操作
    OrderItem orderItem = new OrderItem();
    orderItem.setOrderId(order.getId());     //订单Id
    orderItem.setProduceId(product.getId());  //商品Id
    orderItem.setPurchasePrice(product.getPrice()); //购买价格
    orderItem.setPurchaseNum(count);   //购买数量
    orderItemMapper.insert(orderItem);
    return order.getId();
}

修改controller:TorderController

package com.ff.test.controller;
import com.ff.test.service.ITOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.zip.CheckedOutputStream;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author laozhang
 * @since 2023-05-12
 */
@RestController
@RequestMapping("/order")
public class TOrderController {

    @Autowired
    private ITOrderService orderService;

    @PostMapping("/order")
    public String createOrder(Integer productId, Integer count) {
        //String orderId = orderService.createOrder(productId, count);
        String orderId = orderService.createOrderPessimisticlock(productId, count);
        return orderId;
    }

}

数据库商品表恢复2条数据,其他数据删除

启动9090,9091服务,并使用Jmeter进行测试:

解决问题

分布式锁解决方案_数据库乐观锁实现的分布式锁

 

乐观锁试用于读多的应用类型,可以提高吞吐量

什么是乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改, 所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。

乐观锁实现方式

取出记录时,获取当前 version

更新时,带上这个 version 执行

更新时, set version = newVersion where version = oldVersion

如果 version 不对,就更新失败

编写乐观锁更新语句

修改mapper包中的接口ProductMapper

package com.ss.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ss.demo.domain.Product;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author laozhang
 * @since 2023-04-04
 */
public interface ProductMapper extends BaseMapper<Product> {
    Product findById(@Param("id") Integer id);


    /**
     * 乐观锁操作
     * @param id
     * @param count
     * @return
     */
    int updateProductVersion(Integer id, Integer count, Integer version);
}

修改映射文件ProductMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ss.demo.mapper.ProductMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ss.demo.domain.Product">
        <id column="id" property="id" />
        <result column="product_name" property="productName" />
        <result column="price" property="price" />
        <result column="count" property="count" />
        <result column="product_desc" property="productDesc" />
        <result column="version" property="version" />
    </resultMap>

    <!--根据Id查询数据-->
    <select id="findById" resultType="com.ss.demo.domain.Product">
        SELECT * FROM product WHERE id = #{id} FOR UPDATE
    </select>

    <!--乐观锁操作-->
    <update id="updateProductVersion" parameterType="int" >
        UPDATE product SET count = count - #{count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version}
    </update>
</mapper>

在项目中的service中进行操作修改接口ITOrderService

/**
 * 乐观锁
 * @param productId
 * @param count
 * @return
 */
String createOrderOptmisticlock(Integer productId, Integer count);

修改实现类ITOrderServiceImpl:

/**
 * 乐观锁
 * @param productId
 * @param count
 * @return
 */
@Transactional
@Override
public String createOrderOptmisticlock(Integer productId, Integer count) {
    int retryCount = 0;   //重试次数
    int update = 0;    //更新的结果

    //根据商品id获取商品信息
    Product product = productMapper.selectById(productId);   //如果我们使用乐观锁就得换成普通查询
    if(product == null) {
        throw new RuntimeException("购买商品不存在");
    }
    log.info(Thread.currentThread().getName() +"库存数量" + product.getCount());
    //校验库存
    if(count > product.getCount()) {
        throw new RuntimeException("库存不足");
    }


    /**
     * 乐观锁更新库存
     * 更新失败,说明其他线程已经修改过数据,本地扣减库存失败了,可以进行重试
     * 最多重试3次
     */
    while(retryCount < 3 && update == 0) { //如果符合这这个条件我们重试3次
        update = this.reduceCount(productId, count);
        retryCount++;
    }
    if(update == 0) {
        throw new RuntimeException("库存不足");
    }

    //创建订单操作
    TOrder order = new TOrder();
    order.setOrderStatus(1);
    order.setReceiverName("张三");
    order.setReceiverMobile("12345678765");
    //设置订单价格【商品单价*商品数量】
    order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));
    orderMapper.insert(order);  //插入订单操作
    //创建订单商品表的操作
    OrderItem orderItem = new OrderItem();
    orderItem.setOrderId(order.getId());     //订单Id
    orderItem.setProduceId(product.getId());  //商品Id
    orderItem.setPurchasePrice(product.getPrice()); //购买价格
    orderItem.setPurchaseNum(count);   //购买数量
    orderItemMapper.insert(orderItem);
    return order.getId();
}

/**
 * 更新操作,减库存操作
 * mysql的默认隔离级别为可重复读,导致在同一个事物里面查询3次商品
 * 得到的数据始终是相同的,所以我们提供reduceCount方法,每次操作是都会提供一个新的事物,来去做扣减库存操作
 * @param id
 * @param count
 * @return
 */
@Transactional
public int reduceCount(int id, int count) {
    int result = 0;
    //查询商品操作
    Product product = productMapper.selectById(id);
    //判断库存
    if(product.getCount() >= count) {
        //扣减库存我们这里使用乐观锁操作
        result  = productMapper.updateProductVersion(product.getId(), count, product.getVersion());
    }
    return result;
}

修改controller:TOrderController

package com.ss.demo.controller;
import com.ss.demo.service.ITOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 *  前端控制器
 * </p>
 */
@RestController
@RequestMapping("/order")
public class TOrderController {
    @Autowired
    private ITOrderService orderService;

    @PostMapping("/create")
    public String createOrder(Integer productId, Integer count) {
        //return orderService.createOrder(productId, count);
        return orderService.createOrderOptmisticlock(productId,count);
    }
}

启动9090,9091服务

启动nginx  

启动Jmeter进行测试

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

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

相关文章

geoserver跨域问题多种解决方案

geoserver发布服务完成之后&#xff0c;很重要的一个应用场景是前端服务调用&#xff0c;来展示服务数据&#xff0c;那么很可能遇到一个跨域问题&#xff0c;今天我们分享一下跨越问题的多种解决方案&#xff0c;来适用不同需求的业务场景。 一、nginx服务均衡策略 如果你的…

Spring:Spring 框架概述、IoC 设计思想、依赖注入、各种配置

文章目录 Spring&#xff1a;Day 01一、简介1. 概述2. Spring 组成 二、IoC 设计思想三、编写一个 Spring四、IoC 创建对象方式五、Spring 配置1. 别名2. Bean 的配置3. import 六、依赖注入1. 搭建环境2. Set 注入3. 总结补充&#xff1a;c 命名和 p 命名空间注入 七、Bean 的…

1.nginx基础学习笔记

1.nginx基础 一.Nginx介绍 Nginx是十分轻量级的HTTP服务器。Nginx&#xff0c;它的发音为“engine X”&#xff0c;是一个高性能的HTTP和反向代理服务器&#xff0c;同时也是一个IMAP/POP3/SMTP 代理服务器。Nginx是由俄罗斯人 Igor Sysoev为俄罗斯访问量第二的 Rambler.ru站…

Cisco AnyConnect Secure Mobility Client 4.10.07061 (macOS, Linux, Windows)

Cisco AnyConnect Secure Mobility Client 4.10.07061 (macOS, Linux, Windows) Cisco Secure Client&#xff08;包括 AnyConnect&#xff09; 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-anyconnect-4/&#xff0c;查看最新版。原创作品&#xff0c;转载请保…

Apache Kafka - 安装注意事项

文章目录 概述安装Kafka配置Kafka启动Kafka配置注意事项导图 概述 在现代的大数据时代&#xff0c;消息队列成为了极为重要的组件。Kafka作为一种高吞吐量、低延迟、可扩展的分布式发布订阅消息系统&#xff0c;在大数据领域得到了广泛的应用。来&#xff0c;这里我们将介绍如…

【场景方案】如何去设计并二次封装一个好用的axios,给你提供一个好的参考(1.0版)

文章目录 前言文件结构建议封装的文件结构接口管理文件 二次封装axios的初始配置initutilswebConfig 再封一层环境配置使用 前言 以下演示基于vue3与element-plus 文件结构建议 封装的文件结构 把二次封装axios所有有关的代码全部放在request文件夹中&#xff0c;其中init.js…

【系统移植】SD卡 分区

目录 1、分区框架 2、清空磁盘分区 3、开始分区 4、格式化分区 1、分区框架 制作SD卡时&#xff0c;我们需要对 SD 卡进行分区&#xff0c;每个区存入对应的内容&#xff0c;整体框架如下&#xff1a; 第二扇区&#xff1a;uboot程序从这里开始存储&#xff0c;最开始的第…

Class 07 - 功能包的安装和 tidyverse 介绍

Class 07 - 功能包的安装和 tidyverse 介绍 tidyverse 简介功能包&#xff08;package&#xff09;的安装tidyverse 的安装 功能包&#xff08;package&#xff09;的加载tidyverse 的加载 功能包&#xff08;package&#xff09;的更新tidyverse 核心功能browseVignettes 函数…

组合数学第三讲

composition&#xff08;组成&#xff09; k-composition&#xff1a; 20块巧克力分给4个小朋友&#xff0c;有几种分法&#xff1f; 隔板法&#xff0c;19个间隙插入3个板&#xff0c; 推广&#xff1a;n块分给k个 weak k-composition: 20块巧克力分给4个小朋友&#xff0c;每…

Ajax基础知识点总结

努力前进 目录 为什么需要Ajax&#xff1f; 1.提高用户体验&#xff0c;实现局部刷新效果 2.提高性能和降低带宽消耗 什么是 Ajax Ajax 的工作原理 Ajax的工作过程分为以下几个步骤&#xff1a; 最基础的Ajax代码演示&#xff1a; open函数中的参数分析: 在Ajax中ready…

Dijkstra单源最短路

Dijkstra单源最短路径 什么是单源最短路径 描述&#xff1a;给定一个带权有向图G (V&#xff0c;E)&#xff0c;其中每条边的权时非负数。另外&#xff0c;给定V中的一个顶点&#xff0c;称为源。现在要计算从源到所有其他各顶点的最短路长度。这里路的长度是指路上各边权之…

数据在 Mocaverse 项目启动过程中是如何发挥作用的

日期&#xff1a;2023年5月 数据源&#xff1a; Mocaverse Realm Ticket Collection Airdrop & Mocaverse Optimizes an NFT Project at Launch & Beyond NFT 是 Web3 社区的基础。它们是区块链游戏、DAO 和 metaverses 的入场券&#xff0c;以及成为社区参与者的数字…

[创业之路-69]:对管理理念的理解和解读

目录 前言&#xff1a; 一、管理者与领导者的区别 二、管理活动的分类 三、业务管理&#xff1a;以终为始 3.1 业务目标到高效执行 &#xff08;1&#xff09;先明确要做哪些正确的事 》 需求分析、目标 &#xff08;2&#xff09;再明确怎样正确的做事 》 设计、实现 …

chatgpt赋能Python-python3_pygame

Python3 Pygame&#xff1a;游戏引擎进入开发者的时代 Python是一种常用的编程语言&#xff0c;有许多优秀的库和框架&#xff0c;而其中Pygame是许多游戏开发者的首选。Pygame是一个用Python编写的开源软件包&#xff0c;旨在帮助游戏制作者创建交互式游戏和媒体程序。在本篇…

OpenCV基础操作(2)OpevCV算术运算

OpenCV基础操作(2)OpevCV算术运算 import cv2 as cv import numpy as np一、图像的基础操作 1、获取并修改像素值 你可以根据像素的行和列的坐标获取他的像素值。 对 BGR 图像而言&#xff0c;返回值为 B&#xff0c;G&#xff0c;R 的值。对灰度图像而言&#xff0c;会返回他…

微信小程序node+vue+uniapp课程在线答疑学习答题考试系统

系统主要分为管理员和学生、教师三部分&#xff0c;管理员服务端&#xff1a;首页、个人中心、学生管理、教师管理、课程资源管理、课程类型管理、学习记录管理、系统管理&#xff0c;教师服务端&#xff1a;首页、个人中心、课程资源管理、学习记录管理、试题管理、试卷管理、…

JAVA期末考内容知识点的梳理

作者的话 前言&#xff1a;这些都是很基本的&#xff0c;还有很多没有写出来&#xff0c;重点在于考试复习&#xff0c;包括后四章的内容 前面内容请参考JAVA阶段考内容知识点的梳理 一、集合、流 课堂总结1集合 集合概念&#xff1a; 保存和盛装数据的容器&#xff0c;将许多…

maven的常用命令clean/package/install/deploy

如标题&#xff0c;下面放图&#xff1a; 這就是一个pom对应的maven操作命令&#xff0c; 那这些命令中&#xff0c;最常用的打包项目的命令是什么&#xff1f; 两种最常用打包方法&#xff1a; 1.先 clean&#xff0c;然后 package2.先 clean&#xff0c;然后install 下面…

实验四 车辆定位导航

有想自己动手的同学可在末尾看教程 【实验目的】 1、了解全球定位导航系统的定位原理和电子地图技术&#xff0c;掌握电子地图API使用方法。 2、了解导航数据报文数据格式&#xff0c;解析导航数据并在电子地图上进行导航应用。 【实验性质】 验证性实验。 【实验要求】 1、相…

【动态规划专栏】--基础-- 动态规划经典题型

目录 动态规划 动态规划思维&#xff08;基础&#xff09; 状态表示&#xff08;最重要&#xff09; 状态转移方程&#xff08;最难&#xff09; 初始化&#xff08;细节&#xff09; 填表顺序&#xff08;细节&#xff09; 返回值&#xff08;结果&#xff09; 1、第 …