Redis 深刻學習知識開發方向

Redis特性

爲何Redis快

  1. 單線程,避免線程切換和資源競爭
  2. 非阻塞IO,使用epoll做爲IO多路複用的技術實現,處理客戶端請求時不會阻塞主線程
  3. 純內存訪問

優化網絡延遲

Redis的性能瓶頸多是網絡 優化方案:html

  1. 單機部署採用unix進程間通信代替TCP
  2. multi-key的方式合併指定,減小請求數。如使用mget
  3. 使用transaction、script合併requests和responses
  4. 使用pipeline合併response

數據結構

String

在Redis中String稱爲動態字符串(檢測SDS Simple Dynamic String),內部數據結構相似ArrayList,維護着一個字節數組,而且內部預分配了必定的空間,減小內存頻繁分配。java

內存分配機制:

  • 當字符串長度小於1M時,每次擴容都是加倍現有空間
  • 當字符串長度超過1M時,每次擴容只擴展1MB的空間 字符串的最大長度爲512M

內部編碼

  • int: 8個字節的長整數
  • embstr: 小於等於39字節的字符串
  • raw: 大於39個字節的字符串

數據結構:

struct SDS{
  T capacity;       //數組容量
  T len;            //實際長度
  byte flages;  //標誌位,低三位表示類型
  byte[] content;   //數組內容
}
複製代碼

capacity 和 len兩個屬性都是泛型,爲何不直接用int類型?由於Redis內部有不少優化方案,爲更合理的使用內存,不一樣長度的字符串採用不一樣的數據類型表示,且在建立字符串的時候 len 會和 capacity 同樣大,不產生冗餘的空間,因此String值能夠是字符串、數字(整數、浮點數) 或者 二進制。面試

應用場景

  1. 緩存
  2. 計數 統計訪問次數
  3. 共享session

經常使用命令

set   [key]  [value]   給指定key設置值(set 可覆蓋老的值)
get  [key]   獲取指定key 的值
del  [key]   刪除指定key
exists  [key]  判斷是否存在指定key
mset  [key1]  [value1]  [key2]  [value2] ...... 批量存鍵值對
mget  [key1]  [key2] ......   批量取key
expire [key]  [time]    給指定key 設置過時時間  單位秒
setex    [key]  [time]  [value]  等價於 set + expire 命令組合
setnx  [key]  [value]   若是key不存在則set 建立,不然返回0
incr   [key]           若是value爲整數 可用 incr命令每次自增1
incrby  [key] [number]  使用incrby命令對整數值 進行增長 number

複製代碼

List

Redis中的List和Java的LinkedList很像,底層都是鏈表結構,插入和刪除操做很是快時間複雜度O(1)。當數據量較少時底層的結構爲一塊連續的累成,稱爲ziplist(壓縮列表),它講全部的元素緊挨一塊兒存儲,分配的是一塊連續的內存。當數據較多的時候回變成quicklist(快速與列表)。單純鏈表須要維護prev和next指針須要佔用較多內存。在redis3.2以後採用ziplist+鏈表的混合結構。簡稱爲quicklistredis

內部編碼

  • ziplist,當列表中的元素個數小於list-max-ziplist-entries(默認512),而且列表中每一個元素值都小於list-max-ziplist-value(默認64),來減小內存使用
  • linkedlist: 當列表類型沒法知足ziplist條件時,redis使用linkedlist做爲列表內部實現

應用場景

  1. 消息隊列:經過lpop和rpush(或rpop和lpush)實現隊列功能
  2. 列表,朋友圈點贊列表、評論列表、排行榜。lpush明麗和lrange命令實現最新列表的功能。

經常使用命令

rpush  [key] [value1] [value2] ......    鏈表右側插入

rpop    [key]  移除右側列表頭元素,並返回該元素

lpop   [key]    移除左側列表頭元素,並返回該元素

llen  [key]     返回該列表的元素個數

lrem [key] [count] [value]  刪除列表中與value相等的元素,count是刪除的個數。 count>0 表示從左側開始查找,刪除count個元素,count<0 表示從右側開始查找,刪除count個相同元素,count=0 表示刪除所有相同的元素

