SpringBoot基于Redis的分布式锁实现过程记录

目录
  • 一、概述
  • 二、环境搭建
  • 三、模拟一个库存扣减的场景
  • 四、总结

一、概述

什么是分布式锁

在单机环境中,一般在多并发多线程场景下,出现多个线程去抢占一个资源,这个时候会出现线程同步问题,造成执行的结果没有达到预期。我们会用线程间加锁的方式,比如synchronized,lock,volatile,以及JVM并发包中提供的其他工具类去处理此问题。

但是随着技术的发展,分布式系统的出现,各个应用服务都部署在不同节点,由各自的JVM去操控,资源已经不是在 线程 之间的共享,而是变成了 进程 之间的共享,以上解决线程同步问题的办法已经无法满足。

因此,引入了分布式锁的概念。

分布式锁,既在分布式部署的环境下,通过在外部设置锁,让客户端之间互斥,当多应用发生对共享资源的抢占时,该资源同一时刻只能一个应用访问,从而保证数据一致性

分布式锁满足条件

  • 互斥性:即同一时刻只能一个客户端获得锁,其他客户端必须等待获取锁的客户端主动释放锁或锁超时后再次对资源进行抢占。
  • 避免死锁:这把锁在一段有限的时间之后,一定会被释放(正常释放或异常释放),否则当一个客户端线程获得锁后没有主动释放,也没有设置超时时间,中途宕机等原因,其他线程就会一直获取不了锁
  • 具备可重入特性:同一个线程可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,如果没有可重入锁的支持,在第二次尝试获得锁时将会进入死锁状态。
  • 高可用:获取或释放锁的机制必须高可用且性能佳

分布式锁的重要性不言而喻,原因不在赘述,每一位菜鸟都有理由掌握它。提到分布式锁,解决方案更是乌泱乌泱的,如:

  • 直接通过关系型数据库实现
  • 基于Redission实现
  • 基于Apache Curator实现

本文暂时先介绍一种,基于Redission实现的方式

二、环境搭建

有一个简单的SpringBoot环境即可,便于测试:

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.6.5</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

配置

server:
  port: 7077
spring:
  redis:
    host: 192.144.228.170
    database: 0

启动及配置类

package com.ideax.distributed;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DistributedApplication {
    public static void main(String[] args) {
        SpringApplication.run(DistributedApplication.class,args);
    }

    /**
     * 配置redisson客户端
     * @return org.redisson.Redisson
     * @author zhangxs
     * @date 2022-01-06 10:01
     */
    @Bean
    public Redisson redisson(){
        // 单机模式
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.144.228.170:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
}

三、模拟一个库存扣减的场景

package com.ideax.distributed.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 库存 前端控制器
 * @author zhangxs
 * @date 2022-01-06 09:46
 */
@RequestMapping("/inventory")
@RestController
public class InventoryController {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private Redisson redisson;

    @GetMapping("/minus")
    public ResponseEntity<String> minusInventory(){
        // 分布式高并发场景下,这样肯定不行
        synchronized (this) {
            int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));
            if (stock > 0) {
                int currentStock = stock - 1;
                redisTemplate.opsForValue().set("stock", currentStock + "");
                System.out.println("扣减成功,当前库存为" + currentStock);
            } else {
                System.out.println("库存不足,扣减失败!");
            }
        }
        return ResponseEntity.ok("success");
    }

    @GetMapping("/minus1")
    public ResponseEntity<String> minusInventory1(){
        // 相当于setnx命令
        String lockKey = "lockKey";
        // 务必加try-finally,因为如果服务挂了,锁还得释放
        String clientId = UUID.randomUUID().toString();
        try {
            // 相当于加锁
            // Boolean absent = redisTemplate.opsForValue().setIfAbsent(lockKey, "zxs");
            // 上下两行不能分开写,如果这中间报异常了,依然出现死锁
            // redisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
            Boolean absent = redisTemplate.opsForValue().setIfAbsent(lockKey, clientId,30,TimeUnit.SECONDS);
            if (!absent) {
                return ResponseEntity.notFound().build();
            }

            int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));
            if (stock > 0) {
                int currentStock = stock - 1;
                redisTemplate.opsForValue().set("stock", currentStock + "");
                System.out.println("扣减成功,当前库存为" + currentStock);
            } else {
                System.out.println("库存不足,扣减失败!");
            }
        } finally {
            // 如果系统挂了呢,finally也不起作用了,因此还需要设置超时时间
            // 释放锁之前,判断一下,务必释放的锁是自己持有的锁
            if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
        return ResponseEntity.ok("success");
    }

    /**
     * 终极方案
     */
    @GetMapping("/minus2")
    public ResponseEntity<String> minusInventory2(){
        // redisson解决方案
        String lockKey = "lockKey";
        RLock lock = redisson.getLock(lockKey);
        try {
            // 加锁
            lock.lock();
            int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));
            if (stock > 0) {
                int currentStock = stock - 1;
                redisTemplate.opsForValue().set("stock", currentStock + "");
                System.out.println("扣减成功,当前库存为" + currentStock);
            } else {
                System.out.println("库存不足,扣减失败!");
            }
        } finally {
            // 释放锁
            lock.unlock();
        }
        return ResponseEntity.ok("success");
    }
}

