目录

分布式锁概述

 为什么需要分布式锁

什么是分布式锁

 分布式锁的特点

分布式锁问题_业务介绍

案列介绍

 技术选型

 创建表

创建订单表

创建商品表

创建订单商品关联表

分布式锁问题_创建SpringBoot项目

 引入依赖

修改配置文件

编写主启动类

代码生成

编写创建订单接口

实现创建订单接口

编写创建订单api接口

测试订单

 分布式锁问题_演示问题

 启动订单服务9090

 启动订单服务9091

 创建两个SpringBoot服务

 启动Nginx服务

 配置负载均衡

启动Nginx服务

 启动JMeter压测工具

 设置Http请求参数

 查看库存数据

 查看订单数据

 基于synchronized锁解决超卖问题

事务功能的总体接口设计 

 方法加锁

声明事务管理器

手动创建事务

手动提交事务

分布式锁解决方案

 分布式锁实现方案

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

 基于 Redis 实现的分布式锁

基于 Zookeeper 实现的分布式锁 

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

 什么是悲观锁

演示for update

 测试for update

 项目中使用for update

配置文件添加配置

mapper添加方法

mapper编写语句

修改订单接口实现类

压测吞吐量

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

 什么是乐观锁

 编写乐观锁更新语句

编写创建订单业务层

分布式锁解决方案_Redis实现的分布式锁原理

获取锁

释放锁

 分布式锁解决方案_Redis实现的分布式锁

 引入依赖

添加Redis配置

编写创建订单实现类

分布式锁解决方案_Redis分布式锁误删除问题

 配置锁标识

获取锁

释放锁

分布式锁解决方案_Redis分布式锁不可重入问题

 不可重入问题

 如何解决

 分布式锁解决方案_基于Redisson实现的分布式锁实现

 Redisson介绍

引入Redisson依赖

添加Reids的配置

编写Redis分布式锁工具类

编写创建订单接口实现

分布式锁解决方案_Zookeeper分布式锁原理

 公平锁和可重入锁的原理

什么是可重入锁呢?

 创建临时顺序节点

 ZK分布式锁的实现原理

 释放锁

 分布式锁解决方案_基于Zookeeper实现分布式锁

 简介

 引入Curator依赖

编写Zookeeper配置

编写创建订单接口实现

三种分布式锁对比

 数据库分布式锁实现

Redis 分布式锁实现

Zookeeper 分布式锁实现

汇总对比


分布式锁概述

分布式锁-全面详解(学习总结---从入门到深化)_解决方案

 为什么需要分布式锁

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_02

 在单机部署的系统中,使用线程锁来解决高并发的问题,多线程访问共享变量的问题达到数据一致性,如使用synchornized、 ReentrantLock等。

 

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_03

 但是在后端集群部署的系统中,程序在不同的JVM虚拟机中运行, 且因为synchronized或ReentrantLock都只能保证同一个JVM进程 中保证有效,所以这时就需要使用分布式锁了。

什么是分布式锁

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的 一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享 了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_04

 分布式锁的特点

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_05

分布式锁问题_业务介绍

案列介绍

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_06

 技术选型

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_07

 创建表

创建订单表

CREATE TABLE `t_order`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE
utf8_general_ci NOT NULL,
  `order_status` int(1) NULL DEFAULT NULL
COMMENT '订单状态 1 待支付 2已支付',
  `receiver_name` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '收货人名字',
  `receiver_mobile` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '收货人手机',
  `order_amount` decimal(10, 2) NULL DEFAULT
NULL COMMENT '订单价格',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;

创建商品表

