1. 程式人生 > 實用技巧 >Redis的理解與應用

Redis的理解與應用

1.Redis的介紹;

  Redis是用c語言編寫的,是一個單執行緒,高效能的(key/value)記憶體資料庫,基於記憶體執行並支援持久化的nosql資料庫,主要是用來做快取,但不僅僅只能做快取,比如:redis的計數器生成分散式唯一主鍵,redis實現分散式鎖,佇列,會話快取。

2.Redis的資料型別及API操作:

  1)string型別:string是redis最基本的型別,一個key對應一個value,string型別是二進位制安全的。意思是redis的string可以包含任何資料。比如jpg圖片或者序列化的物件 ,一個redis中字串value最多可以是512M。

    set key value 設定key value

    get key 檢視當前key的值

    del key 刪除key

    append key value 如果key存在,則在指定的key末尾新增,如果key存在則類似set

  2)list型別:list是一個字串連結串列,left、right都可以插入新增;如果鍵不存在,建立新的連結串列;如果鍵已存在,新增內容;如果值全移除,對應的鍵也就消失了。連結串列的操作無論是頭和尾效率都極高,但假如是對中間元素進行操作,效率就很慘淡了。

  Redis 列表是簡單的字串列表,按照插入順序排序。你可以新增一個元素導列表的頭部(左邊)或者尾部(右邊)。它的底層實際是個連結串列。

    lpush key value1 value2 將一個或多個值加入到列表頭部

    rpush key value1 value2 將一個或多個值加入到列表底部

    lpop key 移出並獲取列表第一個元素

    rpop key 移出並獲取列表最後一個元素

    llen 獲取列表長度

  3)set型別:set是string型別的無序,不能重複的集合。

    sadd key value1 value2 向集合中新增一個或多個成員

    smembers key 返回集合中所有成員

    scard key 獲取集合裡面的元素個數

    srem key value 刪除集合中指定元素

    sdiff key1 key2 在第一個set裡面而不在後面任何一個set裡面的項(差集)

    sinter key1 key2 在第一個set和第二個set中都有的 (交集)

    sunion key1 key2 兩個集合所有元素(並集)

  4)hash型別:hash 是一個鍵值對集合,是一個string型別的field和value的對映表,hash特別適合用於儲存物件。kv模式不變,但v是一個鍵值對,類似Java裡面的Map<String,Object>

    hset key (key value) 向hash表中新增一個元素

    hget key key 向hash表中獲取一個元素

    hgetall key 獲取在hash列表中指定key的所有欄位和值

    hdel key key1 key2 刪除一個或多個hash欄位

    hlen key 獲取hash表中欄位數量

    hexits key key 檢視hash表中,指定key(欄位)是否存在

    hkeys key 獲取指定hash表中所有key(欄位)

    hvals key 獲取指定hash表中所有value(值)

  5)zset型別:zset 和 set 一樣也是string型別元素的集合,且不允許重複的成員。不同的是每個元素都會關聯一個double型別的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。zset的成員是唯一的,但分數(score)卻可以重複。

    zadd key score 值 score 值 向集合中新增一個或多個成員

    zrange key 0 -1 表示所有 返回指定集合中所有value

    zrem key score某個對應值(value),可以是多個值 刪除元素

    zcount key 開始score 結束score 獲取分數區間內元素個數

