濮阳杆衣贸易有限公司

主頁 > 知識(shí)庫 > 詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問題

詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問題

熱門標(biāo)簽:宿遷便宜外呼系統(tǒng)平臺(tái) 魔獸2青云地圖標(biāo)注 十堰營銷電銷機(jī)器人哪家便宜 貴州電銷卡外呼系統(tǒng) 北京400電話辦理收費(fèi)標(biāo)準(zhǔn) 超呼電話機(jī)器人 山東外呼銷售系統(tǒng)招商 日本中國地圖標(biāo)注 鄭州人工智能電銷機(jī)器人系統(tǒng)

      自己的項(xiàng)目因?yàn)闀?huì)一直抓取某些信息,但是本地會(huì)和線上經(jīng)常一起跑,造成沖突。這其實(shí)就是我們常說的分布式集群的問題了,本地和線上的服務(wù)器構(gòu)成了集群以及QPS為2的小并發(fā)(其實(shí)也不叫并發(fā),不知道拿什么詞形容?)。

     首先,分布式集群的問題大家都知道,會(huì)造成數(shù)據(jù)庫的插入重復(fù)問題,會(huì)造成一系列的并發(fā)性問題。

     解決的方式呢也大概如下幾點(diǎn),百度以及谷歌上都能搜到的解決方式:

     1:數(shù)據(jù)庫添加唯一索引

     2:設(shè)計(jì)接口冪等性

     3:依靠中間件使用分布式鎖,而分布式鎖又分為Redis和Zookeeper

    由于Zookeeper我沒怎么接觸過,并且我項(xiàng)目中本來就引用了Redis,所以就想著用Redis來做分布式鎖,也高端洋氣上檔次點(diǎn)。

    首先基于Redis的操作,我們必須要保證其原子性,也就是要么全部成功,要么全部失敗,先從Redis的客戶端入手。

    就Redis客戶端而言,我們通過的操作是先使用setnx指令,如果成功則返回1,失敗則返回0

   可是就分布鎖鎖而言,一個(gè)常用的問題就是如果一個(gè)服務(wù)setnx成功了,但是在解鎖的時(shí)候如果發(fā)生了宕機(jī)或者一些特殊因素,導(dǎo)致無法解鎖,那么其他服務(wù)將陷入死鎖的狀態(tài)。所以,我們?cè)谟?setnx 的同時(shí)想著去用 expire 指令對(duì)鎖進(jìn)行一個(gè)過期操作

 

   從指令可以看出 setnx 和 expire 指令是分開的,如果在這中間的空隙過程中如果有特殊因素導(dǎo)致指令無法繼續(xù),也會(huì)導(dǎo)致死鎖的產(chǎn)生。

以下參考自老錢的 Redis 深度歷險(xiǎn):核心原理與應(yīng)用實(shí)踐

   為了解決這個(gè)疑難,Redis 開源社區(qū)涌現(xiàn)了一堆分布式鎖的 library,專門用來解決這個(gè)問題。實(shí)現(xiàn)方法極為復(fù)雜,小白用戶一般要費(fèi)很大的精力才可以搞懂。如果你需要使用分布式鎖,意味著你不能僅僅使用 Jedis 或者 redis-py 就行了,還得引入分布式鎖的 library。

  為了治理這個(gè)亂象,Redis 2.8 版本中作者加入了 set 指令的擴(kuò)展參數(shù),使得 setnx 和 expire 指令可以一起執(zhí)行,徹底解決了分布式鎖的亂象。從此以后所有的第三方分布式鎖 library 可以休息了。

   

  以上都是基于Redis的操作,但是我們?cè)贘AVA中如何去運(yùn)用分布式鎖呢。

  首先在Redis方面我用的是RedisTemplate對(duì)Redis進(jìn)行操作的 ,而RedisTemplate在目前情況下如果不借助于是無法保證其原子性的,所以我們需要借助于Redis的Lua腳本。

   先上Lua腳本的代碼

// 加鎖
if 
    redis.call('setNx',KEYS[1],ARGV[1]) 
  then 
    if redis.call('get',KEYS[1])==ARGV[1] 
    return redis.call('expire',KEYS[1],ARGV[2]) 
  else 
    return 0 
  end 
end
 
// 解鎖
  redis.call('get', KEYS[1]) == ARGV[1] 
then 
  return redis.call('del', KEYS[1]) 
else 
  return 0 

    Java調(diào)用腳本有兩種方式

   1。新建一個(gè)腳本文件,在代碼中調(diào)用其絕對(duì)路徑地址

     redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(地址)));

   2。在Java代碼中以字符串的方式傳入

    redisScript.setScriptText(腳本);

  我是用的第二種方式實(shí)現(xiàn)的,下面是JAVA代碼

