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

基于SpringBoot+Redis實現(xiàn)分布式鎖

 更新時間:2023年05月30日 10:15:38   作者:Neo4j權(quán)威指南  
本文主要介紹了基于SpringBoot+Redis實現(xiàn)分布式鎖,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

隨著現(xiàn)在分布式架構(gòu)越來越盛行,在很多場景下需要使用到分布式鎖。很多小伙伴對于分布式鎖還不是特別了解,所以特地總結(jié)了一篇文章,讓大家一文讀懂分布式鎖的前世今生。

分布式鎖的實現(xiàn)有很多種,比如基于數(shù)據(jù)庫、Redis 、 zookeeper 等實現(xiàn),本文的示例主要介紹使用Redis實現(xiàn)分布式鎖。

一、什么是分布式鎖

分布式鎖,即分布式系統(tǒng)中的鎖,分布式鎖是控制分布式系統(tǒng)有序的對共享資源進(jìn)行操作,在單體應(yīng)用中我們通過鎖實現(xiàn)共享資源訪問,而分布式鎖,就是解決了分布式系統(tǒng)中控制共享資源訪問的問題。

可能初學(xué)的小伙伴就會有疑問,Java多線程中的公平鎖、非公平鎖、自旋鎖、可重入鎖、讀寫鎖、互斥鎖這些都還沒鬧明白呢?怎么又出來一個分布式鎖?

其實,可以這么理解:Java的原生鎖是解決多線程下對于共享資源的操作,而分布式鎖則是多進(jìn)程下對于共享資源的操作。分布式系統(tǒng)中競爭共享資源的最小粒度從線程升級成了進(jìn)程。

分布式鎖經(jīng)被應(yīng)用到各種高并發(fā)的場景下,典場景案例包括:秒殺、車票、訂單、退款、庫存等場景。

二、為什么要使用分布式鎖

在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動作。如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機(jī)之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,這個時候,便需要使用到分布式鎖。

目前幾乎很多大型網(wǎng)站及應(yīng)用都是分布式部署的,如何保證分布式場景中的數(shù)據(jù)一致性問題一直是一個比較重要的話題。在某些場景下,為了保證數(shù)據(jù)的完整性和一致性,我們需要保證一個方法在同一時間內(nèi)只能被同一個線程執(zhí)行,這就需要使用分布式鎖。

圖片

如上圖所示,假設(shè)用戶A和用戶B同時購買了某款商品,訂單創(chuàng)建成功后,下單系統(tǒng)A和下單系統(tǒng)B就會同時對數(shù)據(jù)庫中的該款商品的庫存進(jìn)行扣減。如果此時不加任何控制,系統(tǒng)B提交的數(shù)據(jù)更新就會覆蓋系統(tǒng)A的數(shù)據(jù),導(dǎo)致庫存錯誤,超賣等問題。

三、分布式鎖應(yīng)該具備哪些條件

在介紹分布式鎖的實現(xiàn)方式之前,先了解一下分布式鎖應(yīng)該具備哪些條件:

1、在分布式系統(tǒng)環(huán)境下,一個方法在同一時間只能被一個機(jī)器的一個線程執(zhí)行;

2、高可用的獲取鎖與釋放鎖;

3、高性能的獲取鎖與釋放鎖;

4、具備可重入特性;

5、具備鎖失效機(jī)制,防止死鎖;

6、具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗。

四、分布式鎖的實現(xiàn)方式

隨著業(yè)務(wù)發(fā)展的需要,原來的單體應(yīng)用被演化成分布式集群系統(tǒng)后,由于系統(tǒng)分布在不同機(jī)器上,這就使得原有的并發(fā)控制鎖策略失效,為了解決這個問題就需要一種跨進(jìn)程的互斥機(jī)制來控制共享資源的訪問,這就需要用到分布式鎖!

分布式鎖的實現(xiàn)有多種方式,下面介紹下這幾種分布式鎖的實現(xiàn):

  • 基于數(shù)據(jù)庫實現(xiàn)分布式鎖,(適用于并發(fā)小的系統(tǒng));
  • 基于緩存(Redis等)實現(xiàn)分布式鎖,(效率高,最流行,存在鎖超時的問題);
  • 基于Zookeeper實現(xiàn)分布式鎖,(可靠,但是效率不搞);

盡管有這三種方案,但是不同的業(yè)務(wù)也要根據(jù)自己的情況進(jìn)行選型,他們之間沒有最好只有更適合!

五、基于Redis實現(xiàn)分布式鎖

使用Redis實現(xiàn)分布式鎖是目前比較流行的解決方案,主要是使用Redis 獲取鎖與釋放鎖效率都很高,實現(xiàn)方式也特別簡單。

實現(xiàn)原理:

(1)獲取鎖的時候,使用setnx加鎖,并使用expire命令為鎖添加一個超時時間,超過該時間則自動釋放鎖,鎖的value值為一個隨機(jī)生成的UUID,通過此在釋放鎖的時候進(jìn)行判斷。

(2)獲取鎖的時候還設(shè)置一個獲取的超時時間,若超過這個時間則放棄獲取鎖。

(3)釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執(zhí)行delete進(jìn)行鎖釋放。

接下來我們就一步一步實現(xiàn)Redis 分布式鎖。

第一步,創(chuàng)建Spring Boot項目,并引入相關(guān)依賴。

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-lang3</artifactId>
   </dependency>
   <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>fastjson</artifactId>
       <version>1.2.72</version>
   </dependency>
   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
   </dependency>

第二步,創(chuàng)建Redis分布式鎖通用操作類,示例代碼如下:

@Slf4j
@Component
public class RedisLockUtils {
    @Resource
    private RedisTemplate redisTemplate;
    private static Map<String, LockInfo> lockInfoMap = new ConcurrentHashMap<>();
    private static final Long SUCCESS = 1L;
    public static class LockInfo {
        private String key;
        private String value;
        private int expireTime;
        //更新時間
        private long renewalTime;
        //更新間隔
        private long renewalInterval;
        public static LockInfo getLockInfo(String key, String value, int expireTime) {
            LockInfo lockInfo = new LockInfo();
            lockInfo.setKey(key);
            lockInfo.setValue(value);
            lockInfo.setExpireTime(expireTime);
            lockInfo.setRenewalTime(System.currentTimeMillis());
            lockInfo.setRenewalInterval(expireTime * 2000 / 3);
            return lockInfo;
        }
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
        public int getExpireTime() {
            return expireTime;
        }
        public void setExpireTime(int expireTime) {
            this.expireTime = expireTime;
        }
        public long getRenewalTime() {
            return renewalTime;
        }
        public void setRenewalTime(long renewalTime) {
            this.renewalTime = renewalTime;
        }
        public long getRenewalInterval() {
            return renewalInterval;
        }
        public void setRenewalInterval(long renewalInterval) {
            this.renewalInterval = renewalInterval;
        }
    }
    /**
     * 使用lua腳本更新redis鎖的過期時間
     * @param lockKey
     * @param value
     * @return 成功返回true, 失敗返回false
     */
    public boolean renewal(String lockKey, String value, int expireTime) {
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Boolean.class);
        redisScript.setScriptText(luaScript);
        List<String> keys = new ArrayList<>();
        keys.add(lockKey);
        Object result = redisTemplate.execute(redisScript, keys, value, expireTime);
        log.info("更新redis鎖的過期時間:{}", result);
        return (boolean) result;
    }
    /**
     * @param lockKey    鎖
     * @param value      身份標(biāo)識(保證鎖不會被其他人釋放)
     * @param expireTime 鎖的過期時間(單位:秒)
     * @return 成功返回true, 失敗返回false
     */
    public boolean lock(String lockKey, String value, long expireTime) {
        return redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
    }
    /**
     * redisTemplate解鎖
     * @param key
     * @param value
     * @return 成功返回true, 失敗返回false
     */
    public boolean unlock2(String key, String value) {
        Object currentValue = redisTemplate.opsForValue().get(key);
        boolean result = false;
        if (StringUtils.isNotEmpty(String.valueOf(currentValue)) && currentValue.equals(value)) {
            result = redisTemplate.opsForValue().getOperations().delete(key);
        }
        return result;
    }
    /**
     * 定時去檢查redis鎖的過期時間
     */
    @Scheduled(fixedRate = 5000L)
    @Async("redisExecutor")
    public void renewal() {
        long now = System.currentTimeMillis();
        for (Map.Entry<String, LockInfo> lockInfoEntry : lockInfoMap.entrySet()) {
            LockInfo lockInfo = lockInfoEntry.getValue();
            if (lockInfo.getRenewalTime() + lockInfo.getRenewalInterval() < now) {
                renewal(lockInfo.getKey(), lockInfo.getValue(), lockInfo.getExpireTime());
                lockInfo.setRenewalTime(now);
                log.info("lockInfo {}", JSON.toJSONString(lockInfo));
            }
        }
    }
    /**
     * 分布式鎖設(shè)置單獨(dú)線程池
     * @return
     */
    @Bean("redisExecutor")
    public Executor redisExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setQueueCapacity(1);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("redis-renewal-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        return executor;
    }
}

