Redis基礎(chǔ)馁菜、高級(jí)特性與性能調(diào)優(yōu)

非我原創(chuàng), 轉(zhuǎn)載自Redis基礎(chǔ)最爬、高級(jí)特性與性能調(diào)優(yōu)
本文將從Redis的基本特性入手涉馁,通過(guò)講述Redis的數(shù)據(jù)結(jié)構(gòu)和主要命令對(duì)Redis的基本能力進(jìn)行直觀介紹。之后概覽Redis提供的高級(jí)能力爱致,并在部署烤送、維護(hù)、性能調(diào)優(yōu)等多個(gè)方面進(jìn)行更深入的介紹和指導(dǎo)糠悯。
本文適合使用Redis的普通開(kāi)發(fā)人員帮坚,以及對(duì)Redis進(jìn)行選型妻往、架構(gòu)設(shè)計(jì)和性能調(diào)優(yōu)的架構(gòu)設(shè)計(jì)人員。

目錄

  • 概述
  • Redis的數(shù)據(jù)結(jié)構(gòu)和相關(guān)常用命令
  • 數(shù)據(jù)持久化
  • 內(nèi)存管理與數(shù)據(jù)淘汰機(jī)制
  • Pipelining
  • 事務(wù)與Scripting
  • Redis性能調(diào)優(yōu)
  • 主從復(fù)制與集群分片
  • Redis Java客戶(hù)端的選擇

概述

Redis是一個(gè)開(kāi)源的试和,基于內(nèi)存的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)媒介讯泣,可以作為數(shù)據(jù)庫(kù)、緩存服務(wù)或消息服務(wù)使用阅悍。
Redis支持多種數(shù)據(jù)結(jié)構(gòu)判帮,包括字符串、哈希表溉箕、鏈表晦墙、集合、有序集合肴茄、位圖晌畅、Hyperloglogs等。
Redis具備LRU淘汰寡痰、事務(wù)實(shí)現(xiàn)抗楔、以及不同級(jí)別的硬盤(pán)持久化等能力,并且支持副本集和通過(guò)Redis Sentinel實(shí)現(xiàn)的高可用方案拦坠,同時(shí)還支持通過(guò)Redis Cluster實(shí)現(xiàn)的數(shù)據(jù)自動(dòng)分片能力连躏。

Redis的主要功能都基于單線(xiàn)程模型實(shí)現(xiàn),也就是說(shuō)Redis使用一個(gè)線(xiàn)程來(lái)服務(wù)所有的客戶(hù)端請(qǐng)求贞滨,同時(shí)Redis采用了非阻塞式IO入热,并精細(xì)地優(yōu)化各種命令的算法時(shí)間復(fù)雜度,這些信息意味著:

  • Redis是線(xiàn)程安全的(因?yàn)橹挥幸粋€(gè)線(xiàn)程)晓铆,其所有操作都是原子的勺良,不會(huì)因并發(fā)產(chǎn)生數(shù)據(jù)異常
  • Redis的速度非常快(因?yàn)槭褂梅亲枞絀O骄噪,且大部分命令的算法時(shí)間復(fù)雜度都是O(1))
  • 使用高耗時(shí)的Redis命令是很危險(xiǎn)的尚困,會(huì)占用唯一的一個(gè)線(xiàn)程的大量處理時(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ì)使用方法跨跨,請(qǐng)參考官方文檔:https://redis.io/commands

Key

Redis采用Key-Value型的基本數(shù)據(jù)結(jié)構(gòu),任何二進(jìn)制序列都可以作為Redis的Key使用(例如普通的字符串或一張JPEG圖片)
關(guān)于Key的一些注意事項(xiàng):

  • 不要使用過(guò)長(zhǎng)的Key。例如使用一個(gè)1024字節(jié)的key就不是一個(gè)好主意勇婴,不僅會(huì)消耗更多的內(nèi)存忱嘹,還會(huì)導(dǎo)致查找的效率降低
  • Key短到缺失了可讀性也是不好的,例如"u1000flw"比起"user:1000:followers"來(lái)說(shuō)耕渴,節(jié)省了寥寥的存儲(chǔ)空間拘悦,卻引發(fā)了可讀性和可維護(hù)性上的麻煩
  • 最好使用統(tǒng)一的規(guī)范來(lái)設(shè)計(jì)Key,比如"object-type:id:attr"橱脸,以這一規(guī)范設(shè)計(jì)出的Key可能是"user:1000"或"comment:1234:reply-to"
  • Redis允許的最大Key長(zhǎng)度是512MB(對(duì)Value的長(zhǎng)度限制也是512MB)

String

String是Redis的基礎(chǔ)數(shù)據(jù)類(lèi)型础米,Redis沒(méi)有Int、Float添诉、Boolean等數(shù)據(jù)類(lèi)型的概念屁桑,所有的基本類(lèi)型在Redis中都以String體現(xiàn)。

與String相關(guān)的常用命令:

  • SET:為一個(gè)key設(shè)置value栏赴,可以配合EX/PX參數(shù)指定key的有效期蘑斧,通過(guò)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)

上文提到過(guò),Redis的基本數(shù)據(jù)類(lèi)型只有String今野,但Redis可以把String作為整型或浮點(diǎn)型數(shù)字來(lái)使用葡公,主要體現(xiàn)在INCR罐农、DECR類(lèi)的命令上:

  • 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類(lèi)型為String宠默,并可以轉(zhuǎn)換為64位帶符號(hào)的整型數(shù)字麸恍,否則會(huì)返回錯(cuò)誤。
也就是說(shuō),進(jìn)行INCR/DECR系列命令的value抹沪,必須在[-2^63 ~ 2^63 - 1]范圍內(nèi)刻肄。

前文提到過(guò),Redis采用單線(xiàn)程模型融欧,天然是線(xiàn)程安全的敏弃,這使得INCR/DECR命令可以非常便利的實(shí)現(xiàn)高并發(fā)場(chǎng)景下的精確控制。

例1:庫(kù)存控制

在高并發(fā)場(chǎng)景下實(shí)現(xiàn)庫(kù)存余量的精準(zhǔn)校驗(yàn)噪馏,確保不出現(xiàn)超賣(mài)的情況麦到。

設(shè)置庫(kù)存總量:

SET inv:remain "100"

庫(kù)存扣減+余量校驗(yàn):

DECR inv:remain

當(dāng)DECR命令返回值大于等于0時(shí),說(shuō)明庫(kù)存余量校驗(yàn)通過(guò)欠肾,如果返回小于0的值瓶颠,則說(shuō)明庫(kù)存已耗盡。