3.Redis的持久化機制

  Redis持久化就是在指定的時間間隔內,將記憶體當中的資料集快照寫入磁碟,它恢復時是將快照檔案直接讀到記憶體。

  Redis提供兩種方式進行持久化:

    1)RDB持久化,也是預設的持久化方式

    2)AOF(append only file)持久化

  RDB持久化:

    RDB持久化原理是redis會單獨建立(fork)一個與當前執行緒一模一樣的子程序來進行持久化,這個子執行緒的所有資料(變數。環境變數,程式程式計數器等)都和原程序一模一樣,會先將資料寫入到一個臨時檔案中,待持久化結束了,再用這個臨時檔案替換上次持久化好的檔案,整個過程中,主程序不進行任何的io操作,這就確保了極高的效能。

    什麼時候觸發RDB持久化機制:

      1.在執行shutdown命令時,如果沒有開啟AOF,這時就會觸發RDB持久化。

      2.在執行save命令或者bgsave命令時, save:是隻管儲存,其他不管,全部阻塞; bgsave: redis會在後臺非同步進行快照操作,同時可以響應客戶端的請求。

  AOF持久化:

    AOF持久化的原理是將Reids的操作日誌以追加的方式寫入檔案,讀操作是不記錄的。

    什麼時候觸發AOF持久化機制:

      1.在配置檔案中進行AOF的配置即可。

        no:表示等作業系統進行資料快取同步到磁碟(快,持久化沒保證)

        always:同步持久化,每次發生資料變更時,立即記錄到磁碟(慢,安全)

        everysec:表示每秒同步一次(預設值,很快,但可能會丟失一秒以內的資料)

    AOF重寫機制:

      當AOF檔案增長到一定大小的時候Redis能夠呼叫 bgrewriteaof對日誌檔案進行重寫 。當AOF檔案大小的增長率大於該配置項時自動開啟重寫(這裡指超過原大小的100%)。

  Redis4.0後的混合持久化機制:

    Redis 4.0版本的混合持久化預設關閉的,通過aof-use-rdb-preamble配置引數控制,yes則表示開啟,no表示禁用,5.0之後預設開啟。

    混合持久化是通過bgrewriteaof完成的,不同的是當開啟混合持久化時,fork出的子程序先將共享的記憶體副本全量的以RDB方式寫入aof檔案,然後在將重寫緩衝區的增量命令以AOF方式寫入到檔案,寫入完成後通知主程序更新統計資訊,並將新的含有RDB格式和AOF格式的AOF檔案替換舊的的AOF檔案。簡單的說:新的AOF檔案前半段是RDB格式的全量資料後半段是AOF格式的增量資料,

    優點:混合持久化結合了RDB持久化 和 AOF 持久化的優點, 由於絕大部分都是RDB格式,載入速度快,同時結合AOF,增量的資料以AOF方式儲存了,資料更少的丟失。

    缺點:相容性差,一旦開啟了混合持久化,在4.0之前版本都不識別該aof檔案,同時由於前部分是RDB格式,閱讀性較差

  總結:

    Redis提供了RDB持久化方案,為什麼還要AOF?優化資料丟失問題,rdb會丟失最後一次快照後的資料,aof丟失不會超過2秒的資料。

    如果RDB和AOF同時存在,聽誰的?以AOF為主。

    RDB和AOF相比較有什麼優劣?RDB適合大規模的資料恢復,對資料完整性和一致性不高 , 在一定間隔時間做一次備份,如果Redis意外down機的話,就會丟失最後一次快照後的所有操作,而AOF根據配置項而定。

4.Redis的主從複製:

  主機資料更新後根據配置和策略,自動同步到備機的master/slaver機制,mester以寫為主,slaver以讀為主。

  使用命令 SLAVEOF 動態指定主從關係 ,如果設定了密碼,關聯後使用 config set masterauth 密碼。

  總結:

    一個master可以有多個Slave

    一個slave只能有一個master

    資料流向是單向的,只能從主到從

