Redis分佈式鎖,ZK分佈式鎖,redis緩存穿透,擊穿和雪崩以及解決方案

常用的分佈式鎖和 Redis 和 zk 兩種分佈式鎖的對比:https://www.cnblogs.com/codingmode/p/15331731.html
一、 redis分佈式鎖原理,併發,到Redis裏變成了串行排隊,單線程
實現原理
獲取Redis鎖的命令:
SET resource_name my_random_value NX PX 30000
resource_name:資源名稱,可根據不同的業務區分不同的鎖
my_random_value:隨機值,每個線程的隨機值都不同,用於釋放鎖時的校驗
NX:key不存在的時候設置成功,key存在則設置不成功
PX:自動失效時間,出現異常情況,鎖可以過期自動釋放
利用NX的原子性,多個線程併發時,只有一個線程可以設置成功,設置成功即可獲得鎖,可以執行後續的業務處理。
如果出現異常,超過鎖的有效期,鎖自動釋放,釋放鎖採用Redis的delete命令
釋放鎖時校驗之前設置的隨機數,相同才能釋放
二、基於Redis的Setnx實現分佈式鎖
POM

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

application.properties 配置redis:spring.redis.host=192.168.73.130

package com.example.distributelock.controller;

import com.example.distributelock.lock.RedisLock;
import com.example.distributelock.lock.ZkLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class RedisLockController {
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("redisLock")
    public String redisLock(){
        log.info("我進入了方法!");
        try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){
            if (redisLock.getLock()) {
                log.info("我進入了鎖!!");
                Thread.sleep(15000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法執行完成");
        return "方法執行完成";
    }

    @RequestMapping("zkLock")
    public String zkLock(){
        log.info("我進入了方法!");
        try (ZkLock zkLock = new ZkLock("localhost:2181","order")){
            if (zkLock.getLock()) {
                log.info("我進入了鎖!!");
                Thread.sleep(15000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("方法執行完成");
        return "方法執行完成";
    }
}

Redis的Setnx實現分佈式鎖操作,利用Redis的lua腳本來實現解鎖操作的原子性。參考:https://blog.csdn.net/varyall/article/details/117913979

package com.example.distributelock.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@Slf4j
public class RedisLock implements AutoCloseable {

    private RedisTemplate redisTemplate;
    private String key;
    private String value;
    //單位:秒
    private int expireTime;

    public RedisLock(RedisTemplate redisTemplate,String key,int expireTime){
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.expireTime=expireTime;
        this.value = UUID.randomUUID().toString();
    }

    /**
     * 獲取分佈式鎖
     * @return
     */
    public boolean getLock(){
        RedisCallback<Boolean> redisCallback = connection -> {
            //設置NX
            RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
            //設置過期時間
            Expiration expiration = Expiration.seconds(expireTime);
            //序列化key
            byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
            //序列化value
            byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
            //執行setnx操作
            Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
            return result;
        };

        //獲取分佈式鎖
        Boolean lock = (Boolean)redisTemplate.execute(redisCallback);
        return lock;
    }

    public boolean unLock() {
        String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);
        List<String> keys = Arrays.asList(key);

        Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);
        log.info("釋放鎖的結果:"+result);
        return result;
    }


    @Override
    public void close() throws Exception {
        unLock();
    }
}

redis緩存穿透,擊穿和雪崩以及解決方案:https://blog.csdn.net/m0_37937394/article/details/122564362
一:redis雪崩
redis雪崩是指redis在某個時間大量失效,突然造成數據庫訪問壓力急劇增大,像雪崩一樣,redis雪崩危害巨大,甚至有可能服務器宕機,給公司造成巨大的經濟損失。
解決方案:設置超時時間的時候要設置隨機值,不要設置固定值
二: redis緩存穿透
緩存穿透是指緩存和數據庫中都沒有的數據,而用戶不斷髮起請求。由於緩存是不命中時被動寫的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。
在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
如發起爲id爲“-1”的數據或id爲特別大不存在的數據。這時的用戶很可能是攻擊者,攻擊會導致數據庫壓力過大。
解決方案:
1.設置併發鎖,防止請求大量請求數據庫,如果獲取到鎖了,去數據庫查詢,如果沒有,說明有其他線程在查詢數據庫,那麼只需要重試一下就好了。

public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表緩存值過期
          //設置3min的超時,防止del操作失敗的時候,下次緩存過期一直不能load db
	       if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表設置成功
		       value = db.get(key);
		       redis.set(key, value, expire_secs);
		       redis.del(key_mutex);
	       } else {  //這個時候代表同時候的其他線程已經load db並回設到緩存了,這時候重試獲取緩存值即可
		      Thread.sleep(50);
		      get(key);  //重試
	       }
       } else {
              return value;      
       }
}

 

2.設置攔截器,對於不存在得key,進行攔截
三:緩存擊穿
緩存擊穿是指緩存中沒有但數據庫中有的數據(一般是緩存時間到期),這時由於併發用戶特別多,同時讀緩存沒讀到數據,又同時去數據庫去取數據,引起數據庫壓力瞬間增大,造成過大壓力。
解決方案:
1.設置熱點數據永不過期。
2.加互斥鎖:其他的線程走到這一步拿不到鎖就等着,等第一個線程查詢到了數據,然後做緩存。後面的線程進來發現已經有緩存了,就直接走緩存。

static Lock reenLock = new ReentrantLock();
public String findPubConfigByKey1(String key) throws InterruptedException {
        PubConfig result = new PubConfig();
        // 從緩存讀取數據
        result = redisService.getObject(PubConfigKeyConstants.TABLE_NAME + "_"+key, PubConfig.class) ;
        if (result== null ) {
            if (reenLock.tryLock()) {
                try {
                    System.out.println("拿到鎖了,從DB獲取數據庫後寫入緩存");
                    // 從數據庫查詢數據
                    result = pubConfigRepository.queryPubConfigInfoByKey(key);
                    // 將查詢到的數據寫入緩存
                    Gson g = new Gson();
                    String value = g.toJson(result);
                    redisService.setNx(PubConfigKeyConstants.TABLE_NAME + "_"+key, value);
                } finally {
                    reenLock.unlock();// 釋放鎖
                }
 
            } else {
                // 先查一下緩存
                result = redisService.getObject(PubConfigKeyConstants.TABLE_NAME + "_"+key, PubConfig.class) ;
                if (result== null) {
                    System.out.println("我沒拿到鎖,緩存也沒數據,先小憩一下");
                    Thread.sleep(100);// 小憩一會兒
                    return findPubConfigByKey1(key);// 重試
                }
            }
        }
        return result.getValue();
}

https://baijiahao.baidu.com/s?id=1707897582913572517&wfr=spider&for=pc

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章