假設(shè)同時(shí)有300個(gè)并發(fā)請(qǐng)求進(jìn)行庫(kù)存扣減刺桃,Redis能夠確保這300個(gè)請(qǐng)求分別得到99到-200的返回值步清,每個(gè)請(qǐng)求得到的返回值都是唯一的,絕對(duì)不會(huì)找出現(xiàn)兩個(gè)請(qǐng)求得到一樣的返回值的情況虏肾。

例2:自增序列生成

實(shí)現(xiàn)類(lèi)似于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è)客戶(hù)端同時(shí)向Redis申請(qǐng)自增序列時(shí),Redis能夠確保每個(gè)客戶(hù)端得到的序列值或序列范圍都是全局唯一的吹埠,絕對(duì)不會(huì)出現(xiàn)不同客戶(hù)端得到了重復(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長(zhǎng)度。時(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類(lèi)似淑仆,區(qū)別在于,LPUSHX/RPUSHX操作的key如果不存在哥力,則不會(huì)進(jìn)行任何操作
  • LLEN:返回指定List的長(zhǎng)度蔗怠,時(shí)間復(fù)雜度O(1)
  • LRANGE:返回指定List中指定范圍的元素(雙端包含,即LRANGE key 0 10會(huì)返回11個(gè)元素),時(shí)間復(fù)雜度O(N)寞射。應(yīng)盡可能控制一次獲取的元素?cái)?shù)量最住,一次獲取過(guò)大范圍的List元素會(huì)導(dǎo)致延遲,同時(shí)對(duì)長(zhǎng)度不可預(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長(zhǎng)度悔政。如果指定的元素不存在,返回-1延旧。如果指定key不存在谋国,不會(huì)進(jìn)行任何操作,時(shí)間復(fù)雜度O(N)

由于Redis的List是鏈表結(jié)構(gòu)的迁沫,上述的三個(gè)命令的算法效率較低芦瘾,需要對(duì)List進(jìn)行遍歷,命令的耗時(shí)無(wú)法預(yù)估集畅,在List長(zhǎng)度大的情況下耗時(shí)會(huì)明顯增加近弟,應(yīng)謹(jǐn)慎使用。

換句話(huà)說(shuō)挺智,Redis的List實(shí)際是設(shè)計(jì)來(lái)用于實(shí)現(xiàn)隊(duì)列祷愉,而不是用于實(shí)現(xiàn)類(lèi)似ArrayList這樣的列表的。如果你不是想要實(shí)現(xiàn)一個(gè)雙端出入的隊(duì)列逃贝,那么請(qǐng)盡量不要使用Redis的List數(shù)據(jù)結(jié)構(gòu)谣辞。

為了更好支持隊(duì)列的特性,Redis還提供了一系列阻塞式的操作命令沐扳,如BLPOP/BRPOP等,能夠?qū)崿F(xiàn)類(lèi)似于BlockingQueue的能力句占,即在List為空時(shí)沪摄,阻塞該連接,直到List中有對(duì)象可以出隊(duì)時(shí)再返回。針對(duì)阻塞類(lèi)的命令杨拐,此處不做詳細(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ì)象類(lèi)型的數(shù)據(jù)蜒谤,用Hash中的field對(duì)應(yīng)對(duì)象的field即可。
Hash的優(yōu)點(diǎn)包括:

  • 可以實(shí)現(xiàn)二元查找至扰,如"查找ID為1000的用戶(hù)的年齡"
  • 比起將整個(gè)對(duì)象序列化后作為String存儲(chǔ)的方法鳍徽,Hash能夠有效地減少網(wǎng)絡(luò)傳輸?shù)南?/li>
  • 當(dāng)使用Hash維護(hù)一個(gè)集合時(shí),提供了比List效率高得多的隨機(jī)訪(fǎng)問(wèn)命令

與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í)線(xiàn)性相關(guān)腌紧,對(duì)于尺寸不可預(yù)知的Hash,應(yīng)嚴(yán)格避免使用上面三個(gè)命令畜隶,而改為使用HSCAN命令進(jìn)行游標(biāo)式的遍歷壁肋,具體請(qǐng)見(jiàn) https://redis.io/commands/scan

Set

Redis Set是無(wú)序的号胚,不可重復(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)格避免使用装盯。可以考慮通過(guò)SSCAN命令遍歷獲取相關(guān)Set的全部member(具體請(qǐng)見(jiàn) https://redis.io/commands/scan )甲馋,如果需要做并集/交集/差集計(jì)算埂奈,可以在客戶(hù)端進(jìn)行,或在不服務(wù)實(shí)時(shí)查詢(xún)請(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ù),來(lái)對(duì)Sorted Set做一次性的完整遍歷译蒂,特別是在Sorted Set的尺寸不可預(yù)知的情況下曼月。可以通過(guò)ZSCAN命令來(lái)進(jìn)行游標(biāo)式的遍歷(具體請(qǐng)見(jiàn) https://redis.io/commands/scan )柔昼,或通過(guò)LIMIT參數(shù)來(lái)限制返回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ù)類(lèi)型激率,而是一種將String作為Bitmap使用的方法咳燕。可以理解為將String轉(zhuǎn)換為bit數(shù)組乒躺。使用Bitmap來(lái)存儲(chǔ)true/false類(lèi)型的簡(jiǎn)單數(shù)據(jù)極為節(jié)省空間招盲。

HyperLogLogs是一種主要用于數(shù)量統(tǒng)計(jì)的數(shù)據(jù)結(jié)構(gòu),它和Set類(lèi)似嘉冒,維護(hù)一個(gè)不可重復(fù)的String集合曹货,但是HyperLogLogs并不維護(hù)具體的member內(nèi)容咆繁,只維護(hù)member的個(gè)數(shù)。也就是說(shuō)顶籽,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的類(lèi)型综苔,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中的配置

數(shù)據(jù)持久化

Redis提供了將數(shù)據(jù)定期自動(dòng)持久化至硬盤(pán)的能力抒抬,包括RDB和AOF兩種方案杨刨,兩種方案分別有其長(zhǎng)處和短板,可以配合起來(lái)同時(shí)運(yùn)行擦剑,確保數(shù)據(jù)的穩(wěn)定性妖胀。

必須使用數(shù)據(jù)持久化嗎?

Redis的數(shù)據(jù)持久化機(jī)制是可以關(guān)閉的惠勒。如果你只把Redis作為緩存服務(wù)使用赚抡,Redis中存儲(chǔ)的所有數(shù)據(jù)都不是該數(shù)據(jù)的主體而僅僅是同步過(guò)來(lái)的備份,那么可以關(guān)閉Redis的數(shù)據(jù)持久化機(jī)制纠屋。
但通常來(lái)說(shuō)涂臣,仍然建議至少開(kāi)啟RDB方式的數(shù)據(jù)持久化,因?yàn)椋?/p>

  • RDB方式的持久化幾乎不損耗Redis本身的性能售担,在進(jìn)行RDB持久化時(shí)赁遗,Redis主進(jìn)程唯一需要做的事情就是fork出一個(gè)子進(jìn)程署辉,所有持久化工作都由子進(jìn)程完成
  • Redis無(wú)論因?yàn)槭裁丛騝rash掉之后,重啟時(shí)能夠自動(dòng)恢復(fù)到上一次RDB快照中記錄的數(shù)據(jù)岩四。這省去了手工從其他數(shù)據(jù)源(如DB)同步數(shù)據(jù)的過(guò)程哭尝,而且要比其他任何的數(shù)據(jù)恢復(fù)方式都要快
  • 現(xiàn)在硬盤(pán)那么大,真的不缺那一點(diǎn)地方