(PS:   index 表明元素下標,index 能夠爲負數, index= 表示倒數第一個元素,同理 index=-2 表示倒數第二 個元素。)

lindex [key] [index]  獲取list指定下標的元素 (須要遍歷,時間複雜度爲O(n))

lrange [key]  [start_index] [end_index]   獲取list 區間內的全部元素 (時間複雜度爲 O(n))

ltrim  [key]  [start_index] [end_index]   保留區間內的元素,其餘元素刪除(時間複雜度爲 O(n))

複製代碼

Hash

Redis中的Hash和Java中的HashMap很類似,採用數組+鏈表的結構,當發生hash碰撞時將元素追加到鏈表上。但Redis的Hash 只能是字符串 Hash和String均可以存儲用戶數據。可是Hash能夠對用戶信息的每一個字段單獨存儲;String存儲的是序列化以後的字符串。從修改角度考慮,使用hash存儲可針對某個字段進行修改,網絡帶寬。可是Hash的內存佔用要大於String算法

內部編碼

  1. ziplist(壓縮列表),當Hash類的元素小於hash-max-ziplist-entries(默認512)同時全部值小於hash-max-ziplist-value(默認64),採用ziplist做爲hash的內部實現,ziplist採用更緊湊的數據結構實現多個元素的連續儲存,節約內存比hashtable更優
  2. hashtable(哈希表):當哈希類型沒法知足ziplist條件時,會採用hashable做爲哈希的內部實現,由於此時ziplist讀寫效率降低,而hashtable的讀寫複雜度都爲O(1)

應用場景

  1. 購物車:hset [key] [field] [value] 命令, 能夠實現以用戶Id,商品Id爲field,商品數量爲value,剛好構成了購物車的3個要素。
  2. 存儲對象:hash類型的(key, field, value)的結構與對象的(對象id, 屬性, 值)的結構類似,也能夠用來存儲對象。

經常使用命令

hset  [key]  [field] [value]    新建字段信息

hget  [key]  [field]    獲取字段信息

hdel [key] [field]  刪除字段

hlen  [key]   保存的字段個數

hgetall  [key]  獲取指定key 字典裏的全部字段和值 (字段信息過多,會致使慢查詢 慎用:親身經歷 曾經用過這個這個指令致使線上服務故障)

hmset  [key]  [field1] [value1] [field2] [value2] ......   批量建立

hincr  [key] [field]   對字段值自增

hincrby [key] [field] [number] 對字段值增長number

複製代碼

Set

Redis中的Set和HashSet相似,內部的鍵值對是無序惟一的。它的內部實現至關於一個特殊的字典,字典中全部的vlaue都是一個值null.當集合最後一個元素被移除以後,數據結構自動刪除,內存被回收。數據庫

內部編碼

  • intset(整形集合),當集合中的元素都是整數且個數小於set-max-intset-entries配置(默認512個),Redis會選用intset做爲集合的內部實現,從而減小內存的使用。
  • hashtable,當集合沒法知足intset,redsi會使用hashtable做爲集合內部實現

應用場景

  1. 好友集合
  2. 隨機展現。推薦商家用srandmember中隨機選取幾個
  3. 去重功能

經常使用命令

sadd  [key]  [value]  向指定key的set中添加元素

smembers [key]    獲取指定key 集合中的全部元素

sismember [key] [value]   判斷集合中是否存在某個value

scard [key]    獲取集合的長度

spop  [key]   彈出一個元素

srem [key] [value]  刪除指定元素

複製代碼

zset 有序集合

Zset保證了內部value的惟一性,另外能夠給每一個value賦值score,表明這個value的權重。內部實現用的是一種叫作跳躍列表的數據結構。數組

內部編碼

  • ziplist:當有序列表中的元素小於zset-max-ziplist-entries(默認128),每一個元素值小於zset-max-ziplist-value(默認64),採用ziplist減小內存
  • skiplist(跳躍列表),當ziplist條件不足時,有序集合會使用skiplist做爲內部實現,由於此時ziplist的讀寫效率會降低

應用場景

  1. 排行榜,zset能懟數據進行動態排列

經常使用命令

zadd [key] [score] [value] 向指定key的集合中增長元素

zrange [key] [start_index] [end_index] 獲取下標範圍內的元素列表,按score 排序輸出

