最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼

 更新時間:2020年07月01日 10:01:31   作者:每天都有新收獲  
這篇文章主要介紹了SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

最近在做分塊上傳的業(yè)務(wù),使用到了Redis來維護(hù)上傳過程中的分塊編號。

每上傳完成一個分塊就獲取一下文件的分塊集合,加入新上傳的編號,手動接口測試下是沒有問題的,前端通過并發(fā)上傳調(diào)用就出現(xiàn)問題了,并發(fā)的get再set,就會存在覆蓋寫現(xiàn)象,導(dǎo)致最后的分塊數(shù)據(jù)不對,不能觸發(fā)分塊合并請求。

遇到并發(fā)二話不說先上鎖,針對執(zhí)行代碼塊加了一個JVM鎖之后問題就解決了。

仔細(xì)一想還是不太對,項(xiàng)目是分布式部署的,做了負(fù)載均衡,一個節(jié)點(diǎn)的代碼被鎖住了,請求輪詢到其他節(jié)點(diǎn)還是可以進(jìn)行覆蓋寫,并沒有解決到問題啊

沒辦法,只有用上分布式鎖了。之前對于分布式鎖的理論還是很熟悉的,沒有比較好的應(yīng)用場景就沒寫過具體代碼,趁這個機(jī)會就學(xué)習(xí)使用一下分布式鎖。

理論

分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。是為了解決分布式系統(tǒng)中,不同的系統(tǒng)或是同一個系統(tǒng)的不同主機(jī)共享同一個資源的問題,它通常會采用互斥來保證程序的一致性


通常的實(shí)現(xiàn)方式有三種:

  • 基于 MySQL 的悲觀鎖來實(shí)現(xiàn)分布式鎖,這種方式使用的最少,這種實(shí)現(xiàn)方式的性能不好,且容易造成死鎖,并且MySQL本來業(yè)務(wù)壓力就很大了,再做鎖也不太合適
  • 基于 Redis 實(shí)現(xiàn)分布式鎖,單機(jī)版可用setnx實(shí)現(xiàn),多機(jī)版建議用Radission
  • 基于 ZooKeeper 實(shí)現(xiàn)分布式鎖,利用 ZooKeeper 順序臨時節(jié)點(diǎn)來實(shí)現(xiàn)

為了確保分布式鎖可用,我們至少要確保鎖的實(shí)現(xiàn)同時滿足以下四個條件:

  • 互斥性。在任意時刻,只有一個客戶端能持有鎖。
  • 不會發(fā)生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續(xù)其他客戶端能加鎖。
  • 具有容錯性。只要大部分的Redis節(jié)點(diǎn)正常運(yùn)行,客戶端就可以加鎖和解鎖。
  • 解鈴還須系鈴人。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。

本文就使用的是Redis的setnx實(shí)現(xiàn),如果Redis是多機(jī)版的可以去了解下Radssion,封裝的就特別的好,也是官方推薦的

代碼

1. 加依賴

引入Spring Boot和Redis整合的快速使用依賴

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

2. 加配置

application.properties中加入Redis連接相關(guān)配置

spring.redis.host=xxx
spring.redis.port=6379
spring.redis.database=0
spring.redis.password=xxx
spring.redis.timeout=10000

# 設(shè)置jedis連接池
spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.min-idle=20

3. 重寫Redis的序列化規(guī)則

默認(rèn)使用的JDK的序列化,不自己設(shè)置一下Redis中的數(shù)據(jù)是看不懂的

/**
 * @author Chkl
 * @create 2020/6/7
 * @since 1.0.0
 */
@Component
public class RedisConfig {
  /**
   * 改造RedisTemplate,重寫序列化規(guī)則,避免存入序列化內(nèi)容看不懂
   * @param connectionFactory
   * @return
   */
  @Bean
  public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate redisTemplate = new RedisTemplate();
    redisTemplate.setConnectionFactory(connectionFactory);
    // 設(shè)置key和value的序列化規(guī)則
    redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    return redisTemplate;
  }
}