RDB

采用RDB持久方式炫乓,Redis會(huì)定期保存數(shù)據(jù)快照至一個(gè)rbd文件中刚夺,并在啟動(dòng)時(shí)自動(dòng)加載rdb文件献丑,恢復(fù)之前保存的數(shù)據(jù)末捣。可以在配置文件中配置Redis進(jìn)行快照保存的時(shí)機(jī):

save [seconds] [changes]

意為在[seconds]秒內(nèi)如果發(fā)生了[changes]次數(shù)據(jù)修改创橄,則進(jìn)行一次RDB快照保存箩做,例如

save 60 100

會(huì)讓Redis每60秒檢查一次數(shù)據(jù)變更情況,如果發(fā)生了100次或以上的數(shù)據(jù)變更妥畏,則進(jìn)行RDB快照保存邦邦。
可以配置多條save指令,讓Redis執(zhí)行多級(jí)的快照保存策略醉蚁。
Redis默認(rèn)開(kāi)啟RDB快照燃辖,默認(rèn)的RDB策略如下:

save 900 1
save 300 10
save 60 10000

也可以通過(guò)BGSAVE命令手工觸發(fā)RDB快照保存。

RDB的優(yōu)點(diǎn):

  • 對(duì)性能影響最小网棍。如前文所述黔龟,Redis在保存RDB快照時(shí)會(huì)fork出子進(jìn)程進(jìn)行,幾乎不影響Redis處理客戶(hù)端請(qǐng)求的效率滥玷。
  • 每次快照會(huì)生成一個(gè)完整的數(shù)據(jù)快照文件氏身,所以可以輔以其他手段保存多個(gè)時(shí)間點(diǎn)的快照(例如把每天0點(diǎn)的快照備份至其他存儲(chǔ)媒介中),作為非郴蟪耄可靠的災(zāi)難恢復(fù)手段蛋欣。
  • 使用RDB文件進(jìn)行數(shù)據(jù)恢復(fù)比使用AOF要快很多。

RDB的缺點(diǎn):

  • 快照是定期生成的如贷,所以在Redis crash時(shí)或多或少會(huì)丟失一部分?jǐn)?shù)據(jù)陷虎。
  • 如果數(shù)據(jù)集非常大且CPU不夠強(qiáng)(比如單核CPU),Redis在fork子進(jìn)程時(shí)可能會(huì)消耗相對(duì)較長(zhǎng)的時(shí)間(長(zhǎng)至1秒)杠袱,影響這期間的客戶(hù)端請(qǐng)求尚猿。

AOF

采用AOF持久方式時(shí),Redis會(huì)把每一個(gè)寫(xiě)請(qǐng)求都記錄在一個(gè)日志文件里霞掺。在Redis重啟時(shí)谊路,會(huì)把AOF文件中記錄的所有寫(xiě)操作順序執(zhí)行一遍,確保數(shù)據(jù)恢復(fù)到最新菩彬。

AOF默認(rèn)是關(guān)閉的缠劝,如要開(kāi)啟潮梯,進(jìn)行如下配置:

appendonly yes

AOF提供了三種fsync配置,always/everysec/no惨恭,通過(guò)配置項(xiàng)[appendfsync]指定:

  • appendfsync no:不進(jìn)行fsync秉馏,將flush文件的時(shí)機(jī)交給OS決定,速度最快
  • appendfsync always:每寫(xiě)入一條日志就進(jìn)行一次fsync操作脱羡,數(shù)據(jù)安全性最高萝究,但速度最慢
  • appendfsync everysec:折中的做法,交由后臺(tái)線(xiàn)程每秒fsync一次

隨著AOF不斷地記錄寫(xiě)操作日志锉罐,必定會(huì)出現(xiàn)一些無(wú)用的日志帆竹,例如某個(gè)時(shí)間點(diǎn)執(zhí)行了命令SET key1 "abc",在之后某個(gè)時(shí)間點(diǎn)又執(zhí)行了SET key1 "bcd"脓规,那么第一條命令很顯然是沒(méi)有用的栽连。大量的無(wú)用日志會(huì)讓AOF文件過(guò)大,也會(huì)讓數(shù)據(jù)恢復(fù)的時(shí)間過(guò)長(zhǎng)侨舆。
所以Redis提供了AOF rewrite功能秒紧,可以重寫(xiě)AOF文件,只保留能夠把數(shù)據(jù)恢復(fù)到最新?tīng)顟B(tài)的最小寫(xiě)操作集挨下。
AOF rewrite可以通過(guò)BGREWRITEAOF命令觸發(fā)熔恢,也可以配置Redis定期自動(dòng)進(jìn)行:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

上面兩行配置的含義是,Redis在每次AOF rewrite時(shí)臭笆,會(huì)記錄完成rewrite后的AOF日志大小叙淌,當(dāng)AOF日志大小在該基礎(chǔ)上增長(zhǎng)了100%后,自動(dòng)進(jìn)行AOF rewrite耗啦。同時(shí)如果增長(zhǎng)的大小沒(méi)有達(dá)到64mb凿菩,則不會(huì)進(jìn)行rewrite。