CREATE TABLE `product`  (
  `id` int(11) NOT NULL,
  `product_name` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '商品名字',
  `price` decimal(10, 2) NULL DEFAULT NULL
COMMENT '商品价格',
  `count` bigint(50) UNSIGNED NULL DEFAULT NULL
COMMENT '库存',
  `product_desc` varchar(255) CHARACTER SET
utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '商品描述',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `product` VALUES (1001,'拯救者',100.00, 5,'好用实惠', 1);

创建订单商品关联表

CREATE TABLE `order_item`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE
utf8_general_ci NOT NULL,
  `order_id` varchar(36) CHARACTER SET utf8
COLLATE utf8_general_ci NULL DEFAULT NULL
COMMENT '订单ID',
  `produce_id` int(11) NULL DEFAULT NULL
COMMENT '商品ID',
  `purchase_price` decimal(10, 2) NULL DEFAULT
NULL COMMENT '购买价格',
  `purchase_num` int(11) NULL DEFAULT NULL
COMMENT '购买数量',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE
= utf8_general_ci ROW_FORMAT = Dynamic;

分布式锁问题_创建SpringBoot项目

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_08

 引入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- 模板引擎 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>

修改配置文件

spring:
 application:
   name: lock
 datasource:
   url:
jdbc:mysql://192.168.66.100:3306/distribute?serverTimezone=UTC
   username: root
   password01: 123456
   driver-class-name: com.mysql.cj.jdbc.Driver
server:
 port: 9091

编写主启动类

@Slf4j
@MapperScan("com.itbaizhan.lock.mapper")
@SpringBootApplication
public class LockdemoApplication {
    public static void main(String[] args) {
       SpringApplication.run(LockdemoApplication.class, args);
        log.info("************** 分布式锁 **************");
   }
}

代码生成

使用Mybaits Plus生成订单表、商品表、订单商品关联表的相关代码。

package com.itbaizhan.lock.utils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.Arrays;
import java.util.List;
public class CodeGenerator {
 public static void main(String[] args) {
      FastAutoGenerator.create("jdbc:mysql://192.168.66.100:3306/distribute", "root", "123456")
               .globalConfig(builder -> {
                    builder.author("itbaizhan")// 设置作者
                           .commentDate("MMdd") // 注释日期格式
                           .outputDir(System.getProperty("user.dir")+ "/src/main/java/") // 指定输出目录
                           .fileOverride(); //覆盖文件
               })
                // 包配置
               .packageConfig(builder -> {
                  builder.parent("com.itbaizhan.lock") // 包名前缀
                           .entity("entity")//实体类包名
                           .mapper("mapper")//mapper接口包名
                           .service("service"); //service包名
               })
               .strategyConfig(builder -> {
                    List<String> strings = Arrays.asList("t_order");
                    // 设置需要生成的表名
                    builder.addInclude(strings)
                            // 开始实体类配置
                           .entityBuilder()
                            // 开启lombok模型
                           .enableLombok()
                            //表名下划线转驼峰
                           .naming(NamingStrategy.underline_to_camel)
                            //列名下划线转驼峰
                           .columnNaming(NamingStrategy.underline_to_camel);
               })
               .execute();
   }
}

编写创建订单接口

public interface ITOrderService extends IService<TOrder> {
    /**
     * 创建订单
     * @return
     */
    String createOrder(Integer productId,Integer count);
}

实现创建订单接口

package com.itbaizhan.lock.service.impl;
import com.itbaizhan.lock.entity.OrderItem;
import com.itbaizhan.lock.entity.Product;
import com.itbaizhan.lock.entity.TOrder;
import com.itbaizhan.lock.mapper.OrderItemMapper;
import com.itbaizhan.lock.mapper.ProductMapper;
import com.itbaizhan.lock.mapper.TOrderMapper;
import com.itbaizhan.lock.service.ITOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.locks.ReentrantLock;
/**
* <p>
* 服务实现类
* </p>
*
* @author itbaizhan
* @since 05-25
*/
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {
    @Resource
    OrderItemMapper orderItemMapper;
    @Resource
    ProductMapper productMapper;    
    /**
     * 创建订单
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public  String createOrder(Integer productId,Integer count) {
  // 1、根据商品id查询商品信息
            Product product = productMapper.selectById(productId);
            // 2、判断商品是否存在
            if (product == null){
                throw new RuntimeException("购买商品不存在:" + productId + "不存在");
           }
            // 3、校验库存
            if( count > product.getCount() ){
                throw   new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
           }
            // 4、计算库存
            Integer leftCount = product.getCount() - count;
            // 5、更新库存
            product.setCount(leftCount);
            productMapper.updateById(product);
            // 6、 创建订单
            TOrder order = new TOrder();
            order.setOrderStatus(1);//待处理
            order.setReceiverName("张三");
            order.setReceiverMobile("18587781068");
            order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
            baseMapper.insert(order);
            // 7、 创建订单和商品关系数据
            OrderItem orderItem = new OrderItem();
            orderItem.setOrderId(order.getId());
            orderItem.setProduceId(product.getId());
            orderItem.setPurchasePrice(product.getPrice());
            orderItem.setPurchaseNum(count);
            orderItemMapper.insert(orderItem);
            return order.getId();
   }
}

编写创建订单api接口

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private ITOrderService iOrderService;
    /**
     * 创建订单
     * @param productId 商品id
     * @param count 商品数量
     * @return
     */
    @PostMapping("/create")
    public String createOrder(Integer productId,Integer count){
      return iOrderService.createOrder(productId,count);
   }
}

测试订单

分布式锁-全面详解(学习总结---从入门到深化)_ci_09

 分布式锁问题_演示问题

分布式锁-全面详解(学习总结---从入门到深化)_ci_10

 启动订单服务9090

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_11

 启动订单服务9091

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_12

 创建两个SpringBoot服务

分布式锁-全面详解(学习总结---从入门到深化)_ci_13

 启动Nginx服务

下载Nginx windows服务,官网http://nginx.org/en/download.html

 

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_14

 配置负载均衡

编辑nginx.conf文件添加负载均衡的配置。

upstream test{
      server localhost:9090 ;
      server localhost:9091 ;
   }
    
server {
        listen       80;
        server_name localhost;
    
        location / {
            proxy_pass http://test;
       }
}

启动Nginx服务

在nginx目录下面

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_15

 启动JMeter压测工具

添加线程组 -> HTTP请求 -> 察看结果树 -> 聚合报告

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_16

 设置Http请求参数

分布式锁-全面详解(学习总结---从入门到深化)_ci_17

 查看库存数据

分布式锁-全面详解(学习总结---从入门到深化)_分布式_18

 查看订单数据

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_19

 基于synchronized锁解决超卖问题

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_20

PlatformTransactionManager事务管理器接口 , 事务的 提交、回滚等操作

事务功能的总体接口设计 

三个接口功能一句话总的来说事务管理器基于事务基础信息在操作 事务时候对事务状态进行更新。

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_21

 方法加锁

public synchronized String createOrder(Integer produceId, Integer purchaseNum) { }

声明事务管理器

@Autowired
private PlatformTransactionManager platformTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;

手动创建事务

TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);