第三步,創(chuàng)建RedisTemplate 配置類,配置Redistemplate,示例代碼如下:

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws Exception {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 創(chuàng)建 序列化類
        GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(genericToStringSerializer);
        return redisTemplate;
    }
}

第四步,實現(xiàn)業(yè)務(wù)調(diào)用,這里以扣減庫存為例,示例代碼如下:

@RestController
public class IndexController {
    @Resource
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisLockUtils redisLock;
    @RequestMapping("/deduct-stock")
    public String deductStock() {
        String productId = "product001";
        System.out.println("---------------->>>開始扣減庫存");
        String key = productId;
        String requestId = productId + Thread.currentThread().getId();
        try {
            boolean locked = redisLock.lock(key, requestId, 10);
            if (!locked) {
                return "error";
            }
            //執(zhí)行業(yè)務(wù)邏輯
            //System.out.println("---------------->>>執(zhí)行業(yè)務(wù)邏輯:"+appTitle);
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("product001-stock").toString());
            int currentStock = stock-1;
            redisTemplate.opsForValue().set("product001-stock",currentStock);
            try {
                Random random = new Random();
                Thread.sleep(random.nextInt(3) *1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("---------------->>>扣減庫存結(jié)束:current stock:" + currentStock);
            return "success,current stock:" + currentStock;
        } finally {
            redisLock.unlock2(key, requestId);
        }
    }
}

六、驗證測試

代碼完成之后,開始測試。我們同時啟動兩個實例,端口號為:8888和8889模擬分布式系統(tǒng)。

接下來,我們分別請求:http://localhost:8888/deduct-stock

和http://localhost:8889/deduct-stock,或者使用JMater分別請求這兩個地址,模擬高并發(fā)的情況。

圖片

通過上圖我們可以看到,在批量請求的情況下,庫存扣減也沒有出現(xiàn)問題。說明分布式鎖生效了。

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

相關(guān)文章

  • Spring boot通過切面,實現(xiàn)超靈活的注解式數(shù)據(jù)校驗過程

    Spring boot通過切面,實現(xiàn)超靈活的注解式數(shù)據(jù)校驗過程

    這篇文章主要介紹了Spring boot通過切面,實現(xiàn)超靈活的注解式數(shù)據(jù)校驗過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • spring通過構(gòu)造函數(shù)注入實現(xiàn)方法分析

    spring通過構(gòu)造函數(shù)注入實現(xiàn)方法分析

    這篇文章主要介紹了spring通過構(gòu)造函數(shù)注入實現(xiàn)方法,結(jié)合實例形式分析了spring通過構(gòu)造函數(shù)注入的原理、實現(xiàn)步驟及相關(guān)操作注意事項,需要的朋友可以參考下
    2019-10-10
  • Java使用HttpClient實現(xiàn)Post請求實例

    Java使用HttpClient實現(xiàn)Post請求實例

    本篇文章主要介紹了Java使用HttpClient實現(xiàn)Post請求實例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • Mybatis接口式編程的原理

    Mybatis接口式編程的原理

    mybatis有兩種實現(xiàn)方式,一種可以通過xml配置文件實現(xiàn),其二是面向接口編程的實現(xiàn)。本文重點給大家介紹mybatis接口編程的原理,需要的的朋友參考下
    2017-03-03
  • Java中的AES加密算法用法示例詳解

    Java中的AES加密算法用法示例詳解

    這篇文章主要介紹了Java中的AES加密算法用法的相關(guān)資料,AES是一種廣泛使用的對稱加密算法,支持128位、192位和256位密鑰,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-01-01
  • SpringMVC Mock測試實現(xiàn)原理及實現(xiàn)過程詳解

    SpringMVC Mock測試實現(xiàn)原理及實現(xiàn)過程詳解

    這篇文章主要介紹了SpringMVC Mock測試實現(xiàn)原理及實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-10-10
  • Java中斷線程的方法

    Java中斷線程的方法

    這篇文章主要介紹了Java中斷線程的方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2017-05-05
  • SpringBoot @JsonDeserialize自定義Json序列化方式

    SpringBoot @JsonDeserialize自定義Json序列化方式

    這篇文章主要介紹了SpringBoot @JsonDeserialize自定義Json序列化方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Mybatis防止sql注入原理分析

    Mybatis防止sql注入原理分析

    這篇文章主要介紹了Mybatis防止sql注入原理分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Java中繼承和組合的區(qū)別

    Java中繼承和組合的區(qū)別

    這篇文章主要介紹了Java中繼承和組合的區(qū)別,  繼承是面向?qū)ο笕蠡咎卣髦?繼承,封裝,多態(tài)),繼承就是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,需要的朋友可以參考下
    2023-07-07

最新評論