AOF的優(yōu)點(diǎn):

  • 最安全帜讲,在啟用appendfsync always時(shí)衅谷,任何已寫(xiě)入的數(shù)據(jù)都不會(huì)丟失,使用在啟用appendfsync everysec也至多只會(huì)丟失1秒的數(shù)據(jù)似将。
  • AOF文件在發(fā)生斷電等問(wèn)題時(shí)也不會(huì)損壞获黔,即使出現(xiàn)了某條日志只寫(xiě)入了一半的情況,也可以使用redis-check-aof工具輕松修復(fù)在验。
  • AOF文件易讀玷氏,可修改,在進(jìn)行了某些錯(cuò)誤的數(shù)據(jù)清除操作后腋舌,只要AOF文件沒(méi)有rewrite盏触,就可以把AOF文件備份出來(lái),把錯(cuò)誤的命令刪除,然后恢復(fù)數(shù)據(jù)赞辩。

AOF的缺點(diǎn):

  • AOF文件通常比RDB文件更大
  • 性能消耗比RDB高
  • 數(shù)據(jù)恢復(fù)速度比RDB慢

內(nèi)存管理與數(shù)據(jù)淘汰機(jī)制

最大內(nèi)存設(shè)置

默認(rèn)情況下雌芽,在32位OS中,Redis最大使用3GB的內(nèi)存辨嗽,在64位OS中則沒(méi)有限制世落。

在使用Redis時(shí),應(yīng)該對(duì)數(shù)據(jù)占用的最大空間有一個(gè)基本準(zhǔn)確的預(yù)估糟需,并為Redis設(shè)定最大使用的內(nèi)存屉佳。否則在64位OS中Redis會(huì)無(wú)限制地占用內(nèi)存(當(dāng)物理內(nèi)存被占滿(mǎn)后會(huì)使用swap空間),容易引發(fā)各種各樣的問(wèn)題洲押。

通過(guò)如下配置控制Redis使用的最大內(nèi)存:

maxmemory 100mb

在內(nèi)存占用達(dá)到了maxmemory后武花,再向Redis寫(xiě)入數(shù)據(jù)時(shí),Redis會(huì):

  • 根據(jù)配置的數(shù)據(jù)淘汰策略嘗試淘汰數(shù)據(jù)诅诱,釋放空間
  • 如果沒(méi)有數(shù)據(jù)可以淘汰髓堪,或者沒(méi)有配置數(shù)據(jù)淘汰策略,那么Redis會(huì)對(duì)所有寫(xiě)請(qǐng)求返回錯(cuò)誤娘荡,但讀請(qǐng)求仍然可以正常執(zhí)行

在為Redis設(shè)置maxmemory時(shí),需要注意:

  • 如果采用了Redis的主從同步驶沼,主節(jié)點(diǎn)向從節(jié)點(diǎn)同步數(shù)據(jù)時(shí)炮沐,會(huì)占用掉一部分內(nèi)存空間,如果maxmemory過(guò)于接近主機(jī)的可用內(nèi)存回怜,導(dǎo)致數(shù)據(jù)同步時(shí)內(nèi)存不足大年。所以設(shè)置的maxmemory不要過(guò)于接近主機(jī)可用的內(nèi)存,留出一部分預(yù)留用作主從同步玉雾。

數(shù)據(jù)淘汰機(jī)制

Redis提供了5種數(shù)據(jù)淘汰策略:

  • volatile-lru:使用LRU算法進(jìn)行數(shù)據(jù)淘汰(淘汰上次使用時(shí)間最早的翔试,且使用次數(shù)最少的key),只淘汰設(shè)定了有效期的key
  • allkeys-lru:使用LRU算法進(jìn)行數(shù)據(jù)淘汰复旬,所有的key都可以被淘汰
  • volatile-random:隨機(jī)淘汰數(shù)據(jù)垦缅,只淘汰設(shè)定了有效期的key
  • allkeys-random:隨機(jī)淘汰數(shù)據(jù),所有的key都可以被淘汰
  • volatile-ttl:淘汰剩余有效期最短的key

最好為Redis指定一種有效的數(shù)據(jù)淘汰策略以配合maxmemory設(shè)置驹碍,避免在內(nèi)存使用滿(mǎn)后發(fā)生寫(xiě)入失敗的情況壁涎。

一般來(lái)說(shuō),推薦使用的策略是volatile-lru志秃,并辨識(shí)Redis中保存的數(shù)據(jù)的重要性怔球。對(duì)于那些重要的,絕對(duì)不能丟棄的數(shù)據(jù)(如配置類(lèi)數(shù)據(jù)等)浮还,應(yīng)不設(shè)置有效期竟坛,這樣Redis就永遠(yuǎn)不會(huì)淘汰這些數(shù)據(jù)。對(duì)于那些相對(duì)不是那么重要的,并且能夠熱加載的數(shù)據(jù)(比如緩存最近登錄的用戶(hù)信息担汤,當(dāng)在Redis中找不到時(shí)又官,程序會(huì)去DB中讀取)漫试,可以設(shè)置上有效期六敬,這樣在內(nèi)存不夠時(shí)Redis就會(huì)淘汰這部分?jǐn)?shù)據(jù)。

配置方法:

maxmemory-policy volatile-lru   #默認(rèn)是noeviction驾荣,即不進(jìn)行數(shù)據(jù)淘汰

Pipelining

Pipelining

Redis提供許多批量操作的命令外构,如MSET/MGET/HMSET/HMGET等等,這些命令存在的意義是減少維護(hù)網(wǎng)絡(luò)連接和傳輸數(shù)據(jù)所消耗的資源和時(shí)間播掷。
例如連續(xù)使用5次SET命令設(shè)置5個(gè)不同的key审编,比起使用一次MSET命令設(shè)置5個(gè)不同的key,效果是一樣的歧匈,但前者會(huì)消耗更多的RTT(Round Trip Time)時(shí)長(zhǎng)垒酬,永遠(yuǎn)應(yīng)優(yōu)先使用后者。

然而件炉,如果客戶(hù)端要連續(xù)執(zhí)行的多次操作無(wú)法通過(guò)Redis命令組合在一起勘究,例如:

SET a "abc"
INCR b
HSET c name "hi"

此時(shí)便可以使用Redis提供的pipelining功能來(lái)實(shí)現(xiàn)在一次交互中執(zhí)行多條命令。
使用pipelining時(shí)斟冕,只需要從客戶(hù)端一次向Redis發(fā)送多條命令(以\r\n)分隔口糕,Redis就會(huì)依次執(zhí)行這些命令,并且把每個(gè)命令的返回按順序組裝在一起一次返回磕蛇,比如:

$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG

大部分的Redis客戶(hù)端都對(duì)Pipelining提供支持景描,所以開(kāi)發(fā)者通常并不需要自己手工拼裝命令列表。

Pipelining的局限性

Pipelining只能用于執(zhí)行連續(xù)且無(wú)相關(guān)性的命令秀撇,當(dāng)某個(gè)命令的生成需要依賴(lài)于前一個(gè)命令的返回時(shí)超棺,就無(wú)法使用Pipelining了。

通過(guò)Scripting功能呵燕,可以規(guī)避這一局限性

事務(wù)與Scripting

Pipelining能夠讓Redis在一次交互中處理多條命令棠绘,然而在一些場(chǎng)景下,我們可能需要在此基礎(chǔ)上確保這一組命令是連續(xù)執(zhí)行的虏等。

比如獲取當(dāng)前累計(jì)的PV數(shù)并將其清0

> GET vCount
12384
> SET vCount 0
OK

如果在GET和SET命令之間插進(jìn)來(lái)一個(gè)INCR vCount弄唧,就會(huì)使客戶(hù)端拿到的vCount不準(zhǔn)確。

Redis的事務(wù)可以確保復(fù)數(shù)命令執(zhí)行時(shí)的原子性霍衫。也就是說(shuō)Redis能夠保證:一個(gè)事務(wù)中的一組命令是絕對(duì)連續(xù)執(zhí)行的候引,在這些命令執(zhí)行完成之前,絕對(duì)不會(huì)有來(lái)自于其他連接的其他命令插進(jìn)去執(zhí)行敦跌。

通過(guò)MULTI和EXEC命令來(lái)把這兩個(gè)命令加入一個(gè)事務(wù)中:

> MULTI
OK
> GET vCount
QUEUED
> SET vCount 0
QUEUED
> EXEC
1) 12384
2) OK

Redis在接收到MULTI命令后便會(huì)開(kāi)啟一個(gè)事務(wù)澄干,這之后的所有讀寫(xiě)命令都會(huì)保存在隊(duì)列中但并不執(zhí)行逛揩,直到接收到EXEC命令后,Redis會(huì)把隊(duì)列中的所有命令連續(xù)順序執(zhí)行麸俘,并以數(shù)組形式返回每個(gè)命令的返回結(jié)果辩稽。

可以使用DISCARD命令放棄當(dāng)前的事務(wù),將保存的命令隊(duì)列清空从媚。

需要注意的是逞泄,Redis事務(wù)不支持回滾
如果一個(gè)事務(wù)中的命令出現(xiàn)了語(yǔ)法錯(cuò)誤,大部分客戶(hù)端驅(qū)動(dòng)會(huì)返回錯(cuò)誤拜效,2.6.5版本以上的Redis也會(huì)在執(zhí)行EXEC時(shí)檢查隊(duì)列中的命令是否存在語(yǔ)法錯(cuò)誤喷众,如果存在,則會(huì)自動(dòng)放棄事務(wù)并返回錯(cuò)誤紧憾。
但如果一個(gè)事務(wù)中的命令有非語(yǔ)法類(lèi)的錯(cuò)誤(比如對(duì)String執(zhí)行HSET操作)到千,無(wú)論客戶(hù)端驅(qū)動(dòng)還是Redis都無(wú)法在真正執(zhí)行這條命令之前發(fā)現(xiàn),所以事務(wù)中的所有命令仍然會(huì)被依次執(zhí)行赴穗。在這種情況下憔四,會(huì)出現(xiàn)一個(gè)事務(wù)中部分命令成功部分命令失敗的情況,然而與RDBMS不同般眉,Redis不提供事務(wù)回滾的功能了赵,所以只能通過(guò)其他方法進(jìn)行數(shù)據(jù)的回滾。

通過(guò)事務(wù)實(shí)現(xiàn)CAS

Redis提供了WATCH命令與事務(wù)搭配使用煤篙,實(shí)現(xiàn)CAS樂(lè)觀鎖的機(jī)制斟览。

假設(shè)要實(shí)現(xiàn)將某個(gè)商品的狀態(tài)改為已售:

if(exec(HGET stock:1001 state) == "in stock")
    exec(HSET stock:1001 state "sold");

這一偽代碼執(zhí)行時(shí),無(wú)法確保并發(fā)安全性辑奈,有可能多個(gè)客戶(hù)端都獲取到了"in stock"的狀態(tài),導(dǎo)致一個(gè)庫(kù)存被售賣(mài)多次已烤。

使用WATCH命令和事務(wù)可以解決這一問(wèn)題:

exec(WATCH stock:1001);
if(exec(HGET stock:1001 state) == "in stock") {
    exec(MULTI);
    exec(HSET stock:1001 state "sold");
    exec(EXEC);
}

WATCH的機(jī)制是:在事務(wù)EXEC命令執(zhí)行時(shí)鸠窗,Redis會(huì)檢查被WATCH的key,只有被WATCH的key從WATCH起始時(shí)至今沒(méi)有發(fā)生過(guò)變更胯究,EXEC才會(huì)被執(zhí)行稍计。如果WATCH的key在WATCH命令到EXEC命令之間發(fā)生過(guò)變化,則EXEC命令會(huì)返回失敗裕循。

Scripting

通過(guò)EVAL與EVALSHA命令臣嚣,可以讓Redis執(zhí)行LUA腳本。這就類(lèi)似于RDBMS的存儲(chǔ)過(guò)程一樣剥哑,可以把客戶(hù)端與Redis之間密集的讀/寫(xiě)交互放在服務(wù)端進(jìn)行硅则,避免過(guò)多的數(shù)據(jù)交互,提升性能株婴。

Scripting功能是作為事務(wù)功能的替代者誕生的怎虫,事務(wù)提供的所有能力Scripting都可以做到。Redis官方推薦使用LUA Script來(lái)代替事務(wù),前者的效率和便利性都超過(guò)了事務(wù)大审。

關(guān)于Scripting的具體使用蘸际,本文不做詳細(xì)介紹,請(qǐng)參考官方文檔 https://redis.io/commands/eval

Redis性能調(diào)優(yōu)

盡管Redis是一個(gè)非惩椒觯快速的內(nèi)存數(shù)據(jù)存儲(chǔ)媒介粮彤,也并不代表Redis不會(huì)產(chǎn)生性能問(wèn)題。
前文中提到過(guò)姜骡,Redis采用單線(xiàn)程模型导坟,所有的命令都是由一個(gè)線(xiàn)程串行執(zhí)行的,所以當(dāng)某個(gè)命令執(zhí)行耗時(shí)較長(zhǎng)時(shí)溶浴,會(huì)拖慢其后的所有命令乍迄,這使得Redis對(duì)每個(gè)任務(wù)的執(zhí)行效率更加敏感。

