1. 程式人生 > >redis小結

redis小結

鍵空間 param point 備份 exp 死鎖 0ms 有序 不用

1.redis的簡單介紹

redis是一種高性能的Key-Value類型的內存數據庫,之所以說性能非常好,是因為redis是存放在內存中的,存取都是不用進行io磁盤操作,所以效率十分高。

2.為什麽會使用redis這種數據結構

其一:就是性能好,可以節約大量的時間。其二:在高並發的情況下,如果所有的請求(一般查詢偏多)都直接請求數據庫,會導致數據庫連接異常。如果在這種情況下,先請求redis緩存,即可有效緩解這種問題。

3.redis可支持的數據類型及使用場景

redis除了性能和高並發還能支持多種數據類型,包括:list,set,hash,sorted set, hash共五中數據接口。

4.redis單線程效率為何那麽高

  • 純內存操作,沒有IO磁盤操作,因此效率很高。
  • 單線程操作,避免了頻繁的上下文切換,從一定角度來看也是提高了效率。
  • 采用了非阻塞I/O多路復用機制

5.redis數據持久化

Redis 提供了多種不同級別的持久化方式:

  • RDB 持久化可以在指定的時間間隔內生成數據集的時間點快照(point-in-time snapshot)。
  • AOF 持久化記錄服務器執行的所有寫操作命令,並在服務器啟動時,通過重新執行這些命令來還原數據集。 AOF 文件中的命令全部以 Redis 協議的格式來保存,新命令會被追加到文件的末尾。 Redis 還可以在後臺對 AOF 文件進行重寫(rewrite),使得 AOF 文件的體積不會超出保存數據集狀態所需的實際大小。
  • Redis 還可以同時使用 AOF 持久化和 RDB 持久化。 在這種情況下, 當 Redis 重啟時, 它會優先使用 AOF 文件來還原數據集, 因為 AOF 文件保存的數據集通常比 RDB 文件所保存的數據集更完整。
  • 你甚至可以關閉持久化功能,讓數據只在服務器運行時存在。

了解 RDB 持久化和 AOF 持久化之間的異同是非常重要的, 以下幾個小節將詳細地介紹這這兩種持久化功能, 並對它們的相同和不同之處進行說明。

RDB:RDB 是一個非常緊湊(compact)的文件,它保存了 Redis 在某個時間點上的數據集。 這種文件非常適合用於進行備份。雖然 Redis 允許你設置不同的保存點(save point)來控制保存 RDB 文件的頻率, 但是, 因為RDB 文件需要保存整個數據集的狀態, 所以它並不是一個輕松的操作。 因此你可能會至少 5 分鐘才保存一次 RDB 文件。 在這種情況下, 一旦發生故障停機, 你就可能會丟失好幾分鐘的數據。所以如果不允許丟失數據的RDB不合適。

AOF:使用 AOF 持久化會讓 Redis 變得非常耐久。AOF 文件有序地保存了對數據庫執行的所有寫入操作。對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積。並且效率比RDB低。

究竟選擇哪一種方法:

如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失, 那麽你可以只使用 RDB 持久化。

有很多用戶都只使用 AOF 持久化, 但我們並不推薦這種方式: 因為定時生成 RDB 快照(snapshot)非常便於進行數據庫備份, 並且 RDB 恢復數據集的速度也要比 AOF 恢復的速度要快, 除此之外, 使用 RDB 還可以避免之前提到的 AOF 程序的 bug 。

6.redis分布式鎖的實現

可靠性

首先,為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件:

  1. 互斥性。在任意時刻,只有一個客戶端能持有鎖。
  2. 不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其他客戶端能加鎖。
  3. 具有容錯性。只要大部分的Redis節點正常運行,客戶端就可以加鎖和解鎖。
  4. 解鈴還須系鈴人。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。

加鎖的簡單實現方法:每個客戶端或者說每一個對象對於同一個key都只能執行一次set,保證了加鎖的機制。等操作完成及時解鎖。

public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 嘗試獲取分布式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請求標識
     * @param expireTime 超期時間
     * @return 是否獲取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

解鎖的簡單實現  :根據key對應的value,進行刪除鎖。