手动提交事务

/**
     * 创建订单
     *
     * @param produceId   商品id
     * @param purchaseNum 购买数量
     * @return
     */
    @Override
    public synchronized String createOrder(Integer produceId, Integer purchaseNum) {
        TransactionStatus transaction = platformTransactionManager.getTransaction(trans
actionDefinition);
        // 1、根据商品id获取商品信息
        Product product = productMapper.selectById(produceId);
        // 2、判断商品是否存在
        if (product == null) {
          platformTransactionManager.rollback(transaction);
            throw new RuntimeException("购买商品不存在");
       }
         log.info(Thread.currentThread().getName() + "库存数量" + product.getCount());
        // 3、校验库存
        if (purchaseNum > product.getCount()) {
           platformTransactionManager.rollback(transaction);
            throw new RuntimeException("库存不足");
       }
        // 4、更新库存操作
        int count = product.getCount() - purchaseNum;
        product.setCount(count);
        productMapper.updateById(product);
        // 5、创建订单
        TOrder order = new TOrder();
        order.setOrderStatus(1);//订单状态
        order.setReceiverName("张三");
        order.setReceiverMobile("18587781058");
      order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum)));//订单价格
        baseMapper.insert(order);
        // 6、创建订单和商品关联
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());//订单id
        orderItem.setProduceId(product.getId());// 商品id
        orderItem.setPurchasePrice(product.getPrice());// 购买价格
        orderItem.setPurchaseNum(purchaseNum);// 购买数量
        orderItemMapper.insert(orderItem);
        //提交事务
        platformTransactionManager.commit(transaction);
        return order.getId();
   }

分布式锁解决方案

分布式锁-全面详解(学习总结---从入门到深化)_分布式_22

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

 分布式锁实现方案

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

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_23

 基于数据库实现分布式锁主要是利用数据库的唯一索引来实现,唯 一索引天然具有排他性,这刚好符合我们对锁的要求:同一时刻只能允许一个竞争者获取锁。

 基于 Redis 实现的分布式锁

分布式锁-全面详解(学习总结---从入门到深化)_分布式_24

SETNX key value

基于 Zookeeper 实现的分布式锁 

分布式锁-全面详解(学习总结---从入门到深化)_ci_25

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

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_26

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

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_27

 什么是悲观锁

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

