分布式锁 RedisSon

文章目录

    • 1.什么是分布式锁
    • 2.分布式锁应该具备哪些条件
    • 3.分布式锁主流的实现方案
    • 4.未添加分布式锁存在的问题
      • 4.1测试未添加分布式锁的代码
        • 通过jmeter发送请求
      • 4.2 添加线程同步锁
        • 集群部署
          • 配置nginx
        • 修改jmeter端口号
      • 4.3 使用redis的setnx命令实现分布式锁
        • 解决办法
      • 4.4 使用try、finally优化
      • 4.5 添加分布式锁的过期时间
      • 4.6 解决分布式锁命令的原子性问题
      • 4.7 把线程ID做为分布式锁的value
    • 5.使用redisson实现分布式锁
      • 5.1 添加 Redisson 依赖
      • 5.2 获取 Redisson 客户端实例
      • 5.3 获取分布式锁
      • 5.4 Redisson分布式锁的卖现原理图
    • 6 Redisson相关锁介绍
      • 6.1 可重入锁
      • 6.2 联锁概述
      • 6.3 RedLock

1.什么是分布式锁

问题描述: 随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的 JavaAPI 并不能提供分布式锁的能力,为了解决这个问题就需要一种跨IV的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

与分布式锁相对就的是单体结构中的锁 (单机锁) ,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来互斥以保证共享变量的正确性,其使用范围是在同一个JVM进程中。如果换做是不同机器上的MM进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免操作乱序导致脏数据,此时就需要引入分布式锁了。

2.分布式锁应该具备哪些条件

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
  • 高可用的获取锁与释放锁
  • 高性能的获取锁与释放锁
  • 具备可重入特(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
  • 具备锁失效机制,即自动解锁,防止死锁
  • 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sYnTjYFm-1722400414013)(https://i-blog.csdnimg.cn/direct/b1a7a1d414134b9aa70439cdfcab343a.png#pic_center)]

3.分布式锁主流的实现方案

  • 1.基于缓存(redis等)
  • 2.基于zookeeper

每一种分布式锁解决方案都有各自的优缺点

  • 1.性能:redis最高
  • 2.可靠性:zookeeper最高

这里我们就基于redis实现分布式锁。

4.未添加分布式锁存在的问题

实现秒杀下单减库存案例:
创建springboot项目,导入redis依赖,在yml中进行redis的配置:
依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

配置:

spring:
  redis :
    database:0
    host:localhost
    port:6379

4.1测试未添加分布式锁的代码

操作redis

# 在redis 中设置一个stock等于10
set stock 0
get stock 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dxgajzi1-1722400414016)(https://i-blog.csdnimg.cn/direct/ef0c473317374355ab5a87655c270118.png#pic_center)]

package cn.js;


import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;


@RestController
public class Indexcontroller {
   
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deductstock")
    public String deductstock() {
   
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
   
            int realstock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realstock + "");
            System.out.println("扣减成功,剩余库存:" + realstock);
        } else {
   
            System.out.println("扣减失败,库存不足");
        }
        return "end";
    }
}


