reids的開發實踐

目錄
[TOC]

基本特性以及優勢

==============

  1. 讀寫性能優異:全部在內存中計算,單線程,IO非阻塞。
  2. 支持數據持久化,支持AOF和RDB兩種持久化方式
  3. 數據結構豐富:基於KEY-VALUE除了支持string類型的value外還支持string、hash、set、sortedset、list等數據結構。
  4. 功能豐富:Redis還支持 publish/subscribe, 通知, key 過期等等特性;簡單的事務;pipeline批量命令執行。
  5. 多語言支持: Redis支持的客戶端操作語言非常豐富,基本的主流語言都支持。
  6. 可複製:支持主從複製。
  7. 高可用分佈式: redis sensinel,redis cluster。

各大類型的典型應用場景

================

String類型的典型應用場景—緩存

緩存一致性問題:

操作1 操作2 最終結果
DB 更新成功 緩存更新失敗 數據不一致
緩存更新成功 DB更新失敗 數據不一致
DB 更新成功 緩存淘汰失敗 數據不一致
緩存淘汰成功 DB 更新成失敗 緩存未命中

更新緩存VS淘汰緩存
更新緩存:同事寫入數據庫和緩存
淘汰緩存:只寫入數據庫,刪除緩存; 比更新緩存多了一次miss,需要多查詢一次。

那麼如果選擇??
如果更新的值是直接查詢的,選擇更新; 如果是通過比較複雜的業務邏輯計算的,更新比淘汰緩存代價大,選擇淘汰緩存。

先操作數據庫 or 先操作緩存?
先操作緩存只會造成一次cache miss,不會出現數據不一致的情況, 所以一般選前者。

緩存常見問題以及解決思路:

緩存穿透問題:
緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中時被動寫的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。

緩存穿透的解決方案:有很多種方法可以有效地解決緩存穿透問題,最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更爲簡單粗暴的方法(我們採用的就是這種),如果一個查詢返回的數據爲空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。

緩存雪崩:
緩存雪崩是指在我們設置緩存時採用了相同的過期時間,導致緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。

緩存雪崩的解決方案:
考慮用加鎖或者隊列的方式保證緩存的單線 程(進程)寫,從而避免失效時大量的併發請求落到底層存儲系統上。這裏分享一個簡單方案就時講緩存失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重複率就會降低,就很難引發集體失效的事件。

緩存擊穿:
對於一些設置了過期時間的key,如果這些key可能會在某些時間點被超高併發地訪問,是一種非常“熱點”的數據。這個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的區別在於這裏針對某一key緩存,前者則是很多key。

緩存擊穿解決方案:
給查詢緩存加鎖,如果查詢時緩存不存在則枷鎖去數據庫查詢,結束後釋放該鎖。

String類型的典型應用場景—分佈式鎖

這裏簡單介紹一種單點悲觀鎖;還有比較靠譜的多節點分佈式鎖,或者樂觀鎖這裏就不做介紹。

單點分佈式鎖代碼示例:
一些人可能想直接用setNx命令來實現分佈式鎖,但是會有一個問題,setNx和EXPIRE命令是非原子性操作;如果兩步走會存在一定的問題。

加鎖

    public boolean tryLock(String key, String value, long seconds) {
        Jedis jedis = null;
        String res = null;
        try {
            jedis = getJedis();
            res = jedis.set(key, value, "NX", "EX", seconds);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return "OK".equals(res) ? true : false;
    }

解鎖:

    public boolean unLock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Boolean res = false;
        Jedis jedis = null;
        try {
            jedis = getJedis();
            Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
            if (result.equals(1L)) {
                res = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return res;
    }
發佈了32 篇原創文章 · 獲贊 26 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章