演示for update

开启2个命令行界面

分布式锁-全面详解(学习总结---从入门到深化)_ci_28

 测试for update

分布式锁-全面详解(学习总结---从入门到深化)_分布式_29

 项目中使用for update

配置文件添加配置

mybatis-plus:
 mapper-locations: classpath:mapper/*.xml

mapper添加方法

public interface ProductMapper extends BaseMapper<Product> {
    Product  findById(@Param("id")Integer id);
}

mapper编写语句

<select id="findById" parameterType="int" resultType="com.itbaizhan.lock.entity.Product">
       select * from product where id = #{id} for update
</select>

修改订单接口实现类

@Transactional(rollbackFor = Exception.class)
    @Override
    public  String createOrder(Integer productId, Integer count) {
        // 1、根据商品id查询商品信息
        Product product = productMapper.findById(productId);
        // 2、判断商品是否存在
        if (product == null) {
            throw new RuntimeException("购买商品不存在:" + productId + "不存在");
       }
        // 3、校验库存
        if (count > product.getCount()) {
            throw new RuntimeException("商品" +productId + "仅剩" + product.getCount() + "件,无法购买");
       }
        // 4、计算库存
        Integer leftCount = product.getCount() - count;
        // 5、更新库存
        product.setCount(leftCount);
        productMapper.updateById(product);
        // 6、 创建订单
        TOrder order = new TOrder();
        order.setOrderStatus(1);//待处理
        order.setReceiverName("张三");
        order.setReceiverMobile("18587781068");
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
        baseMapper.insert(order);
        // 7、 创建订单和商品关系数据
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProduceId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(count);
        orderItemMapper.insert(orderItem);
        return order.getId();
   }

压测吞吐量

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_30

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

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_31

 什么是乐观锁

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

分布式锁-全面详解(学习总结---从入门到深化)_ci_32

 编写乐观锁更新语句

<update id="decreaseStockForVersion" parameterType="int" >
        UPDATE product SET count = count - # {count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version}
</update>

编写创建订单业务层

/**
     * 创建订单 乐观锁
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String createOrder(Integer productId, Integer count) throws Exception {
        int retryCount = 0;
        int update = 0;
        // 1、根据商品id查询商品信息
        Product product = productMapper.selectById(productId);
        // 2、判断商品是否存在
        if (product == null) {
            throw new RuntimeException("购买商品不存在:" + productId + "不存在");
       }
        // 3、校验库存
        if (count > product.getCount()) {
            throw new Exception("库存不够");
       }
         // 乐观锁更新库存
        // 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
        // 最多重试3次
        while(retryCount < 3 && update == 0){
            update = this.reduceStock(product.getId(),count);
            retryCount++;
       }
        if (update == 0){
            throw new Exception("库存不够");
       }
        // 6、 创建订单
        TOrder order = new TOrder();
        order.setOrderStatus(1);//待处理
        order.setReceiverName("张三");
        order.setReceiverMobile("18587781068");
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
        baseMapper.insert(order);
        // 7、 创建订单和商品关系数据
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProduceId(product.getId());
        orderItem.setPurchasePrice(product.getPrice());
        orderItem.setPurchaseNum(count);
        orderItemMapper.insert(orderItem);
        return order.getId();
   }
    /**
     * 减库存
     * <p>
     * 由于默认的事务隔离级别是可重复读,produce.findById()
     * 得到的数据始终是相同的,所以需要提取 reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。
     */
    @Transactional(rollbackFor = Exception.class)
    public int reduceStock(int gid,int count) {
        int result = 0;
        //1、查询商品库存
        Product product = productMapper.selectById(gid);
        //2、判断库存是否充足
        if (product.getCount() >= count) {
            //3、减库存
            // 乐观锁更新库存
            result = productMapper.decreaseStockForVersion(gid,count, product.getVersion());
       }
        return result;
   }

分布式锁解决方案_Redis实现的分布式锁原理

获取锁

互斥:确保只有一个线程获得锁

# 添加锁 利用setnx的互斥性
127.0.0.1:6379> setnx lock thread1

释放锁

1、手动释放锁

2、超时释放:获取锁时设置一个超时时间

#释放锁 删除即可
127.0.0.1:6379> del lock

超时释放

127.0.0.1:6379> setnx lock tread1
127.0.0.1:6379> expire lock 5
127.0.0.1:6379> ttl lock

