(尊重勞動成果,轉載請註明出處:https://yangwenqiang.blog.csdn.net/article/details/90321396冷血之心的博客)
關注微信公衆號(文強的技術小屋),學習更多技術知識,一起遨遊知識海洋~
快速導航:
Redis入門總結(一):redis配置文件,五種數據結構,線程模型和持久化方式
這篇文章主要總結了:什麼是redis,redis的特點,配置文件,五種數據結構以及其底層實現原理,線程模型,緩存穿透,緩存雪崩以及緩存與數據庫的讀寫不一致的問題;在此基礎上,介紹了RDB和AOF兩種數據持久化模式。
-
Redis:REmote DIctionary Server(遠程字典服務)
-
定義:
-
由意大利人Salvatore Sanfilippo(網名:antirez)開發的一款內存高速緩存數據庫。是完全開源免費的,用C語言編寫的,遵守BSD協議,高性能的(key/value)分佈式內存數據庫,基於內存運行並支持持久化的NoSQL數據庫。
-
-
優點(特點):
-
速度快,因爲數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1)
-
支持豐富數據類型,支持String,List,Set,Sorted Set,Hash
-
支持事務,操作都是原子性,所謂的原子性就是對數據的更改要麼全部執行,要麼全部不執行
-
豐富的特性:可用於緩存,消息,按key設置過期時間,過期後將會自動刪除
-
-
Redis配置文件(redis.conf):
-
在Redis的解壓目錄下有個很重要的配置文件 redis.conf,我們來依次闡述各部分的重要配置。
-
include:
-
可以將多個配置文件引入
-
-
modules:
-
通過loadmodule配置可以將自定義模塊引入
-
-
network:
-
bind:綁定redis服務器網卡IP,默認爲127.0.0.1,即本地迴環地址。這樣的話,訪問redis服務只能通過本機的客戶端連接,而無法通過遠程連接。如果bind選項爲空的話,那會接受所有來自於可用網絡接口的連接。
-
port:指定redis運行的端口,默認是6379。由於Redis是單線程模型,因此單機開多個Redis進程的時候會修改端口。
-
timeout:設置客戶端連接時的超時時間,單位爲秒。當客戶端在這段時間內沒有發出任何指令,那麼關閉該連接。默認值爲0,表示不關閉。
-
tcp-keepalive :單位是秒,表示將週期性的使用SO_KEEPALIVE檢測客戶端是否還處於健康狀態,避免服務器一直阻塞,官方給出的建議值是300s,如果設置爲0,則不會週期性的檢測。
-
-
general:
-
daemonize:設置爲yes表示指定Redis以守護進程的方式啓動(後臺啓動)。默認值爲 no
-
pidfile:配置PID文件路徑,當redis作爲守護進程運行的時候,它會把 pid 默認寫到 /var/redis/run/redis_6379.pid 文件裏面
-
loglevel :定義日誌級別。默認值爲notice,有如下4種取值:
-
debug(記錄大量日誌信息,適用於開發、測試階段)
-
verbose(較多日誌信息)
-
notice(適量日誌信息,使用於生產環境)
-
warning(僅有部分重要、關鍵信息纔會被記錄)
-
-
logfile :配置log文件地址,默認打印在命令行終端的窗口上
-
databases:設置數據庫的數目。默認的數據庫是DB 0 ,可以在每個連接上使用select <dbid> 命令選擇一個不同的數據庫,dbid是一個介於0到databases - 1 之間的數值。默認值是 16,也就是說默認Redis有16個數據庫。
-
-
snapshotting:(持久化相關配置)
-
save:這裏是用來配置觸發 Redis的持久化條件,也就是什麼時候將內存中的數據保存到硬盤。默認如下配置:
-
save 900 1:表示900 秒內如果至少有 1 個 key 的值變化,則保存
-
save 300 10:表示300 秒內如果至少有 10 個 key 的值變化,則保存
-
save 60 10000:表示60 秒內如果至少有 10000 個 key 的值變化,則保存
-
如果你只是用Redis的緩存功能,不需要持久化,那麼你可以註釋掉所有的 save 行來停用保存功能。
-
可以直接一個空字符串來實現停用:save ""
-
-
stop-writes-on-bgsave-error :默認值爲yes。當啓用了RDB且最後一次後臺保存數據失敗,Redis是否停止接收數據。這會讓用戶意識到數據沒有正確持久化到磁盤上,否則沒有人會注意到災難(disaster)發生了。如果Redis重啓了,那麼又可以重新開始接收數據了
-
rdbcompression ;默認值是yes。對於存儲到磁盤中的快照,可以設置是否進行壓縮存儲。如果是的話,redis會採用LZF算法進行壓縮。如果不想消耗CPU來進行壓縮的話,可以設置爲關閉此功能,但是存儲在磁盤上的快照會比較大。
-
rdbchecksum :默認值是yes。在存儲快照後,我們還可以讓redis使用CRC64算法來進行數據校驗,但是這樣做會增加大約10%的性能消耗,如果希望獲取到最大的性能提升,可以關閉此功能。
-
dbfilename :設置快照的文件名,默認是 dump.rdb
-
dir:設置快照文件的存放路徑,這個配置項一定是個目錄,而不能是文件名。使用上面的 dbfilename 作爲保存的文件名。
-
-
replication:
-
slave-serve-stale-data:默認值爲yes。當一個 slave 與 master 失去聯繫,或者複製正在進行的時候,slave 可能會有兩種表現:
-
如果爲 yes ,slave 仍然會應答客戶端請求,但返回的數據可能是過時,或者數據可能是空的在第一次同步的時候
-
如果爲 no ,在你執行除了 info he salveof 之外的其他命令時,slave 都將返回一個 "SYNC with master in progress" 的錯誤
-
-
slave-read-only:配置Redis的Slave實例是否接受寫操作,即Slave是否爲只讀Redis。默認值爲yes。
-
repl-diskless-sync:主從數據複製是否使用無硬盤複製功能。默認值爲no。
-
repl-diskless-sync-delay:當啓用無硬盤備份,服務器等待一段時間後纔會通過套接字向從站傳送RDB文件,這個等待時間是可配置的。這一點很重要,因爲一旦傳送開始,就不可能再爲一個新到達的從站服務。從站則要排隊等待下一次RDB傳送。因此服務器等待一段時間以期更多的從站到達。延遲時間以秒爲單位,默認爲5秒。要關掉這一功能,只需將它設置爲0秒,傳送會立即啓動。默認值爲5。
-
repl-disable-tcp-nodelay:同步之後是否禁用從站上的TCP_NODELAY 如果你選擇yes,redis會使用較少量的TCP包和帶寬向從站發送數據。但這會導致在從站增加一點數據的延時。 Linux內核默認配置情況下最多40毫秒的延時。如果選擇no,從站的數據延時不會那麼多,但備份需要的帶寬相對較多。默認情況下我們將潛在因素優化,但在高負載情況下或者在主從站都跳的情況下,把它切換爲yes是個好主意。默認值爲no。
-
-
security:
-
rename-command:命令重命名,對於一些危險命令例如:
-
flushdb(清空數據庫)
-
flushall(清空所有記錄)
-
config(客戶端連接後可配置服務器)
-
keys(客戶端連接後可查看所有存在的鍵)
-
-
作爲服務端redis-server,常常需要禁用以上命令來使得服務器更加安全,禁用的具體做法是是:
-
rename-command FLUSHALL ""
-
也可以保留命令但是不能輕易使用,重命名這個命令即可:
-
-
rename-command FLUSHALL abcdefg
-
重啓服務器後則需要使用新命令來執行操作,否則服務器會報錯unknown command。
-
-
-
-
clients:
-
maxclients :設置客戶端最大併發連接數,默認無限制,Redis可以同時打開的客戶端連接數爲Redis進程可以打開的最大文件。 描述符數-32(redis server自身會使用一些),如果設置 maxclients爲0 ,表示不作限制。
-
當客戶端連接數到達限制時,Redis會關閉新的連接並向客戶端返回max number of clients reached錯誤信息。
-
-
memory management(內存管理):
-
maxmemory:設置客戶端最大併發連接數,默認無限制,Redis可以同時打開的客戶端連接數爲Redis進程可以打開的最大文件。描述符數-32(redis server自身會使用一些),如果設置 maxclients爲0 。表示不作限制。當客戶端連接數到達限制時,Redis會關閉新的連接並向客戶端返回max number of clients reached錯誤信息。
-
maxmemory-policy :當內存使用達到最大值時,redis使用的清除策略。
-
volatile-lru 利用LRU算法移除設置過過期時間的key (LRU:最近使用 Least Recently Used )
-
allkeys-lru 利用LRU算法移除任何key
-
volatile-random 移除設置過過期時間的隨機key
-
allkeys-random 移除隨機key
-
volatile-ttl 移除即將過期的key(minor TTL)
-
noeviction noeviction 不移除任何key,只是返回一個寫錯誤 ,默認選項
-
-
maxmemory-samples :LRU 和 minimal TTL 算法都不是精準的算法,但是相對精確的算法(爲了節省內存)。隨意你可以選擇樣本大小進行檢,redis默認選擇3個樣本進行檢測,你可以通過maxmemory-samples進行設置樣本數。
-
-
APPEND ONLY MODE:
-
appendonly:默認redis使用的是rdb方式持久化,這種方式在許多應用中已經足夠用了。但是redis如果中途宕機,會導致可能有幾分鐘的數據丟失,根據save來策略進行持久化,Append Only File是另一種持久化方式, 可以提供更好的持久化特性。Redis會把每次寫入的數據在接收後都寫入appendonly.aof文件,每次啓動時Redis都會先把這個文件的數據讀入內存裏,先忽略RDB文件。默認值爲no。
-
appendfilename :aof文件名,默認是"appendonly.aof"
-
appendfsync:aof持久化策略的配置;no表示不執行fsync,由操作系統保證數據同步到磁盤,速度最快;always表示每次寫入都執行fsync,以保證數據同步到磁盤;everysec表示每秒執行一次fsync,可能會導致丟失這1s數據
-
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。
-
auto-aof-rewrite-percentage:默認值爲100。aof自動重寫配置,當目前aof文件大小超過上一次重寫的aof文件大小的百分之多少進行重寫,即當aof文件增長到一定大小的時候,Redis能夠調用bgrewriteaof對日誌文件進行重寫。當前AOF文件大小是上次日誌重寫得到AOF文件大小的二倍(設置爲100)時,自動啓動新的日誌重寫過程。
-
auto-aof-rewrite-min-size:64mb。設置允許重寫的最小aof文件大小,避免了達到約定百分比但尺寸仍然很小的情況還要重寫。
-
aof-load-truncated:aof文件可能在尾部是不完整的,當redis啓動的時候,aof文件的數據被載入內存。重啓可能發生在redis所在的主機操作系統宕機後,尤其在ext4文件系統沒有加上data=ordered選項,出現這種現象 redis宕機或者異常終止不會造成尾部不完整現象,可以選擇讓redis退出,或者導入儘可能多的數據。如果選擇的是yes,當截斷的aof文件被導入的時候,會自動發佈一個log給客戶端然後load。如果是no,用戶必須手動redis-check-aof修復AOF文件纔可以。默認值爲 yes。
-
-
LUA SCRIPTING:
-
lua-time-limit:一個lua腳本執行的最大時間,單位爲ms。默認值爲5000.
-
-
REDIS CLUSTER:
-
cluster-enabled:集羣開關,默認是不開啓集羣模式。
-
cluster-config-file:集羣配置文件的名稱,每個節點都有一個集羣相關的配置文件,持久化保存集羣的信息。 這個文件並不需要手動配置,這個配置文件有Redis生成並更新,每個Redis集羣節點需要一個單獨的配置文件。請確保與實例運行的系統中配置文件名稱不衝突。默認配置爲nodes-6379.conf。
-
cluster-node-timeout :可以配置值爲15000。節點互連超時的閥值,集羣節點超時毫秒數
-
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將不會嘗試進行故障轉移
-
cluster-migration-barrier :可以配置值爲1。master的slave數量大於該值,slave才能遷移到其他孤立master上,如這個參數若被設爲2,那麼只有當一個主節點擁有2 個可工作的從節點時,它的一個從節點會嘗試遷移。
-
cluster-require-full-coverage:默認情況下,集羣全部的slot有節點負責,集羣狀態才爲ok,才能提供服務。 設置爲no,可以在slot沒有全部分配的時候提供服務。不建議打開該配置,這樣會造成分區的時候,小分區的master一直在接受寫請求,而造成很長時間數據不一致。
-
-
-
redis線程安全問題:
-
單線程指的是網絡請求模塊使用了一個線程(所以不需考慮併發安全性)
-
redis實際上是採用了線程封閉的觀念,把任務封閉在一個線程,自然避免了線程安全問題,不過對於需要依賴多個redis操作的複合操作來說,依然需要鎖,而且有可能是分佈式鎖。
-
-
redis相比memcached有哪些優勢?
-
memcached所有的值均是簡單的字符串,redis作爲其替代者,支持更爲豐富的數據類型
-
redis的速度比memcached快很多
-
redis可以持久化其數據
-
Redis支持數據的備份,即master-slave模式的數據備份。
-
使用底層模型不同,它們之間底層實現方式 以及與客戶端之間通信的應用協議不一樣。Redis直接自己構建了VM 機制 ,因爲一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。
-
value大小:redis最大可以達到1GB,而memcache只有1MB
-
-
Redis支持的數據類型:
-
我們所說的其支持的數據類型都是在說其存儲的value的類型,redis的Key的類型都是字符串。
-
string:
-
redis 中字符串 value 最多可以是 512M。
-
場景:做一些計數功能的緩存
-
-
list:
-
list 列表,它是簡單的字符串列表,按照插入順序排序,你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊),它的底層實際上是個鏈表。
-
場景:可以當作一個簡單消息隊列功能,做基於redis的分頁功能
-
-
set:
-
是一個String類型的無序集合。
-
場景:全局去重
-
-
sorted set:
-
是一個String類型的有序集合,通過給每一個元素一個固定的分數score來保持順序。
-
場景:做排行榜應用,取TopN操作,範圍查找
-
-
hash:
-
hash 是一個鍵值對集合,是一個 string 類型的 key和 value 的映射表,key 還是key,但是value是一個鍵值對(key-value)
-
場景:存放一些結構化的信息
-
-
-
系統常用命令:
-
OBJECT ENCODING key :顯示五種數據類型的底層數據實現結構
-
BGSAVE:後臺異步保存當前數據庫的數據到磁盤(持久化)
-
BGREWIRTEAOF:手動觸發AOF重寫操作,用於減小AOF文件體積
-
-
redis數據類型的底層數據結構:
-
通過OBJECT ENCODING key 可以查看當前key的底層數據結構。
-
簡單動態字符串(simple dynamic string,SDS):
-
SDS 數據類型的定義:
-
len 保存了SDS保存字符串的長度
-
buf[ ] 數組用來保存字符串的每個元素
-
free 記錄了 buf 數組中未使用的字節數量
-
-
使用SDS而不是C語言原生的字符串的優點:
-
常數複雜度獲取字符串長度
-
杜絕緩衝區溢出:
-
當進行字符串修改時,可以根據len屬性檢查空間是否滿足,不滿足會擴容,防止緩衝區溢出
-
-
減少字符串修改時的內存重新分配次數:
-
C語言由於不記錄字符串的長度,所以如果要修改字符串,必須要重新分配內存(先釋放再申請),因爲如果沒有重新分配,字符串長度增大時會造成內存緩衝區溢出,字符串長度減小時會造成內存泄露。
-
SDS在字符串修改時存在空間預分配和惰性空間釋放。
-
-
二進制安全:
-
二進制中可能包含空字符串。C語言原生的字符串以空字符結尾,所以不可以正確的存取
-
SDS中以len屬性來判斷字符串是否結束
-
-
兼容部分C語言中的函數庫
-
SDS 還可以作爲緩衝區(buffer):包括 AOF 模塊中的AOF緩衝區以及客戶端狀態中的輸入緩衝區
-
-
-
鏈表:
-
Redis中自己實現了鏈表,特性如下:
-
雙端:鏈表具有前置節點和後置節點的引用,獲取這兩個節點時間複雜度都爲O(1)。
-
無環:表頭節點的 prev 指針和表尾節點的 next 指針都指向 NULL,對鏈表的訪問都是以 NULL 結束。
-
帶鏈表長度計數器:通過 len 屬性獲取鏈表長度的時間複雜度爲 O(1)。
-
多態:鏈表節點使用 void* 指針來保存節點值,可以保存各種不同類型的值。
-
-
-
字典:
-
字典又稱爲符號表或者關聯數組、或映射(map),是一種用於保存鍵值對的抽象數據結構。字典中的每一個鍵 key 都是唯一的,通過 key 可以對值來進行查找或修改。
-
C 語言中沒有內置這種數據結構的實現,所以字典依然是 Redis自己構建的。Redis 的字典使用哈希表作爲底層實現。
-
擴容和收縮:當哈希表保存的鍵值對太多或者太少時,就要通過 rehash(重新散列)來對哈希表進行相應的擴展或者收縮。
-
如果執行擴展操作,會基於原哈希表創建一個大小等於 ht[0].used*2n 的哈希表
-
相反如果執行的是收縮操作,每次收縮是根據已使用空間縮小一倍創建一個新的哈希表。
-
重新利用上面的哈希算法,計算索引值,然後將鍵值對放到新的哈希表位置上。
-
所有鍵值對都遷徙完畢後,釋放原哈希表的內存空間。
-
-
觸發擴容的條件:
-
服務器目前沒有執行 BGSAVE 命令或者 BGREWRITEAOF 命令,並且負載因子大於等於1。
-
服務器目前正在執行 BGSAVE 命令或者 BGREWRITEAOF 命令,並且負載因子大於等於5。
-
負載因子 = 哈希表已保存節點數量 / 哈希表大小。
-
-
漸近式 rehash
-
擴容和收縮操作不是一次性、集中式完成的,而是分多次、漸進式完成的。如果保存在Redis中的鍵值對只有幾個幾十個,那麼 rehash 操作
-
可以瞬間完成。
-
但是如果鍵值對有幾百萬,幾千萬甚至幾億,那麼要一次性的進行 rehash,勢必會造成Redis一段時間內不能進行別的操作。
-
所以Redis採用漸進式 rehash,這樣在進行漸進式rehash期間,字典的刪除查找更新等操作可能會在兩個哈希表上進行,第一個哈希表沒有找到,就會去第二個哈希表上進行查找。但是進行 增加操作,一定是在新的哈希表上進行的。
-
-
-
跳錶:
-
對於一個有序的鏈表,我們依然只能通過遍歷來查找一個元素,效率低下。
-
跳錶就是將有序的鏈表分層,相當於是創建了多個分層索引,當鏈表元素個數增大的時候,跳錶的查詢效率顯著提升。
-
跳錶的特點:
-
由很多層結構組成;
-
每一層都是一個有序的鏈表,排列順序爲由高層到底層,都至少包含兩個鏈表節點,分別是前面的head節點和後面的nil節點;
-
最底層的鏈表包含了所有的元素;
-
如果一個元素出現在某一層的鏈表中,那麼在該層之下的鏈表也全都會出現(上一層的元素是當前層的元素的子集);
-
鏈表中的每個節點都包含兩個指針,一個指向同一層的下一個鏈表節點,另一個指向下一層的同一個鏈表節點;
-
-
Redis中爲什麼使用跳錶而不使用查詢複雜度同樣爲log(N)的平衡樹?
-
跳錶構建起來比平衡樹容易
-
跳錶支持範圍搜索。
-
-
-
整數集合(intset):
-
用於保存整數值的集合抽象數據類型,它可以保存類型爲int16_t、int32_t 或者int64_t 的整數值,並且保證集合中不會出現重複元素。
-
整數集合的每個元素都是 contents 數組的一個數據項,它們按照從小到大的順序排列,並且不包含任何重複項。
-
length 屬性記錄了 contents 數組的大小。
-
-
壓縮列表:
-
壓縮列表(ziplist)是Redis爲了節省內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序型數據結構
-
一個壓縮列表可以包含任意多個節點(entry),每個節點可以保存一個字節數組或者一個整數值。
-
壓縮列表的原理:
-
壓縮列表並不是對數據利用某種算法進行壓縮,而是將數據按照一定規則編碼在一塊連續的內存區域,目的是節省內存
-
-
-
-
Redis數據類型的實現原理:
-
Redis底層所使用的數據結構我們已經學習了,但是redis並不是直接使用這些數據結構,而是構建了一個對象系統。
-
每次在Redis數據庫中創建一個鍵值對時,至少會創建兩個對象,一個是鍵對象,一個是值對象。
-
Redis中的每個對象都是由 redisObject 結構來表示:
-
type屬性:記錄了對象的類型,五種數據類型string,list,set.sort set ,hash。 命令:type key
-
encoding編碼:記錄了對象的底層編碼(每個對象都至少有兩種編碼)
-
OBJECT ENCODING key :顯示五種數據類型的底層數據實現結構(編碼)
-
-
*ptr:指向底層數據結構的指針
-
五大數據類型對象的編碼:
-
字符串對象:
-
字符串是Redis最基本的數據類型,不僅所有key都是字符串類型,其它幾種數據類型構成的元素也是字符串。注意字符串的長度不能超過512M。
-
編碼:字符串對象的編碼可以是int,raw或者embstr。
-
-
int 編碼:保存的是可以用 long 類型表示的整數值
-
raw 編碼(長):保存長度大於44字節的字符串(redis3.2版本之前是39字節,之後是44字節)
-
embstr 編碼(短)(只讀):保存長度小於44字節的字符串(redis3.2版本之前是39字節,之後是44字節)
-
raw和embstr編碼的區別:
-
embstr與raw都使用redisObject和sds保存數據,區別在於:
-
embstr的使用只分配一次內存空間(因此redisObject和sds是連續的)
-
raw需要分配兩次內存空間(分別爲redisObject和sds分配空間)。
-
因此與raw相比,embstr的好處在於創建時少分配一次空間,刪除時少釋放一次空間,以及對象的所有數據連在一起,尋找方便。
-
而embstr的壞處也很明顯,如果字符串的長度增加需要重新分配內存時,整個redisObject和sds都需要重新分配空間,因此redis中的embstr實現爲只讀。
-
-
編碼可以轉換嗎?
-
當 int 編碼保存的值不再是整數,或大小超過了long的範圍時,自動轉化爲raw。
-
對於 embstr 編碼,由於 Redis 沒有對其編寫任何的修改程序(embstr 是隻讀的),在對embstr對象進行修改時,都會先轉化爲raw再進行修改,因此,只要是修改embstr對象,修改後的對象一定是raw的,無論是否達到了44個字節。
-
-
列表對象(list):
-
列表對象的編碼可以是 ziplist(壓縮列表) 和 linkedlist(雙端鏈表)。
-
當同時滿足下面兩個條件時,使用ziplist(壓縮列表)編碼:
-
列表保存元素個數小於512個
-
每個元素長度小於64字節
-
-
不能滿足這兩個條件的時候使用 linkedlist 編碼。
-
也就是說元素又多又長就用linkedlist,否則就用ziplist
-
-
哈希對象(hash):
-
哈希對象的編碼可以是 ziplist 或者 hashtable。
-
當同時滿足下面兩個條件時,使用ziplist(壓縮列表)編碼:
-
列表保存元素個數小於512個
-
每個元素長度小於64字節
-
-
不能滿足這兩個條件的時候使用 hashtable編碼。
-
-
集合對象(set):
-
集合對象 set 是 string 類型(整數也會轉換成string類型進行存儲)的無序集合。
-
集合對象的編碼可以是 intset 或者 hashtable。intset 編碼的集合對象使用整數集合作爲底層實現,集合對象包含的所有元素都被保存在整數集合中。
-
當集合同時滿足以下兩個條件時,使用 intset 編碼:
-
集合對象中所有元素都是整數
-
集合對象所有元素數量不超過512
-
-
不能滿足這兩個條件的就使用 hashtable 編碼。
-
-
有序集合對象:
-
有序集合對象是有序的。與列表使用索引下標作爲排序依據不同,有序集合爲每個元素設置一個分數(score)作爲排序依據。
-
有序集合的編碼可以是 ziplist 或者 skiplist
-
ziplist 編碼的有序集合對象使用壓縮列表作爲底層實現,每個集合元素使用兩個緊挨在一起的壓縮列表節點來保存,第一個節點保存元素的成員,第二個節點保存元素的分值。並且壓縮列表內的集合元素按分值從小到大的順序進行排列,小的放置在靠近表頭的位置,大的放置在靠近表尾的位置。
-
skiplist 編碼的有序集合對象使用 zet 結構作爲底層實現,一個 zset 結構同時包含一個字典和一個跳躍表:
-
字典的鍵保存元素的值,字典的值則保存元素的分值;跳躍表節點的 object 屬性保存元素的成員,跳躍表節點的 score 屬性保存元素的分值。
-
這兩種數據結構會通過指針來共享相同元素的成員和分值,所以不會產生重複成員和分值,造成內存的浪費。
-
說明:其實有序集合單獨使用字典或跳躍表其中一種數據結構都可以實現,但是這裏使用兩種數據結構組合起來,原因是假如我們單獨使用 字典,雖然能以 O(1) 的時間複雜度查找成員的分值,但是因爲字典是以無序的方式來保存集合元素,所以每次進行範圍操作的時候都要進行排序;假如我們單獨使用跳躍表來實現,雖然能執行範圍操作,但是查找操作有 O(1)的複雜度變爲了O(logN)。因此Redis使用了兩種數據結構來共同實現有序集合。
-
-
-
當有序集合對象同時滿足以下兩個條件時,對象使用 ziplist 編碼:
-
保存的元素數量小於128;
-
保存的所有元素長度都小於64字節。
-
-
不能滿足上面兩個條件的使用 skiplist 編碼。
-
-
-
refcount(引用計數):
-
Redis自己構建了一個內存回收機制,通過在 redisObject 結構中的 refcount 屬性實現。這個屬性會隨着對象的使用狀態而不斷變化:
-
創建一個新對象,屬性 refcount 初始化爲1
-
對象被一個新程序使用,屬性 refcount 加 1
-
對象不再被一個程序使用,屬性 refcount 減 1
-
當對象的引用計數值變爲 0 時,對象所佔用的內存就會被釋放。
-
-
如果出現循環引用導致內存溢出的時候,我們在配置文件中設置了redis的內存清除策略。
-
共享內存:
-
當key不相同但是value相同時,redis將數據庫鍵的值指針指向一個現有值的對象,將被共享的值對象引用refcount 加 1
-
Redis的共享對象目前只支持整數值的字符串對象。之所以如此,實際上是對內存和CPU(時間)的平衡:共享對象雖然會降低內存消耗,但是判斷兩個對象是否相等卻需要消耗額外的時間。對於整數值,判斷操作複雜度爲O(1);對於普通字符串,判斷複雜度爲O(n);而對於哈希、列表、集合和有序集合,判斷的複雜度爲O(n^2)
-
雖然共享對象只能是整數值的字符串對象,但是5種類型都可能使用共享對象(如哈希、列表等的元素可以使用)
-
-
-
lru屬性:
-
該屬性記錄了對象最後一次被命令程序訪問的時間。
-
使用 OBJECT IDLETIME 命令可以打印給定鍵的空轉時長,通過將當前時間減去值對象的 lru 時間計算得到。
-
lru 屬性除了計算空轉時長以外,還可以配合前面內存回收配置使用。
-
如果Redis打開了maxmemory選項,且內存回收算法選擇的是volatile-lru或allkeys—lru,那麼當Redis內存佔用超過maxmemory指定的值時,Redis會優先選擇空轉時間最長的對象進行釋放。
-
-
-
redis單線程爲什麼執行速度如此之快?
-
完全內存計算
-
單線程,避免了上下文切換
-
使用了多路I/O複用,一個線程監控多個IO流,實現及時響應。
-
-
redis的線程模型:
-
redis的線程模型是一種多路I/O複用模型,具體模型圖如下所示:
-
簡單來說,就是redis-client在操作的時候,會產生具有不同事件類型的socket。
-
在服務端,有一段I/O多路複用程序,將其置入隊列之中。
-
然後,文件事件分派器,依次去隊列中取,轉發到不同的事件處理器中。
-
-
I/O多路複用機制:
-
目前支持I/O多路複用的系統調用有 select,pselect,poll,epoll,I/O多路複用就是通過一種機制
-
一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。
-
select,pselect,poll,epoll本質上都是同步I/O,因爲他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。
-
優點:
-
與多進程和多線程技術相比,I/O多路複用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。
-
-
-
常見的多路複用函數:
-
select函數:
-
會修改傳入的參數數組,這個對於一個需要調用很多次的函數,是非常不友好的。
-
有最大監聽連接數1024個的限制
-
如果任何一個sock(I/O stream)出現了數據,select沒有返回具體是哪個返回了數據,需要採用輪詢的方式去遍歷獲取
-
線程不安全(當你在一個線程中已經監聽該socket,另一個線程想要將該socket關閉,則結果會不可預知)
-
“If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
-
-
poll函數:
-
去掉了1024的限制(使用鏈表搞定)
-
不再修改傳入的參數數組
-
依然是線程不安全的
-
-
epoll函數:
-
epoll 不僅返回socket組裏面數據,還會告訴你具體哪個socket有數據
-
線程安全
-
-
-
-
Redis可能出現的問題:
-
緩存雪崩
-
緩存同一時間大面積的失效,這個時候又來了一波請求,結果請求都到數據庫上,從而導致數據庫連接異常。
-
解決方案:
-
給緩存的失效時間,加上一個隨機值,避免集體失效。
-
使用互斥鎖,但是該方案吞吐量明顯下降了。
-
雙緩存。我們有兩個緩存,緩存A和緩存B。緩存A的失效時間爲20分鐘,緩存B不設失效時間。
-
-
-
緩存穿透
-
故意去請求緩存中不存在的數據,導致所有的請求都懟到數據庫上,從而數據庫連接異常。
-
解決方案:
-
利用互斥鎖,緩存失效的時候,先去獲得鎖,得到鎖了,再去請求數據庫。沒得到鎖,則休眠一段時間重試
-
採用異步更新策略,無論key是否取到值,都直接返回。value值中維護一個緩存失效時間,緩存如果過期,異步起一個線程去讀數據庫,更新緩存。需要做緩存預熱(項目啓動前,先加載緩存)操作。
-
提供一個能迅速判斷請求是否有效的攔截機制,比如,利用布隆過濾器,內部維護一系列合法有效的key。迅速判斷出,請求所攜帶的Key是否合法有效。如果不合法,則直接返回
-
-
-
緩存的併發競爭
-
多個子系統來併發競爭更新一個Key
-
解決方案:
-
使用一個分佈式鎖,誰佔有誰更新
-
使用隊列,將更新操作變成串行
-
指定更新的順序,每次更新都有一個version,當每個子系統更新時發現當前version大於自己的version,那麼放棄更新
-
-
-
緩存和數據庫雙寫一致性
-
高併發請求下會導致數據不一致的問題,如果業務需要數據保持強一致性,那麼不可以使用緩存。
-
解決方案:
-
雙刪延時
-
刪除緩存數據
-
更新數據庫數據
-
隔固定的時間再次刪除緩存
-
-
更新數據庫產生的binlog訂閱(使用canal),將有變化的key記錄下來,並且不斷的去刪除緩存(如果上次刪除緩存失敗)
-
-
-
-
Redis數據的過期回收策略與內存淘汰機制:
-
定期刪除+惰性刪除組合
-
定期刪除:
-
當給數據設置的緩存時間到期之後會自動刪除,但是比較消耗CPU,redis是單線程執行的,如果高併發情況下會影響系統性能
-
-
定期刪除+惰性刪除組合:(這個纔是使用的策略)
-
redis默認每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查。因此,如果只採用定期刪除策略,會導致很多key到時間沒有刪除。
-
惰性刪除:
-
在獲取某個key的時候,redis會檢查一下,這個key如果設置了過期時間那麼是否過期了?如果過期了此時就會刪除。
-
-
-
內存淘汰:
-
如果在數據過期之後,你沒有去獲取該數據,那麼就不會被惰性刪除,該數據一直保存,redis內存會越來越高。
-
當內存不足時,通過我們在上邊所介紹的redis.conf配置文件中的配置的maxmemory-policy策略來進行內存淘汰
-
-
-
Redis持久化策略:
-
redis是一個基於內存,並且支持數據持久化的NoSql數據庫。有兩種持久化策略:
-
RDB(快照方式 snapshotting)(全量持久化):
-
把當前內存中的數據集快照寫入磁盤,也就是 Snapshot 快照(數據庫中所有鍵值對數據)。恢復時是將快照文件直接讀到內存裏
-
自動觸發方式:
-
我們在配置文件redis.conf中配置的snapshotting 指定了當多長時間內有多少次save命令時自動觸發持久化操作
-
將這塊的save配置註釋掉或者配置爲空字符串,表示關閉RDB持久化方式
-
-
手動觸發方式:
-
通過bgsave命令,在後臺異步進行生成快照的操作,同時還可以響應客戶都按的請求。
-
通過redis進程fork操作創建子進程,生成快照由子進程負責,完成後自動結束,請求只會在fork階段被阻塞
-
-
快照恢復:
-
將備份文件 (dump.rdb) 移動到 redis 安裝目錄並啓動服務即可,redis就會自動加載文件數據至內存了。Redis 服務器在載入 RDB 文件期間,會一直處於阻塞狀態,直到載入工作完成爲止。
-
-
優缺點:
-
存在數據丟失的問題
-
恢復大數據集時比AOF方式快
-
RDB方式數據沒辦法做到實時持久化/秒級持久化。因爲bgsave每次運行都要執行fork操作創建子進程,屬於重量級操作(內存中的數據被克隆了一份,大致2倍的膨脹性需要考慮),頻繁執行成本過高(影響性能)
-
-
-
AOF(append-only-file)(增量持久化):
-
通過記錄redis服務器所執行的寫命令來記錄數據庫狀態
-
在 redis.conf 配置文件的 APPEND ONLY MODE 下我們可以設置AOF持久化
-
AOF 保存文件的位置和 RDB 保存文件的位置一樣,都是通過 redis.conf 配置文件的 dir 配置
-
數據恢復:
-
重啓 Redis 之後就會進行 AOF 文件的載入
-
異常修復命令:redis-check-aof --fix 進行修復
-
-
AOF日誌重寫:
-
AOF文件可能會隨着服務器運行的時間越來越大,可以利用AOF重寫的功能,來控制AOF文件的大小。
-
AOF重寫功能會首先讀取數據庫中現有的鍵值對狀態,然後根據類型使用一條命令來替代前的鍵值對多條命令。
-
使用命令 bgrewriteaof 來實現AOF重寫
-
-
AOF重寫緩存區:
-
Redis 是單線程工作,如果重寫AOF 需要比較長的時間,那麼在重寫 AOF 期間,Redis將長時間無法處理其他的命令,解決辦法是將 AOF 重寫程序放到子程序中進行,這樣有兩個好處:
-
子進程進行 AOF 重寫期間,服務器進程(父進程)可以繼續處理其他命令。
-
子進程帶有父進程的數據副本,使用子進程而不是線程,可以在避免使用鎖的情況下,保證數據的安全性。
-
-
子進程重寫導致的問題:
-
因爲子進程在進行 AOF 重寫期間,服務器進程依然在處理其它命令,這新的命令有可能也對數據庫進行了修改操作,使得當前數據庫狀態和重寫後的 AOF 文件狀態不一致。
-
-
數據狀態不一致解決辦法:
-
Redis 服務器設置了一個 AOF 重寫緩衝區,這個緩衝區是在創建子進程後開始使用,當Redis服務器執行一個寫命令之後,就會將這個寫命令也發送到 AOF 重寫緩衝區。
-
當子進程完成 AOF 重寫之後,就會給父進程發送一個信號,父進程接收此信號後,就會調用函數將 AOF 重寫緩衝區的內容都寫到新的 AOF 文件中。
-
-
-
優點:
-
AOF 持久化的方法提供了多種的同步頻率,即使使用默認的同步頻率每秒同步一次,Redis 最多也就丟失 1 秒的數據而已。
-
AOF 文件使用 Redis 命令追加的形式來構造,因此,即使 Redis 只能向 AOF 文件寫入命令的片斷,使用 redis-check-aof 工具也很容易修正 AOF 文件。
-
AOF 文件的格式可讀性較強,這也爲使用者提供了更靈活的處理方式。例如,如果我們不小心錯用了 FLUSHALL 命令,在重寫還沒進行時,我們可以手工將最後的 FLUSHALL 命令去掉,然後再使用 AOF 來恢復數據。
-
-
缺點:
-
對於具有相同數據的的 Redis,AOF 文件通常會比 RDF 文件體積更大。
-
雖然 AOF 提供了多種同步的頻率,默認情況下,每秒同步一次的頻率也具有較高的性能。但在 Redis 的負載較高時,RDB 比 AOF 具好更好的性能保證。
-
RDB 使用快照的形式來持久化整個 Redis 數據,而 AOF 只是將每次執行的命令追加到 AOF 文件中,因此從理論上說,RDB 比 AOF 方式更健壯。官方文檔也指出,AOF 的確也存在一些 BUG,這些 BUG 在 RDB 沒有存在。
-
-
-
持久化策略選擇:
-
AOF更安全,可將數據及時同步到文件中,但需要較多的磁盤IO,AOF文件尺寸較大,文件內容恢復相對較慢, 也更完整。
-
RDB持久化,安全性較差,它是正常時期數據備份及 master-slave數據同步的最佳手段,文件尺寸較小,恢復數度較快。
-
-
-
以上內容摘抄總結與網絡,感謝各位前輩的總結與鋪墊。接下來我會繼續更新Redis相關文章,全部會以這種筆記形式給出,大家對筆記中不太懂的地方,可以發表評論,我們一起研究學習,此處權當給大家列個學習提綱了。
如果對你有幫助,記得點贊哦~歡迎大家關注我的博客,可以進羣366533258一起交流學習哦~
本羣給大家提供一個學習交流的平臺,內設菜鳥Java管理員一枚、精通算法的金牌講師一枚、Android管理員一枚、藍牙BlueTooth管理員一枚、Web前端管理一枚以及C#管理一枚。歡迎大家進來交流技術。
關注微信公衆號(文強的技術小屋),學習更多技術知識,一起遨遊知識海洋~