springboot利用redis實現分布式鎖(redis為單機模式)


1.pom文件添加redis支持

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

2.application.properties或者(application.yml)添加redis配置

spring.redis.database=1
spring.redis.host=172.xx.xx.xx
spring.redis.password=123456
spring.redis.port=6379

上面的spring.redis.host替換成自己的redis服務地址,如果沒有用到密碼則刪除spring.redis.password配置即可

 

3.redis工具類

package com.example.demo;

import com.example.demo.extend.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String,String> template;

    @PostConstruct
    public void init(){
        template.setKeySerializer(template.getStringSerializer());
        template.setValueSerializer(template.getStringSerializer());
        template.setHashKeySerializer(template.getStringSerializer());
        template.setHashValueSerializer(template.getStringSerializer());
    }

    /**
     * 通過lua腳本 加鎖並設置過期時間
     * @param key 鎖key值
     * @param value 鎖value值
     * @param expire 過期時間,單位秒
     * @return true:加鎖成功,false:加鎖失敗
     */
    public boolean getLock(String key,String value,String expire){
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>();
        redisScript.setResultType(String.class);
        String strScript = "";
        strScript +="    if redis.call('setNx',KEYS[1],ARGV[1])==1 then ";
        strScript +="        return redis.call('expire',KEYS[1],ARGV[2]) ";
        strScript +="    else";
        strScript +="        return 0 ";
        strScript +="    end ";
        redisScript.setScriptText(strScript);
        try{
            Object result = this.template.execute(redisScript,template.getStringSerializer(),template.getStringSerializer(), Collections.singletonList(key),value,expire);
            System.out.println("redis返回:"+result);
            return "1".equals(""+result);
        }catch (Exception e){
            //可以自己做異常處理
            return false;
        }

    }

    /**
     * 通過lua腳本釋放鎖
     * @param key 鎖key值
     * @param value 鎖value值(僅當redis里面的value值和傳入的相同時才釋放,避免釋放其他線程的鎖)
     * @return true:釋放鎖成功,false:釋放鎖失敗(可能已過期或者已被釋放)
     */
    public boolean releaseLock(String key,String value){
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(String.class);
        String strScript = "";
        strScript +="if redis.call('get',KEYS[1]) == ARGV[1] then ";
        strScript +="    return redis.call('del',KEYS[1]) ";
        strScript +="else ";
        strScript +="    return 0 ";
        strScript +="end ";
        redisScript.setScriptText(strScript);
        try{
            Object result = this.template.execute(redisScript,template.getStringSerializer(),template.getStringSerializer(), Collections.singletonList(key),value);
            return "1".equals(""+result);
        }catch (Exception e){
            //可以自己做異常處理
            return false;
        }
    }
}

 

redis鎖用到的是setNx命令,這個命令的意思是如果redis里面存在這個key則不再添加,如果key不存在則添加成功,當setNx設置成功之后再給這個值設置一個超期時間來防止出現極端情況(斷網,服務終止)導致鎖沒有被及時釋放的情況。

上面的加鎖和解鎖都是通過lua腳本進行,redis里面lua腳本執行時是原子操作,可以保證加鎖和設置超時同時成功或者失敗,不會出現設置值成功 添加超時時間失敗的情況

4.使用

在需要加鎖的地方注入RedisUtil對象即可。有問題的可以留言一起探討細節問題

@Autowired
private RedisLockUtil redisUtil;
boolean lock = this.redisUtil.getLock("FORM_SUBMIT"+formId,formId,"2");
            if(!lock){
                //未獲得鎖
                throw new ServiceException("當前已經有任務在執行!");
            }

//---------執行業務邏輯

if(lock){
                //釋放鎖不關心成功與否
                this.redisUtil.releaseLock("FORM_SUBMIT"+formId,formId);
            }

上面的業務邏輯最好放在try catch中執行,釋放鎖的代碼放到finally里面執行。

 

5.考慮各種情況下會不會導致bug的出現

5.1:加鎖失敗

  拿不到鎖業務邏輯不執行,沒問題

5.2:加鎖成功,釋放鎖失敗

  網絡原因或者其他原因沒有釋放掉,沒關系 ,超時時間過了就會自己釋放,沒問題

5.3:加鎖成功,業務執行時間過長,鎖已經被redis自己釋放

   此種情況需要根據業務的實際情況設置合理的超時時間,可能會出問題,原因在於 基於分布式的系統是無法避免類似的問題,具體可以參考如下博客的文章,

此文章引入了 關於Redis分布式鎖的安全性問題,在分布式系統專家Martin Kleppmann和Redis的作者antirez之間發生過的一場爭論,內容很精彩。https://blog.csdn.net/paincupid/article/details/75094550


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM