常用的分佈式鎖和 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