亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時(shí)間:2020年09月10日 15:31:33   作者:hchvhg  
這篇文章主要介紹了SpringBoot中使用redis做分布式鎖的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

一.模擬問題

最近在公司遇到一個(gè)問題,掛號(hào)系統(tǒng)是做的集群,比如啟動(dòng)了兩個(gè)相同的服務(wù),病人掛號(hào)的時(shí)候可能會(huì)出現(xiàn)同號(hào)的情況,比如兩個(gè)病人掛出來的號(hào)都是上午2號(hào).這就出現(xiàn)了問題,由于是集群部署的,所以單純?cè)诖a中的方法中加鎖是不能解決這種情況的.下面我將模擬這種情況,用redis做分布式鎖來解決這個(gè)問題.

1.新建掛號(hào)明細(xì)表

2.在idea上新建項(xiàng)目

下圖是創(chuàng)建好的項(xiàng)目結(jié)構(gòu),上面那個(gè)parent項(xiàng)目是其他項(xiàng)目不用管它,和新建的沒有關(guān)系

3.開始創(chuàng)建controller,service,dao(mapper),寫好后整體結(jié)構(gòu)如下

這里貼上service實(shí)現(xiàn)類的代碼,主要代碼就是這一塊:

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實(shí)現(xiàn)類
 *
 * @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;
  //獲取當(dāng)前的掛號(hào)序號(hào)
  Map<String, Object> ghxhMap = mzMapper.getGhxh(ksdm,ysdm);
  //如果為空,說明還沒有人掛這個(gè)醫(yī)生的號(hào),當(dāng)前是一號(hào)
  if(ghxhMap == null){
   ghxh = 1;
  }else{
   ghxh = (int)ghxhMap.get("GHXH");
   ghxh++;
  }
  //實(shí)際場(chǎng)景中,先獲取到ghxh后,還會(huì)進(jìn)行收費(fèi)等其他操作,這里模擬一下需要耗費(fèi)時(shí)間,為了方便測(cè)試出現(xiàn)問題,這里時(shí)間設(shè)置稍微長一點(diǎn)
  try {
   Thread.sleep(2000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  //新增掛號(hào)明細(xì)記錄
  mzMapper.addGhmx(ksdm,ysdm,ghxh,brid);
  resultMap.put("code","200");
  resultMap.put("msg","success");
  return resultMap;
 }
}

4.進(jìn)行測(cè)試

1)清空數(shù)據(jù)庫表

2)使用postman發(fā)送post請(qǐng)求,假設(shè)ksdm=1表示皮膚科,ysdm=1表示醫(yī)生華佗,brbh=1表示張三,現(xiàn)在張三去醫(yī)院掛皮膚科華佗醫(yī)生的號(hào),收費(fèi)員就會(huì)操作系統(tǒng)調(diào)用上面寫的掛號(hào)接口.

調(diào)用成功后,看看數(shù)據(jù)庫里的數(shù)據(jù)

可以看到張三掛到了華佗醫(yī)生的第一個(gè)號(hào),接著把請(qǐng)求參數(shù)的brbh改成2表示李四,李四也去掛華佗醫(yī)生的號(hào)

請(qǐng)求成功后查看數(shù)據(jù)庫

可以看到李四掛了華佗醫(yī)生的第二個(gè)號(hào).現(xiàn)在就是正常的掛號(hào),沒有出現(xiàn)問題.

3)postman開第二個(gè)請(qǐng)求窗口,兩個(gè)窗口同時(shí)去掉接口進(jìn)行掛號(hào)

窗口一模擬張三掛號(hào)

窗口二模擬李四掛號(hào)

操作成功后看看數(shù)據(jù)庫

結(jié)果是張三和李四都掛到了三號(hào),這就出現(xiàn)了線程安全問題.

3)使用加鎖的方式解決問題

方法加上鎖之后,測(cè)試確實(shí)沒有問題了,但是實(shí)際情況是集群部署的,并不是只有一個(gè)服務(wù)

4)啟動(dòng)兩個(gè)服務(wù)

idea中設(shè)置允許啟動(dòng)多個(gè)實(shí)例

修改端口號(hào),第一個(gè)啟動(dòng)的端口號(hào)是8080,這里改成8081

5)啟動(dòng)兩個(gè)服務(wù)后再用postman去請(qǐng)求,張三請(qǐng)求8080服務(wù)接口

李四請(qǐng)求8081接口

