Redis簡介一(單機版)

發展歷程

1.0階段

常見文件存儲有兩種方式:磁盤,內存。
涉及到兩種常見問題:尋址,帶寬。

磁盤尋址在毫秒(ms)級,帶寬 xx G
內存尋址在納秒(ns)級,帶寬:很大
磁盤與內存,在尋址方面,慢了10w倍

磁盤有磁道,扇區。一個扇區512byte,容量很小,帶來一個問題,當文件比較大的時候,需要不停的訪問扇區,不停尋址,對應的文件索引也會非常龐大。

內存存儲,數據沒法持久化。

4k對齊:操作系統,無論讀取多大的文件,一次I/O,最少4k大小。也可以設置爲大於4k。

2.0階段

關係型數據庫出現:
數據庫將數據最小的存儲單元稱爲page(oracle爲block),一般爲8k,設置爲4k的整數倍,目的爲了滿足操作系統一次I/O大小。
在數據庫中,可以把數據文件,看做是分拆到 從0-N個page中。
索引:在正常的數據data page之後,再新開一個空間,其中的每個page存儲索引字段對應的信息,以及索引它所關聯的正常數據的data page信息。

在這裏插入圖片描述
當一個查詢條件,通過B/tree索引信息,命中某一個索引,如page1,則可以順勢找到元數據data page2,加載data page2進入內存。

當數據量非常大的時候,數據的贈刪改性能會降低,查詢性能方面,如果只有少量次查詢,查詢性能不會降低很明顯,但是如果高併發查詢時,受限於I/O的讀取容量,會造成大量的page讀取排隊堵塞,性能會下降的很明顯。我們希望速度能夠更快一些。

3.0階段

緩存:memcached,redis

Redis

結構圖:

在這裏插入圖片描述
client與Redis建立連接後,通過epoll多路複用實現單進程高吞吐,

二進制安全

在這裏插入圖片描述
存儲同樣的一個value:中,當shell使用utf-8時,Redis存儲的是3個字節,當shell編碼改成GBK之後,再次存儲”中“,Redis存儲的是2個字節。
Redis爲了二進制安全,以字節流讀取數據,而非字符流。

事務

在這裏插入圖片描述
如圖所示,client1和client2都連接了一個Redis實例。client1和client2都進行事務操作,client1進行更新key,client2進行查詢key後再進行刪除key。
我們都知道Redis對於業務處理這塊,是單進程的,那現在有兩個事務,Redis是如何實現的呢?
一個事務從開始到執行,有下面3個步驟:1.開始事務,2.命令入隊,3.執行事務。
從Redis接到第一個事務開啓命令MULTI,會對每一個事務維護一個命令的緩存隊列,直到Exec執行命令結束:
在這裏插入圖片描述
所以兩個事務,最終的執行順序,看哪個事務的exec命令先行到達。
注意點: Redis的事務,與關係型數據庫的事務有些不一樣。數據庫的事務是原子操作,多個命令如果有任何一個失敗,都會進行回滾。Redis的事務,當執行到某一行之後,如果發生的錯誤,已經執行的命令不會進行回滾,同時,後續的命令依舊會往下執行,所以Redis的事務並不是原子性操作,可以理解爲將一堆命令進行打包執行。

過期淘汰

主要有兩種過期淘汰機制:被動式,主動式

  • 被動式(懶漢式):當客戶端發起一個key的請求時,Redis會檢測當前這個key是否已經過期。這種有一個很明顯的弊端,如果一個key幾十年沒有客戶端進行訪問,那這個key將會一直存在幾十年而不被清理。
  • 主動式:Redis每秒10次執行:
    a.隨機取20個key進行過期檢測
    b.刪除已經過期的key
    c.如果20個key中過期的大於25%,重複執行上述過程
  • AOF文件中處理過期
    爲了獲取正確的行爲而不犧牲一致性,當一個key過期時,DEL命令會被寫入AOF發給salves

持久化

Redis有兩種持久化方式:

  • RDB(全量數據)
  • AOF(增量數據)
RDB:

很好理解,RDB方式,即爲將當前Redis緩存中的所有數據,全部寫入磁盤。問題來了,該怎麼寫入呢?
引入一個問題:假設當前時間節點是早上10:00,Redis中有記錄a=3,b=4,現在需求是需要將這條進行持久化。

  • 方案一:Redis主線程堵塞,拒絕外部一切響應,等將數據寫入磁盤後,再接收外部請求。
  • 方案二:Redis主線不阻塞,繼續響應外部的讀/寫請求,新開一個子線程負責將數據寫入磁盤

