栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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
public class MzServiceImpl implements MzService {
 @Autowired
 private MzMapper mzMapper;
 
 @Override
 public Map gh(String ksdm, String ysdm,String brid) {
  Map resultMap = new HashMap<>();
  int ghxh = 0;
  //获取当前的挂号序号
  Map 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依赖


 org.springframework.boot
 spring-boot-starter-data-redis

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;
 

@Repository
public class RedisLock {
 
 private StringRedisTemplate stringredisTemplate;
 
 public RedisLock(StringRedisTemplate stringredisTemplate) {
  this.stringredisTemplate = stringredisTemplate;
 }
 
 
 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;
 }
 
 
 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;
 }
 
 
 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
public class MzServiceImpl implements MzService {
 @Autowired
 private MzMapper mzMapper;
 @Autowired
 private RedisLock redisLock;
 
 @Override
 public Map gh(String ksdm, String ysdm, String brid) {
  Map resultMap = new HashMap<>();
  int ghxh = 0;
  //加锁操作
  String token = null;
  token = redisLock.lock("gh", 3000,3500);
  try {
   //获取到了锁,执行正常业务
   if (token != null) {
    //获取当前的挂号序号
    Map 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分布式锁内容请搜索考高分网以前的文章或继续浏览下面的相关文章希望大家以后多多支持考高分网!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/131853.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号