兩個(gè)窗口同時(shí)請(qǐng)求的時(shí)候,再次出現(xiàn)了ghxh相同的情況,在方法上加同步鎖不能解決這個(gè)問題.

二.使用redis做分布式鎖解決問題

1.首先需要啟動(dòng)一個(gè)redis,如果不知道redis怎么安裝使用的,可以看我之前寫的"redis的安裝和使用".因?yàn)橹拔以谔摂M機(jī)上安裝過了redis,這里直接啟動(dòng)一個(gè)單節(jié)點(diǎn)redis

2.項(xiàng)目的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;
 }
 
 /**
  * 加鎖,無阻塞
  * 加鎖過程必須設(shè)置過期時(shí)間
  * 如果沒有設(shè)置過期時(shí)間,手動(dòng)釋放鎖的操作出現(xiàn)問題,那么就發(fā)生死鎖,鎖永遠(yuǎn)不能被釋放.
  * 加鎖和設(shè)置過期時(shí)間過程必須是原子操作
  * 如果加鎖后服務(wù)宕機(jī)或程序崩潰,來不及設(shè)置過期時(shí)間,同樣會(huì)發(fā)生死鎖.
  *
  * @param key 鎖id
  * @param expire 過期時(shí)間
  * @return
  */
 public String tryLock(String key, long expire) {
  String token = UUID.randomUUID().toString();
  //setIfAbsent方法:當(dāng)key不存在的時(shí)候,設(shè)置成功并返回true,當(dāng)key存在的時(shí)候,設(shè)置失敗并返回false
  //token是對(duì)應(yīng)的value,expire是緩存過期時(shí)間
  Boolean isSuccess = stringredisTemplate.opsForValue().setIfAbsent(key, token, expire, TimeUnit.MILLISECONDS);
  if (isSuccess) {
   return token;
  }
  return null;
 }
 
 /**
  * 加鎖,有阻塞
  *
  * @param name 鎖名稱
  * @param expire 鎖過期時(shí)間
  * @param timeout 請(qǐng)求超時(shí)時(shí)間
  * @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;
 }
 
 /**
  * 解鎖操作
  * 解鎖必須是解除自己加上的鎖
  * 試想一個(gè)這樣的場(chǎng)景,服務(wù)A加鎖,但執(zhí)行效率非常慢,導(dǎo)致鎖失效后還未執(zhí)行完,但這時(shí)候服務(wù)B已經(jīng)拿到鎖了,這時(shí)候服務(wù)A執(zhí)行完畢了去解鎖,
  * 把服務(wù)B的鎖給解掉了,其他服務(wù)C、D、E...都可以拿到鎖了,這就有問題了.
  * 加鎖的時(shí)候我們可以設(shè)置唯一value,解鎖時(shí)判斷是不是自己先前的value就行了.
  *
  * @param key
  * @param token
  * @return
  */
 public boolean unlock(String key, String token) {
  //解鎖時(shí)需要先取出key對(duì)應(yīng)的value進(jìn)行判斷是否相等,這也是為什么加鎖的時(shí)候需要放不重復(fù)的值作為value
  String value = stringredisTemplate.opsForValue().get("name");
  if (StringUtils.equals(value, token)) {
   stringredisTemplate.delete(key);
   return true;
  }
  return false;
 }
}

5.修改業(yè)務(wù)操作類,用上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實(shí)現(xiàn)類
 *
 * @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 {
   //獲取到了鎖,執(zhí)行正常業(yè)務(wù)
   if (token != null) {
    //獲取當(dāng)前的掛號(hào)序號(hào)
    Map<String, Object> ghxhMap = mzMapper.getGhxh(ksdm, ysdm);
    //如果為空,說明還沒有人掛這個(gè)醫(yī)生的號(hào),當(dāng)前是一號(hào)
    if (ghxhMap == null) {
     ghxh = 1;
    } else {
     ghxh = (int) ghxhMap.get("GHXH");
     ghxh++;
    }
    //實(shí)際場(chǎng)景中,先獲取到ghxh后,還會(huì)進(jìn)行收費(fèi)等其他操作,這里模擬一下需要耗費(fèi)時(shí)間,為了方便測(cè)試出現(xiàn)問題,這里時(shí)間設(shè)置稍微長一點(diǎn)
    try {
     Thread.sleep(2000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    //新增掛號(hào)明細(xì)記錄
    mzMapper.addGhmx(ksdm, ysdm, ghxh, brid);
   } else {
    resultMap.put("code", "401");
    resultMap.put("msg", "其他窗口正在操作,請(qǐng)稍后再試");
    return resultMap;
   }
  } finally {
   //解鎖
   if (token != null) {
    boolean gh = redisLock.unlock("gh", token);
   }
  }
  resultMap.put("code", "200");
  resultMap.put("msg", "success");
  return resultMap;
 }
}