從響應響應速度與吞吐量上考慮,肯定是優先第二種方法,但是如果選擇方案二,會帶來一個問題,如果子線程寫入磁盤需要30min,而主線程在這30min內,會key=a,和key=b的數據,分別進行了5次,8次更新,那麼在這30min內,當子線程真正開始持久化的時候,此時拿到的value,可以是這5次/8次更新中的任意一次的值。即:可能key=a的value保存的是第二次改動的結果,時間10:17,key=b保存的是第7次改動的結果,時間10:26。此時的RDB文件並非10:00時的數據。

那麼Redis是怎麼實現RDB的呢?
首先需要引入幾個概念:

  • 虛擬內存地址
  • 物理內存地址
  • fork
  • copy on write

我們都知道每個線程都有各自的線程空間,以Linux/Uinx系統爲例,export指令,可以讓父進程的數據對子進程可見,也即子線程持有父進程對象的引用在各個的線程空間,每個線程有自己的虛擬內存地址,通過os的地址映射到物理內存地址:
在這裏插入圖片描述
當Redis通過fork方式出來的子線程,便擁有了在某一個時間段,主進程持有數據的完整備份。你肯定要問了,主線程仍在響應客戶端讀寫請求,如果某一份數據,在還沒有寫入磁盤之前,主線程就將數據進行了修改,那子線程通過內存映射後獲取到的值,是否會是修改之後的數據呢?這個時候copy on write便排上用場了。顧名思義,寫的時候,進行復制,即當需要修改數據時,將數據進行復制。以上圖爲例,當主線程在某一時刻,需要將物理內存爲8的數據改成10,最終的地址映射關係將如下:
在這裏插入圖片描述
對應於Redis命令有兩種:

  • save,阻塞,對應於方案一
  • bgsave,非阻塞,對應於方法二

同時,Redis.conf 配置項中的save,實際對應於bgsave命令,接收兩個參數:時間間隔、變更行數
在這裏插入圖片描述

AOF

會將Redis的寫操作日誌記錄到文件中,append only,日誌不停進行追加。

下面設想一下這樣一個問題:
現在有一個小明,非常無聊,在接下來的一年內,不停的往Redis中執行這樣的操作,新增key1,刪除key1,再新增key1,再刪除key1,如此反覆。ok,現在有一個問題,一年之後,這個日誌文件將會有多大?後續將這個文件中每一條指令取出進行數據恢復,最終恢復完成,將需要花費多長時間?

從上面這個例子可以看出這種日誌持久化,非常明顯的優缺點:
優點:能最大限度的保留住每一次數據的變化,數據的完整性好
缺點:文件在不停的append,會導致文件不停膨脹,後續恢復效率很低。

Redis如何解決這個問題呢?
很明顯,需要解決兩個問題:1.日誌文件過大,2.指令重複無意義,如上面的那個例子,一年下來,實際數據,要麼新增一個key,要麼什麼數據都沒有。

Redis AOF重寫:

假設現有如下操作:
1.set key1 xx
2.set key2 xx
3.set key3 xx

10.set key10 xx

執行10次操作,往Redis中添加10條記錄,那麼AOF文件中級存在10條指令,此時Redis中存在10條數據。
當執行AOF重寫後,會直接讀取前Redis中的數據,即最終指令爲:set key1 xx key2 xx key3xx … key10 xx,將10條指令最終合併爲一條。

執行方式:
  • 手動執行
bgrewriteaof
  • 自動執行
auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage

問題:
如果當前指令重寫,以fork子線程方式執行,那麼,如果在子線程執行重寫期間,父線程將某一條數據進行修改了,那麼子線程重寫完畢之後,這條數據的改修指令將不會被記錄進新的AOF文件。
爲解決數據狀態不一致的問題,在父線程fork出子線程之後,會維護一個指令緩衝區,在此期間,父線程繼續響應外部請求,同時會將這段時間內的日誌寫入緩衝區,當子線程重寫完成之後,父線程再將緩衝區內的指令,append進新的AOF文件。

AOF記錄頻次:

  • no :使用os 的讀寫緩衝區,當緩衝區寫滿數據後,append 進AOF文件
  • second(默認):每秒寫入
  • always:每次改動都會寫入AOF
RDB,AOF混寫

Redis 4.0版本開始,支持RDB+AOF混寫模式,即將部分老數據,以RDB形式保存下來,後續指令再以指令形式寫入AOF。

AOF重寫命令執行時,會將當前數據保存爲RDB,期間寫指令進緩衝區,最終緩衝區寫入AOF。注意最終混寫生成的是一個AOF文件,文件裏面,前半部分是rdb的二進制數據,以“redis”字符起頭,文件後半部分是後續緩衝區裏的指令。重寫完成後,後續寫指令繼續append進該AOF文件。

優點:RDB恢復數據會很快,在很快恢復大量數據基礎上,只需要對少了增量數據進行指令修改數據即可,極大提升數據恢復效率。

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