欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實(shí)現(xiàn)redis分布式鎖的三種方式

 更新時(shí)間:2022年08月03日 09:18:58   作者:Ajekseg  
本文主要介紹了Java實(shí)現(xiàn)redis分布式鎖的三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

一、引入原因

在分布式服務(wù)中,常常有如定時(shí)任務(wù)、庫(kù)存更新這樣的場(chǎng)景。

在定時(shí)任務(wù)中,如果不使用quartz這樣的分布式定時(shí)工具,只是簡(jiǎn)單的使用定時(shí)器來(lái)進(jìn)行定時(shí)任務(wù),在服務(wù)分布式部署中,就有可能存在定時(shí)任務(wù)并發(fā)執(zhí)行,造成一些問(wèn)題。

在庫(kù)存更新這樣的場(chǎng)景中,我們服務(wù)對(duì)數(shù)據(jù)庫(kù)同一條記錄進(jìn)行更新,并記錄。對(duì)記錄更新可以使用分布式鎖,但對(duì)操作進(jìn)行記錄時(shí),可能造成讀未提交,造成記錄錯(cuò)亂的情況。

在以上的場(chǎng)景中,我們引入了分布式事務(wù)鎖。

二、分布式鎖實(shí)現(xiàn)過(guò)程中的問(wèn)題

問(wèn)題一:異常導(dǎo)致鎖沒(méi)有釋放

這個(gè)問(wèn)題形成的原因就是程序在獲取到鎖之后,執(zhí)行業(yè)務(wù)的過(guò)程中出現(xiàn)了異常,導(dǎo)致鎖沒(méi)有被釋放。通俗的話(huà)說(shuō):上廁所的人死在了廁所里面,導(dǎo)致“坑位”資源死鎖無(wú)法被釋放。(當(dāng)然這種情況出現(xiàn)的概率很小,但概率小不等于不存在。)

解決方案: 為redis的key設(shè)置過(guò)期時(shí)間,程序異常導(dǎo)致的死鎖,在到達(dá)過(guò)期時(shí)間之后鎖自動(dòng)釋放。也就說(shuō)廁所門(mén)是電子鎖,鎖定的最長(zhǎng)時(shí)間是有限制的,超過(guò)時(shí)長(zhǎng)鎖就會(huì)自動(dòng)打開(kāi)釋放"坑位"資源。

image-20220428112311092

問(wèn)題二:獲取鎖與設(shè)置過(guò)期時(shí)間操作不是原子性的

上文中我們雖然獲取到鎖,也設(shè)置了過(guò)期時(shí)間,看似完美。但是在高并發(fā)的場(chǎng)景下仍然會(huì)出問(wèn)題,因?yàn)?ldquo;獲取鎖”與“設(shè)置過(guò)期時(shí)間”是兩個(gè)redis操作,兩個(gè)redis操作不是原子性的。
可能出現(xiàn)這種情況:就在獲取鎖之后,設(shè)置過(guò)期時(shí)間之前程序宕機(jī)了。鎖被獲取到了但沒(méi)有設(shè)置過(guò)期時(shí)間,最后又成為死鎖。

解決方案: 獲取鎖的同時(shí)設(shè)置過(guò)期時(shí)間

image-20220428112803792

問(wèn)題三:鎖過(guò)期之后被別的線(xiàn)程重新獲取與釋放

這個(gè)問(wèn)題出現(xiàn)的場(chǎng)景是:假如某個(gè)應(yīng)用集群化部署存在多個(gè)進(jìn)程實(shí)例,實(shí)例A、實(shí)例B。實(shí)例A獲取到鎖,但是執(zhí)行過(guò)程超時(shí)了(數(shù)據(jù)庫(kù)層面或其他層面導(dǎo)致操作執(zhí)行超時(shí))。超時(shí)之后鎖被自動(dòng)釋放了,實(shí)例B獲取到鎖,并執(zhí)行業(yè)務(wù)程序,執(zhí)行完成之后把鎖刪除了。