針對(duì)Redis的性能優(yōu)化士败,主要從下面幾個(gè)層面入手:

  • 最初的也是最重要的闯两,確保沒(méi)有讓Redis執(zhí)行耗時(shí)長(zhǎng)的命令
  • 使用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)境帶來(lái)的固有延遲谅将⊙牵可以通過(guò)./redis-cli --intrinsic-latency 100命令查看固有延遲。同時(shí)如果對(duì)Redis的性能有較高要求的話(huà)饥臂,應(yīng)盡可能在物理機(jī)上直接部署Redis逊躁。
  • 檢查數(shù)據(jù)持久化策略
  • 考慮引入讀寫(xiě)分離機(jī)制

長(zhǎng)耗時(shí)命令

Redis絕大多數(shù)讀寫(xiě)命令的時(shí)間復(fù)雜度都在O(1)到O(N)之間,在文本和官方文檔中均對(duì)每個(gè)命令的時(shí)間復(fù)雜度有說(shuō)明隅熙。

通常來(lái)說(shuō)稽煤,O(1)的命令是安全的,O(N)命令在使用時(shí)需要注意囚戚,如果N的數(shù)量級(jí)不可預(yù)知酵熙,則應(yīng)避免使用。例如對(duì)一個(gè)field數(shù)未知的Hash數(shù)據(jù)執(zhí)行HGETALL/HKEYS/HVALS命令驰坊,通常來(lái)說(shuō)這些命令執(zhí)行的很快匾二,但如果這個(gè)Hash中的field數(shù)量極多,耗時(shí)就會(huì)成倍增長(zhǎng)拳芙。
又如使用SUNION對(duì)兩個(gè)Set執(zhí)行Union操作察藐,或使用SORT對(duì)List/Set執(zhí)行排序操作等時(shí),都應(yīng)該嚴(yán)加注意舟扎。

避免在使用這些O(N)命令時(shí)發(fā)生問(wèn)題主要有幾個(gè)辦法:

  • 不要把List當(dāng)做列表使用分飞,僅當(dāng)做隊(duì)列來(lái)使用
  • 通過(guò)機(jī)制嚴(yán)格控制Hash、Set浆竭、Sorted Set的大小
  • 可能的話(huà)浸须,將排序惨寿、并集、交集等操作放在客戶(hù)端執(zhí)行
  • 絕對(duì)禁止使用KEYS命令
  • 避免一次性遍歷集合類(lèi)型的所有成員删窒,而應(yīng)使用SCAN類(lèi)的命令進(jìn)行分批的裂垦,游標(biāo)式的遍歷

Redis提供了SCAN命令,可以對(duì)Redis中存儲(chǔ)的所有key進(jìn)行游標(biāo)式的遍歷肌索,避免使用KEYS命令帶來(lái)的性能問(wèn)題蕉拢。同時(shí)還有SSCAN/HSCAN/ZSCAN等命令,分別用于對(duì)Set/Hash/Sorted Set中的元素進(jìn)行游標(biāo)式遍歷诚亚。SCAN類(lèi)命令的使用請(qǐng)參考官方文檔:https://redis.io/commands/scan

Redis提供了Slow Log功能晕换,可以自動(dòng)記錄耗時(shí)較長(zhǎng)的命令。相關(guān)的配置參數(shù)有兩個(gè):

slowlog-log-slower-than xxxms  #執(zhí)行時(shí)間慢于xxx毫秒的命令計(jì)入Slow Log
slowlog-max-len xxx  #Slow Log的長(zhǎng)度站宗,即最大紀(jì)錄多少條Slow Log

使用SLOWLOG GET [number]命令闸准,可以輸出最近進(jìn)入Slow Log的number條命令。
使用SLOWLOG RESET命令梢灭,可以重置Slow Log

網(wǎng)絡(luò)引發(fā)的延遲

  • 盡可能使用長(zhǎng)連接或連接池夷家,避免頻繁創(chuàng)建銷(xiāo)毀連接
  • 客戶(hù)端進(jìn)行的批量數(shù)據(jù)操作,應(yīng)使用Pipeline特性在一次交互中完成敏释。具體請(qǐng)參照本文的Pipelining章節(jié)

數(shù)據(jù)持久化引發(fā)的延遲

Redis的數(shù)據(jù)持久化工作本身就會(huì)帶來(lái)延遲库快,需要根據(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ī)奶浦,避免過(guò)于頻繁的fork帶來(lái)的延遲

Redis在fork子進(jìn)程時(shí)需要將內(nèi)存分頁(yè)表拷貝至子進(jìn)程阳啥,以占用了24GB內(nèi)存的Redis實(shí)例為例,共需要拷貝24GB / 4kB * 8 = 48MB的數(shù)據(jù)财喳。在使用單Xeon 2.27Ghz的物理機(jī)上,這一fork操作耗時(shí)216ms斩狱。

可以通過(guò)INFO命令返回的latest_fork_usec字段查看上一次fork操作的耗時(shí)(微秒)

Swap引發(fā)的延遲

當(dāng)Linux將Redis所用的內(nèi)存分頁(yè)移至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/<pid>/smaps文件中會(huì)保存進(jìn)程的swap記錄秕岛,通過(guò)查看這個(gè)文件碌燕,能夠判斷Redis的延遲是否由Swap產(chǎn)生误证。如果這個(gè)文件中記錄了較大的Swap size,則說(shuō)明延遲很有可能是Swap造成的修壕。

數(shù)據(jù)淘汰引發(fā)的延遲

當(dāng)同一秒內(nèi)有大量key過(guò)期時(shí)愈捅,也會(huì)引發(fā)Redis的延遲。在使用時(shí)應(yīng)盡量將key的失效時(shí)間錯(cuò)開(kāi)慈鸠。

引入讀寫(xiě)分離機(jī)制