两步合成一步

help set
 SET key value [EX seconds] [PX milliseconds] [NX|XX]
 summary: Set the string value of a key
 since: 1.0.0
 group: string
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> set lock k1 ex 5 nx
OK
127.0.0.1:6379> set lock k1 ex 5 nx
nil

分布式锁-全面详解(学习总结---从入门到深化)_ci_33

 

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_34

 分布式锁解决方案_Redis实现的分布式锁

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_35

 引入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

添加Redis配置

spring:
 redis:
   host: localhost
   port: 6379

编写创建订单实现类

@Override
    public String createOrderRedis(Integer productId, Integer count) throws Exception {
        log.info("*************** 进入方法 **********");
        String key = "lock:";
        String value = UUID.randomUUID().toString();
        // 获取分布式锁
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key+productId,String.valueOf(Thread.currentThread().getId()),30,TimeUnit.SECONDS);
        // 判断是否获取锁成功
        if (!result){
            log.info("我进入了锁");
            return "不允许重复下单";
       }
        try {
            // 1、根据商品id查询商品信息
            Product product = productMapper.selectById(productId);
            // 2、判断商品是否存在 if (product == null) {
                throw new RuntimeException("购买商品不存在:" + productId + "不存在");
           }
            // 3、校验库存
            if (count > product.getCount()) {
                throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
           }
            // 4、计算库存
            Integer leftCount = product.getCount() - count;
            // 5、更新库存
            product.setCount(leftCount);
            productMapper.updateById(product);
            // 6、 创建订单
            TOrder order = new TOrder();
            order.setOrderStatus(1);//待处理
            order.setReceiverName("张三");
            order.setReceiverMobile("18587781068");
            order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
            baseMapper.insert(order);
            // 7、 创建订单和商品关系数据
            OrderItem orderItem = new OrderItem();
            orderItem.setOrderId(order.getId());
            orderItem.setProduceId(product.getId());
            orderItem.setPurchasePrice(product.getPrice());
            orderItem.setPurchaseNum(count);
            orderItemMapper.insert(orderItem);
            return order.getId();
       }catch (Exception e){
            e.printStackTrace();
       }finally {
            // 释放锁
          stringRedisTemplate.delete(key+productId);
       }
        return "创建失败";
   }

分布式锁解决方案_Redis分布式锁误删除问题

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_36

 

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_37

 配置锁标识

private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString().replace("-" ,"");

获取锁

//1、获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 2、获得锁 setnx key   value   time   type
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+produceId, threadId, 30,TimeUnit.SECONDS);

释放锁

// 获取锁标识
  String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + produceId);
            // 判断标识是否一致
            if (s.equals(threadId)){
                // 释放锁
               stringRedisTemplate.delete(KEY_PREFIX + produceId);
           }

分布式锁解决方案_Redis分布式锁不可重入问题

分布式锁-全面详解(学习总结---从入门到深化)_ci_38

 不可重入问题

分布式锁-全面详解(学习总结---从入门到深化)_ci_39

 如何解决

分布式锁-全面详解(学习总结---从入门到深化)_ci_40

 分布式锁解决方案_基于Redisson实现的分布式锁实现

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_41

 Redisson介绍

Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分 布式环境中轻松实现一些Java的对象,Redisson、Jedis、Lettuce 是三个不同的操作 Redis 的客户端,Jedis、Lettuce 的 API 更侧重 对 Reids 数据库的 CRUD(增删改查),而 Redisson API 侧重于分布式开发。

引入Redisson依赖

<dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.17.2</version>
</dependency>

添加Reids的配置

spring:
 redis:
   host: localhost
   port: 6379

编写Redis分布式锁工具类

package com.itbaizhan.lock.utils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class DistributedRedisLock {
    @Autowired
    private RedissonClient redissonClient;
    // 加锁
    public Boolean lock(String lockName) {
        if (redissonClient == null) {
            log.info("DistributedRedisLock redissonClient is null");
            return false;
       }
        try {
            RLock lock = redissonClient.getLock(lockName);
            // 锁15秒后自动释放,防止死锁
            lock.lock(15, TimeUnit.SECONDS);
            log.info("Thread [{}] DistributedRedisLock lock [{}] success",Thread.currentThread().getName(), lockName);
            // 加锁成功
            return true;
       } catch (Exception e) {
            log.error("DistributedRedisLocklock [{}] Exception:", lockName, e);
            return false;
       }
   }
    // 释放锁
    public Boolean unlock(String lockName) {
        if (redissonClient == null) {
            log.info("DistributedRedisLock redissonClient is null");
            return false;
       }
        try {
            RLock lock = redissonClient.getLock(lockName);
            lock.unlock();
            log.info("Thread [{}] DistributedRedisLock unlock [{}] success",Thread.currentThread().getName(), lockName);
            // 释放锁成功
            return true;
} catch (Exception e) {
            log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e);
            return false;
       }
   }
}