解決方案: 在釋放鎖之前判斷一下,這把鎖是不是自己的那一把,如果是別人的鎖你就不要?jiǎng)印T趺磁袛噙@把鎖是不是自己的?加鎖時(shí)為value賦隨機(jī)值,加鎖的隨機(jī)值等于解鎖時(shí)的獲取到的值,才能證明這把鎖是你的。

問(wèn)題四:鎖的釋放不是原子性的

大家仔細(xì)看代碼,鎖的釋放時(shí)三個(gè)操作,這三個(gè)操作不是原子性的。也就是說(shuō)在高并發(fā)的場(chǎng)景下,你剛get到的redis key有可能也被別的線(xiàn)程get了,你剛要?jiǎng)h除別的線(xiàn)程可能已經(jīng)把這個(gè)key刪除了。

解決方案: 我們可以使用redis lua腳本(lua腳本是在一個(gè)事務(wù)里面執(zhí)行的,可以保證原子性)。在Java代碼中可以以字符串的形式存在。如下:

String script = 
	"if redis.call('get', KEYS[1]) == ARGV[1] 
		then return redis.call('del', KEYS[1]) 
	else 
		return 0 
	end";

問(wèn)題五:其他的問(wèn)題?

上面我們分析了很多使用redis實(shí)現(xiàn)分布式鎖可能出現(xiàn)的問(wèn)題及解決方案,其實(shí)在實(shí)際的開(kāi)發(fā)應(yīng)用中還會(huì)有更多的問(wèn)題。比如:

  • 目前我們的程序獲取不到鎖,就無(wú)限的重試,是不是應(yīng)該在重試一定的次數(shù)之后就拋出異常?在有限的時(shí)間內(nèi)通過(guò)異常給用戶(hù)一個(gè)友好的響應(yīng)。比如:程序太忙,請(qǐng)您稍后再試!
  • 程序A沒(méi)有執(zhí)行完成,鎖定的key就過(guò)期了。雖然過(guò)期之后會(huì)自動(dòng)釋放鎖,但是我的程序A的確沒(méi)有執(zhí)行完成啊,也沒(méi)有異常拋出,就是執(zhí)行的時(shí)間比較長(zhǎng),這個(gè)時(shí)候是不是應(yīng)該對(duì)鎖定的key進(jìn)行續(xù)期?

這些問(wèn)題在高并發(fā)場(chǎng)景下會(huì)出現(xiàn),實(shí)際上分布式鎖的細(xì)節(jié)實(shí)踐有很多的現(xiàn)成的解決方案,不用我們?nèi)プ约簩?shí)現(xiàn)。比較完整優(yōu)秀的分布式鎖實(shí)現(xiàn)包括:

RedisLockRegistry是spring-integration-redis中提供redis分布式鎖實(shí)現(xiàn)類(lèi)

基于Redisson實(shí)現(xiàn)分布式鎖原理(Redission是一個(gè)獨(dú)立的redis客戶(hù)端,是與Jedis、Lettuce同級(jí)別的存在)

三、具體實(shí)現(xiàn)

1. RedisTemplate

RedisTemplate<String, String> redisTemplate;

public void updateUserWithRedisLock(SysUser sysUser) throws InterruptedException {
  // 占分布式鎖,去redis占坑
  // 1. 分布式鎖占坑
Boolean lock = redisTemplate.opsForValue().setIfAbsent("SysUserLock" + sysUser.getId(), "value", 30, TimeUnit.SECONDS);
  if(lock) {
    //加鎖成功... 
		// todo business
    
    
    redisTemplate.delete("SysUserLock" + sysUser.getId());   //刪除key,釋放鎖
  } else {
    Thread.sleep(100);   // 加鎖失敗,重試
    updateUserWithRedisLock(sysUser);
  }
}

