Redis入門總結(一):redis配置文件,五種數據結構,線程模型和持久化方式

(尊重勞動成果,轉載請註明出處:https://yangwenqiang.blog.csdn.net/article/details/90321396冷血之心的博客)

關注微信公衆號(文強的技術小屋),學習更多技術知識,一起遨遊知識海洋~

快速導航:

Redis入門總結(一):redis配置文件,五種數據結構,線程模型和持久化方式

Redis入門總結(二):主從複製,事務和發佈訂閱

Redis入門總結(三):redis實現分佈式鎖的正確姿勢

這篇文章主要總結了:什麼是redis,redis的特點,配置文件,五種數據結構以及其底層實現原理,線程模型,緩存穿透,緩存雪崩以及緩存與數據庫的讀寫不一致的問題;在此基礎上,介紹了RDB和AOF兩種數據持久化模式。

  1. Redis:REmote DIctionary Server(遠程字典服務)

    1. 定義:

      1. 由意大利人Salvatore Sanfilippo(網名:antirez)開發的一款內存高速緩存數據庫。是完全開源免費的,用C語言編寫的,遵守BSD協議,高性能的(key/value)分佈式內存數據庫,基於內存運行支持持久化NoSQL數據庫。

    2. 優點(特點):

      1. 速度快,因爲數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1)

      2. 支持豐富數據類型,支持String,List,Set,Sorted Set,Hash

      3. 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要麼全部執行,要麼全部不執行

      4. 豐富的特性:可用於緩存,消息,按key設置過期時間,過期後將會自動刪除

    3. Redis配置文件(redis.conf):

      1. 在Redis的解壓目錄下有個很重要的配置文件 redis.conf,我們來依次闡述各部分的重要配置。

      2. include:

        1. 可以將多個配置文件引入

      3. modules:

        1. 通過loadmodule配置可以將自定義模塊引入

      4. network:

        1. bind:綁定redis服務器網卡IP,默認爲127.0.0.1,即本地迴環地址。這樣的話,訪問redis服務只能通過本機的客戶端連接,而無法通過遠程連接。如果bind選項爲空的話,那會接受所有來自於可用網絡接口的連接。

        2. port:指定redis運行的端口,默認是6379。由於Redis是單線程模型,因此單機開多個Redis進程的時候會修改端口。

        3. timeout:設置客戶端連接時的超時時間,單位爲秒。當客戶端在這段時間內沒有發出任何指令,那麼關閉該連接。默認值爲0,表示不關閉。

        4. tcp-keepalive :單位是秒,表示將週期性的使用SO_KEEPALIVE檢測客戶端是否還處於健康狀態,避免服務器一直阻塞,官方給出的建議值是300s,如果設置爲0,則不會週期性的檢測。

      5. general:

        1. daemonize:設置爲yes表示指定Redis以守護進程的方式啓動(後臺啓動)。默認值爲 no

        2. pidfile:配置PID文件路徑,當redis作爲守護進程運行的時候,它會把 pid 默認寫到 /var/redis/run/redis_6379.pid 文件裏面

        3. loglevel :定義日誌級別。默認值爲notice,有如下4種取值:

          1. debug(記錄大量日誌信息,適用於開發、測試階段)

          2. verbose(較多日誌信息)

          3. notice(適量日誌信息,使用於生產環境)

          4. warning(僅有部分重要、關鍵信息纔會被記錄)

        4. logfile :配置log文件地址,默認打印在命令行終端的窗口上

        5. databases:設置數據庫的數目。默認的數據庫是DB 0 ,可以在每個連接上使用select  <dbid> 命令選擇一個不同的數據庫,dbid是一個介於0到databases - 1 之間的數值。默認值是 16,也就是說默認Redis有16個數據庫。

      6. snapshotting:(持久化相關配置)

        1. save:這裏是用來配置觸發 Redis的持久化條件,也就是什麼時候將內存中的數據保存到硬盤。默認如下配置:

          1. save 900 1:表示900 秒內如果至少有 1 個 key 的值變化,則保存

          2. save 300 10:表示300 秒內如果至少有 10 個 key 的值變化,則保存

          3. save 60 10000:表示60 秒內如果至少有 10000 個 key 的值變化,則保存

          4. 如果你只是用Redis的緩存功能,不需要持久化,那麼你可以註釋掉所有的 save 行來停用保存功能。

          5. 可以直接一個空字符串來實現停用:save ""

        2. stop-writes-on-bgsave-error :默認值爲yes。當啓用了RDB且最後一次後臺保存數據失敗,Redis是否停止接收數據。這會讓用戶意識到數據沒有正確持久化到磁盤上,否則沒有人會注意到災難(disaster)發生了。如果Redis重啓了,那麼又可以重新開始接收數據了

        3. rdbcompression ;默認值是yes。對於存儲到磁盤中的快照,可以設置是否進行壓縮存儲。如果是的話,redis會採用LZF算法進行壓縮。如果不想消耗CPU來進行壓縮的話,可以設置爲關閉此功能,但是存儲在磁盤上的快照會比較大。

        4. rdbchecksum :默認值是yes。在存儲快照後,我們還可以讓redis使用CRC64算法來進行數據校驗,但是這樣做會增加大約10%的性能消耗,如果希望獲取到最大的性能提升,可以關閉此功能。

        5. dbfilename :設置快照的文件名,默認是 dump.rdb

        6. dir:設置快照文件的存放路徑,這個配置項一定是個目錄,而不能是文件名。使用上面的 dbfilename 作爲保存的文件名。

      7. replication:

        1. slave-serve-stale-data:默認值爲yes。當一個 slave 與 master 失去聯繫,或者複製正在進行的時候,slave 可能會有兩種表現:

          1. 如果爲 yes ,slave 仍然會應答客戶端請求,但返回的數據可能是過時,或者數據可能是空的在第一次同步的時候

          2. 如果爲 no ,在你執行除了 info he salveof 之外的其他命令時,slave 都將返回一個 "SYNC with master in progress" 的錯誤

        2. slave-read-only:配置Redis的Slave實例是否接受寫操作,即Slave是否爲只讀Redis。默認值爲yes。

        3. repl-diskless-sync:主從數據複製是否使用無硬盤複製功能。默認值爲no。

        4. repl-diskless-sync-delay:當啓用無硬盤備份,服務器等待一段時間後纔會通過套接字向從站傳送RDB文件,這個等待時間是可配置的。這一點很重要,因爲一旦傳送開始,就不可能再爲一個新到達的從站服務。從站則要排隊等待下一次RDB傳送。因此服務器等待一段時間以期更多的從站到達。延遲時間以秒爲單位,默認爲5秒。要關掉這一功能,只需將它設置爲0秒,傳送會立即啓動。默認值爲5。

        5. repl-disable-tcp-nodelay:同步之後是否禁用從站上的TCP_NODELAY 如果你選擇yes,redis會使用較少量的TCP包和帶寬向從站發送數據。但這會導致在從站增加一點數據的延時。  Linux內核默認配置情況下最多40毫秒的延時。如果選擇no,從站的數據延時不會那麼多,但備份需要的帶寬相對較多。默認情況下我們將潛在因素優化,但在高負載情況下或者在主從站都跳的情況下,把它切換爲yes是個好主意。默認值爲no。

      8. security:

        1. rename-command:命令重命名,對於一些危險命令例如:

          1. flushdb(清空數據庫)

          2. flushall(清空所有記錄)

          3. config(客戶端連接後可配置服務器)

          4. keys(客戶端連接後可查看所有存在的鍵)                   

        2. 作爲服務端redis-server,常常需要禁用以上命令來使得服務器更加安全,禁用的具體做法是是:

          1. rename-command FLUSHALL ""

            1. 也可以保留命令但是不能輕易使用,重命名這個命令即可:

          2. rename-command FLUSHALL abcdefg

            1. 重啓服務器後則需要使用新命令來執行操作,否則服務器會報錯unknown command。

      9. clients:

        1. maxclients設置客戶端最大併發連接數,默認無限制,Redis可以同時打開的客戶端連接數爲Redis進程可以打開的最大文件。  描述符數-32(redis server自身會使用一些),如果設置 maxclients爲0 ,表示不作限制。

        2. 當客戶端連接數到達限制時,Redis會關閉新的連接並向客戶端返回max number of clients reached錯誤信息。

      10. memory management(內存管理):

        1. maxmemory:設置客戶端最大併發連接數,默認無限制,Redis可以同時打開的客戶端連接數爲Redis進程可以打開的最大文件。描述符數-32(redis server自身會使用一些),如果設置 maxclients爲0 。表示不作限制。當客戶端連接數到達限制時,Redis會關閉新的連接並向客戶端返回max number of clients reached錯誤信息。

        2. maxmemory-policy :當內存使用達到最大值時,redis使用的清除策略。

          1. volatile-lru   利用LRU算法移除設置過過期時間的key (LRU:最近使用 Least Recently Used )

          2. allkeys-lru   利用LRU算法移除任何key

          3. volatile-random 移除設置過過期時間的隨機key

          4. allkeys-random  移除隨機key

          5. volatile-ttl   移除即將過期的key(minor TTL)

          6. noeviction  noeviction   不移除任何key,只是返回一個寫錯誤 ,默認選項

        3. maxmemory-samples :LRU 和 minimal TTL 算法都不是精準的算法,但是相對精確的算法(爲了節省內存)。隨意你可以選擇樣本大小進行檢,redis默認選擇3個樣本進行檢測,你可以通過maxmemory-samples進行設置樣本數。

      11. APPEND ONLY MODE:

        1. appendonly:默認redis使用的是rdb方式持久化,這種方式在許多應用中已經足夠用了。但是redis如果中途宕機,會導致可能有幾分鐘的數據丟失,根據save來策略進行持久化,Append Only File是另一種持久化方式,  可以提供更好的持久化特性。Redis會把每次寫入的數據在接收後都寫入appendonly.aof文件,每次啓動時Redis都會先把這個文件的數據讀入內存裏,先忽略RDB文件。默認值爲no。

        2. appendfilename :aof文件名,默認是"appendonly.aof"

        3. appendfsync:aof持久化策略的配置;no表示不執行fsync,由操作系統保證數據同步到磁盤,速度最快;always表示每次寫入都執行fsync,以保證數據同步到磁盤;everysec表示每秒執行一次fsync,可能會導致丟失這1s數據

        4. no-appendfsync-on-rewrite:在aof重寫或者寫入rdb文件的時候,會執行大量IO,此時對於everysec和always的aof模式來說,執行fsync會造成阻塞過長時間,no-appendfsync-on-rewrite字段設置爲默認設置爲no。如果對延遲要求很高的應用,這個字段可以設置爲yes,否則還是設置爲no,這樣對持久化特性來說這是更安全的選擇。   設置爲yes表示rewrite期間對新寫操作不fsync,暫時存在內存中,等rewrite完成後再寫入,默認爲no,建議yes。Linux的默認fsync策略是30秒。可能丟失30秒數據。默認值爲no。

        5. auto-aof-rewrite-percentage:默認值爲100。aof自動重寫配置,當目前aof文件大小超過上一次重寫的aof文件大小的百分之多少進行重寫,即當aof文件增長到一定大小的時候,Redis能夠調用bgrewriteaof對日誌文件進行重寫。當前AOF文件大小是上次日誌重寫得到AOF文件大小的二倍(設置爲100)時,自動啓動新的日誌重寫過程。

        6. auto-aof-rewrite-min-size:64mb。設置允許重寫的最小aof文件大小,避免了達到約定百分比但尺寸仍然很小的情況還要重寫。

        7. aof-load-truncated:aof文件可能在尾部是不完整的,當redis啓動的時候,aof文件的數據被載入內存。重啓可能發生在redis所在的主機操作系統宕機後,尤其在ext4文件系統沒有加上data=ordered選項,出現這種現象  redis宕機或者異常終止不會造成尾部不完整現象,可以選擇讓redis退出,或者導入儘可能多的數據。如果選擇的是yes,當截斷的aof文件被導入的時候,會自動發佈一個log給客戶端然後load。如果是no,用戶必須手動redis-check-aof修復AOF文件纔可以。默認值爲 yes。

      12. LUA SCRIPTING:

        1. lua-time-limit:一個lua腳本執行的最大時間,單位爲ms。默認值爲5000.

      13. REDIS CLUSTER:

        1. cluster-enabled:集羣開關,默認是不開啓集羣模式。

        2. cluster-config-file:集羣配置文件的名稱,每個節點都有一個集羣相關的配置文件,持久化保存集羣的信息。 這個文件並不需要手動配置,這個配置文件有Redis生成並更新,每個Redis集羣節點需要一個單獨的配置文件。請確保與實例運行的系統中配置文件名稱不衝突。默認配置爲nodes-6379.conf。

        3. cluster-node-timeout :可以配置值爲15000。節點互連超時的閥值,集羣節點超時毫秒數

        4. cluster-slave-validity-factor :可以配置值爲10。在進行故障轉移的時候,全部slave都會請求申請爲master,但是有些slave可能與master斷開連接一段時間了,導致數據過於陳舊,這樣的slave不應該被提升爲master。該參數就是用來判斷slave節點與master斷線的時間是否過長。判斷方法是:比較slave斷開連接的時間和(node-timeout * slave-validity-factor) + repl-ping-slave-period     如果節點超時時間爲三十秒, 並且slave-validity-factor爲10,假設默認的repl-ping-slave-period是10秒,即如果超過310秒slave將不會嘗試進行故障轉移

        5. cluster-migration-barrier :可以配置值爲1。master的slave數量大於該值,slave才能遷移到其他孤立master上,如這個參數若被設爲2,那麼只有當一個主節點擁有2 個可工作的從節點時,它的一個從節點會嘗試遷移。

        6. cluster-require-full-coverage:默認情況下,集羣全部的slot有節點負責,集羣狀態才爲ok,才能提供服務。  設置爲no,可以在slot沒有全部分配的時候提供服務。不建議打開該配置,這樣會造成分區的時候,小分區的master一直在接受寫請求,而造成很長時間數據不一致。

    4. redis線程安全問題:

      1. 單線程指的是網絡請求模塊使用了一個線程(所以不需考慮併發安全性)

      2. redis實際上是採用了線程封閉的觀念,把任務封閉在一個線程,自然避免了線程安全問題,不過對於需要依賴多個redis操作的複合操作來說,依然需要鎖,而且有可能是分佈式鎖。

    5. redis相比memcached有哪些優勢?

      1. memcached所有的值均是簡單的字符串,redis作爲其替代者,支持更爲豐富的數據類型

      2. redis的速度比memcached快很多

      3. redis可以持久化其數據

      4. Redis支持數據的備份,即master-slave模式的數據備份。

      5. 使用底層模型不同,它們之間底層實現方式 以及與客戶端之間通信的應用協議不一樣。Redis直接自己構建了VM 機制 ,因爲一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。

      6. value大小:redis最大可以達到1GB,而memcache只有1MB

    6. Redis支持的數據類型:

      1. 我們所說的其支持的數據類型都是在說其存儲的value的類型,redis的Key的類型都是字符串。

      2. string:

        1.  redis 中字符串 value 最多可以是 512M。

        2. 場景:做一些計數功能的緩存

      3. list:

        1. list 列表,它是簡單的字符串列表,按照插入順序排序,你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊),它的底層實際上是個鏈表。

        2. 場景:可以當作一個簡單消息隊列功能,做基於redis的分頁功能

      4. set:

        1. 是一個String類型的無序集合。

        2. 場景:全局去重

      5. sorted set:

        1. 是一個String類型的有序集合,通過給每一個元素一個固定的分數score來保持順序。

        2. 場景:做排行榜應用,取TopN操作,範圍查找

      6. hash:

        1. hash 是一個鍵值對集合,是一個 string 類型的 key和 value 的映射表,key 還是key,但是value是一個鍵值對(key-value)

        2. 場景:存放一些結構化的信息

    7. 系統常用命令:

      1. OBJECT ENCODING    key :顯示五種數據類型的底層數據實現結構

      2. BGSAVE:後臺異步保存當前數據庫的數據到磁盤(持久化)

      3. BGREWIRTEAOF:手動觸發AOF重寫操作,用於減小AOF文件體積

    8. redis數據類型的底層數據結構:

      1. 通過OBJECT ENCODING key 可以查看當前key的底層數據結構。

      2. 簡單動態字符串(simple dynamic string,SDS):

        1. SDS 數據類型的定義

          1. len 保存了SDS保存字符串的長度

          2. buf[ ] 數組用來保存字符串的每個元素

          3. free 記錄了 buf 數組中未使用的字節數量

        2. 使用SDS而不是C語言原生的字符串的優點:

          1. 常數複雜度獲取字符串長度

          2. 杜絕緩衝區溢出:

            1. 當進行字符串修改時,可以根據len屬性檢查空間是否滿足,不滿足會擴容,防止緩衝區溢出

          3. 減少字符串修改時的內存重新分配次數:

            1. C語言由於不記錄字符串的長度,所以如果要修改字符串,必須要重新分配內存(先釋放再申請),因爲如果沒有重新分配,字符串長度增大時會造成內存緩衝區溢出,字符串長度減小時會造成內存泄露。

            2. SDS在字符串修改時存在空間預分配和惰性空間釋放

          4. 二進制安全:

            1. 二進制中可能包含空字符串。C語言原生的字符串以空字符結尾,所以不可以正確的存取

            2. SDS中以len屬性來判斷字符串是否結束

          5. 兼容部分C語言中的函數庫

          6. SDS 還可以作爲緩衝區(buffer):包括 AOF 模塊中的AOF緩衝區以及客戶端狀態中的輸入緩衝區

      3. 鏈表:

        1. Redis中自己實現了鏈表,特性如下:

          1. 雙端:鏈表具有前置節點和後置節點的引用,獲取這兩個節點時間複雜度都爲O(1)。

          2. 無環:表頭節點的 prev 指針和表尾節點的 next 指針都指向 NULL,對鏈表的訪問都是以 NULL 結束。  

          3. 帶鏈表長度計數器:通過 len 屬性獲取鏈表長度的時間複雜度爲 O(1)。

          4. 多態:鏈表節點使用 void* 指針來保存節點值,可以保存各種不同類型的值。

      4. 字典:

        1. 字典又稱爲符號表或者關聯數組、或映射(map),是一種用於保存鍵值對的抽象數據結構。字典中的每一個鍵 key 都是唯一的,通過 key 可以對值來進行查找或修改。

        2. C 語言中沒有內置這種數據結構的實現,所以字典依然是 Redis自己構建的。Redis 的字典使用哈希表作爲底層實現。

        3. 擴容和收縮:當哈希表保存的鍵值對太多或者太少時,就要通過 rehash(重新散列)來對哈希表進行相應的擴展或者收縮。

          1. 如果執行擴展操作,會基於原哈希表創建一個大小等於 ht[0].used*2n 的哈希表

          2. 相反如果執行的是收縮操作,每次收縮是根據已使用空間縮小一倍創建一個新的哈希表。

          3. 重新利用上面的哈希算法,計算索引值,然後將鍵值對放到新的哈希表位置上。

          4. 所有鍵值對都遷徙完畢後,釋放原哈希表的內存空間。

        4. 觸發擴容的條件:

          1. 服務器目前沒有執行 BGSAVE 命令或者 BGREWRITEAOF 命令,並且負載因子大於等於1。

          2. 服務器目前正在執行 BGSAVE 命令或者 BGREWRITEAOF 命令,並且負載因子大於等於5。

          3. 負載因子 = 哈希表已保存節點數量 / 哈希表大小

        5. 漸近式 rehash

          1. 擴容和收縮操作不是一次性、集中式完成的,而是分多次、漸進式完成的。如果保存在Redis中的鍵值對只有幾個幾十個,那麼 rehash 操作

          2. 可以瞬間完成。

          3. 但是如果鍵值對有幾百萬,幾千萬甚至幾億,那麼要一次性的進行 rehash,勢必會造成Redis一段時間內不能進行別的操作。

          4. 所以Redis採用漸進式 rehash,這樣在進行漸進式rehash期間,字典的刪除查找更新等操作可能會在兩個哈希表上進行,第一個哈希表沒有找到,就會去第二個哈希表上進行查找。但是進行 增加操作,一定是在新的哈希表上進行的。

      5. 跳錶:

        1. 對於一個有序的鏈表,我們依然只能通過遍歷來查找一個元素,效率低下。

        2. 跳錶就是將有序的鏈表分層,相當於是創建了多個分層索引,當鏈表元素個數增大的時候,跳錶的查詢效率顯著提升。

        3. 跳錶的特點:

          1. 由很多層結構組成;

          2. 每一層都是一個有序的鏈表,排列順序爲由高層到底層,都至少包含兩個鏈表節點,分別是前面的head節點和後面的nil節點;

          3. 最底層的鏈表包含了所有的元素;

          4. 如果一個元素出現在某一層的鏈表中,那麼在該層之下的鏈表也全都會出現(上一層的元素是當前層的元素的子集);

          5. 鏈表中的每個節點都包含兩個指針,一個指向同一層的下一個鏈表節點,另一個指向下一層的同一個鏈表節點;

        4. Redis中爲什麼使用跳錶而不使用查詢複雜度同樣爲log(N)的平衡樹?

          1. 跳錶構建起來比平衡樹容易

          2. 跳錶支持範圍搜索

      6. 整數集合(intset):

        1. 用於保存整數值的集合抽象數據類型,它可以保存類型爲int16_t、int32_t 或者int64_t 的整數值,並且保證集合中不會出現重複元素。

        2. 整數集合的每個元素都是 contents 數組的一個數據項,它們按照從小到大的順序排列,並且不包含任何重複項

        3. length 屬性記錄了 contents 數組的大小。

      7. 壓縮列表:

        1. 壓縮列表(ziplist)是Redis爲了節省內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序型數據結構

        2. 一個壓縮列表可以包含任意多個節點(entry),每個節點可以保存一個字節數組或者一個整數值。

        3. 壓縮列表的原理:

          1. 壓縮列表並不是對數據利用某種算法進行壓縮,而是將數據按照一定規則編碼在一塊連續的內存區域,目的是節省內存

    9. Redis數據類型的實現原理:

      1. Redis底層所使用的數據結構我們已經學習了,但是redis並不是直接使用這些數據結構,而是構建了一個對象系統。

      2. 每次在Redis數據庫中創建一個鍵值對時,至少會創建兩個對象,一個是鍵對象,一個是值對象。

      3. Redis中的每個對象都是由 redisObject 結構來表示:

        1. type屬性:記錄了對象的類型,五種數據類型string,list,set.sort set ,hash。  命令:type key

        2. encoding編碼:記錄了對象的底層編碼(每個對象都至少有兩種編碼)

          1. OBJECT ENCODING    key :顯示五種數據類型的底層數據實現結構(編碼)

        3. *ptr:指向底層數據結構的指針

      4. 五大數據類型對象的編碼:

        1. 字符串對象:

          1. 字符串是Redis最基本的數據類型,不僅所有key都是字符串類型,其它幾種數據類型構成的元素也是字符串。注意字符串的長度不能超過512M。

          2. 編碼:字符串對象的編碼可以是int,raw或者embstr。

        1. int 編碼:保存的是可以用 long 類型表示的整數值

        2. raw 編碼(長):保存長度大於44字節的字符串(redis3.2版本之前是39字節,之後是44字節)

        3. embstr 編碼(短)(只讀):保存長度小於44字節的字符串(redis3.2版本之前是39字節,之後是44字節)

        4. raw和embstr編碼的區別:

          1. embstr與raw都使用redisObject和sds保存數據,區別在於:

          2. embstr的使用只分配一次內存空間(因此redisObject和sds是連續的)

          3. raw需要分配兩次內存空間(分別爲redisObject和sds分配空間)。

          4. 因此與raw相比,embstr的好處在於創建時少分配一次空間,刪除時少釋放一次空間,以及對象的所有數據連在一起,尋找方便。

          5. 而embstr的壞處也很明顯,如果字符串的長度增加需要重新分配內存時,整個redisObject和sds都需要重新分配空間,因此redis中的embstr實現爲只讀。

        5. 編碼可以轉換嗎?

          1. 當 int 編碼保存的值不再是整數,或大小超過了long的範圍時,自動轉化爲raw。

          2. 對於 embstr 編碼,由於 Redis 沒有對其編寫任何的修改程序(embstr 是隻讀的),在對embstr對象進行修改時,都會先轉化爲raw再進行修改,因此,只要是修改embstr對象,修改後的對象一定是raw的,無論是否達到了44個字節。

        6. 列表對象(list):

          1. 列表對象的編碼可以是 ziplist(壓縮列表) 和 linkedlist(雙端鏈表)。

          2. 當同時滿足下面兩個條件時,使用ziplist(壓縮列表)編碼:

            1. 列表保存元素個數小於512個

            2. 每個元素長度小於64字節

          3. 不能滿足這兩個條件的時候使用 linkedlist 編碼。

          4. 也就是說元素又多又長就用linkedlist,否則就用ziplist

        7. 哈希對象(hash):

          1. 哈希對象的編碼可以是 ziplist 或者 hashtable。

          2. 當同時滿足下面兩個條件時,使用ziplist(壓縮列表)編碼:

            1. 列表保存元素個數小於512個

            2. 每個元素長度小於64字節

          3. 不能滿足這兩個條件的時候使用 hashtable編碼。

        8. 集合對象(set):

          1. 集合對象 set 是 string 類型(整數也會轉換成string類型進行存儲)的無序集合。

          2. 集合對象的編碼可以是 intset 或者 hashtable。intset 編碼的集合對象使用整數集合作爲底層實現,集合對象包含的所有元素都被保存在整數集合中。

          3. 當集合同時滿足以下兩個條件時,使用 intset 編碼:

            1. 集合對象中所有元素都是整數

            2. 集合對象所有元素數量不超過512

          4. 不能滿足這兩個條件的就使用 hashtable 編碼。

        9. 有序集合對象:

          1. 有序集合對象是有序的。與列表使用索引下標作爲排序依據不同,有序集合爲每個元素設置一個分數(score)作爲排序依據。

          2. 有序集合的編碼可以是 ziplist 或者 skiplist

            1. ziplist 編碼的有序集合對象使用壓縮列表作爲底層實現,每個集合元素使用兩個緊挨在一起的壓縮列表節點來保存,第一個節點保存元素的成員,第二個節點保存元素的分值。並且壓縮列表內的集合元素按分值從小到大的順序進行排列,小的放置在靠近表頭的位置,大的放置在靠近表尾的位置。

            2. skiplist 編碼的有序集合對象使用 zet 結構作爲底層實現,一個 zset 結構同時包含一個字典和一個跳躍表:

              1. 字典的鍵保存元素的值,字典的值則保存元素的分值;跳躍表節點的 object 屬性保存元素的成員,跳躍表節點的 score 屬性保存元素的分值。

              2. 這兩種數據結構會通過指針來共享相同元素的成員和分值,所以不會產生重複成員和分值,造成內存的浪費。

              3. 說明:其實有序集合單獨使用字典或跳躍表其中一種數據結構都可以實現,但是這裏使用兩種數據結構組合起來,原因是假如我們單獨使用 字典,雖然能以 O(1) 的時間複雜度查找成員的分值,但是因爲字典是以無序的方式來保存集合元素,所以每次進行範圍操作的時候都要進行排序;假如我們單獨使用跳躍表來實現,雖然能執行範圍操作,但是查找操作有 O(1)的複雜度變爲了O(logN)。因此Redis使用了兩種數據結構來共同實現有序集合。

          3. 當有序集合對象同時滿足以下兩個條件時,對象使用 ziplist 編碼:

            1. 保存的元素數量小於128;

            2. 保存的所有元素長度都小於64字節。

          4. 不能滿足上面兩個條件的使用 skiplist 編碼。

      5. refcount(引用計數):

        1. Redis自己構建了一個內存回收機制,通過在 redisObject 結構中的 refcount 屬性實現。這個屬性會隨着對象的使用狀態而不斷變化:

          1. 創建一個新對象,屬性 refcount 初始化爲1

          2. 對象被一個新程序使用,屬性 refcount 加 1

          3. 對象不再被一個程序使用,屬性 refcount 減 1

          4. 當對象的引用計數值變爲 0 時,對象所佔用的內存就會被釋放。

        2. 如果出現循環引用導致內存溢出的時候,我們在配置文件中設置了redis的內存清除策略。

        3. 共享內存:

          1. 當key不相同但是value相同時,redis將數據庫鍵的值指針指向一個現有值的對象,將被共享的值對象引用refcount 加 1

          2. Redis的共享對象目前只支持整數值的字符串對象。之所以如此,實際上是對內存和CPU(時間)的平衡:共享對象雖然會降低內存消耗,但是判斷兩個對象是否相等卻需要消耗額外的時間。對於整數值,判斷操作複雜度爲O(1);對於普通字符串,判斷複雜度爲O(n);而對於哈希、列表、集合和有序集合,判斷的複雜度爲O(n^2)

          3. 雖然共享對象只能是整數值的字符串對象,但是5種類型都可能使用共享對象(如哈希、列表等的元素可以使用)

      6. lru屬性:

        1. 該屬性記錄了對象最後一次被命令程序訪問的時間。

        2. 使用 OBJECT IDLETIME 命令可以打印給定鍵的空轉時長,通過將當前時間減去值對象的 lru 時間計算得到。

        3. lru 屬性除了計算空轉時長以外,還可以配合前面內存回收配置使用。

        4. 如果Redis打開了maxmemory選項,且內存回收算法選擇的是volatile-lru或allkeys—lru,那麼當Redis內存佔用超過maxmemory指定的值時,Redis會優先選擇空轉時間最長的對象進行釋放。

    10. redis單線程爲什麼執行速度如此之快?

      1. 完全內存計算

      2. 單線程,避免了上下文切換

      3. 使用了多路I/O複用,一個線程監控多個IO流,實現及時響應。

    11. redis的線程模型:

      1. redis的線程模型是一種多路I/O複用模型,具體模型圖如下所示:

      2. 簡單來說,就是redis-client在操作的時候,會產生具有不同事件類型的socket。

        1. 在服務端,有一段I/O多路複用程序,將其置入隊列之中。

        2. 然後,文件事件分派器,依次去隊列中取,轉發到不同的事件處理器中。

      3. I/O多路複用機制:

        1. 目前支持I/O多路複用的系統調用有 select,pselect,poll,epoll,I/O多路複用就是通過一種機制

        2. 一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。

        3. select,pselect,poll,epoll本質上都是同步I/O,因爲他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。

        4. 優點:

          1. 與多進程和多線程技術相比,I/O多路複用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。

      4. 常見的多路複用函數:

        1. select函數:

          1. 會修改傳入的參數數組,這個對於一個需要調用很多次的函數,是非常不友好的。

          2. 有最大監聽連接數1024個的限制

          3. 如果任何一個sock(I/O stream)出現了數據,select沒有返回具體是哪個返回了數據,需要採用輪詢的方式去遍歷獲取

          4. 線程不安全(當你在一個線程中已經監聽該socket,另一個線程想要將該socket關閉,則結果會不可預知)

          5. “If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”

        2. poll函數:

          1. 去掉了1024的限制(使用鏈表搞定)

          2. 不再修改傳入的參數數組

          3. 依然是線程不安全的

        3. epoll函數:

          1. epoll 不僅返回socket組裏面數據,還會告訴你具體哪個socket有數據

          2. 線程安全

    12. Redis可能出現的問題:

      1. 緩存雪崩

        1. 緩存同一時間大面積的失效,這個時候又來了一波請求,結果請求都到數據庫上,從而導致數據庫連接異常。

        2. 解決方案:

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

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

          3. 雙緩存。我們有兩個緩存,緩存A和緩存B。緩存A的失效時間爲20分鐘,緩存B不設失效時間。

      2. 緩存穿透

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

        2. 解決方案:

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

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

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

      3. 緩存的併發競爭

        1. 多個子系統來併發競爭更新一個Key

        2. 解決方案:

          1. 使用一個分佈式鎖,誰佔有誰更新

          2. 使用隊列,將更新操作變成串行

          3. 指定更新的順序,每次更新都有一個version,當每個子系統更新時發現當前version大於自己的version,那麼放棄更新

      4. 緩存和數據庫雙寫一致性

        1. 高併發請求下會導致數據不一致的問題,如果業務需要數據保持強一致性,那麼不可以使用緩存。

        2. 解決方案:

          1. 雙刪延時

            1. 刪除緩存數據

            2. 更新數據庫數據

            3. 隔固定的時間再次刪除緩存

          2. 更新數據庫產生的binlog訂閱(使用canal),將有變化的key記錄下來,並且不斷的去刪除緩存(如果上次刪除緩存失敗)

    13. Redis數據的過期回收策略與內存淘汰機制:

      1. 定期刪除+惰性刪除組合

      2. 定期刪除:

        1. 當給數據設置的緩存時間到期之後會自動刪除,但是比較消耗CPU,redis是單線程執行的,如果高併發情況下會影響系統性能

      3. 定期刪除+惰性刪除組合:(這個纔是使用的策略)

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

        2. 惰性刪除:

          1. 在獲取某個key的時候,redis會檢查一下,這個key如果設置了過期時間那麼是否過期了?如果過期了此時就會刪除。

      4. 內存淘汰:

        1. 如果在數據過期之後,你沒有去獲取該數據,那麼就不會被惰性刪除,該數據一直保存,redis內存會越來越高。

        2. 當內存不足時,通過我們在上邊所介紹的redis.conf配置文件中的配置的maxmemory-policy策略來進行內存淘汰

    14. Redis持久化策略:

      1. redis是一個基於內存,並且支持數據持久化的NoSql數據庫。有兩種持久化策略:

      2. RDB(快照方式 snapshotting)(全量持久化):

        1. 把當前內存中的數據集快照寫入磁盤,也就是 Snapshot 快照(數據庫中所有鍵值對數據)。恢復時是將快照文件直接讀到內存裏

        2. 自動觸發方式:

          1. 我們在配置文件redis.conf中配置的snapshotting 指定了當多長時間內有多少次save命令時自動觸發持久化操作

          2. 將這塊的save配置註釋掉或者配置爲空字符串,表示關閉RDB持久化方式

        3. 手動觸發方式:

          1. 通過bgsave命令,在後臺異步進行生成快照的操作,同時還可以響應客戶都按的請求。

          2. 通過redis進程fork操作創建子進程,生成快照由子進程負責,完成後自動結束,請求只會在fork階段被阻塞

        4. 快照恢復:

          1. 將備份文件 (dump.rdb) 移動到 redis 安裝目錄並啓動服務即可,redis就會自動加載文件數據至內存了。Redis 服務器在載入 RDB 文件期間,會一直處於阻塞狀態,直到載入工作完成爲止。

        5. 優缺點:

          1. 存在數據丟失的問題

          2. 恢復大數據集時比AOF方式快

          3. RDB方式數據沒辦法做到實時持久化/秒級持久化。因爲bgsave每次運行都要執行fork操作創建子進程,屬於重量級操作(內存中的數據被克隆了一份,大致2倍的膨脹性需要考慮),頻繁執行成本過高(影響性能)

      3. AOF(append-only-file)(增量持久化):

        1. 通過記錄redis服務器所執行的寫命令來記錄數據庫狀態

        2. redis.conf 配置文件的 APPEND ONLY MODE 下我們可以設置AOF持久化

        3. AOF 保存文件的位置和 RDB 保存文件的位置一樣,都是通過 redis.conf 配置文件的 dir 配置

        4. 數據恢復:

          1. 重啓 Redis 之後就會進行 AOF 文件的載入

          2. 異常修復命令:redis-check-aof --fix 進行修復

        5. AOF日誌重寫:

          1. AOF文件可能會隨着服務器運行的時間越來越大,可以利用AOF重寫的功能,來控制AOF文件的大小。

          2. AOF重寫功能會首先讀取數據庫中現有的鍵值對狀態,然後根據類型使用一條命令來替代前的鍵值對多條命令

          3. 使用命令 bgrewriteaof 來實現AOF重寫

        6. AOF重寫緩存區:

          1.  Redis 是單線程工作,如果重寫AOF 需要比較長的時間,那麼在重寫 AOF 期間,Redis將長時間無法處理其他的命令,解決辦法是將 AOF 重寫程序放到子程序中進行,這樣有兩個好處:

            1. 子進程進行 AOF 重寫期間,服務器進程(父進程)可以繼續處理其他命令。

            2. 子進程帶有父進程的數據副本,使用子進程而不是線程,可以在避免使用鎖的情況下,保證數據的安全性。

          2. 子進程重寫導致的問題:

            1. 因爲子進程在進行 AOF 重寫期間,服務器進程依然在處理其它命令,這新的命令有可能也對數據庫進行了修改操作,使得當前數據庫狀態和重寫後的 AOF 文件狀態不一致。

          3. 數據狀態不一致解決辦法:

            1. Redis 服務器設置了一個 AOF 重寫緩衝區,這個緩衝區是在創建子進程後開始使用,當Redis服務器執行一個寫命令之後,就會將這個寫命令也發送到 AOF 重寫緩衝區。

            2. 當子進程完成 AOF 重寫之後,就會給父進程發送一個信號,父進程接收此信號後,就會調用函數將 AOF 重寫緩衝區的內容都寫到新的 AOF 文件中。

        7. 優點:

          1. AOF 持久化的方法提供了多種的同步頻率,即使使用默認的同步頻率每秒同步一次,Redis 最多也就丟失 1 秒的數據而已。

          2. AOF 文件使用 Redis 命令追加的形式來構造,因此,即使 Redis 只能向 AOF 文件寫入命令的片斷,使用 redis-check-aof 工具也很容易修正 AOF 文件。

          3. AOF 文件的格式可讀性較強,這也爲使用者提供了更靈活的處理方式。例如,如果我們不小心錯用了 FLUSHALL 命令,在重寫還沒進行時,我們可以手工將最後的 FLUSHALL 命令去掉,然後再使用 AOF 來恢復數據。

        8. 缺點:

          1. 對於具有相同數據的的 Redis,AOF 文件通常會比 RDF 文件體積更大。

          2. 雖然 AOF 提供了多種同步的頻率,默認情況下,每秒同步一次的頻率也具有較高的性能。但在 Redis 的負載較高時,RDB 比 AOF 具好更好的性能保證。

          3. RDB 使用快照的形式來持久化整個 Redis 數據,而 AOF 只是將每次執行的命令追加到 AOF 文件中,因此從理論上說,RDB 比 AOF 方式更健壯。官方文檔也指出,AOF 的確也存在一些 BUG,這些 BUG 在 RDB 沒有存在。

      4. 持久化策略選擇:

        1. AOF更安全,可將數據及時同步到文件中,但需要較多的磁盤IO,AOF文件尺寸較大,文件內容恢復相對較慢, 也更完整。

        2. RDB持久化,安全性較差,它是正常時期數據備份及 master-slave數據同步的最佳手段,文件尺寸較小,恢復數度較快。

 

以上內容摘抄總結與網絡,感謝各位前輩的總結與鋪墊。接下來我會繼續更新Redis相關文章,全部會以這種筆記形式給出,大家對筆記中不太懂的地方,可以發表評論,我們一起研究學習,此處權當給大家列個學習提綱了。

如果對你有幫助,記得點贊哦~歡迎大家關注我的博客,可以進羣366533258一起交流學習哦~

本羣給大家提供一個學習交流的平臺,內設菜鳥Java管理員一枚、精通算法的金牌講師一枚、Android管理員一枚、藍牙BlueTooth管理員一枚、Web前端管理一枚以及C#管理一枚。歡迎大家進來交流技術。

關注微信公衆號(文強的技術小屋),學習更多技術知識,一起遨遊知識海洋~

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