编写创建订单接口实现

/**
     * Redis锁实现
     *
     * @param productId
     * @param count
     * @return
     * @throws Exception
     */
    @Override
    public String createOrderRedis(Integer productId, Integer count) throws Exception {
        //获取锁对象
        if (distributedRedisLock.lock(String.valueOf(productId))) {
            try {
                // 1、根据商品id查询商品信息
                Product product = productMapper.selectById(productId);
                // 2、判断商品是否存在
                if (product == null) {
                throw new RuntimeException("购买商品不存在:" + productId + "不存在");
               }
                // 3、校验库存
                if (count > product.getCount())
               {
                    throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
               }
                // 4、计算库存
                Integer leftCount = product.getCount() - count;
                // 5、更新库存
                product.setCount(leftCount);
                productMapper.updateById(product);
                // 6、 创建订单
                TOrder order = new TOrder();
                order.setOrderStatus(1);//待处理
                order.setReceiverName("张三");
                order.setReceiverMobile("18587781068");
                order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
                baseMapper.insert(order);
                // 7、 创建订单和商品关系数据
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderId(order.getId());
                orderItem.setProduceId(product.getId());
                orderItem.setPurchasePrice(product.getPrice());
                orderItem.setPurchaseNum(count);
                orderItemMapper.insert(orderItem);
                return order.getId();
           } catch (Exception e) {
                e.printStackTrace();
           } finally {
               distributedRedisLock.unlock(String.valueOf(productId));
           }
       }
        return "创建失败";
   }

分布式锁解决方案_Zookeeper分布式锁原理

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_42

 公平锁和可重入锁的原理

分布式锁-全面详解(学习总结---从入门到深化)_分布式_43

 

分布式锁-全面详解(学习总结---从入门到深化)_分布式_44

 这种排队取水模型,就是一种锁的模型。

什么是可重入锁呢?

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_45

 

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_46

 创建临时顺序节点

[zk: localhost:2181(CONNECTED) 1] create -e  -s
/test 123

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_47

 

分布式锁-全面详解(学习总结---从入门到深化)_ci_48

 ZK分布式锁的实现原理

当第一个客户端请求过来时,Zookeeper 客户端会创建一个持久节 点 locks。如果它(Client1)想获得锁,需要在 locks 节点下创建 一个顺序节点 lock1。

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_49

 接着,客户端 Client1 会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock1 是不是排序最小的那一个,如果是,则成功获得锁。

 

分布式锁-全面详解(学习总结---从入门到深化)_ci_50

 这时候如果又来一个客户端 client2 前来尝试获得锁,它会在 locks 下再创建一个临时节点 lock2。

分布式锁-全面详解(学习总结---从入门到深化)_ci_51

 客户端 client2 一样也会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock2 是不是最小的,此时,发现 lock1 才是最小 的,于是获取锁失败。获取锁失败,它是不会甘心的,client2 向它 排序靠前的节点 lock1 注册 Watcher 事件,用来监听 lock1 是否存 在,也就是说 client2 抢锁失败进入等待状态。

 

分布式锁-全面详解(学习总结---从入门到深化)_分布式_52

 此时,如果再来一个客户端Client3来尝试获取锁,它会在 locks 下 再创建一个临时节点 lock3。

 

分布式锁-全面详解(学习总结---从入门到深化)_ci_53

 同样的,client3 一样也会查找 locks 下面的所有临时顺序子节点, 判断自己的节点 lock3 是不是最小的,发现自己不是最小的,就获 取锁失败。它也是不会甘心的,它会向在它前面的节点 lock2 注册 Watcher 事件,以监听 lock2 节点是否存在。

 

分布式锁-全面详解(学习总结---从入门到深化)_分布式_54

 释放锁