Redis的主從復(fù)制能力可以實(shí)現(xiàn)一主多從的多節(jié)點(diǎn)架構(gòu)蓝谨,在這一架構(gòu)下,主節(jié)點(diǎn)接收所有寫(xiě)請(qǐng)求青团,并將數(shù)據(jù)同步給多個(gè)從節(jié)點(diǎn)譬巫。
在這一基礎(chǔ)上,我們可以讓從節(jié)點(diǎn)提供對(duì)實(shí)時(shí)性要求不高的讀請(qǐng)求服務(wù)督笆,以減小主節(jié)點(diǎn)的壓力芦昔。
尤其是針對(duì)一些使用了長(zhǎng)耗時(shí)命令的統(tǒng)計(jì)類(lèi)任務(wù),完全可以指定在一個(gè)或多個(gè)從節(jié)點(diǎn)上執(zhí)行娃肿,避免這些長(zhǎng)耗時(shí)命令影響其他請(qǐng)求的響應(yīng)咕缎。

關(guān)于讀寫(xiě)分離的具體說(shuō)明,請(qǐng)參見(jiàn)后續(xù)章節(jié)

主從復(fù)制與集群分片

主從復(fù)制

Redis支持一主多從的主從復(fù)制架構(gòu)咸作。一個(gè)Master實(shí)例負(fù)責(zé)處理所有的寫(xiě)請(qǐng)求锨阿,Master將寫(xiě)操作同步至所有Slave。
借助Redis的主從復(fù)制记罚,可以實(shí)現(xiàn)讀寫(xiě)分離和高可用:

  • 實(shí)時(shí)性要求不是特別高的讀請(qǐng)求墅诡,可以在Slave上完成,提升效率桐智。特別是一些周期性執(zhí)行的統(tǒng)計(jì)任務(wù)末早,這些任務(wù)可能需要執(zhí)行一些長(zhǎng)耗時(shí)的Redis命令,可以專(zhuān)門(mén)規(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ù)通過(guò)Redis Protocol同步給Slave。之后主從之間的數(shù)據(jù)便一直以Redis Protocol進(jìn)行同步

使用Sentinel做自動(dòng)failover

Redis的主從復(fù)制功能本身只是做數(shù)據(jù)同步躲惰,并不提供監(jiān)控和自動(dòng)failover能力致份,要通過(guò)主從復(fù)制功能來(lái)實(shí)現(xiàn)Redis的高可用,還需要引入一個(gè)組件:Redis Sentinel

Redis Sentinel是Redis官方開(kāi)發(fā)的監(jiān)控組件础拨,可以監(jiān)控Redis實(shí)例的狀態(tài)氮块,通過(guò)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  #多長(zhǎng)時(shí)間沒(méi)有響應(yīng)視為Master失效
sentinel failover-timeout mymaster 180000  #兩次failover嘗試間的間隔時(shí)長(zhǎng)
sentinel parallel-syncs mymaster 1  #如果有多個(gè)Slave曙痘,可以通過(guò)此配置指定同時(shí)從新Master進(jìn)行數(shù)據(jù)同步的Slave數(shù),避免所有Slave同時(shí)進(jìn)行數(shù)據(jù)同步導(dǎo)致查詢(xún)服務(wù)也不可用

另外需要注意的是立肘,Redis Sentinel實(shí)現(xiàn)的自動(dòng)failover不是在同一個(gè)IP和端口上完成的边坤,也就是說(shuō)自動(dòng)failover產(chǎn)生的新Master提供服務(wù)的IP和端口與之前的Master是不一樣的,所以要實(shí)現(xiàn)HA谅年,還要求客戶(hù)端必須支持Sentinel茧痒,能夠與Sentinel交互獲得新Master的信息才行。

集群分片

為何要做集群分片:

  • Redis中存儲(chǔ)的數(shù)據(jù)量大融蹂,一臺(tái)主機(jī)的物理內(nèi)存已經(jīng)無(wú)法容納
  • Redis的寫(xiě)請(qǐng)求并發(fā)量大旺订,一個(gè)Redis實(shí)例以無(wú)法承載

