如何利用Redis分布式锁实现控制并发操作

redis命令解释

说道Redis的分布式锁都是通过setNx命令结合getset来实现的,在讲之前我们先了解下setNx和getset的意思,在redis官网是这样解释的

注:redis的命令都是原子操作

SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

可用版本:

1.0.0+

时间复杂度:

O(1)

返回值:

设置成功,返回 1 。

设置失败,返回 0 。

redis> EXISTS job    # job 不存在
(integer) 0
redis> SETNX job "programmer" # job 设置成功
(integer) 1
redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0
redis> GET job     # 没有被覆盖
"programmer"

GETSET key value

将给定 key 的值设为 value ,并返回 key 的旧值(old value)。

当 key 存在但不是字符串类型时,返回一个错误。

可用版本:

1.0.0+

时间复杂度:

O(1)

返回值:

返回给定 key 的旧值。

当 key 没有旧值时,也即是, key 不存在时,返回 nil 。

redis> GETSET db mongodb # 没有旧值,返回 nil
(nil)
redis> GET db
"mongodb"
redis> GETSET db redis  # 返回旧值 mongodb
"mongodb"
redis> GET db
"redis"

代码示例

注意:为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得,这时就不必解锁了。

我们看下代码涉及以下几个类,这里有关业务逻辑相关的只定义了方法没有具体实现,关键是学习思路

OrderBiz.java

/**
 * 使用redis锁来控制并发抢单
 * @author fuyuwei
 */
public class OrderBiz {
 public int createOrder(){
  // 下单之前的参数、合法性校验这里就不在演示
  OrderLock<Boolean> orderLock = new RedisOrderLock<Boolean>("pro-12345678901");
  boolean isSyn = orderLock.isSyn(new OrderLockBiz<Boolean>(){
   @Override
   public Boolean createOrder() {
    // 省去创建订单逻辑
    return null;
   }
  });
  if(!isSyn){
   BizLogger.info("创建订单失败");
  }
  return 0;
 }
}

OrderLock.java

public interface OrderLock<T> {
 public boolean isSyn(OrderLockBiz<T> orderBiz);

}

OrderLockBiz.java

public interface OrderLockBiz<T> {
 public T createOrder();
}

RedisOrderLock.java

public class RedisOrderLock<T> implements OrderLock<T> {

 // 锁等待超时,防止线程饥饿,永远没有入锁执行代码的机会
 public static final long timeout = 10000;//ms

 // 锁持有超时,防止线程在入锁以后,无限的执行下去,让锁无法释放
 public static final long expireMsecs = 10000;// ms

 public String lockKey = "orderLockKey";

 public Jedis jedis;

 private static volatile JedisPool jedisPool;

 public RedisOrderLock(String lockKey) {
  this.lockKey = lockKey;
 }
 /**
  * 初始化redis
  * @return
  */
 public Jedis getInstance() {
  if(jedisPool == null) {
   synchronized(RedisOrderLock.class) {
    if(jedisPool == null) {
     JedisPoolConfig config = new JedisPoolConfig();
     config.setMaxIdle(100);
     jedisPool = new JedisPool(config,"localhost",6379, 3000,"test");
    }
   }
  }
  return jedisPool.getResource();
 }

 /**
  * 线程安全的业务逻辑处理
  */
 @Override
 public boolean isSyn(OrderLockBiz<T> orderBiz) {
  jedis = this.getInstance();
  try {
   // 获取到锁
   if(acquire(jedis)){
    // 执行创建订单逻辑
    orderBiz.createOrder();
   }else{
    BizLogger.info("waiting other thread creating");
   }
  } catch (Exception e) {
   BizLogger.error(e,"acquire lock failre");
  }finally{
   // 解锁
   this.releaseLock(jedis);
  }
  return false;
 }