setIfAbsent方法的作用是在某一個(gè)lock key不存在的時(shí)候,才能返回true;如果這個(gè)key已經(jīng)存在了就返回false,返回false就是獲取鎖失敗。setIfAbsent函數(shù)功能類(lèi)似于redis命令行setnx。

2. RedisLockRegistry

集成spring-integration-redis

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

注冊(cè)RedisLockRegistry

@Configuration
public class RedisLockConfig {

     @Bean
     public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
         //第一個(gè)參數(shù)redisConnectionFactory
         //第二個(gè)參數(shù)registryKey,分布式鎖前綴,設(shè)置為項(xiàng)目名稱(chēng)會(huì)好些
         //該構(gòu)造方法對(duì)應(yīng)的分布式鎖,默認(rèn)有效期是60秒.可以自定義
         return new RedisLockRegistry(redisConnectionFactory, "boot-launch");
         //return new RedisLockRegistry(redisConnectionFactory, "boot-launch",60);
     }
}

使用RedisLockRegistry

代碼中實(shí)現(xiàn)

@Resource
private RedisLockRegistry redisLockRegistry;

public void updateUser(String userId) {
String lockKey = “config” + userId;
Lock lock = redisLockRegistry.obtain(lockKey); //獲取鎖資源
try {
lock.lock(); //加鎖

//這里寫(xiě)需要處理業(yè)務(wù)的業(yè)務(wù)代碼
} finally {
lock.unlock(); //釋放鎖
}
}

注解實(shí)現(xiàn)

@RedisLock("lock-key")
public void save(){
}

3. 使用redisson實(shí)現(xiàn)分布式鎖

集成redisson

<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>3.15.0</version>
  <exclusions>
    <exclusion>
      <groupId>org.redisson</groupId>
      <!-- 默認(rèn)是 Spring Data Redis v.2.3.x ,所以排除掉-->
      <artifactId>redisson-spring-data-23</artifactId>
    </exclusion>
  </exclusions>
</dependency>

配置

在配置文件中加

spring:
  redis:
    redisson:
      file: classpath:redisson.yaml

然后新建一個(gè)redisson.yaml文件,也放在resouce目錄下

singleServerConfig:
  idleConnectionTimeout: 10000
  connectTimeout: 10000
  timeout: 3000
  retryAttempts: 3
  retryInterval: 1500
  password: 123456
  subscriptionsPerConnection: 5
  clientName: null
  address: "redis://192.168.161.3:6379"
  subscriptionConnectionMinimumIdleSize: 1
  subscriptionConnectionPoolSize: 50
  connectionMinimumIdleSize: 32
  connectionPoolSize: 64
  database: 0
  dnsMonitoringInterval: 5000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}
transportMode: "NIO"

實(shí)現(xiàn)

@Resource
private RedissonClient redissonClient;

public void updateUser(String userId) {
  String lockKey = "config" + userId;
  RLock lock = redissonClient.getLock(lockKey);  //獲取鎖資源
  try {
    lock.lock(10, TimeUnit.SECONDS);   //加鎖,可以指定鎖定時(shí)間

    //這里寫(xiě)需要處理業(yè)務(wù)的業(yè)務(wù)代碼
  } finally {
    lock.unlock();   //釋放鎖
  }
}
  • 相對(duì)于RedisLockRegistry另一個(gè)小優(yōu)點(diǎn)是:我們可以為每一個(gè)鎖指定鎖定的超時(shí)時(shí)間。RedisLockRegistry目前只能針對(duì)所有的鎖設(shè)定統(tǒng)一的超時(shí)時(shí)間
  • 如果業(yè)務(wù)執(zhí)行超時(shí)之后,再去unlock會(huì)拋出java.lang.IllegalMonitorStateException

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

