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

一.模拟问题

最近在公司遇到一个问题,挂号系统是做的集群,比如启动了两个相同的服务,病人挂号的时候可能会出现同号的情况,比如两个病人挂出来的号都是上午2号.这就出现了问题,由于是集群部署的,所以单纯在代码中的方法中加锁是不能解决这种情况的.下面我将模拟这种情况,用redis做分布式锁来解决这个问题.

1.新建挂号明细表

2.在idea上新建项目

下图是创建好的项目结构,上面那个parent项目是其他项目不用管它,和新建的没有关系

3.开始创建controller,service,dao(mapper),写好后整体结构如下

这里贴上service实现类的代码,主要代码就是这一块:

package com.zk.service.impl;

import com.zk.mapper.MzMapper;
import com.zk.service.MzService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

/**
 * 门诊操作service实现类
 *
 * @author zk
 * @date 2020-9-9
 */
@Service
public class MzServiceImpl implements MzService {
 @Autowired
 private MzMapper mzMapper;

 @Override
 public Map<String, Object> gh(String ksdm, String ysdm,String brid) {
  Map<String,Object> resultMap = new HashMap<>();
  int ghxh = 0;
  //获取当前的挂号序号
  Map<String, Object> ghxhMap = mzMapper.getGhxh(ksdm,ysdm);
  //如果为空,说明还没有人挂这个医生的号,当前是一号
  if(ghxhMap == null){
   ghxh = 1;
  }else{
   ghxh = (int)ghxhMap.get("GHXH");
   ghxh++;
  }
  //实际场景中,先获取到ghxh后,还会进行收费等其他操作,这里模拟一下需要耗费时间,为了方便测试出现问题,这里时间设置稍微长一点
  try {
   Thread.sleep(2000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  //新增挂号明细记录
  mzMapper.addGhmx(ksdm,ysdm,ghxh,brid);
  resultMap.put("code","200");
  resultMap.put("msg","success");
  return resultMap;
 }
}

4.进行测试

1)清空数据库表

2)使用postman发送post请求,假设ksdm=1表示皮肤科,ysdm=1表示医生华佗,brbh=1表示张三,现在张三去医院挂皮肤科华佗医生的号,收费员就会操作系统调用上面写的挂号接口.

调用成功后,看看数据库里的数据

可以看到张三挂到了华佗医生的第一个号,接着把请求参数的brbh改成2表示李四,李四也去挂华佗医生的号

请求成功后查看数据库

可以看到李四挂了华佗医生的第二个号.现在就是正常的挂号,没有出现问题.

3)postman开第二个请求窗口,两个窗口同时去掉接口进行挂号

窗口一模拟张三挂号

窗口二模拟李四挂号

操作成功后看看数据库

结果是张三和李四都挂到了三号,这就出现了线程安全问题.

3)使用加锁的方式解决问题

方法加上锁之后,测试确实没有问题了,但是实际情况是集群部署的,并不是只有一个服务

4)启动两个服务

idea中设置允许启动多个实例

修改端口号,第一个启动的端口号是8080,这里改成8081

5)启动两个服务后再用postman去请求,张三请求8080服务接口

李四请求8081接口

两个窗口同时请求的时候,再次出现了ghxh相同的情况,在方法上加同步锁不能解决这个问题.

二.使用redis做分布式锁解决问题

1.首先需要启动一个redis,如果不知道redis怎么安装使用的,可以看我之前写的"redis的安装和使用".因为之前我在虚拟机上安装过了redis,这里直接启动一个单节点redis

2.项目的pom.xml文件引入redis依赖

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

3.在application.yml中配置redis

spring:
 redis:
 host: 192.168.1.6
 port: 6379

4.新建redis锁操作类

package com.zk.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;

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

/**
 * redis锁操作类
 *
 * @author zk
 * @date 2020-9-10
 */
@Repository
public class RedisLock {

 private StringRedisTemplate stringredisTemplate;

 public RedisLock(StringRedisTemplate stringredisTemplate) {
  this.stringredisTemplate = stringredisTemplate;
 }

 /**
  * 加锁,无阻塞
  * 加锁过程必须设置过期时间
  * 如果没有设置过期时间,手动释放锁的操作出现问题,那么就发生死锁,锁永远不能被释放.
  * 加锁和设置过期时间过程必须是原子操作
  * 如果加锁后服务宕机或程序崩溃,来不及设置过期时间,同样会发生死锁.
  *
  * @param key 锁id
  * @param expire 过期时间
  * @return
  */
 public String tryLock(String key, long expire) {
  String token = UUID.randomUUID().toString();
  //setIfAbsent方法:当key不存在的时候,设置成功并返回true,当key存在的时候,设置失败并返回false
  //token是对应的value,expire是缓存过期时间
  Boolean isSuccess = stringredisTemplate.opsForValue().setIfAbsent(key, token, expire, TimeUnit.MILLISECONDS);
  if (isSuccess) {
   return token;
  }
  return null;
 }

 /**
  * 加锁,有阻塞
  *
  * @param name 锁名称
  * @param expire 锁过期时间
  * @param timeout 请求超时时间
  * @return
  */
 public String lock(String name, long expire, long timeout) {
  long startTime = System.currentTimeMillis();
  String token;
  do {
   token = tryLock(name, expire);
   if (token == null) {
    if ((System.currentTimeMillis() - startTime) > (timeout - 50)) {
     break;
    }
    try {
     //try 50 per sec
     Thread.sleep(50);
    } catch (InterruptedException e) {
     e.printStackTrace();
     return null;
    }
   }
  } while (token == null);

  return token;
 }

