Redis 是一個開源的,基於內存的結構化數據存儲媒介,能夠做爲數據庫、緩存服務或消息服務使用。```php
- Redis 支持多種數據結構,包括字符串、哈希表、鏈表、集合、有序集合、位圖、Hyperloglogs 等。
- Redis 具有 LRU 淘汰、事務實現、以及不一樣級別的硬盤持久化等能力,而且支持副本集和經過 Redis Sentinel 實現的高可用方案,同時還支持經過 Redis Cluster 實現的數據自動分片能力。
Redis 的主要功能都基於單線程模型實現,也就是說 Redis 使用一個線程來服務全部的客戶端請求,同時 Redis 採用了非阻塞式 IO,並精細地優化各類命令的算法時間複雜度,這些信息意味着:redis
- Redis 是線程安全的(由於只有一個線程),其全部操做都是原子的,不會因併發產生數據異常
- Redis 的速度很是快(由於使用非阻塞式 IO,且大部分命令的算法時間複雜度都是 O (1))
- 使用高耗時的 Redis 命令是很危險的,會佔用惟一的一個線程的大量處理時間,致使全部的請求都被拖慢。(例如時間複雜度爲 O (N) 的 KEYS 命令,嚴格禁止在生產環境中使用
完整的 Redis 命令集,或瞭解某個命令的詳細使用方法,請參考官方文檔:https://redis.io/commands
Key算法
Redis 採用 Key-Value 型的基本數據結構,任何二進制序列均可以做爲 Redis 的 Key 使用(例如普通的字符串或一張 JPEG 圖片)shell
關於Key的一些注意事項:
數據庫
- 不要使用過長的 Key。例如使用一個 1024 字節的 key 就不是一個好主意,不只會消耗更多的內存,還會致使查找的效率下降
- Key 短到缺失了可讀性也是很差的,例如」u1000flw」 比起」user:1000:followers」 來講,節省了寥寥的存儲空間,卻引起了可讀性和可維護性上的麻煩
- 最好使用統一的規範來設計 Key,好比」object-type:id:attr」,以這一規範設計出的 Key 多是」user:1000″或」comment:1234:reply-to」
- Redis 容許的最大 Key 長度是 512MB(對 Value 的長度限制也是 512MB)
String數組
String 是 Redis 的基礎數據類型,Redis 沒有 Int、Float、Boolean 等數據類型的概念,全部的基本類型在 Redis 中都以 String 體現。緩存
與String相關的經常使用命令:
安全
- SET:爲一個 key 設置 value,能夠配合 EX/PX 參數指定 key 的有效期,經過 NX/XX 參數針對 key 是否存在的狀況進行區別操做,時間複雜度 O (1)
- GET:獲取某個 key 對應的 value,時間複雜度 O (1)
- GETSET:爲一個 key 設置 value,並返回該 key 的原 value,時間複雜度 O (1)
- MSET:爲多個 key 設置 value,時間複雜度 O (N)
- MSETNX:同 MSET,若是指定的 key 中有任意一個已存在,則不進行任何操做,時間複雜度 O (N)
- MGET:獲取多個 key 對應的 value,時間複雜度 O (N)
上文提到過,Redis的基本數據類型只有String,但Redis能夠把String做爲整型或浮點型數字來使用,主要體如今INCR、DECR類的命令上:
性能優化
- INCR:將 key 對應的 value 值自增 1,並返回自增後的值。只對能夠轉換爲整型的 String 數據起做用。時間複雜度 O (1)
- INCRBY:將 key 對應的 value 值自增指定的整型數值,並返回自增後的值。只對能夠轉換爲整型的 String 數據起做用。時間複雜度 O (1)
- DECR/DECRBY:同 INCR/INCRBY,自增改成自減。
- INCR/DECR 系列命令要求操做的 value 類型爲 String,並能夠轉換爲 64 位帶符號的整型數字,不然會返回錯誤。
也就是說,進行 INCR/DECR 系列命令的 value,必須在 [-2^63 ~ 2^63 – 1] 範圍內。
前文提到過,Redis 採用單線程模型,自然是線程安全的,這使得 INCR/DECR 命令能夠很是便利的實現高併發場景下的精確控制。微信
例1:庫存控制
- 在高併發場景下實現庫存餘量的精準校驗,確保不出現超賣的狀況。
- 設置庫存總量:
SET inv:remain "100"
- 庫存扣減 + 餘量校驗:
`DECR inv:remain`
- 當 DECR 命令返回值大於等於 0 時,說明庫存餘量校驗經過,若是返回小於 0 的值,則說明庫存已耗盡。
- 假設同時有 300 個併發請求進行庫存扣減,Redis 可以確保這 300 個請求分別獲得 99 到 - 200 的返回值,每一個請求獲得的返回值都是惟一的,絕對不會找出現兩個請求獲得同樣的返回值的狀況。
例2:自增序列生成
- 實現相似於 RDBMS 的 Sequence 功能,生成一系列惟一的序列號
- 設置序列起始值:
SET sequence "10000"
- 獲取一個序列值:
INCR sequence- 直接將返回值做爲序列使用便可。
- 獲取一批(如 100 個)序列值:
INCRBY sequence 100
- 假設返回值爲 N,那麼 [N – 99 ~ N] 的數值都是可用的序列值。
- 當多個客戶端同時向 Redis 申請自增序列時,Redis 可以確保每一個客戶端獲得的序列值或序列範圍都是全局惟一的,絕對不會出現不一樣客戶端獲得了重複的序列值的狀況。
List
Redis 的 List 是鏈表型的數據結構,可使用 LPUSH/RPUSH/LPOP/RPOP 等命令在 List 的兩端執行插入元素和彈出元素的操做。雖然 List 也支持在特定 index 上插入和讀取元素的功能,但其時間複雜度較高(O (N)),應當心使用。
與List相關的經常使用命令:
- LPUSH:向指定 List 的左側(即頭部)插入 1 個或多個元素,返回插入後的 List 長度。時間複雜度 O (N),N 爲插入元素的數量
- RPUSH:同 LPUSH,向指定 List 的右側(即尾部)插入 1 或多個元素
- LPOP:從指定 List 的左側(即頭部)移除一個元素並返回,時間複雜度 O (1)
- RPOP:同 LPOP,從指定 List 的右側(即尾部)移除 1 個元素並返回
- LPUSHX/RPUSHX:與 LPUSH/RPUSH 相似,區別在於,LPUSHX/RPUSHX 操做的 key 若是不存在,則不會進行任何操做
- LLEN:返回指定 List 的長度,時間複雜度 O (1)
- LRANGE:返回指定 List 中指定範圍的元素(雙端包含,即 LRANGE key 0 10 會返回 11 個元素),時間複雜度 O (N)。應儘量控制一次獲取的元素數量,一次獲取過大範圍的 List 元素會致使延遲,同時對長度不可預知的 List,避免使用 LRANGE key 0 -1 這樣的完整遍歷操做。
應謹慎使用的List相關命令:
- LINDEX:返回指定 List 指定 index 上的元素,若是 index 越界,返回 nil。index 數值是迴環的,即 - 1 表明 List 最後一個位置,-2 表明 List 倒數第二個位置。時間複雜度 O (N)
- LSET:將指定 List 指定 index 上的元素設置爲 value,若是 index 越界則返回錯誤,時間複雜度 O (N),若是操做的是頭 / 尾部的元素,則時間複雜度爲 O (1)
- LINSERT:向指定 List 中指定元素以前 / 以後插入一個新元素,並返回操做後的 List 長度。若是指定的元素不存在,返回 - 1。若是指定 key 不存在,不會進行任何操做,時間複雜度 O (N)
- 因爲 Redis 的 List 是鏈表結構的,上述的三個命令的算法效率較低,須要對 List 進行遍歷,命令的耗時沒法預估,在 List 長度大的狀況下耗時會明顯增長,應謹慎使用。
換句話說,Redis 的 List 實際是設計來用於實現隊列,而不是用於實現相似 ArrayList 這樣的列表的。若是你不是想要實現一個雙端出入的隊列,那麼請儘可能不要使用 Redis 的 List 數據結構。
爲了更好支持隊列的特性,Redis 還提供了一系列阻塞式的操做命令,如 BLPOP/BRPOP 等,可以實現相似於 BlockingQueue 的能力,即在 List 爲空時,阻塞該鏈接,直到 List 中有對象能夠出隊時再返回。針對阻塞類的命令,此處不作詳細探討,請參考官方文檔(https://redis.io/topics/data-types-intro) 中」Blocking operations on lists」 一節。
Hash
- Hash 即哈希表,Redis 的 Hash 和傳統的哈希表同樣,是一種 field-value 型的數據結構,能夠理解成將 HashMap 搬入 Redis。
- Hash 很是適合用於表現對象類型的數據,用 Hash 中的 field 對應對象的 field 便可。
Hash的優勢
- 能夠實現二元查找,如」 查找 ID 爲 1000 的用戶的年齡」
- 比起將整個對象序列化後做爲 String 存儲的方法,Hash 可以有效地減小網絡傳輸的消耗
- 當使用 Hash 維護一個集合時,提供了比 List 效率高得多的隨機訪問命令
與Hash相關的經常使用命令
- HSET:將 key 對應的 Hash 中的 field 設置爲 value。若是該 Hash 不存在,會自動建立一個。時間複雜度 O (1)
- HGET:返回指定 Hash 中 field 字段的值,時間複雜度 O (1)
- HMSET/HMGET:同 HSET 和 HGET,能夠批量操做同一個 key 下的多個 field,時間複雜度:O (N),N 爲一次操做的 field 數量
- HSETNX:同 HSET,但如 field 已經存在,HSETNX 不會進行任何操做,時間複雜度 O (1)
- HEXISTS:判斷指定 Hash 中 field 是否存在,存在返回 1,不存在返回 0,時間複雜度 O (1)
- HDEL:刪除指定 Hash 中的 field(1 個或多個),時間複雜度:O (N),N 爲操做的 field 數量
- HINCRBY:同 INCRBY 命令,對指定 Hash 中的一個 field 進行 INCRBY,時間複雜度 O (1)
應謹慎使用的Hash相關命令:
- HGETALL:返回指定 Hash 中全部的 field-value 對。返回結果爲數組,數組中 field 和 value 交替出現。時間複雜度 O (N)
- HKEYS/HVALS:返回指定 Hash 中全部的 field/value,時間複雜度 O (N)
上述三個命令都會對 Hash 進行完整遍歷,Hash 中的 field 數量與命令的耗時線性相關,對於尺寸不可預知的 Hash,應嚴格避免使用上面三個命令,而改成使用 HSCAN 命令進行遊標式的遍歷,具體請見 https://redis.io/commands/scan
Set
Redis Set 是無序的,不可重複的 String 集合。
與Set相關的經常使用命令
- SADD:向指定 Set 中添加 1 個或多個 member,若是指定 Set 不存在,會自動建立一個。時間複雜度 O (N),N 爲添加的 member 個數
- SREM:從指定 Set 中移除 1 個或多個 member,時間複雜度 O (N),N 爲移除的 member 個數
- SRANDMEMBER:從指定 Set 中隨機返回 1 個或多個 member,時間複雜度 O (N),N 爲返回的 member 個數
- SPOP:從指定 Set 中隨機移除並返回 count 個 member,時間複雜度 O (N),N 爲移除的 member 個數
- SCARD:返回指定 Set 中的 member 個數,時間複雜度 O (1)
- SISMEMBER:判斷指定的 value 是否存在於指定 Set 中,時間複雜度 O (1)
- SMOVE:將指定 member 從一個 Set 移至另外一個 Set
慎用的Set相關命令:
- SMEMBERS:返回指定 Hash 中全部的 member,時間複雜度 O (N)
- SUNION/SUNIONSTORE:計算多個 Set 的並集並返回 / 存儲至另外一個 Set 中,時間複雜度 O (N),N 爲參與計算的全部集合的總 member 數
- SINTER/SINTERSTORE:計算多個 Set 的交集並返回 / 存儲至另外一個 Set 中,時間複雜度 O (N),N 爲參與計算的全部集合的總 member 數
- SDIFF/SDIFFSTORE:計算 1 個 Set 與 1 或多個 Set 的差集並返回 / 存儲至另外一個 Set 中,時間複雜度 O (N),N 爲參與計算的全部集合的總 member 數
上述幾個命令涉及的計算量大,應謹慎使用,特別是在參與計算的 Set 尺寸不可知的狀況下,應嚴格避免使用。能夠考慮經過 SSCAN 命令遍歷獲取相關 Set 的所有 member(具體請見 https://redis.io/commands/scan ),若是須要作並集 / 交集 / 差集計算,能夠在客戶端進行,或在不服務實時查詢請求的 Slave 上進行。
Sorted Set
- Redis Sorted Set 是有序的、不可重複的 String 集合。Sorted Set 中的每一個元素都須要指派一個分數 (score),Sorted Set 會根據 score 對元素進行升序排序。若是多個 member 擁有相同的 score,則以字典序進行升序排序。
- Sorted Set 很是適合用於實現排名。
Sorted Set的主要命令:
- ZADD:向指定 Sorted Set 中添加 1 個或多個 member,時間複雜度 O (Mlog (N)),M 爲添加的 member 數量,N 爲 Sorted Set 中的 member 數量
- ZREM:從指定 Sorted Set 中刪除 1 個或多個 member,時間複雜度 O (Mlog (N)),M 爲刪除的 member 數量,N 爲 Sorted Set 中的 member 數量
- ZCOUNT:返回指定 Sorted Set 中指定 score 範圍內的 member 數量,時間複雜度:O (log (N))
- ZCARD:返回指定 Sorted Set 中的 member 數量,時間複雜度 O (1)
- ZSCORE:返回指定 Sorted Set 中指定 member 的 score,時間複雜度 O (1)
- ZRANK/ZREVRANK:返回指定 member 在 Sorted Set 中的排名,ZRANK 返回按升序排序的排名,ZREVRANK 則返回按降序排序的排名。時間複雜度 O (log (N))
- ZINCRBY:同 INCRBY,對指定 Sorted Set 中的指定 member 的 score 進行自增,時間複雜度 O (log (N))
慎用的Sorted Set相關命令:
- ZRANGE/ZREVRANGE:返回指定 Sorted Set 中指定排名範圍內的全部 member,ZRANGE 爲按 score 升序排序,ZREVRANGE 爲按 score 降序排序,時間複雜度 O (log (N)+M),M 爲本次返回的 member 數
- ZRANGEBYSCORE/ZREVRANGEBYSCORE:返回指定 Sorted Set 中指定 score 範圍內的全部 member,返回結果以升序 / 降序排序,min 和 max 能夠指定爲 - inf 和 + inf,表明返回全部的 member。時間複雜度 O (log (N)+M)
- ZREMRANGEBYRANK/ZREMRANGEBYSCORE:移除 Sorted Set 中指定排名範圍 / 指定 score 範圍內的全部 member。時間複雜度 O (log (N)+M)
上述幾個命令,應儘可能避免傳遞 [0 -1] 或 [-inf +inf] 這樣的參數,來對 Sorted Set 作一次性的完整遍歷,特別是在 Sorted Set 的尺寸不可預知的狀況下。能夠經過 ZSCAN 命令來進行遊標式的遍歷(具體請見 https://redis.io/commands/scan ),或經過 LIMIT 參數來限制返回 member 的數量(適用於 ZRANGEBYSCORE 和 ZREVRANGEBYSCORE 命令),以實現遊標式的遍歷。
Bitmap 和 HyperLogLog
- Redis 的這兩種數據結構相較以前的並不經常使用,在本文中只作簡要介紹,如想要詳細瞭解這兩種數據結構與其相關的命令,請參考官方文檔 https://redis.io/topics/data-types-intro 中的相關章節
- Bitmap 在 Redis 中不是一種實際的數據類型,而是一種將 String 做爲 Bitmap 使用的方法。能夠理解爲將 String 轉換爲 bit 數組。使用 Bitmap 來存儲 true/false 類型的簡單數據極爲節省空間。
- HyperLogLogs 是一種主要用於數量統計的數據結構,它和 Set 相似,維護一個不可重複的 String 集合,可是 HyperLogLogs 並不維護具體的 member 內容,只維護 member 的個數。也就是說,HyperLogLogs 只能用於計算一個集合中不重複的元素數量,因此它比 Set 要節省不少內存空間。
其餘經常使用命令
- EXISTS:判斷指定的 key 是否存在,返回 1 表明存在,0 表明不存在,時間複雜度 O (1)
- DEL:刪除指定的 key 及其對應的 value,時間複雜度 O (N),N 爲刪除的 key 數量
- EXPIRE/PEXPIRE:爲一個 key 設置有效期,單位爲秒或毫秒,時間複雜度 O (1)
- TTL/PTTL:返回一個 key 剩餘的有效時間,單位爲秒或毫秒,時間複雜度 O (1)
- RENAME/RENAMENX:將 key 重命名爲 newkey。使用 RENAME 時,若是 newkey 已經存在,其值會被覆蓋;使用 RENAMENX 時,若是 newkey 已經存在,則不會進行任何操做,時間複雜度 O (1)
- TYPE:返回指定 key 的類型,string, list, set, zset, hash。時間複雜度 O (1)
- CONFIG GET:得到 Redis 某配置項的當前值,可使用 * 通配符,時間複雜度 O (1)
- CONFIG SET:爲 Redis 某個配置項設置新值,時間複雜度 O (1)
- CONFIG REWRITE:讓 Redis 從新加載 redis.conf 中的配置
Redis 提供了將數據按期自動持久化至硬盤的能力,包括 RDB 和 AOF 兩種方案,兩種方案分別有其長處和短板,能夠配合起來同時運行,確保數據的穩定性。
必須使用數據持久化嗎?
Redis 的數據持久化機制是能夠關閉的。若是你只把 Redis 做爲緩存服務使用,Redis 中存儲的全部數據都不是該數據的主體而僅僅是同步過來的備份,那麼能夠關閉 Redis 的數據持久化機制。
但一般來講,仍然建議至少開啓 RDB 方式的數據持久化,由於:
- RDB 方式的持久化幾乎不損耗 Redis 自己的性能,在進行 RDB 持久化時,Redis 主進程惟一須要作的事情就是 fork 出一個子進程,全部持久化工做都由子進程完成
- Redis 不管由於什麼緣由 crash 掉以後,重啓時可以自動恢復到上一次 RDB 快照中記錄的數據。這省去了手工從其餘數據源(如 DB)同步數據的過程,並且要比其餘任何的數據恢復方式都要快
- 如今硬盤那麼大,真的不缺那一點地方
RDB採用RDB持久方式,
- Redis 會按期保存數據快照至一個 rbd 文件中,並在啓動時自動加載 rdb 文件,恢復以前保存的數據。能夠在配置文件中配置 Redis 進行快照保存的時機:
save [seconds] [changes]
意爲在 [seconds] 秒內若是發生了 [changes] 次數據修改,則進行一次 RDB 快照保存,例如
save 60 100
- 會讓 Redis 每 60 秒檢查一次數據變動狀況,若是發生了 100 次或以上的數據變動,則進行 RDB 快照保存。
- 能夠配置多條 save 指令,讓 Redis 執行多級的快照保存策略。
- Redis 默認開啓 RDB 快照,默認的 RDB 策略以下:
save 900 1 save 300 10 save 60 10000
也能夠經過BGSAVE命令手工觸發RDB快照保存。
RDB的優勢:
- 對性能影響最小。如前文所述,Redis 在保存 RDB 快照時會 fork 出子進程進行,幾乎不影響 Redis 處理客戶端請求的效率。
- 每次快照會生成一個完整的數據快照文件,因此能夠輔以其餘手段保存多個時間點的快照(例如把天天 0 點的快照備份至其餘存儲媒介中),做爲很是可靠的災難恢復手段。
- 使用 RDB 文件進行數據恢復比使用 AOF 要快不少。
RDB的缺點:
- 快照是按期生成的,因此在 Redis crash 時或多或少會丟失一部分數據。
- 若是數據集很是大且 CPU 不夠強(好比單核 CPU),Redis 在 fork 子進程時可能會消耗相對較長的時間(長至 1 秒),影響這期間的客戶端請求。
AOF
- 採用 AOF 持久方式時,Redis 會把每個寫請求都記錄在一個日誌文件裏。在 Redis 重啓時,會把 AOF 文件中記錄的全部寫操做順序執行一遍,確保數據恢復到最新。
- AOF 默認是關閉的,如要開啓,進行以下配置:
appendonly yes- AOF 提供了三種 fsync 配置,always/everysec/no,
經過配置項[appendfsync]指定
- appendfsync no:不進行 fsync,將 flush 文件的時機交給 OS 決定,速度最快
- appendfsync always:每寫入一條日誌就進行一次 fsync 操做,數據安全性最高,但速度最慢
appendfsync everysec:折中的作法,交由後臺線程每秒 fsync 一次
- 隨着 AOF 不斷地記錄寫操做日誌,一定會出現一些無用的日誌,例如某個時間點執行了命令 SET key1 「abc」,在以後某個時間點又執行了 SET key1 「bcd」,那麼第一條命令很顯然是沒有用的。大量的無用日誌會讓 AOF 文件過大,也會讓數據恢復的時間過長。
- 因此 Redis 提供了 AOF rewrite 功能,能夠重寫 AOF 文件,只保留可以把數據恢復到最新狀態的最小寫操做集。
- AOF rewrite 能夠經過 BGREWRITEAOF 命令觸發,也能夠配置 Redis 按期自動進行:
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
- 上面兩行配置的含義是,Redis 在每次 AOF rewrite 時,會記錄完成 rewrite 後的 AOF 日誌大小,當 AOF 日誌大小在該基礎上增加了 100% 後,自動進行 AOF rewrite。同時若是增加的大小沒有達到 64mb,則不會進行 rewrite。
AOF的優勢:
- 最安全,在啓用 appendfsync always 時,任何已寫入的數據都不會丟失,使用在啓用 appendfsync everysec 也至多隻會丟失 1 秒的數據。
- AOF 文件在發生斷電等問題時也不會損壞,即便出現了某條日誌只寫入了一半的狀況,也可使用 redis-check-aof 工具輕鬆修復。
- AOF 文件易讀,可修改,在進行了某些錯誤的數據清除操做後,只要 AOF 文件沒有 rewrite,就能夠把 AOF 文件備份出來,把錯誤的命令刪除,而後恢復數據。
AOF的缺點:
- AOF 文件一般比 RDB 文件更大
- 性能消耗比 RDB 高
- 數據恢復速度比 RDB 慢
最大內存設置
- 默認狀況下,在 32 位 OS 中,Redis 最大使用 3GB 的內存,在 64 位 OS 中則沒有限制。
- 在使用 Redis 時,應該對數據佔用的最大空間有一個基本準確的預估,併爲 Redis 設定最大使用的內存。不然在 64 位 OS 中 Redis 會無限制地佔用內存(當物理內存被佔滿後會使用 swap 空間),容易引起各類各樣的問題。
經過以下配置控制 Redis 使用的最大內存:
maxmemory 100mb
在內存佔用達到了 maxmemory 後,再向 Redis 寫入數據時,Redis 會:
- 根據配置的數據淘汰策略嘗試淘汰數據,釋放空間
- 若是沒有數據能夠淘汰,或者沒有配置數據淘汰策略,那麼 Redis 會對全部寫請求返回錯誤,但讀請求仍然能夠正常執行
在爲 Redis 設置 maxmemory 時,須要注意:
- 若是採用了 Redis 的主從同步,主節點向從節點同步數據時,會佔用掉一部份內存空間,若是 maxmemory 過於接近主機的可用內存,致使數據同步時內存不足。因此設置的 maxmemory 不要過於接近主機可用的內存,留出一部分預留用做主從同步。
Redis 提供了 5 種數據淘汰策略:
- volatile-lru:使用 LRU 算法進行數據淘汰(淘汰上次使用時間最先的,且使用次數最少的 key),只淘汰設定了有效期的 key
- allkeys-lru:使用 LRU 算法進行數據淘汰,全部的 key 均可以被淘汰
- volatile-random:隨機淘汰數據,只淘汰設定了有效期的 key
- allkeys-random:隨機淘汰數據,全部的 key 均可以被淘汰
volatile-ttl:淘汰剩餘有效期最短的 key
- 最好爲 Redis 指定一種有效的數據淘汰策略以配合 maxmemory 設置,避免在內存使用滿後發生寫入失敗的狀況。
- 通常來講,推薦使用的策略是 volatile-lru,並辨識 Redis 中保存的數據的重要性。對於那些重要的,絕對不能丟棄的數據(如配置類數據等),應不設置有效期,這樣 Redis 就永遠不會淘汰這些數據。對於那些相對不是那麼重要的,而且可以熱加載的數據(好比緩存最近登陸的用戶信息,當在 Redis 中找不到時,程序會去 DB 中讀取),能夠設置上有效期,這樣在內存不夠時 Redis 就會淘汰這部分數據。
配置方法:
maxmemory-policy volatile-lru #默認是noeviction,即不進行數據淘汰
Pipelining
- Redis 提供許多批量操做的命令,如 MSET/MGET/HMSET/HMGET 等等,這些命令存在的意義是減小維護網絡鏈接和傳輸數據所消耗的資源和時間。
- 例如連續使用 5 次 SET 命令設置 5 個不一樣的 key,比起使用一次 MSET 命令設置 5 個不一樣的 key,效果是同樣的,但前者會消耗更多的 RTT (Round Trip Time) 時長,永遠應優先使用後者。
- 然而,若是客戶端要連續執行的屢次操做沒法經過 Redis 命令組合在一塊兒,例如:
SET a "abc" INCR b HSET c name "hi"
此時即可以使用 Redis 提供的 pipelining 功能來實如今一次交互中執行多條命令。
使用 pipelining 時,只須要從客戶端一次向 Redis 發送多條命令(以 rn)分隔,Redis 就會依次執行這些命令,而且把每一個命令的返回按順序組裝在一塊兒一次返回,好比:
$ (printf "PINGrnPINGrnPINGrn"; sleep 1) | nc localhost 6379 +PONG +PONG +PONG
大部分的 Redis 客戶端都對 Pipelining 提供支持,因此開發者一般並不須要本身手工拼裝命令列表。
Pipelining 的侷限性
- Pipelining 只能用於執行連續且無相關性的命令,當某個命令的生成須要依賴於前一個命令的返回時,就沒法使用 Pipelining 了。
- 經過 Scripting 功能,能夠規避這一侷限性
Pipelining 可以讓 Redis 在一次交互中處理多條命令,然而在一些場景下,咱們可能須要在此基礎上確保這一組命令是連續執行的。
好比獲取當前累計的 PV 數並將其清 0
>GET vCount 12384 > SET vCount 0 OK
- 若是在 GET 和 SET 命令之間插進來一個 INCR vCount,就會使客戶端拿到的 vCount 不許確。
- Redis 的事務能夠確保複數命令執行時的原子性。也就是說 Redis 可以保證:一個事務中的一組命令是絕對連續執行的,在這些命令執行完成以前,絕對不會有來自於其餘鏈接的其餘命令插進去執行。
- 經過 MULTI 和 EXEC 命令來把這兩個命令加入一個事務中:
MULTI OK GET vCount QUEUED SET vCount 0 QUEUED EXEC 1) 12384 2) OK
- Redis 在接收到 MULTI 命令後便會開啓一個事務,這以後的全部讀寫命令都會保存在隊列中但並不執行,直到接收到 EXEC 命令後,Redis 會把隊列中的全部命令連續順序執行,並以數組形式返回每一個命令的返回結果。
- 可使用 DISCARD 命令放棄當前的事務,將保存的命令隊列清空。
- 須要注意的是,Redis 事務不支持回滾:
- 若是一個事務中的命令出現了語法錯誤,大部分客戶端驅動會返回錯誤,2.6.5 版本以上的 Redis 也會在執行 EXEC 時檢查隊列中的命令是否存在語法錯誤,若是存在,則會自動放棄事務並返回錯誤。
- 但若是一個事務中的命令有非語法類的錯誤(好比對 String 執行 HSET 操做),不管客戶端驅動仍是 Redis 都沒法在真正執行這條命令以前發現,因此事務中的全部命令仍然會被依次執行。在這種狀況下,會出現一個事務中部分命令成功部分命令失敗的狀況,然而與 RDBMS 不一樣,Redis 不提供事務回滾的功能,因此只能經過其餘方法進行數據的回滾。
Redis 提供了 WATCH 命令與事務搭配使用,實現 CAS 樂觀鎖的機制。
假設要實現將某個商品的狀態改成已售:
if(exec(HGET stock:1001 state) == "in stock") exec(HSET stock:1001 state "sold");
這一僞代碼執行時,沒法確保併發安全性,有可能多個客戶端都獲取到了」in stock」 的狀態,致使一個庫存被售賣屢次。
使用 WATCH 命令和事務能夠解決這一問題:
exec(WATCH stock:1001); if(exec(HGET stock:1001 state) == "in stock") { exec(MULTI); exec(HSET stock:1001 state "sold"); exec(EXEC); }
WATCH 的機制是:在事務 EXEC 命令執行時,Redis 會檢查被 WATCH 的 key,只有被 WATCH 的 key 從 WATCH 起始時至今沒有發生過變動,EXEC 纔會被執行。若是 WATCH 的 key 在 WATCH 命令到 EXEC 命令之間發生過變化,則 EXEC 命令會返回失敗。
Scripting
- 經過 EVAL 與 EVALSHA 命令,可讓 Redis 執行 LUA 腳本。這就相似於 RDBMS 的存儲過程同樣,能夠把客戶端與 Redis 之間密集的讀 / 寫交互放在服務端進行,避免過多的數據交互,提高性能。
- Scripting 功能是做爲事務功能的替代者誕生的,事務提供的全部能力 Scripting 均可以作到。Redis 官方推薦使用 LUA Script 來代替事務,前者的效率和便利性都超過了事務。
- 關於 Scripting 的具體使用,本文不作詳細介紹,請參考官方文檔
https://redis.io/commands/evalRedis 性能調優
- 儘管 Redis 是一個很是快速的內存數據存儲媒介,也並不表明 Redis 不會產生性能問題。
- 前文中提到過,Redis 採用單線程模型,全部的命令都是由一個線程串行執行的,因此當某個命令執行耗時較長時,會拖慢其後的全部命令,這使得 Redis 對每一個任務的執行效率更加敏感。
針對 Redis 的性能優化,主要從下面幾個層面入手:
- 最初的也是最重要的,確保沒有讓 Redis 執行耗時長的命令
- 使用 pipelining 將連續執行的命令組合執行
- 操做系統的 Transparent huge pages 功能必須關閉:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
- 若是在虛擬機中運行 Redis,可能自然就有虛擬機環境帶來的固有延遲。能夠經過./redis-cli –intrinsic-latency 100 命令查看固有延遲。同時若是對 Redis 的性能有較高要求的話,應儘量在物理機上直接部署 Redis。
- 檢查數據持久化策略
- 考慮引入讀寫分離機制
- 長耗時命令
- Redis 絕大多數讀寫命令的時間複雜度都在 O (1) 到 O (N) 之間,在文本和官方文檔中均對每一個命令的時間複雜度有說明。
- 一般來講,O (1) 的命令是安全的,O (N) 命令在使用時須要注意,若是 N 的數量級不可預知,則應避免使用。例如對一個 field 數未知的 Hash 數據執行 HGETALL/HKEYS/HVALS 命令,一般來講這些命令執行的很快,但若是這個 Hash 中的 field 數量極多,耗時就會成倍增加。
- 又如使用 SUNION 對兩個 Set 執行 Union 操做,或使用 SORT 對 List/Set 執行排序操做等時,都應該嚴加註意。
避免在使用這些 O (N) 命令時發生問題主要有幾個辦法:
- 不要把 List 當作列表使用,僅當作隊列來使用
- 經過機制嚴格控制 Hash、Set、Sorted Set 的大小
- 可能的話,將排序、並集、交集等操做放在客戶端執行
- 絕對禁止使用 KEYS 命令
- 避免一次性遍歷集合類型的全部成員,而應使用 SCAN 類的命令進行分批的,遊標式的遍歷
Redis 提供了 SCAN 命令,能夠對 Redis 中存儲的全部 key 進行遊標式的遍歷,避免使用 KEYS 命令帶來的性能問題。同時還有 SSCAN/HSCAN/ZSCAN 等命令,分別用於對 Set/Hash/Sorted Set 中的元素進行遊標式遍歷。SCAN 類命令的使用請參考官方文檔:
https://redis.io/commands/scan
Redis 提供了 Slow Log 功能,能夠自動記錄耗時較長的命令。相關的配置參數有兩個:
slowlog-log-slower-than xxxms #執行時間慢於xxx毫秒的命令計入Slow Log slowlog-max-len xxx #Slow Log的長度,即最大紀錄多少條Slow Log
- 使用 SLOWLOG GET [number] 命令,能夠輸出最近進入 Slow Log 的 number 條命令。
- 使用 SLOWLOG RESET 命令,能夠重置 Slow Log
網絡引起的延遲
- 儘量使用長鏈接或鏈接池,避免頻繁建立銷燬鏈接
- 客戶端進行的批量數據操做,應使用 Pipeline 特性在一次交互中完成。具體請參照本文的 Pipelining 章節
- 數據持久化引起的延遲
Redis 的數據持久化工做自己就會帶來延遲,須要根據數據的安全級別和性能要求制定合理的持久化策略:
- AOF + fsync always 的設置雖然可以絕對確保數據安全,但每一個操做都會觸發一次 fsync,會對 Redis 的性能有比較明顯的影響
- AOF + fsync every second 是比較好的折中方案,每秒 fsync 一次
- AOF + fsync never 會提供 AOF 持久化方案下的最優性能
- 使用 RDB 持久化一般會提供比使用 AOF 更高的性能,但須要注意 RDB 的策略配置
- 每一次 RDB 快照和 AOF Rewrite 都須要 Redis 主進程進行 fork 操做。fork 操做自己可能會產生較高的耗時,與 CPU 和 Redis 佔用的內存大小有關。根據具體的狀況合理配置 RDB 快照和 AOF Rewrite 時機,避免過於頻繁的 fork 帶來的延遲
Redis 在 fork 子進程時須要將內存分頁表拷貝至子進程,以佔用了 24GB 內存的 Redis 實例爲例,共須要拷貝 24GB / 4kB * 8 = 48MB 的數據。在使用單 Xeon 2.27Ghz 的物理機上,這一 fork 操做耗時 216ms。
能夠經過 INFO 命令返回的 latest_fork_usec 字段查看上一次 fork 操做的耗時(微秒)
Swap 引起的延遲
- 當 Linux 將 Redis 所用的內存分頁移至 swap 空間時,將會阻塞 Redis 進程,致使 Redis 出現不正常的延遲。Swap 一般在物理內存不足或一些進程在進行大量 I/O 操做時發生,應儘量避免上述兩種狀況的出現。
- /proc//smaps 文件中會保存進程的 swap 記錄,經過查看這個文件,可以判斷 Redis 的延遲是否由 Swap 產生。若是這個文件中記錄了較大的 Swap size,則說明延遲頗有多是 Swap 形成的。
數據淘汰引起的延遲
當同一秒內有大量 key 過時時,也會引起 Redis 的延遲。在使用時應儘可能將 key 的失效時間錯開。
引入讀寫分離機制
- Redis 的主從複製能力能夠實現一主多從的多節點架構,在這一架構下,主節點接收全部寫請求,並將數據同步給多個從節點。
- 在這一基礎上,咱們可讓從節點提供對實時性要求不高的讀請求服務,以減少主節點的壓力。
- 尤爲是針對一些使用了長耗時命令的統計類任務,徹底能夠指定在一個或多個從節點上執行,避免這些長耗時命令影響其餘請求的響應。
主從複製
Redis 支持一主多從的主從複製架構。一個 Master 實例負責處理全部的寫請求,Master 將寫操做同步至全部 Slave。
藉助 Redis 的主從複製,能夠實現讀寫分離和高可用:
實時性要求不是特別高的讀請求,能夠在 Slave 上完成,提高效率。特別是一些週期性執行的統計任務,這些任務可能須要執行一些長耗時的 Redis 命令,能夠專門規劃出 1 個或幾個 Slave 用於服務這些統計任務
藉助 Redis Sentinel 能夠實現高可用,當 Master crash 後,Redis Sentinel 可以自動將一個 Slave 晉升爲 Master,繼續提供服務
啓用主從複製很是簡單,只須要配置多個 Redis 實例,在做爲 Slave 的 Redis 實例中配置:
slaveof 192.168.1.1 6379 #指定Master的IP和端口
當 Slave 啓動後,會從 Master 進行一次冷啓動數據同步,由 Master 觸發 BGSAVE 生成 RDB 文件推送給 Slave 進行導入,導入完成後 Master 再將增量數據經過 Redis Protocol 同步給 Slave。以後主從之間的數據便一直以 Redis Protocol 進行同步
使用 Sentinel 作自動 failover
Redis 的主從複製功能自己只是作數據同步,並不提供監控和自動 failover 能力,要經過主從複製功能來實現 Redis 的高可用,還須要引入一個組件:Redis Sentinel
Redis Sentinel 是 Redis 官方開發的監控組件,能夠監控 Redis 實例的狀態,經過 Master 節點自動發現 Slave 節點,並在監測到 Master 節點失效時選舉出一個新的 Master,並向全部 Redis 實例推送新的主從配置。
Redis Sentinel 須要至少部署 3 個實例才能造成選舉關係。
關鍵配置:
另外須要注意的是,Redis Sentinel 實現的自動 failover 不是在同一個 IP 和端口上完成的,也就是說自動 failover 產生的新 Master 提供服務的 IP 和端口與以前的 Master 是不同的,因此要實現 HA,還要求客戶端必須支持 Sentinel,可以與 Sentinel 交互得到新 Master 的信息才行。
集羣分片
爲什麼要作集羣分片:
Redis中存儲的數據量大,一臺主機的物理內存已經沒法容納 Redis的寫請求併發量大,一個Redis實例以沒法承載
當上述兩個問題出現時,就必需要對 Redis 進行分片了。
Redis的分片方案有不少種,例如不少Redis的客戶端都自行實現了分片功能,也有向Twemproxy這樣的以代理方式實現的Redis分片方案。然而首選的方案還應該是Redis官方在3.0版本中推出的Redis Cluster分片方案。
Redis Cluster 的能力
可以自動將數據分散在多個節點上
當訪問的 key 不在當前分片上時,可以自動將請求轉發至正確的分片
當集羣中部分節點失效時仍能提供服務
其中第三點是基於主從複製來實現的,Redis Cluster 的每一個數據分片都採用了主從複製的結構,原理和前文所述的主從複製徹底一致,惟一的區別是省去了 Redis Sentinel 這一額外的組件,由 Redis Cluster 負責進行一個分片內部的節點監控和自動 failover。
Redis Cluster 分片原理
- Redis Cluster 中共有 16384 個 hash slot,Redis 會計算每一個 key 的 CRC16,將結果與 16384 取模,來決定該 key 存儲在哪個 hash slot 中,同時須要指定 Redis Cluster 中每一個數據分片負責的 Slot 數。Slot 的分配在任什麼時候間點均可以進行從新分配。
- 客戶端在對 key 進行讀寫操做時,能夠鏈接 Cluster 中的任意一個分片,若是操做的 key 不在此分片負責的 Slot 範圍內,Redis Cluster 會自動將請求重定向到正確的分片上。
hash tags
- 在基礎的分片原則上,Redis 還支持 hash tags 功能,以 hash tags 要求的格式明明的 key,將會確保進入同一個 Slot 中。例如:{uiv} user:1000 和 {uiv} user:1001 擁有一樣的 hash tag {uiv},會保存在同一個 Slot 中。
- 使用 Redis Cluster 時,pipelining、事務和 LUA Script 功能涉及的 key 必須在同一個數據分片上,不然將會返回錯誤。如要在 Redis Cluster 中使用上述功能,就必須經過 hash tags 來確保一個 pipeline 或一個事務中操做的全部 key 都位於同一個 Slot 中。
- 有一些客戶端(如 Redisson)實現了集羣化的 pipelining 操做,能夠自動將一個 pipeline 裏的命令按 key 所在的分片進行分組,分別發到不一樣的分片上執行。可是 Redis 不支持跨分片的事務,事務和 LUA Script 仍是必須遵循全部 key 在一個分片上的規則要求。
- 主從複製 vs 集羣分片
- 在設計軟件架構時,要如何在主從複製和集羣分片兩種部署方案中取捨呢?
- 從各個方面看,Redis Cluster 都是優於主從複製的方案
Redis Cluster可以解決單節點上數據量過大的問題 Redis Cluster可以解決單節點訪問壓力過大的問題 Redis Cluster包含了主從複製的能力
那是否是表明 Redis Cluster 永遠是優於主從複製的選擇呢?
並非。
軟件架構永遠不是越複雜越好,複雜的架構在帶來顯著好處的同時,必定也會帶來相應的弊端。採用 Redis Cluster 的弊端包括:
- 維護難度增長。在使用 Redis Cluster 時,須要維護的 Redis 實例數倍增,須要監控的主機數量也相應增長,數據備份 / 持久化的複雜度也會增長。同時在進行分片的增減操做時,還須要進行 reshard 操做,遠比主從模式下增長一個 Slave 的複雜度要高。
- 客戶端資源消耗增長。當客戶端使用鏈接池時,須要爲每個數據分片維護一個鏈接池,客戶端同時須要保持的鏈接數成倍增多,加大了客戶端自己和操做系統資源的消耗。
- 性能優化難度增長。你可能須要在多個分片上查看 Slow Log 和 Swap 日誌才能定位性能問題。
事務和 LUA Script 的使用成本增長。在 Redis Cluster 中使用事務和 LUA Script 特性有嚴格的限制條件,事務和 Script 中操做的 key 必須位於同一個分片上,這就使得在開發時必須對相應場景下涉及的 key 進行額外的規劃和規範要求。若是應用的場景中大量涉及事務和 Script 的使用,如何在保證這兩個功能的正常運做前提下把數據平均分到多個數據分片中就會成爲難點。
- 因此說,在主從複製和集羣分片兩個方案中作出選擇時,應該從應用軟件的功能特性、數據和訪問量級、將來發展規劃等方面綜合考慮,只在確實有必要引入數據分片時再使用 Redis Cluster。
- 綜合上面幾點考慮,若是單臺主機的可用物理內存徹底足以支撐對 Redis 的容量需求,且 Redis 面臨的併發寫壓力距離 Benchmark 值還尚有距離,建議採用主從複製的架構,能夠省去不少沒必要要的麻煩。同時,若是應用中大量使用 pipelining 和事務,也建議儘量選擇主從複製架構,能夠減小設計和開發時的複雜度。
動動小手點點關注吧!
Java架構師學習公衆號!
一個專一分享架構乾貨的微信公衆號
以爲本文有用就把文章分享給更多的人看到吧!