5.Redis的哨兵模式:

  哨兵的核心功能是主節點的自動故障轉移。通俗來講哨兵模式的出現是就是為了解決我們主從複製模式中需要我們人為操作的東西變為自動版,並且它比人為要更及時

  哨兵主要功能(做了哪些事)

    1)監控(Monitoring):哨兵會不斷地檢查主節點和從節點是否運作正常。

    2)自動故障轉移(Automatic Failover):當主節點不能正常工作時,哨兵會開始自動故障轉移操作,它會將失效主節點的其中一個從節點升級為新的主節點,並讓其他從節點改為複製新的主節點。

    3)配置提供者(Configuration Provider):客戶端在初始化時,通過連線哨兵來獲得當前Redis服務的主節點地址。

    4)通知(Notification):哨兵可以將故障轉移的結果傳送給客戶端。

    其中,監控和自動故障轉移功能,使得哨兵可以及時發現主節點故障並完成轉移;而配置提供者和通知功能,則需要在與客戶端的互動中才能體現。

  哨兵模式的原理:

    1)主觀下線:在心跳檢測的定時任務中,如果其他節點超過一定時間沒有回覆,哨兵節點就會將其進行主觀下線。顧名思義,主觀下線的意思是一個哨兵節點“主觀地”判斷下線;與主觀下線相對應的是客觀下線。

    2)客觀下線:哨兵節點在對主節點進行主觀下線後,會通過sentinel is-master-down-by-addr命令詢問其他哨兵節點該主節點的狀態;如果判斷主節點下線的哨兵數量達到一定數值,則對該主節點進行客觀下線。

    需要特別注意的是,客觀下線是主節點才有的概念;如果從節點和哨兵節點發生故障,被哨兵主觀下線後,不會再有後續的客觀下線和故障轉移操作。

  定時任務:

    每個哨兵節點維護了3個定時任務。定時任務的功能分別如下:

    1)每10秒通過向主從節點發送info命令獲取最新的主從結構;發現slave節點 確定主從關係

    2)每2秒通過釋出訂閱功能獲取其他哨兵節點的資訊;SUBSCRIBE c2 PUBLISH c2 hello-redis

    3)每1秒通過向其他節點發送ping命令進行心跳檢測,判斷是否下線(monitor)。

  哨兵模式選舉領導者:

    當主節點被判斷客觀下線以後,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,並由該領導者節點對其進行故障轉移操作。

    監視該主節點的所有哨兵都有可能被選為領導者,選舉使用的演算法是Raft演算法;Raft演算法的基本思路是先到先得:即在一輪選舉中,哨兵A向B傳送成為領導者的申請,如果B沒有同意過其他哨兵,則會同意A成為領導者。一般來說,哨兵選擇的過程很快,誰先完成客觀下線,一般就能成為領導者。

    故障轉移:選舉出的領導者哨兵,開始進行故障轉移操作,該操作大體可以分為3個步驟:

    1)在從節點中選擇新的主節點:選擇的原則是,

     1.首先過濾掉不健康的從節點;

     2.然後選擇優先順序最高的從節點(由replica-priority指定);如果優先順序無法區分,

     3.則選擇複製偏移量最大的從節點;如果仍無法區分,

     4.則選擇runid最小的從節點。

    2)更新主從狀態:通過slaveof no one命令,讓選出來的從節點成為主節點;並通過slaveof命令讓其他節點成為其從節點。

    3)將已經下線的主節點(即6379)保持關注,當6379從新上線後設置為新的主節點的從節點

  總結:

    在主從複製的基礎上,哨兵引入了主節點的自動故障轉移,進一步提高了Redis的高可用性;但是哨兵的缺陷同樣很明顯:哨兵無法對從節點進行自動故障轉移,在讀寫分離場景下,從節點故障會導致讀服務不可用,需要我們對從節點做額外的監控、切換操作。此外,哨兵仍然沒有解決寫操作無法負載均衡、及儲存能力受到單機限制的問題

6.Redis cluster高可用叢集:

  Redis cluster叢集是一個由多個主從節點群組成的分散式伺服器群,它具有複製、高可用和分片特性。Redis cluster叢集不需要sentinel哨兵也能完成節點移除和故障轉移的功能。需要將每個節點設定成叢集模式,這種叢集模式沒有中心節點,可水平擴充套件,據官方文件稱可以線性擴充套件到1000節點。redis cluster叢集的效能和高可用性均優於之前版本的哨兵模式,且叢集配置非常簡單。

  Redis cluster叢集的優點:

    通過叢集,Redis解決了寫操作無法負載均衡,以及儲存能力受到單機限制的問題,實現了較為完善的高可用方案。