4. 如何正確的上鎖

直接上代碼

@Component
public class RedisLock {

  @Autowired
  private StringRedisTemplate redisTemplate;

  private long timeout = 3000;

  /**
   * 上鎖
   * @param key 鎖標(biāo)識
   * @param value 線程標(biāo)識
   * @return 上鎖狀態(tài)
   */
  public boolean lock(String key, String value) {
    long start = System.currentTimeMillis();
    while (true) {
      //檢測是否超時
      if (System.currentTimeMillis() - start > timeout) {
        return false;
      }
      //執(zhí)行set命令
      Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);//1
      //是否成功獲取鎖
      if (absent) {
        return true;
      }
      return false;
    }
  }
}

核心代碼就是

Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.MILLISECONDS);

setIfAbsent方法就相當(dāng)于命令行下的Setnx方法,指定的 key 不存在時,為 key 設(shè)置指定的值

參數(shù)分別是key、value、超時時間和時間單位

  • key,表示針對于這段資源的唯一標(biāo)識
  • value,表示針對于這個線程的唯一標(biāo)識。為什么有了key了還需要設(shè)置value呢,就是為了滿足四個條件的最后一個:解鈴還須系鈴人。只有通過key和value的組合才能保證解鎖時是同一個線程來解鎖
  • 超時時間,必須和setnx一起進(jìn)行操作,不能再setnx結(jié)束后再執(zhí)行。如果加鎖成功了,還沒有設(shè)置過期時間就宕機(jī)了,鎖就永遠(yuǎn)不會過期,變成死鎖

5. 如何正確解鎖

@Component
public class RedisLock {
  @Autowired
  private StringRedisTemplate redisTemplate;

  @Autowired
  private DefaultRedisScript<Long> redisScript;

  private static final Long RELEASE_SUCCESS = 1L;


  /**
   * 解鎖
   * @param key 鎖標(biāo)識
   * @param value 線程標(biāo)識
   * @return 解鎖狀態(tài)
   */
  public boolean unlock(String key, String value) {
    //使用Lua腳本:先判斷是否是自己設(shè)置的鎖,再執(zhí)行刪除
    Long result = redisTemplate.execute(redisScript, Arrays.asList(key,value));
    //返回最終結(jié)果
    return RELEASE_SUCCESS.equals(result);
  }


  /**
   * @return lua腳本
   */
  @Bean
  public DefaultRedisScript<Long> defaultRedisScript() {
    DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
    defaultRedisScript.setResultType(Long.class);
    defaultRedisScript.setScriptText("if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end");
    return defaultRedisScript;
  }
}

解鎖過程需要兩步操作

1.判斷操作線程是否是加鎖的線程
2.如果是加鎖線程,執(zhí)行解鎖操作

這兩步操作也需要原子的進(jìn)行操作,但是Redis不支持這兩步的合并的操作,所以,就只有使用lua腳本實(shí)現(xiàn)來保證原子性咯
如果在判斷是加鎖的線程之后,并且執(zhí)行解鎖之前,鎖到期了,被其他線程獲得鎖了,這時候再進(jìn)行解鎖就會解掉其他線程的鎖,使得不滿足解鈴還須系鈴人

6. 實(shí)際應(yīng)用

