分布式锁的使用

Posted Chiba Nuoyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式锁的使用相关的知识,希望对你有一定的参考价值。

一、分布式锁介绍


由于传统的锁是基于Tomcat服务器的内部对象,搭建了集群之后,导致锁失效,所以要使用分布式锁来处理↓

分布式锁介绍

二、分布式锁解决方案


3.1 搭建环境

创建SpringBoot工程,起名为秒杀second-kill,勾选web依赖↓

抢购的业务逻辑代码↓

package com.ljh.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class SecondKillController {
    //1. 准备商品的库存
    public static Map<String,Integer> itemStock = new HashMap<>();
    //2. 准备商品的订单
    public static Map<String,Integer> itemOrder = new HashMap<>();

    static{
        itemStock.put("牙刷",10000);
        itemOrder.put("牙刷",0);
    }

    @GetMapping("/kill")//item表示用户访问的商品名称,等会浏览器地址栏我会输入牙刷
    public String kill(String item) throws InterruptedException {
        //1. 减库存
        Integer stock = itemStock.get(item);
        
        if(stock <= 0){
            return "商品库存数不足!!!";
        }
        
        Thread.sleep(100);//用睡眠来模拟消耗时间
        itemStock.put(item,stock-1);

        //2. 创建订单
        Thread.sleep(100);
        itemOrder.put(item,itemOrder.get(item)+1);

        //3. 返回信息
        return "抢购成功!!!" + item + ": 剩余库存数为" + itemStock.get(item) + ",订单数为" + itemOrder.get(item);
    }
}

用浏览器访问我们的控制器方法,库存和订单确实加起来是10000,没有超卖,但是我们别忘了还有其他用户的访问↓

http://localhost:8080/kill?item=牙刷

下载ab压力测试工具软件,解压软件即可,找到bin目录,cmd打开,输入下面地址来模拟1000个请求和500并发↓

下载链接:https://pan.baidu.com/s/1yHeVgXN94kram3Jxj5V13w
提取码:6qfh

ab -n 请求数 -c 并发数 访问的路径(复制浏览器地址过来,浏览器地址栏对牙刷进行了url编码所以是这样的↓)
ab -n 1000 -c 500 http://localhost:8080/kill?item=%E7%89%99%E5%88%B7

enter回车,看到执行完了请求和并发,然后打开浏览器地址值,再访问发现库存和订单加起来不是10000,超卖了↓

3.2 Zookeeper实现分布式锁原理图解

Zookeeper实现分布式锁原理图解

这里用临时节点有个好处,断开连接,锁资源就会消失,不用设置过期时间,而下面用redis做分布式锁需要过期时间

3.3 Zookeeper实现分布式锁代码实现

在上面写的秒杀工程里面导入依赖来解决问题

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.6.0</version>
    <exclusions>
        <exclusion>
            <artifactId>slf4j-api</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
        <exclusion>
            <artifactId>slf4j-log4j12</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
        <exclusion>
            <artifactId>log4j</artifactId>
            <groupId>log4j</groupId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.7.1</version>
</dependency>

配置类

package com.ljh.config;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ZkConfig {
    @Bean
    public CuratorFramework cf(){
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);//需要指定重试间隔和重试次数

        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectString("10.20.159.39:2181,10.20.159.39:2182,10.20.159.39:2183")
                .retryPolicy(retryPolicy)
                .build();

        curatorFramework.start();

        return curatorFramework;
    }
}

在业务代码中添加分布式锁

InterProcessMutex lock = new InterProcessMutex(cf,"/lock");

//加锁
lock.acquire();
lock.acquire(1,TimeUnit.SECONDS); //指定排队多久放弃获取锁资源

//----------------业务逻辑代码------------------------

//释放锁
lock.release();
package com.ljh.controller;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
public class SecondKillController {
    //1. 准备商品的库存
    public static Map<String,Integer> itemStock = new HashMap<>();
    //2. 准备商品的订单
    public static Map<String,Integer> itemOrder = new HashMap<>();

    static{
        itemStock.put("牙刷",10000);
        itemOrder.put("牙刷",0);
    }

    @Autowired
    private CuratorFramework cf;

    @GetMapping("/kill")//item表示用户访问的商品名称,等会浏览器地址栏我会输入牙刷
    public String kill(String item) throws Exception {
        InterProcessMutex lock = new InterProcessMutex(cf,"/lock");

        //加锁
        lock.acquire();
        //lock.acquire(1,TimeUnit.SECONDS); //指定排队多久放弃获取锁资源

        //----------------业务逻辑代码↓------------------------
        //1. 减库存
        Integer stock = itemStock.get(item);

        if(stock <= 0){
            return "商品库存数不足!!!";
        }

        Thread.sleep(100);//用睡眠来模拟消耗时间
        itemStock.put(item,stock-1);

        //2. 创建订单
        Thread.sleep(100);
        itemOrder.put(item,itemOrder.get(item)+1);
        //----------------业务逻辑代码↑------------------------

        //释放锁
        lock.release();

        //3. 返回信息
        return "抢购成功!!!" + item + ": 剩余库存数为" + itemStock.get(item) + ",订单数为" + itemOrder.get(item);
    }
}
package com.ljh.controller;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
public class SecondKillController2 {
    //1. 准备商品的库存
    public static Map<String,Integer> itemStock = new HashMap<>();
    //2. 准备商品的订单
    public static Map<String,Integer> itemOrder = new HashMap<>();