public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 釋放分布式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請求標識
     * @return 是否釋放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call(‘get‘, KEYS[1]) == ARGV[1] then return redis.call(‘del‘, KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

  應用場景:使用redis實現分布式鎖,達到分布式部署場景下用戶借款串行處理(防止產生多筆重復借款)。

7.redis的過期策略以及內存淘汰機制

redis采用的是定期刪除和惰性刪除,而還有一種叫定時刪除,

為什麽不用定時刪除策略?

定時刪除,用一個定時器來負責監視key,過期則自動刪除。雖然內存及時釋放,但是十分消耗CPU資源。在大並發請求下,CPU要將時間應用在處理請求,而不是刪除key,因此沒有采用這一策略.

定期刪除,redis默認每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查(如果每隔100ms,全部key進行檢查,redis豈不是卡死)。因此,如果只采用定期刪除策略,會導致很多key到時間沒有刪除。

於是,惰性刪除派上用場。也就是說在你獲取某個key的時候,redis會檢查一下,這個key如果設置了過期時間那麽是否過期了?如果過期了此時就會刪除。

如果定期刪除沒刪除key。然後你也沒即時去請求key,也就是說惰性刪除也沒生效。這樣,redis的內存會越來越高。那麽就應該采用內存淘汰機制。

內存淘汰機制:

在redis.conf中有一行配置

# maxmemory-policy volatile-lru (最大內存策略)

一共6種方式:

1)noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。應該沒人用吧。

2)allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key。推薦使用,目前項目在用這種。

3)allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。應該也沒人用吧,你不刪最少使用Key,去隨機刪。

4)volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的key。這種情況一般是把redis既當緩存,又做持久化存儲的時候才用。不推薦

5)volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個key。依然不推薦

6)volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的key優先移除。不推薦

8.redis所帶來的問題以及解決方案

(一)緩存和數據庫雙寫一致性問題

具體問題是指在更新數據庫時,因為有緩存存在,會導致緩存和數據庫中數據不一致的問題。

一般解決方案有以下幾種:

(1)先更新數據庫,在更新緩存。

多線程情況下:

1)線程A更新了數據庫
2)線程B更新了數據庫
3)線程B更新了緩存
4)線程A更新了緩存

明顯應該A先更新緩存,可B卻先更新了緩存,導致了臟數據。並且如果寫操作比較多,緩存還沒有被讀就一直在更新,就會浪費這次操作,影響性能。

因此:一般都采用這種方式解決一致性問題。

(2)先刪除緩存,在更新數據庫。

多線程的案例:

1)請求A進行寫操作,刪除緩存
2)請求B查詢發現緩存不存在
3)請求B去數據庫查詢得到舊值
4)請求B將舊值寫入緩存
5)請求A將新值寫入數據庫

導致了緩存和數據庫不一致。如果沒有key過期作廢的機制,這個臟數據就會一直存在。

(3)先更新數據庫,在刪除緩存。

發生這種情況的案例:

1)緩存剛好失效
2)請求A查詢數據庫,得一個舊值
3)請求B將新值寫入數據庫
4)請求B刪除緩存
5)請求A將查到的舊值寫入緩存
如果發生上述情況,確實是會發生臟數據。但是概率很小。因為步驟5)一般都會在步驟3)前執行。因為讀操作遠快於寫操作,所以這種策略比較常用。

(二)緩存雪崩問題

可能是並發大量請求導致緩存失效(查不到值),即緩存同一時間大面積的失效,這個時候又來了一波請求,結果請求都懟到數據庫上,從而導致數據庫連接異常。

解決方案:

(1)給緩存的失效時間,加上一個隨機值,避免集體失效。

(2)使用互斥鎖,但是該方案吞吐量明顯下降了。

(3)雙緩存。我們有兩個緩存,緩存A和緩存B。緩存A的失效時間為20分鐘,緩存B不設失效時間。自己做緩存預熱操作。然後細分以下幾個小點

  • I 從緩存A讀數據庫,有則直接返回

  • II A沒有數據,直接從B讀數據,直接返回,並且異步啟動一個更新線程。

  • III 更新線程同時更新緩存A和緩存B。

(三)緩存擊穿問題

緩存穿透,即黑客故意去請求緩存中不存在的數據,導致所有的請求都懟到數據庫上,從而數據庫連接異常。

解決方案:

(1)利用互斥鎖,緩存中不存在數據的時候,先去獲得鎖,得到鎖了,再去請求數據庫。沒得到鎖,則休眠一段時間重試

(2)采用異步更新策略,無論key是否取到值,都直接返回。value值中維護一個緩存失效時間,緩存如果過期,異步起一個線程去讀數據庫,更新緩存。需要做緩存預熱(項目啟動前,先加載緩存)操作。

(3)提供一個能迅速判斷請求是否有效的攔截機制,比如,利用布隆過濾器,內部維護一系列合法有效的key。迅速判斷出,請求所攜帶的Key是否合法有效。如果不合法,則直接返回。

(四)緩存的並發競爭key問題

多個子系統同時set同一個值,導致競爭key的問題。

解決方案:

(1)如果對這個key操作,不要求順序

這種情況下,準備一個分布式鎖,大家去搶鎖,搶到鎖就做set操作即可,比較簡單。

(2)如果對這個key操作,要求順序

系統A key 1 {valueA 3:00}

系統B key 1 {valueB 3:05}

系統C key 1 {valueC 3:10}

當系統搶到分布式鎖的時候,還要判斷時間戳,如果小與緩存中的時間戳,則不去set操作。

redis小結