zrevrange [key] [start_index] [end_index]  獲取範圍內的元素列表 ,按score排序 逆序輸出

zcard [key]  獲取集合列表的元素個數

zrank [key] [value]  獲取元素再集合中的排名

zrangebyscore [key] [score1] [score2]  輸出score範圍內的元素列表

zrem [key] [value]  刪除元素

zscore [key] [value] 獲取元素的score


複製代碼

持久化

RDB

RDB持久化是把當前進程數據生成快照保存到硬盤的過程,觸發RDB持久化的過程分爲手動觸發(bgsave命令)緩存

RDB配置

  • 保存:RDB文件保存在dir配置指定目錄下,文件名可經過dbfilename配置。
  • 壓縮:Redis默認採用LZF算法對生成的RDB文件進行壓縮處理,壓縮後的文件遠遠小於內存大小,默認開啓
  • 自動觸發配置
save 900 1 
save 300 10
save 60 10000

900秒以內,若是超過1個key被修改,則發起快照保存;
300秒內,若是超過10個key被修改,則發起快照保存;
1分鐘以內,若是1萬個key被修改,則發起快照保存;
複製代碼

bgsave流程說明

  1. 執行bgsave命令,Redis父線程判斷是否有其餘正在執行的子進程(如RDB/AOF子進程)
  2. 父線程fork子進程,fork操做過程當中會父線程會阻塞
  3. fork完成後,bgsave返回「background saving started」信息,並不在阻塞父進程
  4. 子進程創造RDB文件,根據父線程內存生成臨時快照文件,完成後對原有文件進行原子替換,
  5. 子進程發送信號給父進程表示完成,父進程更新統計信息

優缺點

優勢安全

  1. RDB採用緊密壓縮的二進制文件,表明Redis在某個時間上的數據快照,很是適合備份和全量複製等場景
  2. Redis加載RDB恢復數據遠遠塊於AOF方式 缺點
  3. 沒有辦法作到實時持久化/秒級持久化。bgsave命令每次運行都要執行fork操做建立子進程,屬於重量級操做,頻繁執行成本太高
  4. 採用二進制格式保存,存在老版本redis服務沒法兼容新版RDB格式的問題

AOF

AOF(append only file)持久化;以獨立日誌的方式記錄每次寫命令,重啓時再衝洗執行AOF命令文件達到回覆數據的目的。bash

AOF配置

appendfsync yes   #默認不開啓
appendfsync always     #每次有數據修改發生時都會寫入AOF文件。
appendfsync everysec   #每秒鐘同步一次,該策略爲AOF的缺省策略。
複製代碼

AOF使用流程

  1. 全部的寫入命令會追加到aof_buf(緩衝區)中
  2. AOF緩衝區根據對應的策略向硬盤作同步
  3. 隨着AOF文件的增大,須要按期對AOF文件進行重寫
  4. 當Redis服務器重啓時,能夠僞裝AOF文件進行數據恢復

優缺點

能保存較實時的數據,存儲的數據文件較大,速度慢於RDB

思考

  1. 爲啥AOF採用文件協議格式? 文件協議具備良好的兼容性,開啓AOF後,全部寫入命令都包含追加操做,直接採用協議格式,避免了二次處理開銷 文件協議具備可行性,方便直接修改和處理
  2. AOF爲何將數據最佳到aof_buf中 Redis使用單線程響應命令,若是每次寫AOF命令都追加到磁盤,性能徹底取決於磁盤的負載。多種緩衝區同步磁盤的策略,在性能和安全性方面作出平衡。

內存管理

設置內存上線

maxmemory限制的是Redis實際使用的內存量,也就是used_memory統計項對應的內存,因爲內存碎片率的存在,實際消費的內存可能迴避maxmemory設置更大。

目的

  1. 用於緩衝場景,當超出內存上限maxmemory時使用LUR等刪除策略釋放空間
  2. 防止所用內存超過服務器物理內存

動態設置內存上限

config set maxmemory 6GB

內存回收策略