通过jmeter发送请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5NVk3qU-1722400414016)(https://i-blog.csdnimg.cn/direct/3429f43f8f854fd195e66e1e40e9ae76.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hdTFecNu-1722400414017)(https://i-blog.csdnimg.cn/direct/c121e5c892e14a6681ce09be00b6c402.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HYpALTw-1722400414017)(https://i-blog.csdnimg.cn/direct/22282de5812d4e29b1f8c43d1102ae8d.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wIKS2vCT-1722400414018)(https://i-blog.csdnimg.cn/direct/3b9acb34bae2425f94f9dde86d028117.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5BlOmXYR-1722400414019)(https://i-blog.csdnimg.cn/direct/e4b0d67689d6424db2ee1debe209e436.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eHVCF8OT-1722400414019)(https://i-blog.csdnimg.cn/direct/cae1990a429a488393effb3f0bfb3ba1.png#pic_center)]

通过命令再次查看redis

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcxYDh65-1722400414021)(https://i-blog.csdnimg.cn/direct/55c527fe429945c3b534b2a75259d1a7.png#pic_center)]

我们先清空redis再试一次

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFGbIALw-1722400414021)(https://i-blog.csdnimg.cn/direct/5147f817740d4572b8e6117133c092bc.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1JSAMlg-1722400414022)(https://i-blog.csdnimg.cn/direct/f8c28a34e4644eb6afaa91ee8f375d05.png#pic_center)]

再次通过jmeter发送请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ghnRsbPi-1722400414023)(https://i-blog.csdnimg.cn/direct/389a3714469a484a9ad71557fe9a98e9.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JVMIYBVA-1722400414023)(https://i-blog.csdnimg.cn/direct/b66a747e9aca4e869dd94c5c3138d6bd.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9t14x1E-1722400414024)(https://i-blog.csdnimg.cn/direct/90d241b1fb7d4bed948ee5f5fccdd3f7.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7lvc4UU-1722400414024)(https://i-blog.csdnimg.cn/direct/f4def8064b70485eab74a13dbfaf09f7.png#pic_center)]

两次结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JkP4qbls-1722400414025)(https://i-blog.csdnimg.cn/direct/cce8674899264981a02de741c1b9b6ee.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Scub7Otw-1722400414025)(https://i-blog.csdnimg.cn/direct/31b5243e1308427a9ad5827e2dd746d3.png#pic_center)]

研究代码发现存在线程安全问题,接下来可以加个同步代码锁synchronized进行优化

4.2 添加线程同步锁

package cn.js;


import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;


@RestController
public class Indexcontroller {
   
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deductstock")
    public String deductstock() {
   
        
        synchronized (this){
   
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
   
                int realstock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realstock + "");
                System.out.println("扣减成功,剩余库存:" + realstock);
            } else {
   
                System.out.println("扣减失败,库存不足");
            }
           
        }
	 return "end";
    }
}


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ryOIcER2-1722400414026)(https://i-blog.csdnimg.cn/direct/1c6cf8df169d46efa7407b83576df1a5.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3rdlaJos-1722400414026)(https://i-blog.csdnimg.cn/direct/21e9dd73363048a594cacbd4100a837a.png#pic_center)]

目前我们的环境是单体服务,可以通过 synchronized JVM锁进行解决,但是如果是集群部署的话,synchronized锁是没有用的

集群部署
配置nginx
打开nginx中的nginx.conf文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Idv02YWX-1722400414027)(https://i-blog.csdnimg.cn/direct/d8c2882fd7464544964532c5802a8cbd.png#pic_center)]

在.idea的workspace.xml中配置

<option name="configurationTypes">
<set>
<option value="springBootApplicationconfigurationType"/>
set>
option>
修改jmeter端口号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-80Aims7W-1722400414027)(https://i-blog.csdnimg.cn/direct/b435e4a76504444f890db6d169514e1c.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jFMG2ZdQ-1722400414028)(https://i-blog.csdnimg.cn/direct/ee238cfb50cd4623ba4d774f604fe665.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hEjuSheC-1722400414028)(https://i-blog.csdnimg.cn/direct/11782201a0774907b7c80918d0c43ae4.png#pic_center)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ql0rntWa-1722400414029)(https://i-blog.csdnimg.cn/direct/180409fe2da6446ca9628af0483fcd6d.png#pic_center)]

可以看到集群部署会出现超卖问题

重启IDEA

最后使用imeter工具,模拟高并发场景进行压力测试,查看控制台打印日志,发现还是出现库存量一样的数据,说明超卖问题还是存在。

分析得出:在分布式环境下synchronized是不起作用的,因为一个synchronized只在一个tomcat的iym进程内有效,在一个分布式系统,如何解决并发的资源争抢问题呢?

4.3 使用redis的setnx命令实现分布式锁

解决办法
可以通过redis的setnx命令的互斥性:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S17k8hea-1722400414029)(https://i-blog.csdnimg.cn/direct/5f8df106ef884ba9ae9f4331ca63564b.png#pic_center)]

package cn.js;


import org.springframework

你可能感兴趣的:(中间件,分布式,redis)