本文將從Redis的基本特性入手,通過講述Redis的數(shù)據(jù)結(jié)構(gòu)和主要命令對(duì)Redis的基本能力進(jìn)行直觀介紹。之后在性能調(diào)優(yōu)等方面進(jìn)行更深入的介紹和指導(dǎo)。
概述
Redis 是一個(gè)開源的,基于內(nèi)存的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)媒介,可以作為數(shù)據(jù)庫、緩存服務(wù)或消息服務(wù)使用。
Redis 支持多種數(shù)據(jù)結(jié)構(gòu),包括字符串、哈希表、鏈表、集合、有序集合、位圖、Hyperloglogs 等。
Redis 具備 LRU 淘汰、事務(wù)實(shí)現(xiàn)、以及不同級(jí)別的硬盤持久化等能力,并且支持副本集和通過 Redis Sentinel 實(shí)現(xiàn)的高可用方案,同時(shí)還支持通過 Redis Cluster 實(shí)現(xiàn)的數(shù)據(jù)自動(dòng)分片能力。
Redis 的主要功能都基于單線程模型實(shí)現(xiàn),也就是說 Redis 使用一個(gè)線程來服務(wù)所有的客戶端請(qǐng)求,同時(shí) Redis 采用了非阻塞式 IO,并精細(xì)地優(yōu)化各種命令的算法時(shí)間復(fù)雜度,這些信息意味著:
Redis 是線程安全的(因?yàn)橹挥幸粋€(gè)線程),其所有操作都是原子的,不會(huì)因并發(fā)產(chǎn)生數(shù)據(jù)異常
Redis 的速度非??欤ㄒ?yàn)槭褂梅亲枞?IO,且大部分命令的算法時(shí)間復(fù)雜度都是 O(1))
使用高耗時(shí)的 Redis 命令是很危險(xiǎn)的,會(huì)占用唯一的一個(gè)線程的大量處理時(shí)間,導(dǎo)致所有的請(qǐng)求都被拖慢。(例如時(shí)間復(fù)雜度為 O(N) 的 KEYS 命令,嚴(yán)格禁止在生產(chǎn)環(huán)境中使用)
Redis 的數(shù)據(jù)結(jié)構(gòu)和相關(guān)常用命令
本節(jié)中將介紹 Redis 支持的主要數(shù)據(jù)結(jié)構(gòu),以及相關(guān)的常用 Redis 命令。本節(jié)只對(duì) Redis 命令進(jìn)行扼要的介紹,且只列出了較常用的命令。如果想要了解完整的 Redis 命令集,或了解某個(gè)命令的詳細(xì)使用方法,
常用命令一、Key
Redis 采用 Key-Value 型的基本數(shù)據(jù)結(jié)構(gòu),任何二進(jìn)制序列都可以作為 Redis 的 Key 使用(例如普通的字符串或一張 JPEG 圖片)關(guān)于 Key 的一些注意事項(xiàng):
不要使用過長的 Key。例如使用一個(gè) 1024 字節(jié)的 key 就不是一個(gè)好主意,不僅會(huì)消耗更多的內(nèi)存,還會(huì)導(dǎo)致查找的效率降低
Key 短到缺失了可讀性也是不好的,例如”u1000flw” 比起”user:1000:followers” 來說,節(jié)省了寥寥的存儲(chǔ)空間,卻引發(fā)了可讀性和可維護(hù)性上的麻煩
最好使用統(tǒng)一的規(guī)范來設(shè)計(jì) Key,比如”object-type:id:attr”,以這一規(guī)范設(shè)計(jì)出的 Key 可能是”user:1000” 或”comment:1234:reply-to”
Redis 允許的最大 Key 長度是 512MB(對(duì) Value 的長度限制也是 512MB)
常用命令二、String
String 是 Redis 的基礎(chǔ)數(shù)據(jù)類型,Redis 沒有 Int、Float、Boolean 等數(shù)據(jù)類型的概念,所有的基本類型在 Redis 中都以 String 體現(xiàn)。
與 String 相關(guān)的常用命令:
SET:為一個(gè) key 設(shè)置 value,可以配合 EX/PX 參數(shù)指定 key 的有效期,通過 NX/XX 參數(shù)針對(duì) key 是否存在的情況進(jìn)行區(qū)別操作,時(shí)間復(fù)雜度 O(1)
GET:獲取某個(gè) key 對(duì)應(yīng)的 value,時(shí)間復(fù)雜度 O(1)
GETSET:為一個(gè) key 設(shè)置 value,并返回該 key 的原 value,時(shí)間復(fù)雜度 O(1)
MSET:為多個(gè) key 設(shè)置 value,時(shí)間復(fù)雜度 O(N)
MSETNX:同 MSET,如果指定的 key 中有任意一個(gè)已存在,則不進(jìn)行任何操作,時(shí)間復(fù)雜度 O(N)
MGET:獲取多個(gè) key 對(duì)應(yīng)的 value,時(shí)間復(fù)雜度 O(N)
上文提到過,Redis 的基本數(shù)據(jù)類型只有 String,但 Redis 可以把 String 作為整型或浮點(diǎn)型數(shù)字來使用,主要體現(xiàn)在 INCR、DECR 類的命令上:
INCR:將 key 對(duì)應(yīng)的 value 值自增 1,并返回自增后的值。只對(duì)可以轉(zhuǎn)換為整型的 String 數(shù)據(jù)起作用。時(shí)間復(fù)雜度 O(1)
INCRBY:將 key 對(duì)應(yīng)的 value 值自增指定的整型數(shù)值,并返回自增后的值。只對(duì)可以轉(zhuǎn)換為整型的 String 數(shù)據(jù)起作用。時(shí)間復(fù)雜度 O(1)
DECR/DECRBY:同 INCR/INCRBY,自增改為自減。
INCR/DECR 系列命令要求操作的 value 類型為 String,并可以轉(zhuǎn)換為 64 位帶符號(hào)的整型數(shù)字,否則會(huì)返回錯(cuò)誤。
也就是說,進(jìn)行 INCR/DECR 系列命令的 value,必須在 [-2^63 ~ 2^63 - 1] 范圍內(nèi)。
前文提到過,Redis 采用單線程模型,天然是線程安全的,這使得 INCR/DECR 命令可以非常便利的實(shí)現(xiàn)高并發(fā)場(chǎng)景下的精確控制。
例 1:庫存控制
在高并發(fā)場(chǎng)景下實(shí)現(xiàn)庫存余量的精準(zhǔn)校驗(yàn),確保不出現(xiàn)超賣的情況。
設(shè)置庫存總量:
SET inv:remain "100"
庫存扣減 + 余量校驗(yàn):
DECR inv:remain
當(dāng) DECR 命令返回值大于等于 0 時(shí),說明庫存余量校驗(yàn)通過,如果返回小于 0 的值,則說明庫存已耗盡。
假設(shè)同時(shí)有 300 個(gè)并發(fā)請(qǐng)求進(jìn)行庫存扣減,Redis 能夠確保這 300 個(gè)請(qǐng)求分別得到 99 到 - 200 的返回值,每個(gè)請(qǐng)求得到的返回值都是唯一的,絕對(duì)不會(huì)找出現(xiàn)兩個(gè)請(qǐng)求得到一樣的返回值的情況。
例 2:自增序列生成
實(shí)現(xiàn)類似于 RDBMS 的 Sequence 功能,生成一系列唯一的序列號(hào)
設(shè)置序列起始值:
SET sequence "10000"
獲取一個(gè)序列值:
INCR sequence
直接將返回值作為序列使用即可。
獲取一批(如 100 個(gè))序列值:
INCRBY sequence 100
假設(shè)返回值為 N,那么 [N - 99 ~ N] 的數(shù)值都是可用的序列值。
當(dāng)多個(gè)客戶端同時(shí)向 Redis 申請(qǐng)自增序列時(shí),Redis 能夠確保每個(gè)客戶端得到的序列值或序列范圍都是全局唯一的,絕對(duì)不會(huì)出現(xiàn)不同客戶端得到了重復(fù)的序列值的情況。
常用命令三、List
Redis 的 List 是鏈表型的數(shù)據(jù)結(jié)構(gòu),可以使用 LPUSH/RPUSH/LPOP/RPOP 等命令在 List 的兩端執(zhí)行插入元素和彈出元素的操作。雖然 List 也支持在特定 index 上插入和讀取元素的功能,但其時(shí)間復(fù)雜度較高(O(N)),應(yīng)小心使用。
與 List 相關(guān)的常用命令:
LPUSH:向指定 List 的左側(cè)(即頭部)插入 1 個(gè)或多個(gè)元素,返回插入后的 List 長度。時(shí)間復(fù)雜度 O(N),N 為插入元素的數(shù)量
RPUSH:同 LPUSH,向指定 List 的右側(cè)(即尾部)插入 1 或多個(gè)元素
LPOP:從指定 List 的左側(cè)(即頭部)移除一個(gè)元素并返回,時(shí)間復(fù)雜度 O(1)
RPOP:同 LPOP,從指定 List 的右側(cè)(即尾部)移除 1 個(gè)元素并返回
LPUSHX/RPUSHX:與 LPUSH/RPUSH 類似,區(qū)別在于,LPUSHX/RPUSHX 操作的 key 如果不存在,則不會(huì)進(jìn)行任何操作
LLEN:返回指定 List 的長度,時(shí)間復(fù)雜度 O(1)
LRANGE:返回指定 List 中指定范圍的元素(雙端包含,即 LRANGE key 0 10 會(huì)返回 11 個(gè)元素),時(shí)間復(fù)雜度 O(N)。應(yīng)盡可能控制一次獲取的元素?cái)?shù)量,一次獲取過大范圍的 List 元素會(huì)導(dǎo)致延遲,同時(shí)對(duì)長度不可預(yù)知的 List,避免使用 LRANGE key 0 -1 這樣的完整遍歷操作。
應(yīng)謹(jǐn)慎使用的 List 相關(guān)命令:
LINDEX:返回指定 List 指定 index 上的元素,如果 index 越界,返回 nil。index 數(shù)值是回環(huán)的,即 - 1 代表 List 最后一個(gè)位置,-2 代表 List 倒數(shù)第二個(gè)位置。時(shí)間復(fù)雜度 O(N)
LSET:將指定 List 指定 index 上的元素設(shè)置為 value,如果 index 越界則返回錯(cuò)誤,時(shí)間復(fù)雜度 O(N),如果操作的是頭 / 尾部的元素,則時(shí)間復(fù)雜度為 O(1)
LINSERT:向指定 List 中指定元素之前 / 之后插入一個(gè)新元素,并返回操作后的 List 長度。如果指定的元素不存在,返回 - 1。如果指定 key 不存在,不會(huì)進(jìn)行任何操作,時(shí)間復(fù)雜度 O(N)
由于 Redis 的 List 是鏈表結(jié)構(gòu)的,上述的三個(gè)命令的算法效率較低,需要對(duì) List 進(jìn)行遍歷,命令的耗時(shí)無法預(yù)估,在 List 長度大的情況下耗時(shí)會(huì)明顯增加,應(yīng)謹(jǐn)慎使用。
換句話說,Redis 的 List 實(shí)際是設(shè)計(jì)來用于實(shí)現(xiàn)隊(duì)列,而不是用于實(shí)現(xiàn)類似 ArrayList 這樣的列表的。如果你不是想要實(shí)現(xiàn)一個(gè)雙端出入的隊(duì)列,那么請(qǐng)盡量不要使用 Redis 的 List 數(shù)據(jù)結(jié)構(gòu)。
為了更好支持隊(duì)列的特性,Redis 還提供了一系列阻塞式的操作命令,如 BLPOP/BRPOP 等,能夠?qū)崿F(xiàn)類似于 BlockingQueue 的能力,即在 List 為空時(shí),阻塞該連接,直到 List 中有對(duì)象可以出隊(duì)時(shí)再返回。針對(duì)阻塞類的命令,此處不做詳細(xì)探討,請(qǐng)參考官方文檔(https://redis.io/topics/data-types-intro) 中”Blocking operations on lists” 一節(jié)。
常用命令四、Hash
Hash 即哈希表,Redis 的 Hash 和傳統(tǒng)的哈希表一樣,是一種 field-value 型的數(shù)據(jù)結(jié)構(gòu),可以理解成將 HashMap 搬入 Redis。
Hash 非常適合用于表現(xiàn)對(duì)象類型的數(shù)據(jù),用 Hash 中的 field 對(duì)應(yīng)對(duì)象的 field 即可。
Hash 的優(yōu)點(diǎn)包括:
可以實(shí)現(xiàn)二元查找,如” 查找 ID 為 1000 的用戶的年齡”
比起將整個(gè)對(duì)象序列化后作為 String 存儲(chǔ)的方法,Hash 能夠有效地減少網(wǎng)絡(luò)傳輸?shù)南?/p>
當(dāng)使用 Hash 維護(hù)一個(gè)集合時(shí),提供了比 List 效率高得多的隨機(jī)訪問命令
與 Hash 相關(guān)的常用命令:
HSET:將 key 對(duì)應(yīng)的 Hash 中的 field 設(shè)置為 value。如果該 Hash 不存在,會(huì)自動(dòng)創(chuàng)建一個(gè)。時(shí)間復(fù)雜度 O(1)
HGET:返回指定 Hash 中 field 字段的值,時(shí)間復(fù)雜度 O(1)
HMSET/HMGET:同 HSET 和 HGET,可以批量操作同一個(gè) key 下的多個(gè) field,時(shí)間復(fù)雜度:O(N),N 為一次操作的 field 數(shù)量
HSETNX:同 HSET,但如 field 已經(jīng)存在,HSETNX 不會(huì)進(jìn)行任何操作,時(shí)間復(fù)雜度 O(1)
HEXISTS:判斷指定 Hash 中 field 是否存在,存在返回 1,不存在返回 0,時(shí)間復(fù)雜度 O(1)
HDEL:刪除指定 Hash 中的 field(1 個(gè)或多個(gè)),時(shí)間復(fù)雜度:O(N),N 為操作的 field 數(shù)量
HINCRBY:同 INCRBY 命令,對(duì)指定 Hash 中的一個(gè) field 進(jìn)行 INCRBY,時(shí)間復(fù)雜度 O(1)
應(yīng)謹(jǐn)慎使用的 Hash 相關(guān)命令:
HGETALL:返回指定 Hash 中所有的 field-value 對(duì)。返回結(jié)果為數(shù)組,數(shù)組中 field 和 value 交替出現(xiàn)。時(shí)間復(fù)雜度 O(N)
HKEYS/HVALS:返回指定 Hash 中所有的 field/value,時(shí)間復(fù)雜度 O(N)
上述三個(gè)命令都會(huì)對(duì) Hash 進(jìn)行完整遍歷,Hash 中的 field 數(shù)量與命令的耗時(shí)線性相關(guān),對(duì)于尺寸不可預(yù)知的 Hash,應(yīng)嚴(yán)格避免使用上面三個(gè)命令,而改為使用 HSCAN 命令進(jìn)行游標(biāo)式的遍歷,
常用命令五、Set
Redis Set 是無序的,不可重復(fù)的 String 集合。
與 Set 相關(guān)的常用命令:
SADD:向指定 Set 中添加 1 個(gè)或多個(gè) member,如果指定 Set 不存在,會(huì)自動(dòng)創(chuàng)建一個(gè)。時(shí)間復(fù)雜度 O(N),N 為添加的 member 個(gè)數(shù)
SREM:從指定 Set 中移除 1 個(gè)或多個(gè) member,時(shí)間復(fù)雜度 O(N),N 為移除的 member 個(gè)數(shù)
SRANDMEMBER:從指定 Set 中隨機(jī)返回 1 個(gè)或多個(gè) member,時(shí)間復(fù)雜度 O(N),N 為返回的 member 個(gè)數(shù)
SPOP:從指定 Set 中隨機(jī)移除并返回 count 個(gè) member,時(shí)間復(fù)雜度 O(N),N 為移除的 member 個(gè)數(shù)
SCARD:返回指定 Set 中的 member 個(gè)數(shù),時(shí)間復(fù)雜度 O(1)
SISMEMBER:判斷指定的 value 是否存在于指定 Set 中,時(shí)間復(fù)雜度 O(1)
SMOVE:將指定 member 從一個(gè) Set 移至另一個(gè) Set
慎用的 Set 相關(guān)命令:
SMEMBERS:返回指定 Hash 中所有的 member,時(shí)間復(fù)雜度 O(N)
SUNION/SUNIONSTORE:計(jì)算多個(gè) Set 的并集并返回 / 存儲(chǔ)至另一個(gè) Set 中,時(shí)間復(fù)雜度 O(N),N 為參與計(jì)算的所有集合的總 member 數(shù)
SINTER/SINTERSTORE:計(jì)算多個(gè) Set 的交集并返回 / 存儲(chǔ)至另一個(gè) Set 中,時(shí)間復(fù)雜度 O(N),N 為參與計(jì)算的所有集合的總 member 數(shù)
SDIFF/SDIFFSTORE:計(jì)算 1 個(gè) Set 與 1 或多個(gè) Set 的差集并返回 / 存儲(chǔ)至另一個(gè) Set 中,時(shí)間復(fù)雜度 O(N),N 為參與計(jì)算的所有集合的總 member 數(shù)。
上述幾個(gè)命令涉及的計(jì)算量大,應(yīng)謹(jǐn)慎使用,特別是在參與計(jì)算的 Set 尺寸不可知的情況下,應(yīng)嚴(yán)格避免使用??梢钥紤]通過 SSCAN 命令遍歷獲取相關(guān) Set 的全部 member(具體請(qǐng)見 https://redis.io/commands/scan ),如果需要做并集 / 交集 / 差集計(jì)算,可以在客戶端進(jìn)行,或在不服務(wù)實(shí)時(shí)查詢請(qǐng)求的 Slave 上進(jìn)行。
常用命令六、Sorted Set
Redis Sorted Set 是有序的、不可重復(fù)的 String 集合。Sorted Set 中的每個(gè)元素都需要指派一個(gè)分?jǐn)?shù) (score),Sorted Set 會(huì)根據(jù) score 對(duì)元素進(jìn)行升序排序。如果多個(gè) member 擁有相同的 score,則以字典序進(jìn)行升序排序。
Sorted Set 非常適合用于實(shí)現(xiàn)排名。
Sorted Set 的主要命令:
ZADD:向指定 Sorted Set 中添加 1 個(gè)或多個(gè) member,時(shí)間復(fù)雜度 O(Mlog(N)),M 為添加的 member 數(shù)量,N 為 Sorted Set 中的 member 數(shù)量
ZREM:從指定 Sorted Set 中刪除 1 個(gè)或多個(gè) member,時(shí)間復(fù)雜度 O(Mlog(N)),M 為刪除的 member 數(shù)量,N 為 Sorted Set 中的 member 數(shù)量
ZCOUNT:返回指定 Sorted Set 中指定 score 范圍內(nèi)的 member 數(shù)量,時(shí)間復(fù)雜度:O(log(N))
ZCARD:返回指定 Sorted Set 中的 member 數(shù)量,時(shí)間復(fù)雜度 O(1)
ZSCORE:返回指定 Sorted Set 中指定 member 的 score,時(shí)間復(fù)雜度 O(1)
ZRANK/ZREVRANK:返回指定 member 在 Sorted Set 中的排名,ZRANK 返回按升序排序的排名,ZREVRANK 則返回按降序排序的排名。時(shí)間復(fù)雜度 O(log(N))
ZINCRBY:同 INCRBY,對(duì)指定 Sorted Set 中的指定 member 的 score 進(jìn)行自增,時(shí)間復(fù)雜度 O(log(N))
慎用的 Sorted Set 相關(guān)命令:
ZRANGE/ZREVRANGE:返回指定 Sorted Set 中指定排名范圍內(nèi)的所有 member,ZRANGE 為按 score 升序排序,ZREVRANGE 為按 score 降序排序,時(shí)間復(fù)雜度 O(log(N)+M),M 為本次返回的 member 數(shù)
ZRANGEBYSCORE/ZREVRANGEBYSCORE:返回指定 Sorted Set 中指定 score 范圍內(nèi)的所有 member,返回結(jié)果以升序 / 降序排序,min 和 max 可以指定為 - inf 和 + inf,代表返回所有的 member。時(shí)間復(fù)雜度 O(log(N)+M)
ZREMRANGEBYRANK/ZREMRANGEBYSCORE:移除 Sorted Set 中指定排名范圍 / 指定 score 范圍內(nèi)的所有 member。時(shí)間復(fù)雜度 O(log(N)+M)
上述幾個(gè)命令,應(yīng)盡量避免傳遞 [0 -1] 或 [-inf +inf] 這樣的參數(shù),來對(duì) Sorted Set 做一次性的完整遍歷,特別是在 Sorted Set 的尺寸不可預(yù)知的情況下??梢酝ㄟ^ ZSCAN 命令來進(jìn)行游標(biāo)式的遍歷(具體請(qǐng)見 https://redis.io/commands/scan ),或通過 LIMIT 參數(shù)來限制返回 member 的數(shù)量(適用于 ZRANGEBYSCORE 和 ZREVRANGEBYSCORE 命令),以實(shí)現(xiàn)游標(biāo)式的遍歷。
常用命令七、Bitmap 和 HyperLogLog
Redis 的這兩種數(shù)據(jù)結(jié)構(gòu)相較之前的并不常用,在本文中只做簡(jiǎn)要介紹,如想要詳細(xì)了解這兩種數(shù)據(jù)結(jié)構(gòu)與其相關(guān)的命令,請(qǐng)參考官方文檔 https://redis.io/topics/data-types-intro 中的相關(guān)章節(jié)
Bitmap 在 Redis 中不是一種實(shí)際的數(shù)據(jù)類型,而是一種將 String 作為 Bitmap 使用的方法??梢岳斫鉃閷?String 轉(zhuǎn)換為 bit 數(shù)組。使用 Bitmap 來存儲(chǔ) true/false 類型的簡(jiǎn)單數(shù)據(jù)極為節(jié)省空間。
HyperLogLogs 是一種主要用于數(shù)量統(tǒng)計(jì)的數(shù)據(jù)結(jié)構(gòu),它和 Set 類似,維護(hù)一個(gè)不可重復(fù)的 String 集合,但是 HyperLogLogs 并不維護(hù)具體的 member 內(nèi)容,只維護(hù) member 的個(gè)數(shù)。也就是說,HyperLogLogs 只能用于計(jì)算一個(gè)集合中不重復(fù)的元素?cái)?shù)量,所以它比 Set 要節(jié)省很多內(nèi)存空間。
其他常用命令
EXISTS:判斷指定的 key 是否存在,返回 1 代表存在,0 代表不存在,時(shí)間復(fù)雜度 O(1)
DEL:刪除指定的 key 及其對(duì)應(yīng)的 value,時(shí)間復(fù)雜度 O(N),N 為刪除的 key 數(shù)量
EXPIRE/PEXPIRE:為一個(gè) key 設(shè)置有效期,單位為秒或毫秒,時(shí)間復(fù)雜度 O(1)
TTL/PTTL:返回一個(gè) key 剩余的有效時(shí)間,單位為秒或毫秒,時(shí)間復(fù)雜度 O(1)
RENAME/RENAMENX:將 key 重命名為 newkey。使用 RENAME 時(shí),如果 newkey 已經(jīng)存在,其值會(huì)被覆蓋;使用 RENAMENX 時(shí),如果 newkey 已經(jīng)存在,則不會(huì)進(jìn)行任何操作,時(shí)間復(fù)雜度 O(1)
TYPE:返回指定 key 的類型,string, list, set, zset, hash。時(shí)間復(fù)雜度 O(1)
CONFIG GET:獲得 Redis 某配置項(xiàng)的當(dāng)前值,可以使用 * 通配符,時(shí)間復(fù)雜度 O(1)
CONFIG SET:為 Redis 某個(gè)配置項(xiàng)設(shè)置新值,時(shí)間復(fù)雜度 O(1)
CONFIG REWRITE:讓 Redis 重新加載 redis.conf 中的配置
Redis 性能調(diào)優(yōu)
盡管 Redis 是一個(gè)非??焖俚膬?nèi)存數(shù)據(jù)存儲(chǔ)媒介,也并不代表 Redis 不會(huì)產(chǎn)生性能問題。前文中提到過,Redis 采用單線程模型,所有的命令都是由一個(gè)線程串行執(zhí)行的,所以當(dāng)某個(gè)命令執(zhí)行耗時(shí)較長時(shí),會(huì)拖慢其后的所有命令,這使得 Redis 對(duì)每個(gè)任務(wù)的執(zhí)行效率更加敏感。
針對(duì) Redis 的性能優(yōu)化,主要從下面幾個(gè)層面入手:
最初的也是最重要的,確保沒有讓 Redis 執(zhí)行耗時(shí)長的命令
使用 pipelining 將連續(xù)執(zhí)行的命令組合執(zhí)行
操作系統(tǒng)的 Transparent huge pages 功能必須關(guān)閉:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
如果在虛擬機(jī)中運(yùn)行 Redis,可能天然就有虛擬機(jī)環(huán)境帶來的固有延遲??梢酝ㄟ^./redis-cli —intrinsic-latency 100 命令查看固有延遲。同時(shí)如果對(duì) Redis 的性能有較高要求的話,應(yīng)盡可能在物理機(jī)上直接部署 Redis。
檢查數(shù)據(jù)持久化策略
考慮引入讀寫分離機(jī)制
長耗時(shí)命令
Redis 絕大多數(shù)讀寫命令的時(shí)間復(fù)雜度都在 O(1) 到 O(N) 之間,在文本和官方文檔中均對(duì)每個(gè)命令的時(shí)間復(fù)雜度有說明。
通常來說,O(1) 的命令是安全的,O(N) 命令在使用時(shí)需要注意,如果 N 的數(shù)量級(jí)不可預(yù)知,則應(yīng)避免使用。例如對(duì)一個(gè) field 數(shù)未知的 Hash 數(shù)據(jù)執(zhí)行 HGETALL/HKEYS/HVALS 命令,通常來說這些命令執(zhí)行的很快,但如果這個(gè) Hash 中的 field 數(shù)量極多,耗時(shí)就會(huì)成倍增長。
又如使用 SUNION 對(duì)兩個(gè) Set 執(zhí)行 Union 操作,或使用 SORT 對(duì) List/Set 執(zhí)行排序操作等時(shí),都應(yīng)該嚴(yán)加注意。
避免在使用這些 O(N) 命令時(shí)發(fā)生問題主要有幾個(gè)辦法:
不要把 List 當(dāng)做列表使用,僅當(dāng)做隊(duì)列來使用
通過機(jī)制嚴(yán)格控制 Hash、Set、Sorted Set 的大小
可能的話,將排序、并集、交集等操作放在客戶端執(zhí)行
絕對(duì)禁止使用 KEYS 命令
避免一次性遍歷集合類型的所有成員,而應(yīng)使用 SCAN 類的命令進(jìn)行分批的,游標(biāo)式的遍歷
Redis 提供了 SCAN 命令,可以對(duì) Redis 中存儲(chǔ)的所有 key 進(jìn)行游標(biāo)式的遍歷,避免使用 KEYS 命令帶來的性能問題。同時(shí)還有 SSCAN/HSCAN/ZSCAN 等命令,分別用于對(duì) Set/Hash/Sorted Set 中的元素進(jìn)行游標(biāo)式遍歷。SCAN 類命令的使用請(qǐng)參考官方文檔:https://redis.io/commands/scan
Redis 提供了 Slow Log 功能,可以自動(dòng)記錄耗時(shí)較長的命令。相關(guān)的配置參數(shù)有兩個(gè):
slowlog-log-slower-than xxxms #執(zhí)行時(shí)間慢于xxx毫秒的命令計(jì)入Slow Logslowlog-max-len xxx #Slow Log的長度,即最大紀(jì)錄多少條Slow Log
使用SLOWLOG GET [number]命令,可以輸出最近進(jìn)入 Slow Log 的 number 條命令。使用SLOWLOG RESET命令,可以重置 Slow Log
網(wǎng)絡(luò)引發(fā)的延遲
盡可能使用長連接或連接池,避免頻繁創(chuàng)建銷毀連接
客戶端進(jìn)行的批量數(shù)據(jù)操作,應(yīng)使用 Pipeline 特性在一次交互中完成。具體請(qǐng)參照本文的 Pipelining 章節(jié)
數(shù)據(jù)持久化引發(fā)的延遲
Redis 的數(shù)據(jù)持久化工作本身就會(huì)帶來延遲,需要根據(jù)數(shù)據(jù)的安全級(jí)別和性能要求制定合理的持久化策略:
AOF + fsync always 的設(shè)置雖然能夠絕對(duì)確保數(shù)據(jù)安全,但每個(gè)操作都會(huì)觸發(fā)一次 fsync,會(huì)對(duì) Redis 的性能有比較明顯的影響
AOF + fsync every second 是比較好的折中方案,每秒 fsync 一次
AOF + fsync never 會(huì)提供 AOF 持久化方案下的最優(yōu)性能使用 RDB 持久化通常會(huì)提供比使用 AOF 更高的性能,但需要注意 RDB 的策略配置
每一次 RDB 快照和 AOF Rewrite 都需要 Redis 主進(jìn)程進(jìn)行 fork 操作。fork 操作本身可能會(huì)產(chǎn)生較高的耗時(shí),與 CPU 和 Redis 占用的內(nèi)存大小有關(guān)。根據(jù)具體的情況合理配置 RDB 快照和 AOF Rewrite 時(shí)機(jī),避免過于頻繁的 fork 帶來的延遲
Redis 在 fork 子進(jìn)程時(shí)需要將內(nèi)存分頁表拷貝至子進(jìn)程,以占用了 24GB 內(nèi)存的 Redis 實(shí)例為例,共需要拷貝 24GB / 4kB * 8 = 48MB 的數(shù)據(jù)。在使用單 Xeon 2.27Ghz 的物理機(jī)上,這一 fork 操作耗時(shí) 216ms。
可以通過 INFO 命令返回的 latest_fork_usec 字段查看上一次 fork 操作的耗時(shí)(微秒)
Swap 引發(fā)的延遲
當(dāng) Linux 將 Redis 所用的內(nèi)存分頁移至 swap 空間時(shí),將會(huì)阻塞 Redis 進(jìn)程,導(dǎo)致 Redis 出現(xiàn)不正常的延遲。Swap 通常在物理內(nèi)存不足或一些進(jìn)程在進(jìn)行大量 I/O 操作時(shí)發(fā)生,應(yīng)盡可能避免上述兩種情況的出現(xiàn)。
/proc//smaps 文件中會(huì)保存進(jìn)程的 swap 記錄,通過查看這個(gè)文件,能夠判斷 Redis 的延遲是否由 Swap 產(chǎn)生。如果這個(gè)文件中記錄了較大的 Swap size,則說明延遲很有可能是 Swap 造成的。
數(shù)據(jù)淘汰引發(fā)的延遲
當(dāng)同一秒內(nèi)有大量 key 過期時(shí),也會(huì)引發(fā) Redis 的延遲。在使用時(shí)應(yīng)盡量將 key 的失效時(shí)間錯(cuò)開。
引入讀寫分離機(jī)制
Redis 的主從復(fù)制能力可以實(shí)現(xiàn)一主多從的多節(jié)點(diǎn)架構(gòu),在這一架構(gòu)下,主節(jié)點(diǎn)接收所有寫請(qǐng)求,并將數(shù)據(jù)同步給多個(gè)從節(jié)點(diǎn)。
在這一基礎(chǔ)上,我們可以讓從節(jié)點(diǎn)提供對(duì)實(shí)時(shí)性要求不高的讀請(qǐng)求服務(wù),以減小主節(jié)點(diǎn)的壓力。
尤其是針對(duì)一些使用了長耗時(shí)命令的統(tǒng)計(jì)類任務(wù),完全可以指定在一個(gè)或多個(gè)從節(jié)點(diǎn)上執(zhí)行,避免這些長耗時(shí)命令影響其他請(qǐng)求的響應(yīng)。
關(guān)于讀寫分離的具體說明,請(qǐng)參見后續(xù)章節(jié)
主從復(fù)制與集群分片
主從復(fù)制
Redis 支持一主多從的主從復(fù)制架構(gòu)。一個(gè) Master 實(shí)例負(fù)責(zé)處理所有的寫請(qǐng)求,Master 將寫操作同步至所有 Slave。
借助 Redis 的主從復(fù)制,可以實(shí)現(xiàn)讀寫分離和高可用:
實(shí)時(shí)性要求不是特別高的讀請(qǐng)求,可以在 Slave 上完成,提升效率。特別是一些周期性執(zhí)行的統(tǒng)計(jì)任務(wù),這些任務(wù)可能需要執(zhí)行一些長耗時(shí)的 Redis 命令,可以專門規(guī)劃出 1 個(gè)或幾個(gè) Slave 用于服務(wù)這些統(tǒng)計(jì)任務(wù)
借助 Redis Sentinel 可以實(shí)現(xiàn)高可用,當(dāng) Master crash 后,Redis Sentinel 能夠自動(dòng)將一個(gè) Slave 晉升為 Master,繼續(xù)提供服務(wù)
啟用主從復(fù)制非常簡(jiǎn)單,只需要配置多個(gè) Redis 實(shí)例,在作為 Slave 的 Redis 實(shí)例中配置:
slaveof 192.168.1.1 6379 #指定Master的IP和端口
當(dāng) Slave 啟動(dòng)后,會(huì)從 Master 進(jìn)行一次冷啟動(dòng)數(shù)據(jù)同步,由 Master 觸發(fā) BGSAVE 生成 RDB 文件推送給 Slave 進(jìn)行導(dǎo)入,導(dǎo)入完成后 Master 再將增量數(shù)據(jù)通過 Redis Protocol 同步給 Slave。之后主從之間的數(shù)據(jù)便一直以 Redis Protocol 進(jìn)行同步
使用 Sentinel 做自動(dòng) failover
Redis 的主從復(fù)制功能本身只是做數(shù)據(jù)同步,并不提供監(jiān)控和自動(dòng) failover 能力,要通過主從復(fù)制功能來實(shí)現(xiàn) Redis 的高可用,還需要引入一個(gè)組件:Redis Sentinel
Redis Sentinel 是 Redis 官方開發(fā)的監(jiān)控組件,可以監(jiān)控 Redis 實(shí)例的狀態(tài),通過 Master 節(jié)點(diǎn)自動(dòng)發(fā)現(xiàn) Slave 節(jié)點(diǎn),并在監(jiān)測(cè)到 Master 節(jié)點(diǎn)失效時(shí)選舉出一個(gè)新的 Master,并向所有 Redis 實(shí)例推送新的主從配置。
Redis Sentinel 需要至少部署 3 個(gè)實(shí)例才能形成選舉關(guān)系。
關(guān)鍵配置:
sentinel monitor mymaster 127.0.0.1 6379 2 #Master實(shí)例的IP、端口,以及選舉需要的贊成票數(shù)sentinel down-after-milliseconds mymaster 60000 #多長時(shí)間沒有響應(yīng)視為Master失效sentinel failover-timeout mymaster 180000 #兩次failover嘗試間的間隔時(shí)長sentinel parallel-syncs mymaster 1 #如果有多個(gè)Slave,可以通過此配置指定同時(shí)從新Master進(jìn)行數(shù)據(jù)同步的Slave數(shù),避免所有Slave同時(shí)進(jìn)行數(shù)據(jù)同步導(dǎo)致查詢服務(wù)也不可用
另外需要注意的是,Redis Sentinel 實(shí)現(xiàn)的自動(dòng) failover 不是在同一個(gè) IP 和端口上完成的,也就是說自動(dòng) failover 產(chǎn)生的新 Master 提供服務(wù)的 IP 和端口與之前的 Master 是不一樣的,所以要實(shí)現(xiàn) HA,還要求客戶端必須支持 Sentinel,能夠與 Sentinel 交互獲得新 Master 的信息才行。
集群分片
為何要做集群分片:
Redis 中存儲(chǔ)的數(shù)據(jù)量大,一臺(tái)主機(jī)的物理內(nèi)存已經(jīng)無法容納
Redis 的寫請(qǐng)求并發(fā)量大,一個(gè) Redis 實(shí)例以無法承載
當(dāng)上述兩個(gè)問題出現(xiàn)時(shí),就必須要對(duì) Redis 進(jìn)行分片了。
Redis 的分片方案有很多種,例如很多 Redis 的客戶端都自行實(shí)現(xiàn)了分片功能,也有向 Twemproxy 這樣的以代理方式實(shí)現(xiàn)的 Redis 分片方案。然而首選的方案還應(yīng)該是 Redis 官方在 3.0 版本中推出的 Redis Cluster 分片方案。
本文不會(huì)對(duì) Redis Cluster 的具體安裝和部署細(xì)節(jié)進(jìn)行介紹,重點(diǎn)介紹 Redis Cluster 帶來的好處與弊端。
Redis Cluster 的能力
能夠自動(dòng)將數(shù)據(jù)分散在多個(gè)節(jié)點(diǎn)上
當(dāng)訪問的 key 不在當(dāng)前分片上時(shí),能夠自動(dòng)將請(qǐng)求轉(zhuǎn)發(fā)至正確的分片
當(dāng)集群中部分節(jié)點(diǎn)失效時(shí)仍能提供服務(wù)
其中第三點(diǎn)是基于主從復(fù)制來實(shí)現(xiàn)的,Redis Cluster 的每個(gè)數(shù)據(jù)分片都采用了主從復(fù)制的結(jié)構(gòu),原理和前文所述的主從復(fù)制完全一致,唯一的區(qū)別是省去了 Redis Sentinel 這一額外的組件,由 Redis Cluster 負(fù)責(zé)進(jìn)行一個(gè)分片內(nèi)部的節(jié)點(diǎn)監(jiān)控和自動(dòng) failover。
Redis Cluster 分片原理
Redis Cluster 中共有 16384 個(gè) hash slot,Redis 會(huì)計(jì)算每個(gè) key 的 CRC16,將結(jié)果與 16384 取模,來決定該 key 存儲(chǔ)在哪一個(gè) hash slot 中,同時(shí)需要指定 Redis Cluster 中每個(gè)數(shù)據(jù)分片負(fù)責(zé)的 Slot 數(shù)。Slot 的分配在任何時(shí)間點(diǎn)都可以進(jìn)行重新分配。
客戶端在對(duì) key 進(jìn)行讀寫操作時(shí),可以連接 Cluster 中的任意一個(gè)分片,如果操作的 key 不在此分片負(fù)責(zé)的 Slot 范圍內(nèi),Redis Cluster 會(huì)自動(dòng)將請(qǐng)求重定向到正確的分片上。
hash tags
在基礎(chǔ)的分片原則上,Redis 還支持 hash tags 功能,以 hash tags 要求的格式明明的 key,將會(huì)確保進(jìn)入同一個(gè) Slot 中。例如:{uiv}user:1000 和 {uiv}user:1001 擁有同樣的 hash tag {uiv},會(huì)保存在同一個(gè) Slot 中。
使用 Redis Cluster 時(shí),pipelining、事務(wù)和 LUA Script 功能涉及的 key 必須在同一個(gè)數(shù)據(jù)分片上,否則將會(huì)返回錯(cuò)誤。如要在 Redis Cluster 中使用上述功能,就必須通過 hash tags 來確保一個(gè) pipeline 或一個(gè)事務(wù)中操作的所有 key 都位于同一個(gè) Slot 中。
有一些客戶端(如 Redisson)實(shí)現(xiàn)了集群化的 pipelining 操作,可以自動(dòng)將一個(gè) pipeline 里的命令按 key 所在的分片進(jìn)行分組,分別發(fā)到不同的分片上執(zhí)行。但是 Redis 不支持跨分片的事務(wù),事務(wù)和 LUA Script 還是必須遵循所有 key 在一個(gè)分片上的規(guī)則要求。
主從復(fù)制 vs 集群分片
在設(shè)計(jì)軟件架構(gòu)時(shí),要如何在主從復(fù)制和集群分片兩種部署方案中取舍呢?
從各個(gè)方面看,Redis Cluster 都是優(yōu)于主從復(fù)制的方案
Redis Cluster 能夠解決單節(jié)點(diǎn)上數(shù)據(jù)量過大的問題
Redis Cluster 能夠解決單節(jié)點(diǎn)訪問壓力過大的問題
Redis Cluster 包含了主從復(fù)制的能力
那是不是代表 Redis Cluster 永遠(yuǎn)是優(yōu)于主從復(fù)制的選擇呢?
并不是。
軟件架構(gòu)永遠(yuǎn)不是越復(fù)雜越好,復(fù)雜的架構(gòu)在帶來顯著好處的同時(shí),一定也會(huì)帶來相應(yīng)的弊端。采用 Redis Cluster 的弊端包括:
維護(hù)難度增加。在使用 Redis Cluster 時(shí),需要維護(hù)的 Redis 實(shí)例數(shù)倍增,需要監(jiān)控的主機(jī)數(shù)量也相應(yīng)增加,數(shù)據(jù)備份 / 持久化的復(fù)雜度也會(huì)增加。同時(shí)在進(jìn)行分片的增減操作時(shí),還需要進(jìn)行 reshard 操作,遠(yuǎn)比主從模式下增加一個(gè) Slave 的復(fù)雜度要高。
客戶端資源消耗增加。當(dāng)客戶端使用連接池時(shí),需要為每一個(gè)數(shù)據(jù)分片維護(hù)一個(gè)連接池,客戶端同時(shí)需要保持的連接數(shù)成倍增多,加大了客戶端本身和操作系統(tǒng)資源的消耗。
性能優(yōu)化難度增加。你可能需要在多個(gè)分片上查看 Slow Log 和 Swap 日志才能定位性能問題。
事務(wù)和 LUA Script 的使用成本增加。在 Redis Cluster 中使用事務(wù)和 LUA Script 特性有嚴(yán)格的限制條件,事務(wù)和 Script 中操作的 key 必須位于同一個(gè)分片上,這就使得在開發(fā)時(shí)必須對(duì)相應(yīng)場(chǎng)景下涉及的 key 進(jìn)行額外的規(guī)劃和規(guī)范要求。如果應(yīng)用的場(chǎng)景中大量涉及事務(wù)和 Script 的使用,如何在保證這兩個(gè)功能的正常運(yùn)作前提下把數(shù)據(jù)平均分到多個(gè)數(shù)據(jù)分片中就會(huì)成為難點(diǎn)。
所以說,在主從復(fù)制和集群分片兩個(gè)方案中做出選擇時(shí),應(yīng)該從應(yīng)用軟件的功能特性、數(shù)據(jù)和訪問量級(jí)、未來發(fā)展規(guī)劃等方面綜合考慮,只在確實(shí)有必要引入數(shù)據(jù)分片時(shí)再使用 Redis Cluster。下面是一些建議:
需要在 Redis 中存儲(chǔ)的數(shù)據(jù)有多大?未來 2 年內(nèi)可能發(fā)展為多大?這些數(shù)據(jù)是否都需要長期保存?是否可以使用 LRU 算法進(jìn)行非熱點(diǎn)數(shù)據(jù)的淘汰?綜合考慮前面幾個(gè)因素,評(píng)估出 Redis 需要使用的物理內(nèi)存。
用于部署 Redis 的主機(jī)物理內(nèi)存有多大?有多少可以分配給 Redis 使用?對(duì)比 (1) 中的內(nèi)存需求評(píng)估,是否足夠用?
Redis 面臨的并發(fā)寫壓力會(huì)有多大?在不使用 pipelining 時(shí),Redis 的寫性能可以超過 10 萬次 / 秒(更多的 benchmark 可以參考 https://redis.io/topics/benchmarks )
在使用 Redis 時(shí),是否會(huì)使用到 pipelining 和事務(wù)功能?使用的場(chǎng)景多不多?綜合上面幾點(diǎn)考慮,如果單臺(tái)主機(jī)的可用物理內(nèi)存完全足以支撐對(duì) Redis 的容量需求,且 Redis 面臨的并發(fā)寫壓力距離 Benchmark 值還尚有距離,建議采用主從復(fù)制的架構(gòu),可以省去很多不必要的麻煩。同時(shí),如果應(yīng)用中大量使用 pipelining 和事務(wù),也建議盡可能選擇主從復(fù)制架構(gòu),可以減少設(shè)計(jì)和開發(fā)時(shí)的復(fù)雜度。
-
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208721 -
key
+關(guān)注
關(guān)注
0文章
48瀏覽量
12807 -
Redis
+關(guān)注
關(guān)注
0文章
370瀏覽量
10830 -
string
+關(guān)注
關(guān)注
0文章
40瀏覽量
4715
原文標(biāo)題:Redis 基礎(chǔ)、高級(jí)特性與性能調(diào)優(yōu) | 一文看全
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論