四、总结

  • @GetMapping("/minus") public ResponseEntity<String> minusInventory():初步实现方式,在单线程环境下可以使用,但是在分布式高并发场景下,毫无疑问是肯定不行的
  • @GetMapping("/minus1") public ResponseEntity<String> minusInventory1():进阶实现方式,在一般的不拘小节的公司,勉强够用,但是你需要考虑一下,锁过期时间,到底设置多少才能完美呢?
  • @GetMapping("/minus2") public ResponseEntity<String> minusInventory2():终极实现方式,redisson帮我们解决了上面的实现方式出现的尴尬情况

到此这篇关于SpringBoot基于Redis的分布式锁实现的文章就介绍到这了,更多相关SpringBoot基于Redis的分布式锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot redis分布式锁代码实例

    这篇文章主要介绍了springboot redis分布式锁代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 随着微服务等分布式架构的快速发展及应用,在很多情况下,我们都会遇到在并发情况下多个线程竞争资源的情况,比如我们耳熟能详的秒杀活动,多平台多用户对同一个资源进行操作等场景等.分布式锁的实现方式有很多种,比如基于数据库.Zookeeper.Redis等,本文我们主要介绍Spring Boot整合Redis实现分布式锁. 工具类如下: i

  • SpringBoot中使用redis做分布式锁的方法

    一.模拟问题 最近在公司遇到一个问题,挂号系统是做的集群,比如启动了两个相同的服务,病人挂号的时候可能会出现同号的情况,比如两个病人挂出来的号都是上午2号.这就出现了问题,由于是集群部署的,所以单纯在代码中的方法中加锁是不能解决这种情况的.下面我将模拟这种情况,用redis做分布式锁来解决这个问题. 1.新建挂号明细表 2.在idea上新建项目 下图是创建好的项目结构,上面那个parent项目是其他项目不用管它,和新建的没有关系 3.开始创建controller,service,dao(mapp

  • SpringBoot使用Redis实现分布式锁

    前言 在单机应用时代,我们对一个共享的对象进行多线程访问的时候,使用java的synchronized关键字或者ReentrantLock类对操作的对象加锁就可以解决对象的线程安全问题. 分布式应用时代这个方法却行不通了,我们的应用可能被部署到多台机器上,运行在不同的JVM里,一个对象可能同时存在多台机器的内存中,怎样使共享对象同时只被一个线程处理就成了一个问题. 在分布式系统中为了保证一个对象在高并发的情况下只能被一个线程使用,我们需要一种跨JVM的互斥机制来控制共享资源的访问,此时就需要用到

  • springboot+redis分布式锁实现模拟抢单

    本篇内容主要讲解的是redis分布式锁,这个在各大厂面试几乎都是必备的,下面结合模拟抢单的场景来使用她:本篇不涉及到的redis环境搭建,快速搭建个人测试环境,这里建议使用docker:本篇内容节点如下: jedis的nx生成锁 如何删除锁 模拟抢单动作(10w个人开抢) jedis的nx生成锁 对于java中想操作redis,好的方式是使用jedis,首先pom中引入依赖: <dependency> <groupId>redis.clients</groupId> &

  • 基于springboot实现redis分布式锁的方法

    在公司的项目中用到了分布式锁,但只会用却不明白其中的规则 所以写一篇文章来记录 使用场景:交易服务,使用redis分布式锁,防止重复提交订单,出现超卖问题 分布式锁的实现方式 基于数据库乐观锁/悲观锁 Redis分布式锁(本文) Zookeeper分布式锁 redis是如何实现加锁的? 在redis中,有一条命令,实现锁 SETNX key value 该命令的作用是将 key 的值设为 value ,当且仅当 key 不存在.若给定的 key 已经存在,则 SETNX不做任何动作.设置成功,返

  • SpringBoot基于Redis的分布式锁实现过程记录

    目录 一.概述 二.环境搭建 三.模拟一个库存扣减的场景 四.总结 一.概述 什么是分布式锁 在单机环境中,一般在多并发多线程场景下,出现多个线程去抢占一个资源,这个时候会出现线程同步问题,造成执行的结果没有达到预期.我们会用线程间加锁的方式,比如synchronized,lock,volatile,以及JVM并发包中提供的其他工具类去处理此问题. 但是随着技术的发展,分布式系统的出现,各个应用服务都部署在不同节点,由各自的JVM去操控,资源已经不是在 线程 之间的共享,而是变成了 进程 之间的

  • 基于redis实现分布式锁的原理与方法

    前言 系统的不断扩大,分布式锁是最基本的保障.与单机的多线程不一样的是,分布式跨多个机器.线程的共享变量无法跨机器. 为了保证一个在高并发存场景下只能被同一个线程操作,java并发处理提供ReentrantLock或Synchronized进行互斥控制.但是这仅仅对单机环境有效.我们实现分布式锁大概通过三种方式. redis实现分布式锁 数据库实现分布式锁 zk实现分布式锁 今天我们介绍通过redis实现分布式锁.实际上这三种和java对比看属于一类.都是属于程序外部锁. 原理剖析 上述三种分布

  • 基于Redis实现分布式锁的方法(lua脚本版)

    1.前言 在Java中,我们通过锁来避免由于竞争而造成的数据不一致问题.通常我们使用synchronized .Lock来实现.但是Java中的锁只能保证在同一个JVM进程内中可用,在跨JVM进程,例如分布式系统上则不可靠了. 2.分布式锁 分布式锁,是一种思想,它的实现方式有很多,如基于数据库实现.基于缓存(Redis等)实现.基于Zookeeper实现等等.为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件 互斥性:在任意时刻,只有一个客户端能持有锁. 不会发生死锁:即使客户端

  • Java基于redis实现分布式锁

    为了保证一个在高并发存场景下只能被同一个线程操作,java并发处理提供ReentrantLock或Synchronized进行互斥控制.但是这仅仅对单机环境有效.我们实现分布式锁大概通过三种方式. redis实现分布式锁 数据库实现分布式锁 zk实现分布式锁 实际上这三种和java对比看属于一类.都是属于程序外部锁. 原理剖析 上述三种分布式锁都是通过各自为依据对各个请求进行上锁,解锁从而控制放行还是拒绝.redis锁是基于其提供的setnx命令. setnx当且仅当key不存在.若给定key已

  • 详解基于redis实现分布式锁

    前言 为了保证一个在高并发存场景下只能被同一个线程操作,java并发处理提供ReentrantLock或Synchronized进行互斥控制.但是这仅仅对单机环境有效.我们实现分布式锁大概通过三种方式. redis实现分布式锁 数据库实现分布式锁 zk实现分布式锁 原理剖析 上述三种分布式锁都是通过各自为依据对各个请求进行上锁,解锁从而控制放行还是拒绝.redis锁是基于其提供的setnx命令. setnx当且仅当key不存在.若给定key已经存在,则setnx不做任何动作.setnx是一个原子

  • 详解Java如何实现基于Redis的分布式锁

    前言 单JVM内同步好办, 直接用JDK提供的锁就可以了,但是跨进程同步靠这个肯定是不可能的,这种情况下肯定要借助第三方,我这里实现用Redis,当然还有很多其他的实现方式.其实基于Redis实现的原理还算比较简单的,在看代码之前建议大家先去看看原理,看懂了之后看代码应该就容易理解了. 我这里不实现JDK的java.util.concurrent.locks.Lock接口,而是自定义一个,因为JDK的有个newCondition方法我这里暂时没实现.这个Lock提供了5个lock方法的变体,可以

  • 浅谈Java(SpringBoot)基于zookeeper的分布式锁实现

    通过zookeeper实现分布式锁 1.创建zookeeper的client 首先通过CuratorFrameworkFactory创建一个连接zookeeper的连接CuratorFramework client public class CuratorFactoryBean implements FactoryBean<CuratorFramework>, InitializingBean, DisposableBean { private static final Logger LOGG

  • SpringBoot集成redis实现分布式锁的示例代码

    1.准备 使用redis实现分布式锁,需要用的setnx(),所以需要集成Jedis 需要引入jar,jar最好和redis的jar版本对应上,不然会出现版本冲突,使用的时候会报异常redis.clients.jedis.Jedis.set(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String; 我使用的redis版本是2.3.0,Jedis使用的是3.3.0 <de

  • php基于redis的分布式锁实例详解

    在使用分布式锁进行互斥资源访问时候,我们很多方案是采用redis的实现. 固然,redis的单节点锁在极端情况也是有问题的,假设你的业务允许偶尔的失效,使用单节点的redis锁方案就足够了,简单而且效率高. redis锁失效的情况: 客户端1从master节点获取了锁 master宕机了,存储锁的key还没来得及同步到slave节点上 slave升级为master 客户端2从新的master上获取到同一个资源的锁 于是,客户端1和客户端2同事持有了同一个资源的锁,锁的安全性被打破. 如果我们不考

  • 基于Redis实现分布式锁以及任务队列

    一.前言 双十一刚过不久,大家都知道在天猫.京东.苏宁等等电商网站上有很多秒杀活动,例如在某一个时刻抢购一个原价1999现在秒杀价只要999的手机时,会迎来一个用户请求的高峰期,可能会有几十万几百万的并发量,来抢这个手机,在高并发的情形下会对数据库服务器或者是文件服务器应用服务器造成巨大的压力,严重时说不定就宕机了,另一个问题是,秒杀的东西都是有量的,例如一款手机只有10台的量秒杀,那么,在高并发的情况下,成千上万条数据更新数据库(例如10台的量被人抢一台就会在数据集某些记录下 减1),那次这个

随机推荐