    static{
        itemStock.put("牙刷",10000);
        itemOrder.put("牙刷",0);
    }

    @Autowired
    private CuratorFramework cf;

    @RequestMapping(value = "/seckill")
    public String seckill(String gname) throws Exception {
        InterProcessMutex interProcessMutex = new InterProcessMutex(cf, "/seckill_lock");

        //获取锁
        //interProcessMutex.acquire();//没有获取到锁就一直等待
        if(interProcessMutex.acquire(1, TimeUnit.SECONDS)){//等待1s就放弃获取锁资源
            //1.先减库存
            Integer stock = itemStock.get(gname);
            if (stock <= 0) {
                return "库存不足。。。。";
            }
            itemStock.put(gname, stock - 1);
            Thread.sleep(100);

            //2.加订单数量
            Integer count = itemOrder.get(gname);
            itemOrder.put(gname, count + 1);
            Thread.sleep(100);

            //释放锁
            interProcessMutex.release();

            return "抢购成功【" + gname + "】,库存:" + itemStock.get(gname) + ",订单:" + itemOrder.get(gname);
        }else{
            return "请稍后再试";
        }
    }
}

修改完代码重启秒杀工程,发现不管用浏览器访问,还是用之前的ab压测工具软件访问,库存和订单加起来都是10000

注意,如果启动报下面这个错,要重启防火墙,然后关闭防火墙↓

systemctl start firewalld
systemctl stop firewalld

3.4 Redis实现分布式锁原理图解

Redis实现分布式锁原理

这里出现了异常不走后面释放锁资源的代码怎么办?简单,为锁资源设置过期时间来防止死锁的发生

3.5 Redis实现分布式锁代码实现

上面的秒杀工程导入redis依赖,添加配置文件

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# yml配置文件,配置redis服务器的ip和端口↓
spring:
  redis:
    host: xx.xx.xx.xx #redis服务器的ip地址
    port: xxxx #redis端口

工具类

package com.ljh.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisLockUtil {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean lock(String key,String value,int second){
        return redisTemplate.opsForValue().setIfAbsent(key,value,second, TimeUnit.SECONDS);
    }

    public void unlock(String key){
        redisTemplate.delete(key);
    }
}

修改业务逻辑代码

@Autowired
private RedisLockUtil lock;

@GetMapping("/redis/kill")
public String redisKill(String item) throws Exception {
    //加锁
    if(lock.lock(item,System.currentTimeMillis() + "",1)){
        //业务代码...
        //释放锁
        lock.unlock(item);
    }
}
package com.ljh.controller;

import com.ljh.utils.RedisLockUtil;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
public class SecondKillController {
    //1. 准备商品的库存
    public static Map<String,Integer> itemStock = new HashMap<>();
    //2. 准备商品的订单
    public static Map<String,Integer> itemOrder = new HashMap<>();

    static{
        itemStock.put("牙刷",10000);
        itemOrder.put("牙刷",0);
    }

    @Autowired
    private RedisLockUtil lock;

    @GetMapping("/kill")//item表示用户访问的商品名称,等会浏览器地址栏我会输入牙刷
    public String kill(String item) throws Exception {
        //加锁
        if(lock.lock(item,System.currentTimeMillis() + "",1)){
            //业务代码
            //----------------业务逻辑代码↓------------------------
            //1. 减库存
            Integer stock = itemStock.get(item);

            if(stock <= 0){
                return "商品库存数不足!!!";
            }

            Thread.sleep(100);//用睡眠来模拟消耗时间
            itemStock.put(item,stock-1);

            //2. 创建订单
            Thread.sleep(100);
            itemOrder.put(item,itemOrder.get(item)+1);
            //----------------业务逻辑代码↑------------------------

            //释放锁
            lock.unlock(item);
        }

        //3. 返回信息
        return "抢购成功!!!" + item + ": 剩余库存数为" + itemStock.get(item) + ",订单数为" + itemOrder.get(item);
    }
}

修改完代码重启秒杀工程,发现不管用浏览器访问,还是用之前的ab压测工具软件访问,库存和订单加起来都是10000

以上是关于分布式锁的使用的主要内容,如果未能解决你的问题,请参考以下文章

为啥基于锁的程序不能组成正确的线程安全片段?

编程实践分布式锁的实现代码

锁的原理和使用场景,乐观锁悲观锁公平锁非公平锁,基于数据库RedisZookeeper实现分布式锁的原理及代码实现

分布式锁三种解决方案

LockSupport.java 中的 FIFO 互斥代码片段

分布式锁的作用及实现