沒有使用分布式鎖時的保存文件分塊的代碼

	/**
   * 保存文件分塊編號到redis
   * @param chunkNumber 分塊號
   * @param identifier 文件唯一編號
   * @return 文件分塊的大小
   */
  @Override
  public Integer saveChunk(Integer chunkNumber, String identifier) {
  	//從Redis獲取已經(jīng)存在的分塊編號集合
    Set<Integer> oldChunkNumber = (Set<Integer>) JSON.parseObject(redisOperator.get("chunkNumberList_"+identifier),Set.class);
    //如果不存在分塊集合,創(chuàng)建一個集合
    if (Objects.isNull(oldChunkNumber)) {
      Set<Integer> newChunkNumber = new HashSet<>();
      newChunkNumber.add(chunkNumber);
      redisOperator.set("chunkNumberList_"+identifier, JSON.toJSONString(newChunkNumber),36000);
      return newChunkNumber.size();
     //如果分塊集合已經(jīng)存在了,就添加一個編號
    } else {
      oldChunkNumber.add(chunkNumber);
      redisOperator.set("chunkNumberList_"+identifier, JSON.toJSONString(oldChunkNumber),36000);
      return oldChunkNumber.size();
    }
  }

存在的問題是:當(dāng)并發(fā)的請求進(jìn)來之后,可能獲取同一個狀態(tài)的集合進(jìn)行修改,修改后直接寫入,造成同一個狀態(tài)獲得的集合操作線程覆蓋寫的現(xiàn)象

使用分布式鎖保證同時只能有一個線程能獲取到集合并進(jìn)行修改,避免了覆蓋寫現(xiàn)象

使用分布式鎖代碼

/**
   * 保存文件分塊編號到redis
   * @param chunkNumber 分塊號
   * @param identifier 文件唯一編號
   * @return 文件分塊的大小
   */
  @Override
  public Integer saveChunk(Integer chunkNumber, String identifier) {
  	//通過UUID生成一個請求線程識別標(biāo)志作為鎖的value
    String threadUUID = CoreUtil.getUUID();
    //上鎖,以共享資源標(biāo)識:文件唯一編號,作為key,以線程標(biāo)識UUID作為value
    redisLock.lock(identifier,threadUUID);
    //從Redis獲取已經(jīng)存在的分塊編號集合
    Set<Integer> oldChunkNumber = (Set<Integer>) JSON.parseObject(redisOperator.get("chunkNumberList_"+identifier),Set.class);
    //如果不存在分塊集合,創(chuàng)建一個集合
    if (Objects.isNull(oldChunkNumber)) {
      Set<Integer> newChunkNumber = new HashSet<>();
      newChunkNumber.add(chunkNumber);
      redisOperator.set("chunkNumberList_"+identifier, JSON.toJSONString(newChunkNumber),36000);
      //解鎖
      redisLock.unlock(identifier,threadUUID);
      return newChunkNumber.size();
     //如果分塊集合已經(jīng)存在了,就添加一個編號
    } else {
      oldChunkNumber.add(chunkNumber);
      redisOperator.set("chunkNumberList_"+identifier, JSON.toJSONString(oldChunkNumber),36000);
      //解鎖
      redisLock.unlock(identifier,threadUUID);
      return oldChunkNumber.size();
    }
  }

代碼中使用的共享資源標(biāo)識是文件唯一編號identifier,它能標(biāo)識加鎖代碼段中的唯一資源,即key為"chunkNumberList_"+identifier的集合

代碼中使用的線程唯一標(biāo)識是UUID,能保證加鎖和解鎖時獲取的標(biāo)識不會重復(fù)