如果是任务完成,Client1 会显式调用删除 lock1 的指令。

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_55

 如果是客户端故障了,根据临时节点得特性,lock1 是会自动删除的。

 

分布式锁-全面详解(学习总结---从入门到深化)_ci_56

 lock1 节点被删除后,Client2 可开心了,因为它一直监听着 lock1。lock1 节点删除,Client2 立刻收到通知,也会查找 locks 下面的所有临时顺序子节点,发下 lock2 是最小,就获得锁。

 

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_57

 同理,Client2 获得锁之后,Client3 也对它虎视眈眈:

分布式锁-全面详解(学习总结---从入门到深化)_分布式_58

 分布式锁解决方案_基于Zookeeper实现分布式锁

分布式锁-全面详解(学习总结---从入门到深化)_分布式锁_59

 简介

Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封 装的一套高级API 简化了ZooKeeper的操作。

 

分布式锁-全面详解(学习总结---从入门到深化)_解决方案_60

 引入Curator依赖

<dependency>
         <groupId>org.apache.curator</groupId>
          <artifactId>curator-framework</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>5.2.0</version>
        </dependency>

编写Zookeeper配置

@Configuration
public class ZookeeperConfig {
    @Bean
    public CuratorFramework zookeeperClient() {
        CuratorFramework client = CuratorFrameworkFactory.builder()
               .connectString("127.0.0.1:2181")
               .sessionTimeoutMs(5000)
               .connectionTimeoutMs(5000)
               .retryPolicy(new ExponentialBackoffRetry(1000, 3))
               //.namespace("test")
               .build();
        client.start();
        return client;
   }
}

编写创建订单接口实现

使用InterProcessMutex的acquire和release方法,来获取和释放锁。

@Autowired
    CuratorFramework client;
    @Override
    public String createOrderZookeeper(Integer productId, Integer count) throws Exception 
     {
        // client cruator中zk客户端对象   path 抢锁路径同一个锁path需要一致
        InterProcessMutex lock = new InterProcessMutex(client, "/lockPath");
        //第一个属性:定时的时间数字
        //第二个属性:定义时间的单位
        if (lock.acquire(3, TimeUnit.SECONDS))
          {
            try {
                // 1、根据商品id查询商品信息
                Product product = productMapper.selectById(productId);
                // 2、判断商品是否存在
                if (product == null) {
            throw new RuntimeException("购买商品不存在:" + productId + "不存在");
               }
                // 3、校验库存
                if (count > product.getCount())
                 {
                    throw new RuntimeException("商品" + productId + "仅剩" +
product.getCount() + "件,无法购买");
               }
                // 4、计算库存
                Integer leftCount = product.getCount() - count;
                // 5、更新库存
                product.setCount(leftCount);
                productMapper.updateById(product);
                // 6、 创建订单
                TOrder order = new TOrder();
                order.setOrderStatus(1);//待处理
                order.setReceiverName("张三");
                order.setReceiverMobile("18587781068");
                order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
                baseMapper.insert(order);
                // 7、 创建订单和商品关系数据
                OrderItem orderItem = new OrderItem();
                orderItem.setOrderId(order.getId());
                orderItem.setProduceId(product.getId());
                orderItem.setPurchasePrice(product.getPrice());
                orderItem.setPurchaseNum(count);
                orderItemMapper.insert(orderItem);
                return order.getId();
           } finally {
                lock.release();
           }
       }
        return "创建失败";
   }

三种分布式锁对比

分布式锁-全面详解(学习总结---从入门到深化)_ci_61

 数据库分布式锁实现

优点:简单,使用方便,不需要引入 Redis、Zookeeper 等中间件。

缺点:1、不适合高并发的场景 2、db 操作性能较差

Redis 分布式锁实现

优点:1、性能好,适合高并发场景 2、较轻量级 3、有较好的框架支持,如 Redisson

 缺点:1、过期时间不好控制 2、需要考虑锁被别的线程误删场景

Zookeeper 分布式锁实现

优点:1、有较好的性能和可靠性 2、有封装较好的框架,如 Curator

缺点:1、性能不如 Redis 实现的分布式锁  2、比较重的分布式锁。

汇总对比

1、从性能角度:Redis > Zookeeper >= 数据库

2、从实现的复杂性角度:Zookeeper > Redis > 数据库

3、从可靠性角度:Zookeeper > Redis > 数据库