 /**
  * accqure lock
  * @param jedis
  * @return
  * @throws InterruptedException
  */
 public synchronized boolean acquire(Jedis jedis){
  boolean locked = false;
  while(timeout > 0){
   long expires = System.currentTimeMillis() + expireMsecs + 1;
   // 10秒之后锁到期
   String expiresStr = String.valueOf(expires);
   // 获取到锁
   if(jedis.setnx(lockKey, expiresStr) == 1){
    locked = true;
    return locked;
   }
   // 没有获取到锁
   String oldValue = jedis.get(lockKey);
   // expireMsecs(10秒)锁的有效期内无法进入if判断,如果锁超时了
   if(oldValue != null
     && Long.parseLong(oldValue) < System.currentTimeMillis()){
    // 如果锁超时重新设置
    String oldValue_ = jedis.getSet(lockKey, expiresStr);
    // 值相同说明是同一个线程的操作,获取锁成功
    if(Long.valueOf(oldValue_) == Long.valueOf(oldValue)){
     locked = true;
    }else{
     // 被其他线程抢先获取锁
     locked = false;
    }
   }
   // 锁没有超时,继续等待
   return false;
  }
 }
 /**
  * 释放锁
  * @param jedis
  */
 public synchronized void releaseLock(Jedis jedis){
  try {
   long current = System.currentTimeMillis();
   // 避免删除非自己获取得到的锁
   if (current < Long.valueOf(jedis.get(lockKey)))
    jedis.del(lockKey);
  } catch (Exception e) {
   e.printStackTrace();
  }finally{
   // 把用完的连接放到连接池汇中供其他线程调用
   jedisPool.returnResource(jedis);
  }
 }
}