到此這篇關(guān)于SpringBoot整合Redis正確的實(shí)現(xiàn)分布式鎖的示例代碼的文章就介紹到這了,更多相關(guān)SpringBoot整合Redis分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Elasticsearch term 查詢之精確值搜索功能實(shí)現(xiàn)

    Elasticsearch term 查詢之精確值搜索功能實(shí)現(xiàn)

    term查詢是Elasticsearch中用于精確值搜索的一種基本方式,通過了解 term 查詢的工作原理和使用方法,你可以更好地利用 Elasticsearch 進(jìn)行結(jié)構(gòu)化數(shù)據(jù)的搜索和分析,本文將詳細(xì)介紹 term 查詢的工作原理、使用場景以及如何在 Elasticsearch 中應(yīng)用它,感興趣的朋友一起看看吧
    2024-06-06
  • 詳細(xì)解讀Druid數(shù)據(jù)庫連接池的使用

    詳細(xì)解讀Druid數(shù)據(jù)庫連接池的使用

    這篇文章主要介紹了Druid數(shù)據(jù)庫連接池的使用,數(shù)據(jù)庫連接池負(fù)責(zé)分配、管理和釋放數(shù)據(jù)庫連接,它允許應(yīng)用程序重復(fù)使用一個現(xiàn)有的數(shù)據(jù)庫連接,而不是重新建立一個,需要的朋友可以參考下
    2023-03-03
  • SpringCloud之熔斷監(jiān)控Hystrix Dashboard的實(shí)現(xiàn)

    SpringCloud之熔斷監(jiān)控Hystrix Dashboard的實(shí)現(xiàn)

    這篇文章主要介紹了SpringCloud之熔斷監(jiān)控Hystrix Dashboard的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • SpringBoot JPA實(shí)現(xiàn)查詢多值

    SpringBoot JPA實(shí)現(xiàn)查詢多值

    這篇文章主要為大家詳細(xì)介紹了SpringBoot JPA實(shí)現(xiàn)查詢多值,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • java垃圾收集器與內(nèi)存分配策略詳解

    java垃圾收集器與內(nèi)存分配策略詳解

    本篇文章主要介紹了Java垃圾收集器與內(nèi)存分配策略的方法和原理總結(jié),Java垃圾回收器是Java虛擬機(jī)的重要模塊,具有一定的參考價值,有興趣的可以了解一下
    2021-08-08
  • 淺談Java生命周期管理機(jī)制

    淺談Java生命周期管理機(jī)制

    最近有位細(xì)心的朋友在閱讀筆者的文章時,對java類的生命周期問題有一些疑惑,筆者打開百度搜了一下相關(guān)的問題,看到網(wǎng)上的資料很少有把這個問題講明白的,主要是因?yàn)槟壳皣鴥?nèi)java方面的教材大多只是告訴你“怎樣做”,但至于“為什么這樣做”卻不多說
    2016-01-01
  • java:程序包org.bouncycastle.jce.provider不存在問題及解決

    java:程序包org.bouncycastle.jce.provider不存在問題及解決

    這篇文章主要介紹了java:程序包org.bouncycastle.jce.provider不存在問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • 一小時迅速入門Mybatis之初識篇

    一小時迅速入門Mybatis之初識篇

    這篇文章主要介紹了迅速入門Mybatis之初識篇,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • SpringMVC表單標(biāo)簽知識點(diǎn)詳解

    SpringMVC表單標(biāo)簽知識點(diǎn)詳解

    這篇文章主要為大家詳細(xì)介紹了SpringMVC表單標(biāo)簽知識點(diǎn),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • 深入淺析Netty 在 Dubbo 中是如何應(yīng)用的

    深入淺析Netty 在 Dubbo 中是如何應(yīng)用的

    國內(nèi)知名框架 Dubbo 底層使用的是 Netty 作為網(wǎng)絡(luò)通信,那么內(nèi)部到底是如何使用的呢?今天通過本文給大家詳細(xì)講解,對Netty 在 Dubbo中應(yīng)用相關(guān)知識感興趣的朋友跟隨小編一起看看吧
    2020-05-05

最新評論

洛川县| 平武县| 锡林浩特市| 蓬溪县| 辉南县| 时尚| 积石山| 罗山县| 松江区| 朝阳区| 永德县| 阜阳市| 延庆县| 吉木乃县| 托克逊县| 宁强县| 专栏| 长治县| 岚皋县| 黑河市| 田阳县| 图们市| 兴仁县| 松江区| 昌乐县| 城口县| 镶黄旗| 临西县| 新营市| 驻马店市| 安吉县| 缙云县| 大埔区| 商南县| 宁陵县| 渝北区| 杭州市| 惠来县| 安仁县| 同心县| 海门市|