1、可以通過object encoding命令查詢內部編碼
內部編碼:
string:raw int embstr(小於39)
Hash:hashtable ziplist
List:linked list ziplist
Set :hashtable intset
Zset skiplist ziplist
這樣設計的有點:改進內部編碼,對外的數據結構和命令沒有影響;多種內部編碼實現可以在不同的場景下發揮各自的優勢。
Redis的單線程模型:使用了單線程架構和IO多路複用模型來實現高性能的內存數據庫服務。
單線程性能高的原因:
- 純內存訪問,數據放在內存中,訪問快;
- 非阻塞IO,使用epoll作爲IO多路複用技術的實現,加上自身的事件處理模型將epoll中的鏈接 讀寫 關閉都轉換爲事件,不在網絡上浪費過多的時間。
- 單線程避免了現成切換和竟態產生的消耗。
單線程模型不會同時有兩個命令同時執行,沒有併發問題。但是若是一個命令執行時間過長,會造成其他命令的阻塞。因爲redis是面向快速執行場景的數據庫。
String:
命令 set setnx setex:
SET key value含義:
將字符串值 value 關聯到 key . 如果 key 已經持有其他值, SET 就覆寫舊值,無視類型。
SETEX key seconds value
將值 value 關聯到 key ,並將 key 的生存時間設爲 seconds (以秒爲單位)。如果 key 已經存在, SETEX 命令將覆寫舊值。
返回值:
設置成功時返回 OK 。當 seconds 參數不合法時,返回一個錯誤。
SETNX key value 含義: setxx,與nx相反,鍵必須存在才能設置成功,用戶更新。
將 key 的值設爲 value ,當且僅當 key 不存在。 若給定的 key 已經存在,則 SETNX 不做任何動作。
SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。 設置成功,返回 1 . 設置失敗,返回 0 。
GETSET key value 含義:
將給定 key 的值設爲 value ,並返回 key 的舊值(old value)。
當 key 存在但不是字符串類型時,返回一個錯誤。
返回給定 key 的舊值. 當 key 沒有舊值時,也即是, key 不存在時,返回 nil 。
因爲 SET 命令可以通過參數來實現和 SETNX 、 SETEX 和 PSETEX 三個命令的效果,所以將來的 Redis 版本可能會廢棄並最終移除 SETNX 、 SETEX 和 PSETEX 這三個命令。
SET key-with-expire-time "hello" EX 10086 ,爲key設置過期時間。
SET key-with-expire-and-NX "hello" EX 10086 NX 若是key不存在 則設置,並設置過期時間。
Setnx的應用場景,若是多個客戶端同時執行setnx key value,只能有一個客戶端設置成功,setnx可以作爲分佈式鎖的一種實現方案。
批量獲取命令:mset:提高開發效率,減少網絡消耗
計數:
incr key.自增;decr decr 自減,incrby 自增指定數字 decrby 自減指定數字 incrbyfloat 自增浮點數
String使用場景:緩存 ,計數 ,共享session ,限速(限制發短信次數,)
Hash
Hget hset hgetall 存儲結構信息。
List:列表原始有序(可通過下標獲取),可重複
查詢:lindex key index:獲取列表指定索引下標的元素。
Lrem key count value :刪除指定元素,從列表中找到等於value的元素進行刪除,
Ltrim key start end :按照索引範圍修建列表 ,保留list的start end之間的元素。
阻塞操作命令:blpop brpop, blpop key timeout;timeout 阻塞時間。
列表爲空:timeout=3 ,客戶端等3s後返回,timeout=0則一直阻塞。列表不爲空 則會立即返回。
list使用場景:
消息隊列:lpush +brpop 可實現阻塞隊列,
文章列表:分頁展示每個人的文章列表。若是列表較大,獲取中間數據比較慢,可以考慮將列表做二級拆分。
Lpush +lpop =stack(棧);
lpush +rpop =queue;l
push +ltrim = capped collection (有限集合),
lpush +brpop = message queue.
Set
不允許重複元素,元素無序;
Sadd,添加元素, sad key element ;srem 刪除元素 ;scard key 計算元素個數。Sismember key element ,判斷元素是否在集合中。Srandmember key count,隨機的返回count元素。Smembers key:獲取所有元素。Sinter key1 key2 ,求交集
Suinon:求並集;sdiff 求差集
使用場景:標籤(tag);sdd = tagging(標籤);spop/srandmember=random item(生成隨機數,抽獎);sadd+sinter=social graph(社交需求)
有序集合 zset :爲每個元素設置一個分數作爲排序的依據。Zadd key score member,也存在nx xx ch incr選項,ch表示此次操作後發生變化的個數。
Zrank key member 計算成員的排名 從低到高,zrevrank key member 從高到底排名。Zincrby key increment member .增加成員的分數。
使用場景:排行榜系統,獲取贊數。
鍵管理:單個建管理:rename :重命名,重命名的key若是存在,則值被覆蓋。爲了避免強行rename,使用renamenx,只有newkey不存在的時候纔會覆蓋。重命名會刪除舊鍵,若鍵對應的值比較大,會存在阻塞redis的可能。
隨機返回一個鍵 randomkey
鍵過期:expire ttl(顯示剩餘過期時間,-1表示鍵沒有過期時間,-2表示鍵不存在),persist key:清除鍵的過期時間。對於字符串類型,執行set會去掉過期時間。Redis不支持二級數據結構(hash 列表)內部元素的過期功能。不能對列表類型的一個元素設置過期時間。Setex= set+expire ,不但是原子執行 還減少了一次網絡通訊時間。
Keys獲取鍵可能會產生阻塞,類似還有hgetall smembers zrange,優化可以使用hscan ssan zscan.但是scan的過程中若是有鍵的變化,新增的可能遍歷不到等。
Pipeline:流水線機制。很多命令不支持批量執行的;例如hgetall,若要執行n次hgetall命令,需要消耗多次網絡連接,pipeline可以實現將一組Redis命令進行組裝,通過一次網絡傳輸給redis,在將這組redis命令的執行結果按順序返回給客戶端。
原生批量命令與pipeline的對比:
原生批量命令是原子的,pipeline不是,原生批量命令是一個命令對應多個key,pipeline支持多個命令,原生批量命令是Redis服務端支持實現的,pipeline需要服務端和客戶端的共同實現。
爲了保證這條命令組合的原子性,Redis提供了簡單的事務功能及集成lua腳本來解決這個問題。將一組命令放在multi exec命令之間。之間的命令是原子順序執行的,當exec時纔會真正的執行命令,若其中一個命令執行了 另外一個命令運行出錯,redis不會回滾。同時也無法實現命令之間的邏輯計算關係。
Redis執行lua有兩種方式:使用eval命名,客戶端將腳本作爲字符串發到服務器,服務器將結果返回給客戶端。
使用evalsha命令,將lua腳本加載到Redis服務端,得到腳本的sha校驗和,evalsha命令使用sha作爲參數直接執行對應的lua腳本,避免每次發送lua腳本的開銷。腳本常駐服務端,腳本功能得到複用。
Lua腳本在Redis是原子執行的,執行過程中不會插入其他命令。
Lua的缺點:lua執行時間太長,阻礙客戶端命令時,需要script kill;將腳本殺掉。不過lua腳本正在執行寫操作的時候,scriptkill將不會生效。
Bitmaps:
Redis(減少內存使用量)提供bitmaps這個數據接口實現對位的操作。Bitmaps本身不是一種數據結構,實際上是字符串,但是他可以對字符串的位進行操作。Bitmaps可以認爲是一個以爲爲單位的數組,數組的每個單元只能存儲0和1,數組的下表在bitmaps叫做偏移量。
案例:記錄每個用戶是否訪問過某網站,將訪問的用戶記做1,沒有訪問過記做0,用偏移量作爲用戶的id。Setbit key offset value 初始化的時候爲0,很多應用的id以(10000)等開頭,若直接和bitmap對應,造成浪費,一般將id減去指定數字,不然偏移量太大,初始化的過程比較慢,造成redis阻塞。
獲取數據getbit key offset
獲取bitmap指定範圍值爲1的個數:bitcount start end
Bitmaps間的運算:bitop 是一個複合操作,可以做多個bitmap的and or not xor等操作。
Bitmap的使用注意:若是一億用戶,每天只有10W訪問量,大量的0用戶,這時就不合適使用bitmaps.
Hyperloglog
Hyperloglog實際上是字符串類型,是一種基數算法(給定一個含有重複元素的有限集合,計算其不重複的元素的個數),通過hyperloglog利用極小的內存空間完成獨立總數的統計。數據集可以是IP ID EMAIL等。有三個命令:pfadd pfcount pfmerge
選用pyperloglog的考慮:只爲了計算獨立總數,不需要獲取單條數據;
可以容忍一定的誤差率,畢竟內存佔用上優勢很大。
用來統計:某家店鋪今天有多少不同用戶訪問;某家店鋪今天接待了多少不同買家這類信息,思想:hash函數,將同一個用戶hash到同一位中。
發佈訂閱:
Redis提供了發佈訂閱的消息機制,發佈訂閱中不直接通信,發佈者客戶端向指定的頻道發佈消息,訂閱該頻道的每個客戶端都可以收到該消息。
Publish channel message 發佈消息
訂閱消息:subscribe channel訂閱消息 可以一次訂閱多個頻道。
客戶端在執行訂閱命令之後進入了訂閱狀態,只能接收subscribe unsubscribe(取消訂閱 channel)psubscribe punsubscribe(這兩個是支持正則形式的channel)四個命令。
新開啓的訂閱客戶端,無法接收到該頻道之前的消息,因爲redis不會對發佈的消息進行持久化。
Redis的發佈訂閱消息系統粗糙,無法實現消息堆積和回溯。
使用場景:聊天室 公告牌 服務之間利用消息解耦的都可以。
GEO (地理信息定位)支持存儲地理位置信息用來實現附近位置,搖一搖等依賴地理位置信息的功能。
GEO實現是zset
4章客戶端
客戶端服務器通信使用的協議是resp(redis序列化協議).服務端返回結果格式:狀態回覆:+;錯誤回覆-;整數回覆:;字符串回覆$,多條字符串回覆 *。
Java的redis客戶端:jedis屬於java的第三方開發包,下載jedis.jar加入到項目中。
Jedis實現了pipeline,新建對象,進行操作即可。
輸入緩衝區:redis爲每個客戶端分配了輸入緩衝區,作用就是將客戶端發送的命令臨時保存,同時redis從緩衝區里拉取命令並執行,每個客戶端緩衝區不能超過1G,超過後將會關閉。
輸入緩衝區不受maxmemory控制,若redis存儲了2G數據,輸入緩衝區使用了3G,超過maxmemory的限制,可能會產生數據丟失,鍵值淘汰 ooM等情況。
兩種情況導致輸入緩衝區過大:redis的處理速度跟不上輸入速度,並且每次進入緩衝區的命令包含了大量的bigkey;
另外一種情況是redis發生了阻塞,短期內不能處理命令,造成客戶端積壓。
解決方案:監控 client list
輸出緩衝區:
保存命令執行的結果返回給客戶端,爲redis和客戶端交互返回結果提供緩衝。輸出緩衝區也不首maxmemory的限制。
及時監控內存,一旦發現內存抖動頻繁,可能就是輸出緩衝區過大導致的。
Monitor:該命令用戶監控redis正在執行的命令,monitor能監聽到所有的命令,缺點:一旦Redis的併發量太大,monitor客戶端的緩衝會暴漲,可能會瞬間站會大量內存。
5章,持久化
Redis支持RDB AOF兩種持久化機制,可以避免因進程退出造成的數據丟失問題,當下次重啓時利用之前的持久化的文件即可實現數據恢復。
RDB把當前進程數據生成快照保存到硬盤,手動觸發(save:保存數據期間會阻塞, bgsave:fock子進程,子進程完成持久化,fock期間會阻塞,一般時間短)和自動觸發(save m n :m秒有n個更新時;從節點執行全量複製操作時,主節點自動執行bgsave生成rdb文件發給從節點;執行shutdown時,沒有開啓aof就會自動執行bgsave;執行rebug reload時會加載redis.)
Redis默認採用LZF算法對Rdb文件壓縮。
RDB優點:壓縮的二進制文件,文件小,含有某個時間點的數據快照,數據恢復快,適合備份,災難恢復
缺點:沒辦法實時持久化(bgsave每次運行會fock進程,成本高,不能頻繁操作)。
Aof:獨立日誌的方式記錄每次寫命令,重啓時重新執行AOF文件的命令恢復數據,主要是解決數據實時持久化的問題,主流持久化方式。Aof的工作流程:所有命令追加寫入AOF緩存;緩存根據策略向硬盤同步操作(AOF文件同步); 文件重寫:AOF越來越大需定期重寫,達到壓縮的目的。 重啓加載:服務器重啓時,加載sof文件恢復數據。
爲什麼命令先寫緩存?:單線程模式,每次都寫硬盤,性能完全取決於當前硬盤的負載,阻塞客戶端。
緩存同步文件的策略:always no everysec(默認,命令寫入緩存後,調用系統write操作,不對AOF文件做fsync同步,同步操作由專門的線程每秒調用一次,持久化到1秒前操作):
重寫:將Redis進程內的數據轉化爲寫命令同步到Aof文件的過程。子進程重寫期間使用copy-on write(寫時複製,在併發訪問的時候, 需要修改的元素,不直接修改容器,若是先複製一份副本,在副本上修改,修改完成後將指向原來容器的引用指向副本容器。讀原來的文件,寫副本,有數據不一致問題,可以最終一致。)機制與父進程共享內存,避免內存消耗翻倍。AOF重寫期間還需要維護重寫緩衝區,保存新的寫入命令避免數據丟失。
重啓加載:AOF開啓且存在AOF文件時,優先加載AOF文件,沒有采取找RDB文件。
持久化阻塞主線程的場景有:fock阻塞和AOF追加阻塞(超過2s數據還沒用同步完,則主線程阻塞,直到同步完成);
6章複製
保證分佈式Redis高可用:
- 複製的使用方式:只能有主到從複製(從節點的寫,無法同步到主)命令:從節點配置slaveof masterhost masterport.
斷開復制:從節點執行slaveof no one;slaveof 還可以切主(當前從節點對主節點的複製切換到另一個主節點,slaveof newMasterIp newMasterPort),切主的操作流程:1.斷開與舊主節點複製關係。2與新主節點建立複製關係;3刪除從節點所有的數據,4對新主節點進行復制操作。
- 複製支持的拓撲結構:一主一從(寫併發較大時,可以只在從節點開啓AOF),一主多從(多個從節點實現讀寫分離),樹狀(實現數據一層一層複製,減少主節點同步的數量)
- 分析複製的原理:過程 保存主節點信息;主從建立socket連接;發送ping命令(檢測主從之間的網絡套接字是否可用,主節點是否可以接受命令);權限驗證;同步數據集(主節點將全部數據發給從節點,分爲全量同步和部分同步);命令持續複製(主節點持續把寫命令發送給從節點,保證主從數據的一致性)。
全量複製(開銷大) 部分複製,主從維持心跳(ping命令);複製過程是異步的(主節點處理完寫直接返回,不等待從節點複製完成,從節點有數據延遲的情況)。使用從節點用於讀寫分離存在數據延遲,過期數據,從節點可用性等問題。
- 複製過程中的問題:
7章 阻塞
單線程架構,所有的讀寫操作都在一條主線程中完成,高併發場景,若是阻塞,就是噩夢。
阻塞內在原因:不合理的使用API或數據結構(使用keys sort hgetall等),CPU飽和,持久化阻塞等。(慢查詢、fork aof阻塞 lua執行太長)
外在原因:CPU競爭,內存交換,網絡問題。
8章 理解內存
1、內存消耗分析:info memory統計內存;內存消耗=自身內存+對象內存+緩衝內存+內存碎片
對象內存:sizeof(key)+sizeof(value);緩衝內存=客戶端緩衝+複製積壓緩衝區(實現部分複製)+AOF緩衝
2、管理內存的原理與方法:通過內存上限和回收策略實現內存管理。
內存回收策略:刪除到達過期時間的鍵對象:採用惰性刪除(客戶端讀取時判斷超時,超時會刪除並返空)和定時任務刪除(定時任務,每秒運行10次,根據鍵的過期比例,使用快慢等回收鍵:定時任務在每個數據庫空間隨機檢查20鍵,發現過期刪除,若是超過25%的鍵過期,則循環回收知道不足25%爲止,若回收超時(25ms超時時間)則在觸發之前再次快速運行回收(快速超時時間1ms,其他和慢回收一樣))機制實現
內存使用到達maxmemory上限時觸發內存溢出控制策略:6種
1noeviction.默認策略:不刪除數據,拒絕所有寫入,返回OOM錯誤。只支持讀
2.volatile-lru:根據LRU算法刪除設置超時時間的鍵,若沒有可刪除的,則退回1模式
3.all-key-lru:不管設置沒設置超時時間,都採用lru算法,知道足夠空間
4 allkey-random;隨機刪除所有鍵,
5volatile-random 隨機刪除過期鍵。
Volatile-ttl:根據鍵的ttl屬性,刪除最近將要過期的數據,若是沒有,則退回1模式。
3、內存優化技巧:
1.使用scan +object idletime 命令批量查詢哪些鍵長時間未被訪問,清理,降低內存佔用。
2、高併發場景下,建議字符串長度控制在39字節內,減少創建RedisObject內存分配次數,提高性能。
3.縮減鍵和值的長度。
4.共享對象池,Redis內部維護0-9999的整數對象池,但是用到LRU的淘汰時Redis禁用共享對象池。
5字符串優化 ; 編碼優化;控制鍵的數量
9章 哨兵(故障自動處理 Redis sentinel實現故障發現 故障自動轉移 配置中心客戶端通知)
10章集羣
11章緩存設計
緩存能加速應用的讀寫速度,同時降低後端負載。
1、緩存的收益和成本:收益-加速讀寫,降低後端負載;成本-數據不一致,代碼維護成本;運維成本。緩存使用場景:開銷大的複雜計算;加速請求響應。
2、緩存更新策略的選擇和使用場景:低一致性業務建議配置最大內存和淘汰策略;高一致性業務使用超時剔除和主動更新(可以利用ans通知緩存更新)
3、緩存力度控制方法:緩存全部內容 or部分內容?
4、緩存穿透問題:穿透是查詢一個不存在的數據,緩存和存儲層都不會命中;緩存穿透將導致不存在的數據每次請求都要到存儲層查詢,原因:代碼數據問題,惡意攻擊
解決:1緩存空對象(問題:緩存需要空間多,設置更短的超時時間;數據不一致)2.對key做過濾攔截
5、緩存無底洞問題優化:添加太多的節點導致 集羣性能降低。批量操作key在不同的節點上,多網絡延遲
6、緩存雪崩問題優化:緩存突然大量失效,請求打到存儲層 調用暴增。解決:1、保證緩存層服務的高可用性;2、依賴隔離組件爲後端限流並降級。使用鎖,一次只能一個請求訪問後端獲取數據;緩存失效後端異步更新,
7、熱點key重建優化
熱點key,併發量大,重建緩存不能在短時間完成,可能是一個複雜計算,例如複雜SQL,多個依賴等。互斥鎖來實現:setnx;2 永不過期:緩存層面不過期;功能層面 爲每個value設置一個邏輯過期時間(超時後,使用單獨現成去構建緩存),但是會有數據不一致的風險。