7.Redis快取的使用問題:

  1)快取粒度的控制

    快取粒度問題就是我們在使用快取時,是將所有資料快取還是快取部分資料?

    | 資料型別 | 通用性 | 空間佔用(記憶體空間+網路位元速率) | 程式碼維護 |

    | :----------: | :--------: | :------------------------------------------: | :----------: |

    | 全部資料 | 高 | 大 | 簡單 |

    | 部分資料 | 低 | 小 | 較為複雜 |

   快取粒度問題是一個容易被忽視的問題,如果使用不當,可能會造成很多無用空間的浪費,可能會造成網路頻寬的浪費,可能會造成程式碼通用性較差等情況,必須學會綜合資料通用性、空間佔用比、程式碼維護性 三點評估取捨因素權衡使用。
  2)快取穿透問題
    快取穿透是指查詢一個一定不存在的資料,由於快取不命中,並且出於容錯考慮, 如果從儲存層查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到儲存層去查詢,失去了快取的意義。
    快取穿透的危害:對底層資料來源壓力過大,有些底層資料來源不具備高併發性。 例如mysql一般來說單臺能夠扛1000-QPS就已經很不錯了
    快取穿透的解決方案:快取空物件,如果第一次去資料庫查詢沒有查到資料,就設定成一個具有過期時間的空值,防止短時間多次去資料庫查詢。
  3)快取擊穿
    快取擊穿是指快取中沒有但資料庫中有的資料(一般是快取時間到期),這時由於併發使用者特別多,同時讀快取沒讀到資料,又同時去資料庫去取資料,引起資料庫壓力瞬間增大,造成過大壓力
    我們知道,使用快取,如果獲取不到,才會去資料庫裡獲取。但是如果是熱點 key,訪問量非常的大,資料庫在重建快取的時候,會出現很多執行緒同時重建的情況。因為高併發導致的大量熱點的 key 在重建還沒完成的時候,不斷被重建快取的過程,由於大量執行緒都去做重建快取工作,導致伺服器拖慢的情況。
    快取擊穿解決方案:
      1.互斥鎖:第一次獲取快取的時候,加一個鎖,然後查詢資料庫,接著是重建快取。這個時候,另外一個請求又過來獲取快取,發現有個鎖,這個時候就去等待,之後都是一次等待的過程,直到重建完成以後,鎖解除後再次獲取快取命中。
public String getKey(String key){
    String value = redis.get(key);
    if(value == null){
        String mutexKey = "mutex:key:"+key; //設定互斥鎖的key
        if(redis.set(mutexKey,"1","ex 180","nx")){ //給這個key上一把鎖,ex表示只有一個執行緒能執行,過期時間為180秒
          value = db.get(key);
          redis.set(key,value);
          redis.delete(mutexKety);
  }else{
        // 其他的執行緒休息100毫秒後重試
        Thread.sleep(100);
        getKey(key);
  }
 }
 return value;
}

      互斥鎖的優點是思路非常簡單,具有一致性,但是互斥鎖也有一定的問題,就是大量執行緒在等待的問題。存在死鎖的可能性。

    4)快取雪崩問題

      快取雪崩是指機器宕機或在我們設定快取時採用了相同的過期時間,導致快取在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。

        1:在快取失效後,通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。

        2:做二級快取,A1為原始快取,A2為拷貝快取,A1失效時,可以訪問A2,A1快取失效時間設定為短期,A2設定為長期

        3:不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻。

        4:如果快取資料庫是分散式部署,將熱點資料均勻分佈在不同搞得快取資料庫中。