以上这篇如何利用Redis分布式锁实现控制并发操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • redis分布式锁及会出现的问题解决

    一.redis实现分布式锁的主要原理: 1.加锁 最简单的方法是使用setnx命令.key是锁的唯一标识,按业务来决定命名.比如想要给一种商品的秒杀活动加锁,可以给key命名为 "lock_sale_商品ID" .而value设置成什么呢?我们可以姑且设置成1.加锁的伪代码如下: setnx(key,1) 当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁:当一个线程执行setnx返回0,说明key已经存在,该线程抢锁失败. 2.解锁 有加锁就得有解锁.当得到锁的

  • SpringBoot使用Redis实现分布式锁

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

  • SpringBoot整合Redis正确的实现分布式锁的示例代码

    前言 最近在做分块上传的业务,使用到了Redis来维护上传过程中的分块编号. 每上传完成一个分块就获取一下文件的分块集合,加入新上传的编号,手动接口测试下是没有问题的,前端通过并发上传调用就出现问题了,并发的get再set,就会存在覆盖写现象,导致最后的分块数据不对,不能触发分块合并请求. 遇到并发二话不说先上锁,针对执行代码块加了一个JVM锁之后问题就解决了. 仔细一想还是不太对,项目是分布式部署的,做了负载均衡,一个节点的代码被锁住了,请求轮询到其他节点还是可以进行覆盖写,并没有解决到问题啊

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

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

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

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

  • 如何利用Redis分布式锁实现控制并发操作

    redis命令解释 说道Redis的分布式锁都是通过setNx命令结合getset来实现的,在讲之前我们先了解下setNx和getset的意思,在redis官网是这样解释的 注:redis的命令都是原子操作 SETNX key value 将 key 的值设为 value ,当且仅当 key 不存在. 若给定的 key 已经存在,则 SETNX 不做任何动作. SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写. 可用版本: 1.0.0+ 时间复杂度: O(1)

  • 关于SpringBoot 使用 Redis 分布式锁解决并发问题

    目录 问题背景 解决方案 主要实现原理: 可靠性: SpringBoot 集成使用 Redis 分布式锁 使用示例 参考文档 问题背景 现在的应用程序架构中,很多服务都是多副本运行,从而保证服务的稳定性.一个服务实例挂了,其他服务依旧可以接收请求.但是服务的多副本运行随之也会引来一些分布式问题,比如某个接口的处理逻辑是这样的:接收到请求后,先查询 DB 看是否有相关的数据,如果没有则插入数据,如果有则更新数据.在这种场景下如果相同的 N 个请求并发发到后端服务实例,就会出现重复插入数据的情况:

  • SpringBoot利用注解来实现Redis分布式锁

    目录 一.业务背景 二.分析流程 加锁 超时问题 解决方案:增加一个「续时」 三.设计方案 四.实操 相关属性类配置 核心切面拦截的操作 五.开始测试 六.总结 一.业务背景 有些业务请求,属于耗时操作,需要加锁,防止后续的并发操作,同时对数据库的数据进行操作,需要避免对之前的业务造成影响. 二.分析流程 使用 Redis 作为分布式锁,将锁的状态放到 Redis 统一维护,解决集群中单机 JVM 信息不互通的问题,规定操作顺序,保护用户的数据正确. 梳理设计流程 新建注解 @interface

  • 基于redis分布式锁实现秒杀功能

    最近在项目中遇到了类似"秒杀"的业务场景,在本篇博客中,我将用一个非常简单的demo,阐述实现所谓"秒杀"的基本思路. 业务场景 所谓秒杀,从业务角度看,是短时间内多个用户"争抢"资源,这里的资源在大部分秒杀场景里是商品:将业务抽象,技术角度看,秒杀就是多个线程对资源进行操作,所以实现秒杀,就必须控制线程对资源的争抢,既要保证高效并发,也要保证操作的正确. 一些可能的实现 刚才提到过,实现秒杀的关键点是控制线程对资源的争抢,根据基本的线程知识,可

  • Redis分布式锁升级版RedLock及SpringBoot实现方法

    分布式锁概览 在多线程的环境下,为了保证一个代码块在同一时间只能由一个线程访问,Java中我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式.但是现在公司都是流行分布式架构,在分布式环境下,如何保证不同节点的线程同步执行呢?因此就引出了分布式锁,它是控制分布式系统之间互斥访问共享资源的一种方式. 在一个分布式系统中,多台机器上部署了多个服务,当客户端一个用户发起一个数据插入请求时,如果没有分布式锁机制保证,那么那多台机器上的多个服务可能进行并发插

  • Redis分布式锁的实现方式(redis面试题)

    什么是分布式锁? 要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁.进程锁. 线程锁:主要用来给方法.代码块加锁.当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段.线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state). 进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronize

  • redis分布式锁的go-redis实现方法详解

    在分布式的业务中 , 如果有的共享资源需要安全的被访问和处理 , 那就需要分布式锁 分布式锁的几个原则; 1.「锁的互斥性」:在分布式集群应用中,共享资源的锁在同一时间只能被一个对象获取. 2. 「可重入」:为了避免死锁,这把锁是可以重入的,并且可以设置超时. 3. 「高效的加锁和解锁」:能够高效的加锁和解锁,获取锁和释放锁的性能也好. 4. 「阻塞.公平」:可以根据业务的需要,考虑是使用阻塞.还是非阻塞,公平还是非公平的锁. redis实现分布式锁主要靠setnx命令 1. 当key存在时失败

  • 详解redis分布式锁的这些坑

    一.白话分布式 什么是分布式,用最简单的话来说,就是为了较低单个服务器的压力,将功能分布在不同的机器上面,本来一个程序员可以完成一个项目:需求->设计->编码->测试 但是项目多的时候,一个人也扛不住,这就需要不同的人进行分工合作了 这就是一个简单的分布式协同工作了: 二.分布式锁 首先看一个问题,如果说某个环节被终止或者别侵占,就会发生不可知的事情 这就会出现,设计好的或者设计的半成品会被破坏,导致后面环节出错: 这时候,我们就需要引入分布式锁的概念: 何为分布式锁 当在分布式模型下,

  • Go 语言下基于Redis分布式锁的实现方式

    分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁. 项目地址: https://github.com/Spongecaptain/redisLock 1. Go 原生的互斥锁 Go 原生的互斥锁即 sync 包下的 M

随机推荐