相關(guān)文章

  • Drools Fusion(CEP)定義及使用方法講解

    Drools Fusion(CEP)定義及使用方法講解

    今天小編就為大家分享一篇關(guān)于Drools Fusion(CEP)定義及使用方法講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-03-03
  • 淺談Spring6中的反射機(jī)制

    淺談Spring6中的反射機(jī)制

    Java反射機(jī)制是Java語(yǔ)言中一種動(dòng)態(tài)(運(yùn)行時(shí))訪(fǎng)問(wèn)、檢測(cè)、修改它本身的能力,主要作用是動(dòng)態(tài)(運(yùn)行時(shí))獲取類(lèi)的完整結(jié)構(gòu)信息、調(diào)用對(duì)象的方法,需要的朋友可以參考下
    2023-05-05
  • 關(guān)于ElasticSearch的常用增刪改查DSL和代碼

    關(guān)于ElasticSearch的常用增刪改查DSL和代碼

    這篇文章主要介紹了關(guān)于ElasticSearch的常用增刪改查DSL和代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • 深入了解Java I/O 之File類(lèi)

    深入了解Java I/O 之File類(lèi)

    這篇文章主要介紹了Java I/O深入學(xué)習(xí)之File和RandomAccessFile, I/O系統(tǒng)即輸入/輸出系統(tǒng),對(duì)于一門(mén)程序語(yǔ)言來(lái)說(shuō),創(chuàng)建一個(gè)好的輸入/輸出系統(tǒng)并非易事。需要的朋友可以參考下
    2021-08-08
  • Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之二手書(shū)商城系統(tǒng)的實(shí)現(xiàn)

    Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之二手書(shū)商城系統(tǒng)的實(shí)現(xiàn)

    這是一個(gè)使用了java+JSP+Springboot+maven+mysql+ThymeLeaf+FTP開(kāi)發(fā)的二手書(shū)商城系統(tǒng),是一個(gè)畢業(yè)設(shè)計(jì)的實(shí)戰(zhàn)練習(xí),具有在線(xiàn)書(shū)城該有的所有功能,感興趣的朋友快來(lái)看看吧
    2022-01-01
  • SpringBoot整合EasyExcel的完整過(guò)程記錄

    SpringBoot整合EasyExcel的完整過(guò)程記錄

    easyexcel是阿里巴巴旗下開(kāi)源項(xiàng)目,主要用于Excel文件的導(dǎo)入和導(dǎo)出處理,下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合EasyExcel的完整過(guò)程,需要的朋友可以參考下
    2021-12-12
  • 超細(xì)致講解Spring框架 JdbcTemplate的使用

    超細(xì)致講解Spring框架 JdbcTemplate的使用

    在之前的Javaweb學(xué)習(xí)中,學(xué)習(xí)了手動(dòng)封裝JdbcTemplate,其好處是通過(guò)(sql語(yǔ)句+參數(shù))模板化了編程。而真正的JdbcTemplate類(lèi),是Spring框架為我們寫(xiě)好的。它是 Spring 框架中提供的一個(gè)對(duì)象,是對(duì)原始 Jdbc API 對(duì)象的簡(jiǎn)單封裝。
    2021-09-09
  • SpringBoot中使用Redis的完整實(shí)例

    SpringBoot中使用Redis的完整實(shí)例

    這篇文章主要給大家介紹了關(guān)于SpringBoot中使用Redis的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Mybatis-Plus實(shí)現(xiàn)公共字段自動(dòng)填充的項(xiàng)目實(shí)踐

    Mybatis-Plus實(shí)現(xiàn)公共字段自動(dòng)填充的項(xiàng)目實(shí)踐

    本文主要介紹了Mybatis-Plus實(shí)現(xiàn)公共字段自動(dòng)填充的項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • SpringBoot默認(rèn)使用HikariDataSource數(shù)據(jù)源方式

    SpringBoot默認(rèn)使用HikariDataSource數(shù)據(jù)源方式

    這篇文章主要介紹了SpringBoot默認(rèn)使用HikariDataSource數(shù)據(jù)源方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10

最新評(píng)論