當(dāng)上述兩個(gè)問(wèn)題出現(xiàn)時(shí),就必須要對(duì)Redis進(jìn)行分片了超燃。
Redis的分片方案有很多種区拳,例如很多Redis的客戶(hù)端都自行實(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帶來(lái)的好處與弊端届良。

Redis Cluster的能力

  • 能夠自動(dòng)將數(shù)據(jù)分散在多個(gè)節(jié)點(diǎn)上
  • 當(dāng)訪(fǎng)問(wèn)的key不在當(dāng)前分片上時(shí)笆凌,能夠自動(dòng)將請(qǐng)求轉(zhuǎn)發(fā)至正確的分片
  • 當(dāng)集群中部分節(jié)點(diǎn)失效時(shí)仍能提供服務(wù)

其中第三點(diǎn)是基于主從復(fù)制來(lái)實(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取模荚藻,來(lái)決定該key存儲(chǔ)在哪一個(gè)hash slot中呻右,同時(shí)需要指定Redis Cluster中每個(gè)數(shù)據(jù)分片負(fù)責(zé)的Slot數(shù)。Slot的分配在任何時(shí)間點(diǎn)都可以進(jìn)行重新分配鞋喇。

客戶(hù)端在對(duì)key進(jìn)行讀寫(xiě)操作時(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中使用上述功能枚冗,就必須通過(guò)hash tags來(lái)確保一個(gè)pipeline或一個(gè)事務(wù)中操作的所有key都位于同一個(gè)Slot中缓溅。

有一些客戶(hù)端(如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ù)量過(guò)大的問(wèn)題
  • Redis Cluster能夠解決單節(jié)點(diǎn)訪(fǎng)問(wèn)壓力過(guò)大的問(wèn)題
  • Redis Cluster包含了主從復(fù)制的能力

那是不是代表Redis Cluster永遠(yuǎn)是優(yōu)于主從復(fù)制的選擇呢居灯?

并不是。

軟件架構(gòu)永遠(yuǎn)不是越復(fù)雜越好贫堰,復(fù)雜的架構(gòu)在帶來(lái)顯著好處的同時(shí)穆壕,一定也會(huì)帶來(lái)相應(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ù)雜度要高熄云。
  • 客戶(hù)端資源消耗增加。當(dāng)客戶(hù)端使用連接池時(shí)妙真,需要為每一個(gè)數(shù)據(jù)分片維護(hù)一個(gè)連接池缴允,客戶(hù)端同時(shí)需要保持的連接數(shù)成倍增多,加大了客戶(hù)端本身和操作系統(tǒng)資源的消耗珍德。
  • 性能優(yōu)化難度增加练般。你可能需要在多個(gè)分片上查看Slow Log和Swap日志才能定位性能問(wèn)題矗漾。
  • 事務(wù)和LUA Script的使用成本增加。在Redis Cluster中使用事務(wù)和LUA Script特性有嚴(yán)格的限制條件薄料,事務(wù)和Script中操作的key必須位于同一個(gè)分片上敞贡,這就使得在開(kāi)發(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)誊役。

所以說(shuō),在主從復(fù)制和集群分片兩個(gè)方案中做出選擇時(shí)谷市,應(yīng)該從應(yīng)用軟件的功能特性蛔垢、數(shù)據(jù)和訪(fǎng)問(wèn)量級(jí)傀广、未來(lái)發(fā)展規(guī)劃等方面綜合考慮翠订,只在確實(shí)有必要引入數(shù)據(jù)分片時(shí)再使用Redis Cluster辕坝。
下面是一些建議:

  1. 需要在Redis中存儲(chǔ)的數(shù)據(jù)有多大疏遏?未來(lái)2年內(nèi)可能發(fā)展為多大芋酌?這些數(shù)據(jù)是否都需要長(zhǎng)期保存答憔?是否可以使用LRU算法進(jìn)行非熱點(diǎn)數(shù)據(jù)的淘汰攘已?綜合考慮前面幾個(gè)因素女蜈,評(píng)估出Redis需要使用的物理內(nèi)存验烧。
  2. 用于部署Redis的主機(jī)物理內(nèi)存有多大板驳?有多少可以分配給Redis使用?對(duì)比(1)中的內(nèi)存需求評(píng)估碍拆,是否足夠用若治?
  3. Redis面臨的并發(fā)寫(xiě)壓力會(huì)有多大?在不使用pipelining時(shí)感混,Redis的寫(xiě)性能可以超過(guò)10萬(wàn)次/秒(更多的benchmark可以參考 https://redis.io/topics/benchmarks
  4. 在使用Redis時(shí)端幼,是否會(huì)使用到pipelining和事務(wù)功能?使用的場(chǎng)景多不多弧满?

綜合上面幾點(diǎn)考慮婆跑,如果單臺(tái)主機(jī)的可用物理內(nèi)存完全足以支撐對(duì)Redis的容量需求,且Redis面臨的并發(fā)寫(xiě)壓力距離Benchmark值還尚有距離庭呜,建議采用主從復(fù)制的架構(gòu)滑进,可以省去很多不必要的麻煩。同時(shí)募谎,如果應(yīng)用中大量使用pipelining和事務(wù)扶关,也建議盡可能選擇主從復(fù)制架構(gòu),可以減少設(shè)計(jì)和開(kāi)發(fā)時(shí)的復(fù)雜度数冬。

Redis Java客戶(hù)端的選擇

Redis的Java客戶(hù)端很多节槐,官方推薦的有三種:Jedis、Redisson和lettuce。

在這里對(duì)Jedis和Redisson進(jìn)行對(duì)比介紹

Jedis:

  • 輕量疯淫,簡(jiǎn)潔地来,便于集成和改造
  • 支持連接池
  • 支持pipelining、事務(wù)熙掺、LUA Scripting、Redis Sentinel咕宿、Redis Cluster
  • 不支持讀寫(xiě)分離币绩,需要自己實(shí)現(xiàn)
  • 文檔差(真的很差,幾乎沒(méi)有……)

Redisson:

  • 基于Netty實(shí)現(xiàn)府阀,采用非阻塞IO缆镣,性能高
  • 支持異步請(qǐng)求
  • 支持連接池
  • 支持pipelining、LUA Scripting试浙、Redis Sentinel董瞻、Redis Cluster
  • 不支持事務(wù),官方建議以L(fǎng)UA Scripting代替事務(wù)
  • 支持在Redis Cluster架構(gòu)下使用pipelining
  • 支持讀寫(xiě)分離田巴,支持讀負(fù)載均衡钠糊,在主從復(fù)制和Redis Cluster架構(gòu)下都可以使用
  • 內(nèi)建Tomcat Session Manager,為T(mén)omcat 6/7/8提供了會(huì)話(huà)共享功能
  • 可以與Spring Session集成壹哺,實(shí)現(xiàn)基于Redis的會(huì)話(huà)共享
  • 文檔較豐富抄伍,有中文文檔

對(duì)于Jedis和Redisson的選擇,同樣應(yīng)遵循前述的原理管宵,盡管Jedis比起Redisson有各種各樣的不足截珍,但也應(yīng)該在需要使用Redisson的高級(jí)特性時(shí)再選用Redisson,避免造成不必要的程序復(fù)雜度提升箩朴。

Jedis:
github:https://github.com/xetorthio/jedis
文檔:https://github.com/xetorthio/jedis/wiki

Redisson:
github:https://github.com/redisson/redisson
文檔:https://github.com/redisson/redisson/wiki

作者:kelgon
鏈接:http://www.reibang.com/p/2f14bc570563
來(lái)源:簡(jiǎn)書(shū)
簡(jiǎn)書(shū)著作權(quán)歸作者所有岗喉,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末炸庞,一起剝皮案震驚了整個(gè)濱河市钱床,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌燕雁,老刑警劉巖诞丽,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拐格,居然都是意外死亡僧免,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)捏浊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)懂衩,“玉大人,你說(shuō)我怎么就攤上這事∽嵌矗” “怎么了牵敷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)法希。 經(jīng)常有香客問(wèn)我枷餐,道長(zhǎng),這世上最難降的妖魔是什么苫亦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任毛肋,我火速辦了婚禮,結(jié)果婚禮上屋剑,老公的妹妹穿的比我還像新娘润匙。我一直安慰自己,他們只是感情好唉匾,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布孕讳。 她就那樣靜靜地躺著,像睡著了一般巍膘。 火紅的嫁衣襯著肌膚如雪厂财。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天典徘,我揣著相機(jī)與錄音蟀苛,去河邊找鬼。 笑死逮诲,一個(gè)胖子當(dāng)著我的面吹牛帜平,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梅鹦,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼裆甩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了齐唆?” 一聲冷哼從身側(cè)響起嗤栓,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箍邮,沒(méi)想到半個(gè)月后茉帅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锭弊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年堪澎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片味滞。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡樱蛤,死狀恐怖钮呀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昨凡,我是刑警寧澤爽醋,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站便脊,受9級(jí)特大地震影響蚂四,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哪痰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一证杭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妒御,春花似錦、人聲如沸镇饺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奸笤。三九已至惋啃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間监右,已是汗流浹背边灭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留健盒,地道東北人绒瘦。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像扣癣,于是被迫代替她去往敵國(guó)和親惰帽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容