6.再用postman開兩個(gè)窗口去請(qǐng)求,和上面的操作一樣,然后再看數(shù)據(jù)庫的數(shù)據(jù)

問題解決,不會(huì)再出現(xiàn)重復(fù)的ghxh.

總結(jié)

到此這篇關(guān)于SpringBoot中使用redis做分布式鎖的方法的文章就介紹到這了,更多相關(guān)SpringBoot redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java數(shù)據(jù)結(jié)構(gòu)之棧的詳解

    Java數(shù)據(jù)結(jié)構(gòu)之棧的詳解

    這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之棧簡單操作的相關(guān)資料,需要的朋友可以參考下,希望能夠給你帶來幫助
    2021-09-09
  • Spring Boot 中常用的注解@RequestParam及基本用法

    Spring Boot 中常用的注解@RequestParam及基本用法

    @RequestParam 是 Spring Framework 和 Spring Boot 中常用的注解之一,用于從請(qǐng)求中獲取參數(shù)值,本文給大家介紹Spring Boot 中常用的注解@RequestParam,感興趣的朋友一起看看吧
    2023-10-10
  • Java結(jié)構(gòu)型模式之橋接模式詳解

    Java結(jié)構(gòu)型模式之橋接模式詳解

    橋接模式是一種很實(shí)用的結(jié)構(gòu)型模式,如果系統(tǒng)中某個(gè)類存在兩個(gè)獨(dú)立變化的維度,通過橋接模式將這兩個(gè)維度分離出來,使兩者可以獨(dú)立擴(kuò)展
    2023-02-02
  • Java多線程中Lock的使用小結(jié)

    Java多線程中Lock的使用小結(jié)

    jdk1.5 以后,提供了各種鎖,本文主要介紹了Java多線程中Lock的使用小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Maven模版Bug及解決辦法

    Maven模版Bug及解決辦法

    默認(rèn),會(huì)幫我們創(chuàng)建src/main/resources 按照Maven的規(guī)范,Maven會(huì)有3個(gè)目錄,分別是: src/main/java : java源文件存放位置 src/main/resource : resource資源,如配置文件等 src/test/java : 測(cè)試代碼源文件存放位置
    2016-04-04
  • Mybatis查詢時(shí)數(shù)據(jù)丟失的問題及解決

    Mybatis查詢時(shí)數(shù)據(jù)丟失的問題及解決

    Mybatis查詢時(shí)數(shù)據(jù)丟失的問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Mybatis源碼解析之mapper接口的代理模式詳解

    Mybatis源碼解析之mapper接口的代理模式詳解

    這篇文章主要介紹了Mybatis源碼解析之mapper接口的代理模式詳解,在mybatis中執(zhí)行sql時(shí)有兩種方式,一種是基于statementId,也就是直接調(diào)用SqlSession的方法,需要的朋友可以參考下
    2023-12-12
  • SpringBoot項(xiàng)目為何引入大量的starter?如何自定義starter?

    SpringBoot項(xiàng)目為何引入大量的starter?如何自定義starter?

    這篇文章主要介紹了SpringBoot項(xiàng)目為何引入大量的starter?如何自定義starter?文章基于這兩個(gè)問題展開全文,需要的小伙伴可以參考一下
    2022-04-04
  • Java Flink窗口觸發(fā)器Trigger的用法詳解

    Java Flink窗口觸發(fā)器Trigger的用法詳解

    Trigger(窗口觸發(fā)器)決定了窗口(由 WindowAssigner 產(chǎn)生)什么時(shí)候調(diào)用窗口處理函數(shù)。可以根據(jù)指定的時(shí)間或數(shù)據(jù)元素條件來決定什么時(shí)候觸發(fā)。本文將詳細(xì)講講其用法,需要的可以參考一下
    2022-07-07
  • Spring Security 中細(xì)化權(quán)限粒度的方法

    Spring Security 中細(xì)化權(quán)限粒度的方法

    這篇文章主要介紹了Spring Security 中細(xì)化權(quán)限粒度的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09

最新評(píng)論