redis在分佈式環境的應用,分佈式鎖redisson(待補充)

1.緩存使用

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
本地緩存
在這裏插入圖片描述
本地緩存在分佈式項目下的問題(各個服務擁有各個服務的緩存組件)
商品服務
同一個用戶在訪問時可能會訪問不同的服務,這樣就會導致之前在別的服務上的緩存無法使用,也會產生數據一致性的問題

分佈式緩存
在這裏插入圖片描述

2.分佈式應用redis的使用

引入依賴
在這裏插入圖片描述
簡單配置reids地址等信息

使用spring提供的StringRedisTemplate來操作redis

例子:保存字符串並查詢
在這裏插入圖片描述
實際應用(簡單使用)
在這裏插入圖片描述
壓力測試下出現的問題-堆外內存溢出
在這裏插入圖片描述
問題解決:切換使用jedis
1.引入依賴
在這裏插入圖片描述

3.緩存穿透,雪崩,擊穿

在這裏插入圖片描述
在這裏插入圖片描述
大量請求同時訪問一條數據
在這裏插入圖片描述

4.加鎖方式解決緩存穿透

4.1本地鎖

單體如何應用加鎖?
加鎖應注意鎖的時序問題(數據庫查到結果後立即將結果放到緩存中且在將數據放在redis中之後再釋放鎖,查出數據後需要將數據放到redis這是一個短暫的過程,但若在這個過程中恰好有一個請求進入且鎖釋放就會出現2次查詢數據庫的現象)
加鎖後應再次判斷緩存數據是否存在有就直接返回。(因爲以有所有的線程都已經執行了外層的判斷且在synchronized處等待,若不加內層判斷當第一個拿到鎖的請求執行完後之後所有的線程都會依次的查詢數據庫)
在這裏插入圖片描述
本地鎖存在的問題
本地所只能鎖住當前服務,而分佈式下會有應用集羣,每把鎖只能鎖住當前應用就會出先多個請求同時訪問數據庫。

4.2分佈式下如何加鎖

在這裏插入圖片描述
在這裏插入圖片描述
使用redisTemplate提供的setIfAbsent(“lock”,“任意字符串”)方法佔位;方法意爲,若多個請求同時訪問只有一個能將數據放入lock字段且返回true,其他返回false
在這裏插入圖片描述
返回爲true的進行邏輯代碼到數據庫查詢相關數據並放入緩存,且將之前lock數據刪除供其他服務獲取;
返回爲false的再去重新佔位獲取鎖;(synchronized鎖是自旋鎖當代碼執行到大括號結尾時會自動釋放)應注意對內存溢出異常此處建議加上休眠時間。

問題:若在某個請求獲取到鎖後出現異常或者斷電就會出現佔位數據lock無法被刪除,導致所有請求全部在等待鎖的問題即死鎖問題

在這裏插入圖片描述
注佔位和設置過期時間必須要是原子的,要麼都成功要麼都失敗
在這裏插入圖片描述
**問題:**如果在我們執行業務代碼時,我們前面的佔位lock數據已經過期,這樣的話,我們在執行到刪除佔位lock數據時,會刪除一個不存在的數據,更嚴重的話若時第二個請求剛好佔到位了就會出現將我們第二佔位lock數據刪除,但三個請求也是如此.
若業務超時也會造成安全隱患。
在這裏插入圖片描述
在這裏插入圖片描述
**問題:**因爲在我們從redis中查出自己的佔位數據是需要短暫的時間 ,若在這短暫的時間例佔位數據過期,且我們剛好完成了if(uuid.equals(lockValue))的判斷,這時再有新的請求進入,那麼就會重新添加佔位數據,我們再執行刪除佔位數據,就會刪除新的請求的佔位數據,之後新的請求在進行判斷時就會出現死鎖問題,

所以刪除佔位數據與查詢自己的佔位符操作也應該是原子的
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
總結 :分佈式鎖應該原子加鎖(setnx)解鎖(Lua腳本解鎖)。

5.分佈式鎖-Redisson

