SpringBoot使用Redis实现分布式锁

前言

在单机应用时代,我们对一个共享的对象进行多线程访问的时候,使用java的synchronized关键字或者ReentrantLock类对操作的对象加锁就可以解决对象的线程安全问题。

分布式应用时代这个方法却行不通了,我们的应用可能被部署到多台机器上,运行在不同的JVM里,一个对象可能同时存在多台机器的内存中,怎样使共享对象同时只被一个线程处理就成了一个问题。

在分布式系统中为了保证一个对象在高并发的情况下只能被一个线程使用,我们需要一种跨JVM的互斥机制来控制共享资源的访问,此时就需要用到我们的分布式锁了。

分布式锁一般有三种实现方式:1.通过数据库实现分布式锁;2.通过缓存(Redis等)实现分布式锁;3.通过Zookeeper实现分布式锁。本篇文章主要介绍第二种通过Redis实现分布式锁的方式。

分布式锁的需要具备的条件

为了保证分布式锁的可用性,需要具备一下五点条件:

1、在同一时间保证只有一台机器的一个线程可以持有锁。
2、不能发生死锁,无论何时持有锁的机器崩溃挂掉了都要能自动释放锁。
3、高效的获取和释放锁。
4、具备非阻塞性,一旦获取不到锁就立刻返回加锁失败。
5、独占性,即自己加的锁只有自己才能释放。

代码实现

组件依赖

首先在pom.xml文件中添加依赖:

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

加锁代码

代码如下:

/**
 * 获取锁
 * @param lockKey 锁
 * @param identity 身份标识(保证锁不会被其他人释放)
 * @param expireTime 锁的过期时间(单位:秒)
 * @return
 */
public boolean lock(String lockKey, String identity, long expireTime){
  boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS);
  return opsForValue;
}

加锁的方法只需要三个参数:lockKey、identity、expireTime。

  • 第一个参数lockKey为key,一个资源对应一个唯一的key。
  • 第二个参数identity为身份标识,作为此key对应的value存储,为了判断在释放锁时是不是和加锁的身份相同,防止别人释放锁。
  • 第三个参数expireTime为过期时间,此参数保证程序加锁后崩溃导致不能主动释放锁的时候自动释放锁,防止出现死锁。

为什么使用setIfAbsent方法呢?这个方法的好处就是,如果redis中已经存在这个key了,就会返回失败,并且不改变redis中的数据,这样就不会把别的线程的加的锁给覆盖掉。

解锁代码

代码如下:

/**
 * 释放锁
 * @param lockKey 锁
 * @param identity 身份标识(保证锁不会被其他人释放)
 * @return
 */
public boolean releaseLock(String lockKey, String identity){
  String luaScript =
    "if " +
    "  redis.call('get', KEYS[1]) == ARGV[1] " +
    "then " +
    "  return redis.call('del', KEYS[1]) " +
    "else " +
    "  return 0 " +
    "end";
  DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
  redisScript.setResultType(Boolean.class);
  redisScript.setScriptText(luaScript);
  List<String> keys = new ArrayList<>();
  keys.add(lockKey);
  boolean result = redisTemplate.execute(redisScript, keys, identity);
  return result;
}

解锁的方法只需两个参数:lockKey、identity。

  • 第一个参数lockKey为key,一个资源对应一个唯一的key。
  • 第二个参数identity为身份标识,作为此key对应的value存储,为了判断在释放锁时是不是和加锁的身份相同,防止别人释放锁。

此处使用Lua脚本来判断身份,身份相同就删除,身份不同就不对数据做操作并返回失败。为什么要使用Lua脚本呢?这是为了要保证操作的原子性,redis在执行Lua脚本的时候是把脚本当作一个命令来执行的,我们都知道redis的命令是都是原子操作,这样就保证了操作的原子性。

测试代码

package com.qixi.lock.demo.lockdemo.controller;

import com.qixi.lock.demo.lockdemo.util.RedisLock;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试分布式锁
 * @author ZhengNC
 * @date 2020/5/13 17:27
 */
@RestController
@RequestMapping("test")
public class TestRedisLockController {

  private final String lockKeyName = "testKey";

  @Autowired
  private RedisLock redisLock;

  /**
   * 测试加锁
   * @param id 加锁的资源id
   * @param identity 身份标识
   * @return
   */
  @GetMapping("lock")
  public String lock(@RequestParam("id") String id,
            @RequestParam("identity") String identity){
    String lockKey = lockKeyName+":"+id;
    boolean lockSuccess = redisLock.lock(lockKey, identity, 60);
    String result = "lock failed";
    if (lockSuccess){
      result = "lock success";
    }
    return result;
  }

  /**
   * 测试释放锁
   * @param id 释放锁的资源id
   * @param identity 身份标识
   * @return
   */
  @GetMapping("release")
  public String release(@RequestParam("id") String id,
            @RequestParam("identity") String identity){
    String lockKey = lockKeyName+":"+id;
    boolean releaseSuccess = redisLock.releaseLock(lockKey, identity);
    String result = "release failed";
    if (releaseSuccess){
      result = "release success";
    }
    return result;
  }
}
package com.qixi.lock.demo.lockdemo.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁Redis工具类
 * @author ZhengNC
 * @date 2020/5/13 17:27
 */
@Component
public class RedisLock {

  @Autowired
  private RedisTemplate<String, String> redisTemplate;

  /**
   * 获取锁
   * @param lockKey 锁
   * @param identity 身份标识(保证锁不会被其他人释放)
   * @param expireTime 锁的过期时间(单位:秒)
   * @return
   */
  public boolean lock(String lockKey, String identity, long expireTime){
    boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS);
    return lockResult;
  }

  /**
   * 释放锁
   * @param lockKey 锁
   * @param identity 身份标识(保证锁不会被其他人释放)
   * @return
   */
  public boolean releaseLock(String lockKey, String identity){
    String luaScript =
        "if " +
        "  redis.call('get', KEYS[1]) == ARGV[1] " +
        "then " +
        "  return redis.call('del', KEYS[1]) " +
        "else " +
        "  return 0 " +
        "end";
    DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
    redisScript.setResultType(Boolean.class);
    redisScript.setScriptText(luaScript);
    List<String> keys = new ArrayList<>();
    keys.add(lockKey);
    boolean result = redisTemplate.execute(redisScript, keys, identity);
    return result;
  }
}

结语

感谢大家阅读我的文章,更欢迎大家指出我的问题,希望能在这里通过讨论取得共同的进步。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • springboot redis分布式锁代码实例

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

  • SpringBoot集成Redisson实现分布式锁的方法示例

    上篇 <SpringBoot 集成 redis 分布式锁优化>对死锁的问题进行了优化,今天介绍的是 redis 官方推荐使用的 Redisson ,Redisson 架设在 redis 基础上的 Java 驻内存数据网格(In-Memory Data Grid),基于NIO的 Netty 框架上,利用了 redis 键值数据库.功能非常强大,解决了很多分布式架构中的问题. Github的wiki地址: https://github.com/redisson/redisson/wiki 官方文档

  • SpringBoot使用Redisson实现分布式锁(秒杀系统)

    前面讲完了Redis的分布式锁的实现,接下来讲Redisson的分布式锁的实现,一般提及到Redis的分布式锁我们更多的使用的是Redisson的分布式锁,Redis的官方也是建议我们这样去做的.Redisson点我可以直接跳转到Redisson的官方文档. 1.1.引入Maven依赖 <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter&l

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

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

  • 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

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

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

  • SpringBoot使用Redis实现分布式锁

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

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

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

  • SpringBoot之使用Redis实现分布式锁(秒杀系统)

    一.Redis分布式锁概念篇 建议直接采用Redis的官方推荐的Redisson作为redis的分布式锁 1.1.为什么要使用分布式锁 我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的Java多线程的18般武艺进行处理,并且可以完美的运行,毫无Bug! 注意这是单机应用,也就是所有的请求都会分配到当前服务器的JVM内部,然后映射为操作系统的线程进行处理!而这个共享变量只是在这个JVM内部的一块内存空间! 后来业务发展,需要做集群,一个应用需要部署到几台机

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

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

  • 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是一个原子

  • springboot 集成redission 以及分布式锁的使用详解

    目录 springboot集成redission及分布式锁的使用 1.引入jar包 2.增加Configuration类 3.使用redission分布式锁 Springboot整合Redisson 锁 一.依赖 二.配置文件 三.锁的使用 四.分布式秒杀 五.redis锁 单机版可用,分布式用Redisson springboot集成redission及分布式锁的使用 1.引入jar包 <dependency> <groupId>org.redisson</groupId&

随机推荐