 /**
  * 解锁操作
  * 解锁必须是解除自己加上的锁
  * 试想一个这样的场景,服务A加锁,但执行效率非常慢,导致锁失效后还未执行完,但这时候服务B已经拿到锁了,这时候服务A执行完毕了去解锁,
  * 把服务B的锁给解掉了,其他服务C、D、E...都可以拿到锁了,这就有问题了.
  * 加锁的时候我们可以设置唯一value,解锁时判断是不是自己先前的value就行了.
  *
  * @param key
  * @param token
  * @return
  */
 public boolean unlock(String key, String token) {
  //解锁时需要先取出key对应的value进行判断是否相等,这也是为什么加锁的时候需要放不重复的值作为value
  String value = stringredisTemplate.opsForValue().get("name");
  if (StringUtils.equals(value, token)) {
   stringredisTemplate.delete(key);
   return true;
  }
  return false;
 }
}

5.修改业务操作类,用上RedisLock

package com.zk.service.impl;

import com.zk.mapper.MzMapper;
import com.zk.service.MzService;
import com.zk.util.RedisLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 门诊操作service实现类
 *
 * @author zk
 * @date 2020-9-9
 */
@Service
public class MzServiceImpl implements MzService {
 @Autowired
 private MzMapper mzMapper;
 @Autowired
 private RedisLock redisLock;

 @Override
 public Map<String, Object> gh(String ksdm, String ysdm, String brid) {
  Map<String, Object> resultMap = new HashMap<>();
  int ghxh = 0;
  //加锁操作
  String token = null;
  token = redisLock.lock("gh", 3000,3500);
  try {
   //获取到了锁,执行正常业务
   if (token != null) {
    //获取当前的挂号序号
    Map<String, Object> ghxhMap = mzMapper.getGhxh(ksdm, ysdm);
    //如果为空,说明还没有人挂这个医生的号,当前是一号
    if (ghxhMap == null) {
     ghxh = 1;
    } else {
     ghxh = (int) ghxhMap.get("GHXH");
     ghxh++;
    }
    //实际场景中,先获取到ghxh后,还会进行收费等其他操作,这里模拟一下需要耗费时间,为了方便测试出现问题,这里时间设置稍微长一点
    try {
     Thread.sleep(2000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    //新增挂号明细记录
    mzMapper.addGhmx(ksdm, ysdm, ghxh, brid);
   } else {
    resultMap.put("code", "401");
    resultMap.put("msg", "其他窗口正在操作,请稍后再试");
    return resultMap;
   }
  } finally {
   //解锁
   if (token != null) {
    boolean gh = redisLock.unlock("gh", token);
   }
  }
  resultMap.put("code", "200");
  resultMap.put("msg", "success");
  return resultMap;
 }
}

6.再用postman开两个窗口去请求,和上面的操作一样,然后再看数据库的数据

问题解决,不会再出现重复的ghxh.

总结

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

(0)

相关推荐

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

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

  • springboot redis分布式锁代码实例

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

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

  • SpringBoot使用Redis实现分布式锁

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

  • 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中使用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内部的一块内存空间! 后来业务发展,需要做集群,一个应用需要部署到几台机

  • SpringBoot中使用Redis作为全局锁示例过程

    目录 一.模拟没有锁情况下的资源竞争 二.使用redis加锁 微服务的项目中,一个服务我们启动多份,在不同的进程中.这些服务是无状态的,而由数据存储容器(mysql/redis/es)进行状态数据的持久化.这就会导致资源竞争,出现多线程的问题. 一.模拟没有锁情况下的资源竞争 public class CommonConsumerService { //库存个数 static int goodsCount = 900; //卖出个数 static int saleCount = 0; publi

  • 使用Redis实现分布式锁的方法

    目录 Redis 中的分布式锁如何使用 分布式锁的使用场景 使用 Redis 来实现分布式锁 使用 set key value px milliseconds nx 实现 SETNX+Lua 实现 使用 Redlock 实现分布式锁 锁的续租 看看 SETEX 的源码 为什么 Redis 可以用来做分布式锁 分布式锁如何选择 总结 参考 Redis 中的分布式锁如何使用 分布式锁的使用场景 为了保证我们线上服务的并发性和安全性,目前我们的服务一般抛弃了单体应用,采用的都是扩展性很强的分布式架构.

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

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

  • Redis实现分布式锁的方法示例

    之前我们使用的定时任务都是只部署在了单台机器上,为了解决单点的问题,为了保证一个任务,只被一台机器执行,就需要考虑锁的问题,于是就花时间研究了这个问题.到底怎样实现一个分布式锁呢? 锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,如果考虑使用redis来实现一个分布式锁,最简单的方案就是在实例里面创建一个键值,释放锁的时候,将键值删除.但是一个可靠完善的分布式锁需要考虑的细节比较多,我们就来看看如何写一个正确的分布式锁. 单机版分布式锁 SETNX 所以我们直接基于 redis 的 s

  • Redis数据库中实现分布式锁的方法

    分布式锁是一个在很多环境中非常有用的原语,它是不同进程互斥操作共享资源的唯一方法.有很多的开发库和博客描述如何使用Redis实现DLM(Distributed Lock Manager),但是每个开发库使用不同的方式,而且相比更复杂的设计与实现,很多库使用一些简单低可靠的方式来实现. 这篇文章尝试提供更标准的算法来使用Redis实现分布式锁.我们提出一种算法,叫做Relock,它实现了我们认为比vanilla单一实例方式更安全的DLM(分布式锁管理).我们希望社区分析它并提供反馈,以做为更加复杂

  • 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去操控,资源已经不是在 线程 之间的共享,而是变成了 进程 之间的

随机推荐