/**
   * 獲取鎖
   * @param lockKey
   * @param value
   * @param expireTime:?jiǎn)挝?秒
   * @return
   */
  public boolean getLock(String lockKey, String value, int expireTime){
    boolean ret = false;
    try{
      String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
 
      RedisScriptLong> redisScript = new DefaultRedisScript>(script, Long.class);
 
      Object result = redisTemplate.execute(redisScript,new StringRedisSerializer(),new StringRedisSerializer(), Collections.singletonList(lockKey),value,expireTime + "");
      System.out.println(result + "-----------");
      //Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey),value,expireTime + "");
 
      if(SUCCESS.equals(result)){
        return true;
      }
 
    }catch(Exception e){
      e.printStackTrace();
    }
    return ret;
  }
 
  /**
   * 釋放鎖
   * @param lockKey
   * @param value
   * @return
   */
  public boolean releaseLock(String lockKey, String value){
 
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
 
    RedisScriptLong> redisScript = new DefaultRedisScript>(script, Long.class);
 
    Object result = redisTemplate.execute(redisScript,new StringRedisSerializer(),new StringRedisSerializer(), Collections.singletonList(lockKey),value);
    if(SUCCESS.equals(result)) {
      return true;
    }
 
    return false;
  }

   以上代碼已經(jīng)在我的項(xiàng)目中確切可以使用了。但是在使用的過程中遇到了許多問題。

   1:java.lang.IllegalStateException

   在返回值方面,會(huì)經(jīng)常報(bào)IllegalStateException。

RedisScriptString> redisScript = new DefaultRedisScript>(script, String.class);

   用String類型時(shí)候,經(jīng)常會(huì)報(bào)類型轉(zhuǎn)換異常。我在代碼中使用的Long類型接收該類型,在命令行中我們也看到命令行結(jié)果返回的是數(shù)字0或者1,保險(xiǎn)起見我們也可以用Object對(duì)象來接收結(jié)果集。

 2:ERR value is not an integer or out of range

  這個(gè)問題糾結(jié)了我一個(gè)下午至少,Redis報(bào)的異常都是很深的,從跟蹤源碼的時(shí)候看到,我們?cè)谡{(diào)用redisTemplate.execute的方法時(shí)候,如果不傳序列化的參數(shù)的時(shí)候,代碼默認(rèn)調(diào)用的是 Jdkserializationredisserializer 來進(jìn)行序列化和反序列化操作,這是jdk自帶的序列化操作,使用該序列化的對(duì)象必須要實(shí)現(xiàn)Serializable接口。所以該序列化接口是用于對(duì)實(shí)體類的序列化。

   所以在進(jìn)行 execute 操作的時(shí)候,我們傳入 Stringredisserializer,該序列化接口是專用于對(duì)字符串類型的序列化操作。具體的區(qū)別可以去這兩個(gè)類的源碼中看下他們的加密方式。 

因?yàn)闀r(shí)間以及個(gè)人能力的問題,對(duì)部分源碼有點(diǎn)未理解,所以沒有做到全方位的解讀這些異常的原因,以后有機(jī)會(huì)會(huì)將源碼細(xì)讀并分析其異常原因。

到此這篇關(guān)于詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問題的文章就介紹到這了,更多相關(guān)RedisTemplate 分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • 詳解redis分布式鎖的這些坑
  • 基于Redis實(shí)現(xiàn)分布式鎖的方法(lua腳本版)
  • SpringBoot之使用Redis實(shí)現(xiàn)分布式鎖(秒殺系統(tǒng))
  • 詳解Redis 分布式鎖遇到的序列化問題
  • redisson分布式鎖的用法大全
  • php基于redis的分布式鎖實(shí)例詳解
  • Redis分布式鎖升級(jí)版RedLock及SpringBoot實(shí)現(xiàn)方法
  • 利用redis實(shí)現(xiàn)分布式鎖,快速解決高并發(fā)時(shí)的線程安全問題
  • 詳解基于redis實(shí)現(xiàn)分布式鎖

標(biāo)簽:朝陽 江蘇 吉安 北京 楊凌 大慶 果洛 臺(tái)州

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問題》,本文關(guān)鍵詞  詳解,RedisTemplate,下,Redis,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問題》相關(guān)的同類信息!
  • 本頁收集關(guān)于詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問題的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    东阳市| 乌兰县| 新沂市| 尤溪县| 南康市| 霍邱县| 资溪县| 松溪县| 定襄县| 灵丘县| 疏附县| 嘉义市| 岳阳县| 饶河县| 吉隆县| 盘锦市| 通城县| 旅游| 福安市| 临湘市| 娱乐| 昌江| 东乡族自治县| 沁阳市| 梁山县| 封丘县| 弥勒县| 五指山市| 军事| 兴安盟| 彭水| 潼南县| 南乐县| 枞阳县| 博客| 中西区| 合阳县| 龙井市| 廊坊市| 鹤山市| 万盛区|