刪除過時鍵對象

  • 惰性刪除:客戶端讀取帶有超時屬性的鍵時,若是已經超過鍵設置的過時實際,會執行刪除命令並返回爲空
  • 定時任務刪除:Redis內部維護一個定時任務,默認每秒運行10秒,定時任務中刪除邏輯採用自適應算法 定時刪除自適應算法(默認採用慢模式運行)
  1. 定時任務在每一個數據庫空間隨機檢查20個鍵,當發現過時刪除對應鍵
  2. 若是超過檢查數25%的鍵過時,循環執行回收邏輯知道不足25%或運行超時爲止,慢模式下超時時間爲25毫秒
  3. 若是回收超時,Redis觸發內部時間再次以快模式運行回收鍵任務,快模式下超時時間爲1毫秒且2秒內只能運行一次(快慢模式內部刪除邏輯相同,只是執行的超時時間不一樣)

內存溢出控制策略

  1. volatile-lru:從已設置過時時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
  2. volatile-ttl:從已設置過時時間的數據集(server.db[i].expires)中挑選將要過時的數據淘汰
  3. volatile-random:從已設置過時時間的數據集(server.db[i].expires)中任意選擇數據淘汰
  4. allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰allkeys-
  5. random:從數據集(server.db[i].dict)中任意選擇數據淘汰
  6. no-enviction(默認):拒絕全部寫請求,只響應讀請求

持久化中的過時鍵

  • RDB 文件分爲兩個階段,RDB 文件生成階段和加載階段。
    1. 從內存狀態持久化成 RDB(文件)的時候,會對 key 進行過時檢查,過時的鍵不會被保存到新的 RDB 文件中,所以 Redis 中的過時鍵不會對生成新 RDB 文件產生任何影響。
    2. 若是 Redis 是主服務器運行模式的話,在載入 RDB 文件時,程序會對文件中保存的鍵進行檢查,過時鍵不會被載入到數據庫中。因此過時鍵不會對載入 RDB 文件的主服務器形成影響;
    3. 若是 Redis 是從服務器運行模式的話,在載入 RDB 文件時,不論鍵是否過時都會被載入到數據庫中。但因爲主從服務器在進行數據同步時,從服務器的數據會被清空。因此通常來講,過時鍵對載入 RDB 文件的從服務器也不會形成影響。

緩存雪崩、緩存穿透、緩存擊透、緩存降級等問題

緩存雪崩

緩存雪崩是指一段時間內緩存集中失效的問題。全部的查詢都落到的數據庫上,對數據庫CPU和內存形成巨大壓力,嚴重的會形成數據庫宕機。 解決方案:

  1. 緩存時間增長隨機因子,經量分散緩存過時時間
  2. 經過消息隊列方式老保證不會有大量線程對數據進行一次性讀寫

緩存穿透

緩存穿透指緩存和數據庫中都沒有數據用戶要查詢的數據,每次都進行2次查詢。如有人惡意攻擊,對數據庫形成壓力可能會壓垮數據庫。 解決方案:

  1. 布隆過濾用於存儲可能訪問的key,布隆過濾用於大數據量的集合中判斷元素是否存在(必定不存在或可能存在)
  2. 對於數據庫取不到的數據,寫入緩存中。緩存時間能夠設置適當短一點。

緩存擊透

緩存擊透是指緩存中沒有可是數據庫中有的數據,因爲併發量大,同時讀取緩存沒有數據而致使同時去數據庫中取數據,形成數據庫壓力過大。 解決方案:

  1. 熱點數據不過時
  2. 設置互斥鎖,當獲取緩存爲空時候上鎖,從數據庫加載完畢後是否鎖。若其餘線程獲取鎖,睡眠50ms後從新嘗試。這裏的鎖須要考慮java併發包和集羣環境下的分佈式鎖

緩存降級

緩存降級指當訪問量忽然劇增,服務出現問題或者非核心業務影響到核心業務性能時,須要包裝服務仍是可用。系統可用根據一些關鍵數據進行降級,來保證核心業務的可用。例如redis中適當刪除非關鍵緩存數據。

參考文章

付磊、張益軍《Redis開發與運維》

Redis 性能問題分析

Redis數據結構及對應使用場景,看一次就整明白得了

Redis面試全攻略,讀完這個就能夠和麪試官大戰幾個回合了

#TODO 針對Redis數據結構學習+針對分佈式和運維相關的瞭解

相關文章
相關標籤/搜索