juc下的所有鎖都是本地鎖只能鎖住本地應用在分佈式環境下無法使用,我們使用Redission框架,redisson官方文檔

1.導入依賴

<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.12.0</version>
</dependency>

2.程序化配置測試redisson
所有的redisson操作都是用redissonClient

@Configuration
public class RedissonConfig {

    @Bean(destroyMethod="shutdown")
    RedissonClient redisson() throws IOException {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.1.5:6379");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

測試代碼

    @Autowired
    RedisClient redisClient;
    @Test
    void contextLoads1() {
        System.out.println(redisClient);
    }

5.1 lock鎖測試

基於Redis的Redisson分佈式可重入鎖RLock Java對象實現了java.util.concurrent.locks.Lock接口。
redisson的鎖底層使用juc實現
lock.lock();:有自動續期功能;
lock.lock(10 , TimeUnit.Seconds);設置自動解鎖時間自動續期失效導致問題
在這裏插入圖片描述

看門狗自動續期和自動解鎖原理解析
在給鎖添加自動過期時間之後,自動續期功能失效,導致解鎖失敗,
在這裏插入圖片描述每隔十秒鐘都會自動再次續期,續成30s

5.2 讀寫鎖的使用測試

基於Redis的Redisson分佈式可重入讀寫鎖RReadWriteLock Java對象實現了java.util.concurrent.locks.ReadWriteLock接口。其中讀鎖和寫鎖都繼承了RLock接口
模仿讀寫鎖業務
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

5.3閉鎖測試(等待)

基於Redisson的Redisson分佈式閉鎖(CountDownLatch)Java對象RCountDownLatch採用了與java.util.concurrent.CountDownLatch相似的接口和用法。

模擬:放假學校鎖門狀態,要學校5個班裏所有的人全部走完才能鎖門;
在這裏插入圖片描述

5.4信號量測試(用可用於分佈式的限制流量)

基於Redis的Redisson的分佈式信號量(Semaphore)Java對象RSemaphore採用了與java.util.concurrent.Semaphore相似的接口和用法。

模擬車庫停車,總共三個車位給redis中添加一個key =“park” value="3"停車就會減一個車位,開走就會多一個車位,沒車位時會線程阻塞等待。
在這裏插入圖片描述

6.緩存一致性解決

模擬業務,若我們緩存品牌信息後,又修改了商品信息,這時再去查詢品牌就會出現緩存與數據庫數據不一致問題,提供兩種解決方案

改數據庫同時寫入緩存(雙寫模式)最終一致性前提是,寫入緩存時必須設置緩存過期時間。
兩個請求同時訪問:寫數據庫1操作完成後用戶網絡卡頓,此時寫數據庫2與寫緩存2都完成後,才執行寫緩存1。就出現了數據的不一種
在這裏插入圖片描述
改數據庫之後將緩存設爲失效(失效模式)
在這裏插入圖片描述

在這裏插入圖片描述
canal
在這裏插入圖片描述

我們系統的一致性解決方案:
1.緩存的所有數據都有過期時間,數據過期下一次查詢就能主動更新
2.讀寫數據時加上分佈式讀寫鎖

7.整合redis緩存的使用(這裏只簡單過一下詳細可參考spring緩存抽象、整合redis

1.依賴導入

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

2.配置
springcache 給我們的寫好了的自動配置
CacheAutoConfiguration會導入RedisAtuoConfiguration;RedisAtuoConfiguration中自動配置好了CacheManager組件
我們只需要配置緩存的類型爲redis
spring.cache.type=redis
其他配置:
在這裏插入圖片描述
3.使用@cacheable後的默認行爲與自定義
默認行爲:

  • 如果緩存中有方法不再調用
  • key默認自動生成緩存的名字爲SimpleKey
  • 緩存的value的值,默認使用java序列化機制,將序列化後的數據存到redis
  • 默認失效時間爲-1(永不失效)
    自定義我們自己的行爲
  • 自定義緩存的名字(cacheName/value="#root.arg[0] "可使用#p0/#a0#root.arg[0]獲取方法中參數的值作爲緩存名#root也有很多內置對象如method,target等)
  • 自定義過期時間(在配置文件中spring.cache.redis.tine-to-live=6000設置緩存過期時間,單位爲毫秒)
  • 自定義序列化機制(默認爲jdk的序列化格式)
    修改爲自定義序列化配置(參考spring緩存抽象、整合redis)

在這裏插入圖片描述

分區名時spring中的一個概念就是cacheable(value=“product” key=“product1”)value的值就是分區名,key的值纔是真正的緩存名
在這裏插入圖片描述

總結:分佈式中的緩存一致性解決方案,
雙寫模式使用@cacheput(value=“product”,key="#product.id")
失效模式使用@cacheEvict(value=“product”,allEntries=true)
注:@caching可以實現多個@cacheput 或@cacheEvict操作

8.springcache的不足

在這裏插入圖片描述

總結:

常規數據(讀多寫少,及時性和一致性要求不高的數據)的可以使用spring-cache。寫模式只要緩存的數據有過期時間就可以。也可以使用加讀寫鎖保證併發。
特殊數據:特殊設計

穿透:是指高併發環境下,多個請求同時查詢一個一定不存在的數據,由於緩存不命中,將查詢數據庫,但數據庫中也沒有這條記錄,這將導致這個不存在的數據每次請求都要到數據庫去查,失去了緩存的意義。
解決:null結果緩存,並加入短暫獲取時間,springcache提供了一個配置ache-null-value將此配置開啓即可緩存空數據

雪崩:是指在我們設置緩存時採用了相同的過期時間,導致緩存在某一刻同時失效,請求全部訪問數據庫導致數據庫壓力過重雪崩
解決:我們只需要爲所有的緩存設置一個過期時間即可(因爲數據在緩存的時候大多數都是在不同的時間點,設置獲取時間之後,他們也會在不同的時間點過期)spring-cache設置緩存時間spring.cache.redis.time-to-live=3600000 單位爲毫秒位爲毫秒

擊穿:對於一些設置了過期時間的key,如果這個key是一個超高併發訪問的熱點數據,且這個key在大量請求進入後剛好失效,那麼所有的請求都將落到數據庫,
解決:加鎖大量併發只讓一個人去查,其他人等待,查到以後釋放鎖,其他人獲取鎖先查緩存就會有數據。
springcache提供了@cacheable(sync=true)(加鎖解決擊穿)/或者自己加鎖應注意鎖的添加(setnx)和刪除(lua腳本)都應是原子性的

spring-cache存在的不足
**問題出現:**因爲是在分佈式集羣應用中.jnc下的鎖和spring提供的sync都是本地鎖只能鎖住當前應用,在集羣中無效,且我們要保證添加鎖和刪除鎖是原子操作否則就會出現安全問題。
我們的最終解決方案就是使用redis官方提供的解決方案,redisson:
redisson提供了一系列的分佈式的Java常用對象,還提供了許多分佈式服務。我們可以使用它提供的分佈式鎖, 包括lock鎖(),讀寫鎖,閉鎖(學校放假所有人走完才能關門),信號值(3個車位停車),
springboot中給我們提供了redisson對象供我們操作使用 lock.lock(“product”)只要鎖的名字一樣就是同一把鎖.避免了分佈式中鎖對象不同問題。
在使用lock鎖的時候應注意如果我們不指定鎖的過期時間,只要佔鎖成功就會開啓一個定時任務,給我們的鎖就會自動續期,直到業務執行完畢,所以我們在使用時都應指定鎖的過期時間,就算業務超時也會自動解鎖。

緩存一致性
若我們緩存某個信息後,又修改了這個信息,這時再去查詢品牌就會出現緩存與數據庫數據不一致問題,提供兩種解決方案
雙寫模式:修改數據庫時,給緩存也更新
失效模式:修改數據庫時,將緩存設置爲失效下次查詢自動更新。
在這裏插入圖片描述

至此分佈式下緩存大致思路如此,待整理。

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