數(shù)據(jù)庫(kù)相關(guān)(6)-- Redis

Redis是一個(gè)開(kāi)源的內(nèi)存中的數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng)拴念,它可以用作:數(shù)據(jù)庫(kù)、緩存和消息中間件洽沟。是速度非常快的非關(guān)系型(NoSQL)內(nèi)存鍵值數(shù)據(jù)庫(kù)蜗细,可以存儲(chǔ)鍵和五種不同類型的值之間的映射。鍵的類型只能為字符串,值支持五種數(shù)據(jù)類型:字符串炉媒、列表踪区、集合、散列表吊骤、有序集合缎岗。

它支持多種類型的數(shù)據(jù)結(jié)構(gòu),如字符串(String)白粉,散列(Hash)传泊,列表(List),集合(Set)鸭巴,有序集合(Sorted Set或者是ZSet)與范圍查詢眷细,Bitmaps,Hyperloglogs 和地理空間(Geospatial)索引半徑查詢鹃祖。其中常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)類型有:String溪椎、List、Set恬口、Hash校读、ZSet這5種。

Redis 支持很多特性祖能,例如將內(nèi)存中的數(shù)據(jù)持久化到硬盤中歉秫,使用復(fù)制來(lái)擴(kuò)展讀性能,使用分片來(lái)擴(kuò)展寫性能养铸。

常見(jiàn)操作:

STRING

> set hello world

OK

> get hello

"world"

> del hello

(integer) 1

> get hello

(nil)

LIST

> rpush list-key item

(integer) 1

> rpush list-key item2

(integer) 2

> rpush list-key item

(integer) 3

> lrange list-key 0 -1

1) "item"

2) "item2"

3) "item"

> lindex list-key 1

"item2"

> lpop list-key

"item"

> lrange list-key 0 -1

1) "item2"

2) "item"

SET

> sadd set-key item

(integer) 1

> sadd set-key item2

(integer) 1

> sadd set-key item3

(integer) 1

> sadd set-key item

(integer) 0

> smembers set-key

1) "item"

2) "item2"

3) "item3"

> sismember set-key item4

(integer) 0

> sismember set-key item

(integer) 1

> srem set-key item2

(integer) 1

> srem set-key item2

(integer) 0

> smembers set-key

1) "item"

2) "item3"

HASH

> hset hash-key sub-key1 value1

(integer) 1

> hset hash-key sub-key2 value2

(integer) 1

> hset hash-key sub-key1 value1

(integer) 0

> hgetall hash-key

1) "sub-key1"

2) "value1"

3) "sub-key2"

4) "value2"

> hdel hash-key sub-key2

(integer) 1

> hdel hash-key sub-key2

(integer) 0

> hget hash-key sub-key1

"value1"

> hgetall hash-key

1) "sub-key1"

2) "value1"

ZSET

> zadd zset-key 728 member1

(integer) 1

> zadd zset-key 982 member0

(integer) 1

> zadd zset-key 982 member0

(integer) 0

> zrange zset-key 0 -1 withscores

1) "member1"

2) "728"

3) "member0"

4) "982"

> zrangebyscore zset-key 0 800 withscores

1) "member1"

2) "728"

> zrem zset-key member1

(integer) 1

> zrem zset-key member1

(integer) 0

> zrange zset-key 0 -1 withscores

1) "member0"

2) "982"

數(shù)據(jù)結(jié)構(gòu)

在Redis中雁芙,hash表被稱為字典(dictionary),采用了典型的鏈?zhǔn)浇鉀Q沖突方法揭厚,即:當(dāng)有多個(gè)key/value的key的映射值(每對(duì)key/value保存之前却特,會(huì)先通過(guò)類似HASH(key) MOD N的方法計(jì)算一個(gè)值,以便確定其對(duì)應(yīng)的hash table的位置)相同時(shí)筛圆,會(huì)將這些value以單鏈表的形式保存裂明;同時(shí)為了控制哈希表所占內(nèi)存大小,redis采用了雙哈希表(ht[2])結(jié)構(gòu)太援,并逐步擴(kuò)大哈希表容量(桶的大忻龌蕖)的策略,也即漸進(jìn)式rehash, 即在以后的操作(find, set, get等)中慢慢的copy進(jìn)去提岔。

具體過(guò)程為:剛開(kāi)始仙蛉,哈希表ht[0]的桶大小為4,哈希表ht[1]的桶大小為0碱蒙,待沖突嚴(yán)重(redis有一定的判斷條件)后荠瘪,ht[1]中桶的大小增為ht[0]的兩倍夯巷,并逐步(注意這個(gè)詞:”逐步”)將哈希表ht[0]中元素遷移(稱為“reHash”)到ht[1],待ht[0]中所有元素全部遷移到ht[1]后哀墓,再將ht[1]交給ht[0](這里僅僅是C語(yǔ)言地址交換)趁餐,之后重復(fù)上面的過(guò)程。

基本操作:

Redis中hash table主要有以下幾個(gè)對(duì)外提供的接口:dictCreate篮绰、dictAdd后雷、dictReplace、dictDelete吠各、dictFind臀突、dictEmpty等,而這些接口調(diào)用了一些基礎(chǔ)操作贾漏,包括:_dictRehashStep候学,_dictKeyIndex等。

hash table在一定情況下會(huì)觸發(fā)rehash操作磕瓷,即:將第一個(gè)hash table中的數(shù)據(jù)逐步轉(zhuǎn)移到第二個(gè)hash table中盒齿。

(1)觸發(fā)條件:當(dāng)?shù)谝粋€(gè)表的元素?cái)?shù)目大于桶數(shù)目且元素?cái)?shù)目與桶數(shù)目比值大于5時(shí),hash 表就會(huì)擴(kuò)張困食,擴(kuò)大后新表的大小為舊表的2倍边翁。

(2)轉(zhuǎn)移策略:為了避免一次性轉(zhuǎn)移帶來(lái)的開(kāi)銷,Redis采用了平攤開(kāi)銷的策略硕盹,即:將轉(zhuǎn)移代價(jià)平攤到每個(gè)基本操作中符匾,如:dictAdd、dictReplace瘩例、dictFind中啊胶,每執(zhí)行一次這些基本操作會(huì)觸發(fā)一個(gè)桶中元素的遷移操作。在此垛贤,有讀者可能會(huì)問(wèn)焰坪,如果這樣的話,如果舊hash table非常大聘惦,什么時(shí)候才能遷移完某饰。為了提高前移速度,Redis有一個(gè)周期性任務(wù)serverCron善绎,每隔一段時(shí)間會(huì)遷移100個(gè)桶黔漂。

需要注意的是當(dāng)我們創(chuàng)建hash表示默認(rèn)存儲(chǔ)結(jié)構(gòu),并不是dict禀酱,而是ziplist結(jié)構(gòu)炬守,hash_max_ziplist_entries和hash_max_ziplist_value值作為閥值,hash_max_ziplist_entries表示一旦ziplist中元素?cái)?shù)量超過(guò)該值剂跟,則需要轉(zhuǎn)換為dict結(jié)構(gòu)减途;hash_max_ziplist_value表示一旦ziplist中數(shù)據(jù)長(zhǎng)度大于該值酣藻,則需要轉(zhuǎn)換為dict結(jié)構(gòu)。

數(shù)據(jù)庫(kù)的工作模式按存儲(chǔ)方式可分為:硬盤數(shù)據(jù)庫(kù)和內(nèi)存數(shù)據(jù)庫(kù)观蜗。Redis 將數(shù)據(jù)儲(chǔ)存在內(nèi)存里面臊恋,讀寫數(shù)據(jù)的時(shí)候都不會(huì)受到硬盤 I/O 速度的限制衣洁,所以速度極快墓捻。

(1)硬盤數(shù)據(jù)庫(kù)的工作模式:

(2)內(nèi)存數(shù)據(jù)庫(kù)的工作模式:

鍵的過(guò)期時(shí)間

Redis 可以為每個(gè)鍵設(shè)置過(guò)期時(shí)間,當(dāng)鍵過(guò)期時(shí)坊夫,會(huì)自動(dòng)刪除該鍵砖第。

對(duì)于散列表這種容器,只能為整個(gè)鍵設(shè)置過(guò)期時(shí)間(整個(gè)散列表)环凿,而不能為鍵里面的單個(gè)元素設(shè)置過(guò)期時(shí)間梧兼。

?數(shù)據(jù)淘汰策略

可以設(shè)置內(nèi)存最大使用量,當(dāng)內(nèi)存使用量超出時(shí)智听,會(huì)施行數(shù)據(jù)淘汰策略羽杰。

Reids 具體有 6 種淘汰策略:

作為內(nèi)存數(shù)據(jù)庫(kù),出于對(duì)性能和內(nèi)存消耗的考慮到推,Redis 的淘汰算法實(shí)際實(shí)現(xiàn)上并非針對(duì)所有 key考赛,而是抽樣一小部分并且從中選出被淘汰的 key。

使用Redis 緩存數(shù)據(jù)時(shí)莉测,為了提高緩存命中率颜骤,需要保證緩存數(shù)據(jù)都是熱點(diǎn)數(shù)據(jù)〉仿保可以將內(nèi)存最大使用量設(shè)置為熱點(diǎn)數(shù)據(jù)占用的內(nèi)存量忍抽,然后啟用 allkeys-lru 淘汰策略,將最近最少使用的數(shù)據(jù)淘汰董朝。

Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略鸠项,LFU 策略通過(guò)統(tǒng)計(jì)訪問(wèn)頻率,將訪問(wèn)頻率最少的鍵值對(duì)淘汰子姜。

持久化

Redis 是內(nèi)存型數(shù)據(jù)庫(kù)祟绊,為了保證數(shù)據(jù)在斷電后不會(huì)丟失,需要將內(nèi)存中的數(shù)據(jù)持久化到硬盤上闲询。

RDB 持久化

將某個(gè)時(shí)間點(diǎn)的所有數(shù)據(jù)都存放到硬盤上久免。

可以將快照復(fù)制到其它服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本。

如果系統(tǒng)發(fā)生故障扭弧,將會(huì)丟失最后一次創(chuàng)建快照之后的數(shù)據(jù)阎姥。

如果數(shù)據(jù)量很大,保存快照的時(shí)間會(huì)很長(zhǎng)鸽捻。

AOF 持久化

將寫命令添加到AOF 文件(Append Only File)的末尾呼巴。

使用AOF 持久化需要設(shè)置同步選項(xiàng)泽腮,從而確保寫命令什么時(shí)候會(huì)同步到磁盤文件上。這是因?yàn)閷?duì)文件進(jìn)行寫入并不會(huì)馬上將內(nèi)容同步到磁盤上衣赶,而是先存儲(chǔ)到緩沖區(qū)诊赊,然后由操作系統(tǒng)決定什么時(shí)候同步到磁盤。有以下同步選項(xiàng):

????●?always選項(xiàng)會(huì)嚴(yán)重減低服務(wù)器的性能府瞄;

????● everysec選項(xiàng)比較合適碧磅,可以保證系統(tǒng)崩潰時(shí)只會(huì)丟失一秒左右的數(shù)據(jù),并且 Redis 每秒執(zhí)行一次同步對(duì)服務(wù)器性能幾乎沒(méi)有任何影響遵馆;

????●?no選項(xiàng)并不能給服務(wù)器性能帶來(lái)多大的提升鲸郊,而且也會(huì)增加系統(tǒng)崩潰時(shí)數(shù)據(jù)丟失的數(shù)量。

隨著服務(wù)器寫請(qǐng)求的增多货邓,AOF文件會(huì)越來(lái)越大秆撮。Redis 提供了一種將 AOF 重寫的特性,能夠去除AOF 文件中的冗余寫命令换况。

Redis分片

說(shuō)明:早期如果使用一臺(tái)redis時(shí),其中保存的是服務(wù)器中全部的緩存數(shù)據(jù).這樣都放到一臺(tái)服務(wù)器中會(huì)有風(fēng)險(xiǎn),如果單臺(tái)服務(wù)器宕機(jī)將直接影響整個(gè)服務(wù)职辨。

策略:采用分片的技術(shù),將原來(lái)由一臺(tái)服務(wù)器維護(hù)整個(gè)緩存,現(xiàn)在換為由多臺(tái)服務(wù)器共同維護(hù)整個(gè)緩存.假設(shè)之前一臺(tái)服務(wù)器維護(hù)的內(nèi)存數(shù)據(jù)量在9G.現(xiàn)在如果采用分片技術(shù)使用3臺(tái)服務(wù)器共同維護(hù)內(nèi)存空間,每臺(tái)服務(wù)器維護(hù)3G內(nèi)存。

好處:

1.提高每臺(tái)服務(wù)器的響應(yīng)時(shí)間戈二。

?2.容災(zāi)性較好舒裤。

假設(shè)有4 個(gè) Reids 實(shí)例 R0,R1挽拂,R2惭每,R3,還有很多表示用戶的鍵 user:1亏栈,user:2台腥,... ,有不同的方式來(lái)選擇一個(gè)指定的鍵存儲(chǔ)在哪個(gè)實(shí)例中绒北。

最簡(jiǎn)單的方式是范圍分片黎侈,例如用戶 id 從 0~1000 的存儲(chǔ)到實(shí)例 R0 中,用戶id 從 1001~2000 的存儲(chǔ)到實(shí)例 R1 中闷游,等等峻汉。但是這樣需要維護(hù)一張映射范圍表,維護(hù)操作代價(jià)很高脐往。

還有一種方式是哈希分片休吠,使用 CRC32 哈希函數(shù)將鍵轉(zhuǎn)換為一個(gè)數(shù)字,再對(duì)實(shí)例數(shù)量求模就能知道應(yīng)該存儲(chǔ)的實(shí)例业簿。

根據(jù)執(zhí)行分片的位置瘤礁,可以分為三種分片方式:

客戶端分片:客戶端使用一致性哈希等算法決定鍵應(yīng)當(dāng)分布到哪個(gè)節(jié)點(diǎn)。

代理分片:將客戶端請(qǐng)求發(fā)送到代理上梅尤,由代理轉(zhuǎn)發(fā)請(qǐng)求到正確的節(jié)點(diǎn)上柜思。

服務(wù)器分片:Redis Cluster岩调。

Redis使用場(chǎng)景

計(jì)數(shù)器

可以對(duì)String 進(jìn)行自增自減運(yùn)算,從而實(shí)現(xiàn)計(jì)數(shù)器功能赡盘。

Redis 這種內(nèi)存型數(shù)據(jù)庫(kù)的讀寫性能非常高号枕,很適合存儲(chǔ)頻繁讀寫的計(jì)數(shù)量。

緩存

將熱點(diǎn)數(shù)據(jù)放到內(nèi)存中陨享,設(shè)置內(nèi)存的最大使用量以及淘汰策略來(lái)保證緩存的命中率葱淳。

查找表

例如DNS 記錄就很適合使用 Redis 進(jìn)行存儲(chǔ)。

查找表和緩存類似霉咨,也是利用了 Redis 快速的查找特性蛙紫。但是查找表的內(nèi)容不能失效,而緩存的內(nèi)容可以失效途戒,因?yàn)榫彺娌蛔鳛榭煽康臄?shù)據(jù)來(lái)源。

消息隊(duì)列

List 是一個(gè)雙向鏈表僵驰,可以通過(guò)lpop 和 lpush 寫入和讀取消息喷斋。不過(guò)最好使用Kafka、RabbitMQ 等消息中間件蒜茴。

會(huì)話緩存

可以使用Redis 來(lái)統(tǒng)一存儲(chǔ)多臺(tái)應(yīng)用服務(wù)器的會(huì)話信息星爪。當(dāng)應(yīng)用服務(wù)器不再存儲(chǔ)用戶的會(huì)話信息,也就不再具有狀態(tài)粉私,一個(gè)用戶可以請(qǐng)求任意一個(gè)應(yīng)用服務(wù)器顽腾,從而更容易實(shí)現(xiàn)高可用性以及可伸縮性。

分布式鎖實(shí)現(xiàn)

在分布式場(chǎng)景下诺核,無(wú)法使用單機(jī)環(huán)境下的鎖來(lái)對(duì)多個(gè)節(jié)點(diǎn)上的進(jìn)程進(jìn)行同步抄肖。

可以使用Reids 自帶的 SETNX 命令實(shí)現(xiàn)分布式鎖,除此之外窖杀,還可以使用官方提供的 RedLock 分布式鎖實(shí)現(xiàn)漓摩。

其它

Set 可以實(shí)現(xiàn)交集、并集等操作入客,從而實(shí)現(xiàn)共同好友等功能管毙。

ZSet 可以實(shí)現(xiàn)有序性操作,從而實(shí)現(xiàn)排行榜等功能桌硫。

Redis的幾個(gè)常見(jiàn)面試問(wèn)題:

(1)為什么使用redis夭咬?

(2)使用redis有什么缺點(diǎn)?

(3)單線程的redis為什么這么快铆隘?

(4)Redis的數(shù)據(jù)類型及使用場(chǎng)景

(5)Redis的過(guò)期策略和內(nèi)存淘汰機(jī)制卓舵?

(6)Redis和數(shù)據(jù)庫(kù)雙寫一致性問(wèn)題

(7)如何應(yīng)對(duì)緩存穿透和緩存雪崩問(wèn)題?

(8)如何解決redis并發(fā)競(jìng)爭(zhēng)key問(wèn)題咖驮?

1边器、為什么使用redis

主要是從兩個(gè)角度去考慮:性能和并發(fā)训枢。當(dāng)然,redis還具備可以做分布式鎖等其他功能忘巧,但是如果只是為了分布式鎖這些其他功能恒界,完全還有其他中間件(如zookpeer等)代替,并不是非要使用redis砚嘴。因此十酣,這個(gè)問(wèn)題主要從性能和并發(fā)兩個(gè)角度去答。

(一)性能

如下圖所示际长,我們?cè)谂龅叫枰獔?zhí)行耗時(shí)特別久耸采,且結(jié)果不頻繁變動(dòng)的SQL,就特別適合將運(yùn)行結(jié)果放入緩存工育。這樣虾宇,后面的請(qǐng)求就去緩存中讀取,使得請(qǐng)求能夠迅速響應(yīng)如绸。

(二)并發(fā)

如下圖所示嘱朽,在大并發(fā)的情況下,所有的請(qǐng)求直接訪問(wèn)數(shù)據(jù)庫(kù)怔接,數(shù)據(jù)庫(kù)會(huì)出現(xiàn)連接異常教届。這個(gè)時(shí)候惹资,就需要使用redis做一個(gè)緩沖操作冰单,讓請(qǐng)求先訪問(wèn)到redis肖抱,而不是直接訪問(wèn)數(shù)據(jù)庫(kù)。

2瓦侮、使用redis有什么缺點(diǎn)

大家用redis這么久艰赞,這個(gè)問(wèn)題是必須要了解的,基本上使用redis都會(huì)碰到一些問(wèn)題脏榆,常見(jiàn)的也就幾個(gè)猖毫。

(1)緩存和數(shù)據(jù)庫(kù)雙寫一致性問(wèn)題

(2)緩存雪崩問(wèn)題

(3)緩存擊穿問(wèn)題

(4)緩存的并發(fā)競(jìng)爭(zhēng)問(wèn)題

3、單線程的redis為什么這么快

這個(gè)問(wèn)題其實(shí)是對(duì)redis內(nèi)部機(jī)制的一個(gè)考察须喂。其實(shí)根據(jù)博主的面試經(jīng)驗(yàn)吁断,很多人其實(shí)都不知道redis是單線程工作模型。所以坞生,這個(gè)問(wèn)題還是應(yīng)該要復(fù)習(xí)一下的仔役。Redis采用的是基于內(nèi)存的單進(jìn)程單線程模型的 KV 數(shù)據(jù)庫(kù),由C語(yǔ)言編寫是己,官方提供的數(shù)據(jù)是可以達(dá)到100000+的QPS(每秒內(nèi)查詢次數(shù))又兵。

主要是以下幾點(diǎn):

(1)純內(nèi)存操作

(2)單線程操作,避免了頻繁的上下文切換和各種鎖的問(wèn)題

(3)采用了非阻塞I/O多路復(fù)用機(jī)制

(4)底層模型不同,Redis直接自己構(gòu)建了VM 機(jī)制 沛厨,因?yàn)橐话愕南到y(tǒng)調(diào)用系統(tǒng)函數(shù)的話宙地,會(huì)浪費(fèi)一定的時(shí)間去移動(dòng)和請(qǐng)求

單線程操作的理解:

這里我們一直在強(qiáng)調(diào)的單線程,只是在處理我們的網(wǎng)絡(luò)請(qǐng)求的時(shí)候只有一個(gè)線程來(lái)處理逆皮,一個(gè)正式的Redis Server運(yùn)行的時(shí)候肯定是不止一個(gè)線程的宅粥,這里需要大家明確的注意一下!例如Redis進(jìn)行持久化的時(shí)候會(huì)以子進(jìn)程或者子線程的方式執(zhí)行(具體是子線程還是子進(jìn)程待讀者深入研究)

I/O多路復(fù)用的理解:

我們現(xiàn)在要仔細(xì)的說(shuō)一說(shuō)I/O多路復(fù)用機(jī)制电谣,因?yàn)檫@個(gè)說(shuō)法實(shí)在是太通俗了秽梅,通俗到一般人都不懂是什么意思。多路I/O復(fù)用模型是利用 select剿牺、poll企垦、epoll可以同時(shí)監(jiān)察多個(gè)流的 I/O 事件的能力,在空閑的時(shí)候晒来,會(huì)把當(dāng)前線程阻塞掉钞诡,當(dāng)有一個(gè)或多個(gè)流有 I/O 事件時(shí),就從阻塞態(tài)中喚醒潜索,于是程序就會(huì)輪詢一遍所有的流(epoll 是只輪詢那些真正發(fā)出了事件的流)臭增,并且只依次順序的處理就緒的流,這種做法就避免了大量的無(wú)用操作竹习。

這里“多路”指的是多個(gè)網(wǎng)絡(luò)連接,“復(fù)用”指的是復(fù)用同一個(gè)線程列牺。采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò) IO 的時(shí)間消耗)整陌,且 Redis 在內(nèi)存中操作數(shù)據(jù)的速度非常快瞎领,也就是說(shuō)內(nèi)存內(nèi)的操作不會(huì)成為影響Redis性能的瓶頸泌辫,主要由以上幾點(diǎn)造就了 Redis 具有很高的吞吐量。

博主打一個(gè)比方:小曲在S城開(kāi)了一家快遞店九默,負(fù)責(zé)同城快送服務(wù)震放。小曲因?yàn)橘Y金限制,雇傭了一批快遞員驼修,然后小曲發(fā)現(xiàn)資金不夠了殿遂,只夠買一輛車送快遞。

經(jīng)營(yíng)方式一

客戶每送來(lái)一份快遞乙各,小曲就讓一個(gè)快遞員盯著墨礁,然后快遞員開(kāi)車去送快遞。慢慢的小曲就發(fā)現(xiàn)了這種經(jīng)營(yíng)方式存在下述問(wèn)題

幾十個(gè)快遞員基本上時(shí)間都花在了搶車上了耳峦,大部分快遞員都處在閑置狀態(tài)恩静,誰(shuí)搶到了車,誰(shuí)就能去送快遞

隨著快遞的增多蹲坷,快遞員也越來(lái)越多驶乾,小曲發(fā)現(xiàn)快遞店里越來(lái)越擠邑飒,沒(méi)辦法雇傭新的快遞員了

快遞員之間的協(xié)調(diào)很花時(shí)間

綜合上述缺點(diǎn),小曲痛定思痛级乐,提出了下面的經(jīng)營(yíng)方式

經(jīng)營(yíng)方式二

小曲只雇傭一個(gè)快遞員疙咸。然后呢,客戶送來(lái)的快遞唇牧,小曲按送達(dá)地點(diǎn)標(biāo)注好罕扎,然后依次放在一個(gè)地方。最后丐重,那個(gè)快遞員依次的去取快遞腔召,一次拿一個(gè),然后開(kāi)著車去送快遞扮惦,送好了就回來(lái)拿下一個(gè)快遞臀蛛。

對(duì)比

上述兩種經(jīng)營(yíng)方式對(duì)比,是不是明顯覺(jué)得第二種崖蜜,效率更高浊仆,更好呢。在上述比喻中:

每個(gè)快遞員——————>每個(gè)線程

每個(gè)快遞——————–>每個(gè)socket(I/O流)

快遞的送達(dá)地點(diǎn)————–>socket的不同狀態(tài)

客戶送快遞請(qǐng)求————–>來(lái)自客戶端的請(qǐng)求

小曲的經(jīng)營(yíng)方式————–>服務(wù)端運(yùn)行的代碼

一輛車———————->CPU的核數(shù)

于是我們有如下結(jié)論

1豫领、經(jīng)營(yíng)方式一就是傳統(tǒng)的并發(fā)模型抡柿,每個(gè)I/O流(快遞)都有一個(gè)新的線程(快遞員)管理。

2等恐、經(jīng)營(yíng)方式二就是I/O多路復(fù)用洲劣。只有單個(gè)線程(一個(gè)快遞員),通過(guò)跟蹤每個(gè)I/O流的狀態(tài)(每個(gè)快遞的送達(dá)地點(diǎn))课蔬,來(lái)管理多個(gè)I/O流囱稽。

下面類比到真實(shí)的redis線程模型,如圖所示

參照上圖二跋,簡(jiǎn)單來(lái)說(shuō)战惊,就是。我們的redis-client在操作的時(shí)候扎即,會(huì)產(chǎn)生具有不同事件類型的socket吞获。在服務(wù)端,有一段I/0多路復(fù)用程序铺遂,將其置入隊(duì)列之中衫哥。然后,文件事件分派器襟锐,依次去隊(duì)列中取撤逢,轉(zhuǎn)發(fā)到不同的事件處理器中。

需要說(shuō)明的是,這個(gè)I/O多路復(fù)用機(jī)制蚊荣,redis還提供了select初狰、epoll、evport互例、kqueue等多路復(fù)用函數(shù)庫(kù)奢入,大家可以自行去了解。

擴(kuò)展:

以下也是你應(yīng)該知道的幾種模型媳叨,祝你的面試一臂之力腥光!

1、單進(jìn)程多線程模型:MySQL糊秆、Memcached武福、Oracle(Windows版本);

2痘番、多進(jìn)程模型:Oracle(Linux版本)捉片;

3、Nginx有兩類進(jìn)程汞舱,一類稱為Master進(jìn)程(相當(dāng)于管理進(jìn)程)伍纫,另一類稱為Worker進(jìn)程(實(shí)際工作進(jìn)程)。啟動(dòng)方式有兩種:

(1)單進(jìn)程啟動(dòng):此時(shí)系統(tǒng)中僅有一個(gè)進(jìn)程昂芜,該進(jìn)程既充當(dāng)Master進(jìn)程的角色莹规,也充當(dāng)Worker進(jìn)程的角色。

(2)多進(jìn)程啟動(dòng):此時(shí)系統(tǒng)有且僅有一個(gè)Master進(jìn)程泌神,至少有一個(gè)Worker進(jìn)程工作访惜。

(3)Master進(jìn)程主要進(jìn)行一些全局性的初始化工作和管理Worker的工作;事件處理是在Worker中進(jìn)行的腻扇。

4redis的數(shù)據(jù)類型砾嫉,以及每種數(shù)據(jù)類型的使用場(chǎng)景

是不是覺(jué)得這個(gè)問(wèn)題很基礎(chǔ)幼苛,其實(shí)我也這么覺(jué)得。然而根據(jù)面試經(jīng)驗(yàn)發(fā)現(xiàn)焕刮,至少百分八十的人答不上這個(gè)問(wèn)題舶沿。建議,在項(xiàng)目中用到后配并,再類比記憶括荡,體會(huì)更深,不要硬記溉旋』澹基本上,一個(gè)合格的程序員,五種類型都會(huì)用到邑闲。

一共五種:

(1)String

這個(gè)其實(shí)沒(méi)啥好說(shuō)的算行,最常規(guī)的set/get操作,value可以是String也可以是數(shù)字苫耸。一般做一些復(fù)雜的計(jì)數(shù)功能的緩存州邢。

(2)hash

這里value存放的是結(jié)構(gòu)化的對(duì)象,比較方便的就是操作其中的某個(gè)字段褪子。博主在做單點(diǎn)登錄的時(shí)候量淌,就是用這種數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)用戶信息,以cookieId作為key嫌褪,設(shè)置30分鐘為緩存過(guò)期時(shí)間呀枢,能很好的模擬出類似session的效果。

(3)list

使用List的數(shù)據(jù)結(jié)構(gòu)渔扎,可以做簡(jiǎn)單的消息隊(duì)列的功能硫狞。另外還有一個(gè)就是,可以利用lrange命令晃痴,做基于redis的分頁(yè)功能残吩,性能極佳,用戶體驗(yàn)好倘核。本人還用一個(gè)場(chǎng)景泣侮,很合適---取行情信息。就也是個(gè)生產(chǎn)者和消費(fèi)者的場(chǎng)景紧唱。LIST可以很好的完成排隊(duì)活尊,先進(jìn)先出的原則。

(4)set

因?yàn)閟et堆放的是一堆不重復(fù)值的集合漏益。所以可以做全局去重的功能蛹锰。為什么不用JVM自帶的Set進(jìn)行去重?因?yàn)槲覀兊南到y(tǒng)一般都是集群部署绰疤,使用JVM自帶的Set铜犬,比較麻煩,難道為了一個(gè)做一個(gè)全局去重轻庆,再起一個(gè)公共服務(wù)癣猾,太麻煩了。

另外余爆,就是利用交集纷宇、并集、差集等操作蛾方,可以計(jì)算共同喜好像捶,全部的喜好上陕,自己獨(dú)有的喜好等功能。

(5)sorted set

sorted set多了一個(gè)權(quán)重參數(shù)score,集合中的元素能夠按score進(jìn)行排列作岖∷衾可以做排行榜應(yīng)用,取TOP N操作痘儡。

5辕万、redis的過(guò)期策略以及內(nèi)存淘汰機(jī)制

這個(gè)問(wèn)題其實(shí)相當(dāng)重要,到底redis有沒(méi)用到家沉删,這個(gè)問(wèn)題就可以看出來(lái)渐尿。比如你redis只能存5G數(shù)據(jù),可是你寫了10G矾瑰,那會(huì)刪5G的數(shù)據(jù)砖茸。怎么刪的,這個(gè)問(wèn)題思考過(guò)么殴穴?還有凉夯,你的數(shù)據(jù)已經(jīng)設(shè)置了過(guò)期時(shí)間,但是時(shí)間到了采幌,內(nèi)存占用率還是比較高劲够,有思考過(guò)原因么?

redis采用的是定期刪除+惰性刪除策略。

為什么不用定時(shí)刪除策略?

定時(shí)刪除,用一個(gè)定時(shí)器來(lái)負(fù)責(zé)監(jiān)視key,過(guò)期則自動(dòng)刪除休傍。雖然內(nèi)存及時(shí)釋放征绎,但是十分消耗CPU資源。在大并發(fā)請(qǐng)求下磨取,CPU要將時(shí)間應(yīng)用在處理請(qǐng)求人柿,而不是刪除key,因此沒(méi)有采用這一策略。

定期刪除+惰性刪除是如何工作的呢?

定期刪除忙厌,redis默認(rèn)每個(gè)100ms檢查凫岖,是否有過(guò)期的key,有過(guò)期key則刪除。需要說(shuō)明的是逢净,redis不是每個(gè)100ms將所有的key檢查一次隘截,而是隨機(jī)抽取進(jìn)行檢查(如果每隔100ms,全部key進(jìn)行檢查,redis豈不是卡死)汹胃。因此,如果只采用定期刪除策略东臀,會(huì)導(dǎo)致很多key到時(shí)間沒(méi)有刪除着饥。

于是,惰性刪除派上用場(chǎng)惰赋。也就是說(shuō)在你獲取某個(gè)key的時(shí)候宰掉,redis會(huì)檢查一下呵哨,這個(gè)key如果設(shè)置了過(guò)期時(shí)間那么是否過(guò)期了?如果過(guò)期了此時(shí)就會(huì)刪除轨奄。

采用定期刪除+惰性刪除就沒(méi)其他問(wèn)題了么?

不是的孟害,如果定期刪除沒(méi)刪除key。然后你也沒(méi)及時(shí)去請(qǐng)求key挪拟,也就是說(shuō)惰性刪除也沒(méi)生效挨务。這樣,redis的內(nèi)存會(huì)越來(lái)越高玉组。那么就應(yīng)該采用內(nèi)存淘汰機(jī)制谎柄。

在redis.conf中有一行配置 #maxmemory-policy volatile-lru

該配置就是配內(nèi)存淘汰策略的(什么,你沒(méi)配過(guò)惯雳?好好反省一下自己)

1)noeviction:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí)朝巫,新寫入操作會(huì)報(bào)錯(cuò)。應(yīng)該沒(méi)人用吧石景。

2)allkeys-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí)劈猿,在鍵空間中,移除最近最少使用的key潮孽。推薦使用揪荣,目前項(xiàng)目在用這種。

3)allkeys-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí)恩商,在鍵空間中变逃,隨機(jī)移除某個(gè)key。應(yīng)該也沒(méi)人用吧怠堪,你不刪最少使用Key揽乱,去隨機(jī)刪。

4)volatile-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí)粟矿,在設(shè)置了過(guò)期時(shí)間的鍵空間中凰棉,移除最近最少使用的key。這種情況一般是把redis既當(dāng)緩存陌粹,又做持久化存儲(chǔ)的時(shí)候才用撒犀。不推薦

5)volatile-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中掏秩,隨機(jī)移除某個(gè)key或舞。依然不推薦

6)volatile-ttl:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在設(shè)置了過(guò)期時(shí)間的鍵空間中蒙幻,有更早過(guò)期時(shí)間的key優(yōu)先移除映凳。不推薦

ps:如果沒(méi)有設(shè)置expire 的key, 不滿足先決條件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行為, 和noeviction(不刪除) 基本上一致。

過(guò)期策略對(duì)應(yīng)的面試題比如:mySQL里有2000w數(shù)據(jù)邮破,redis中只存20w的數(shù)據(jù)诈豌,如何保證redis中的數(shù)據(jù)都是熱點(diǎn)數(shù)據(jù)

6仆救、redis和數(shù)據(jù)庫(kù)雙寫一致性問(wèn)題

分析:一致性問(wèn)題是分布式常見(jiàn)問(wèn)題,還可以再分為最終一致性和強(qiáng)一致性矫渔。數(shù)據(jù)庫(kù)和緩存雙寫彤蔽,就必然會(huì)存在不一致的問(wèn)題。答這個(gè)問(wèn)題庙洼,先明白一個(gè)前提顿痪。就是如果對(duì)數(shù)據(jù)有強(qiáng)一致性要求,不能放緩存送膳。我們所做的一切员魏,只能保證最終一致性。另外叠聋,我們所做的方案其實(shí)從根本上來(lái)說(shuō)撕阎,只能說(shuō)降低不一致發(fā)生的概率,無(wú)法完全避免碌补。因此虏束,有強(qiáng)一致性要求的數(shù)據(jù),不能放緩存厦章。

首先镇匀,采取正確更新策略,先更新數(shù)據(jù)庫(kù)袜啃,再刪緩存汗侵。其次,因?yàn)榭赡艽嬖趧h除緩存失敗的問(wèn)題群发,提供一個(gè)補(bǔ)償措施即可晰韵,例如利用消息隊(duì)列。

將不一致分為三種情況:

1. 數(shù)據(jù)庫(kù)有數(shù)據(jù)熟妓,緩存沒(méi)有數(shù)據(jù)雪猪;

2. 數(shù)據(jù)庫(kù)有數(shù)據(jù),緩存也有數(shù)據(jù)起愈,數(shù)據(jù)不相等只恨;

3. 數(shù)據(jù)庫(kù)沒(méi)有數(shù)據(jù),緩存有數(shù)據(jù)抬虽。

在討論這三種情況之前官觅,先說(shuō)明一下我使用緩存的策略,也是大多數(shù)人使用的策略阐污,叫做 Cache Aside Pattern缰猴。簡(jiǎn)而言之,就是

1. 首先嘗試從緩存讀取疤剑,讀到數(shù)據(jù)則直接返回滑绒;如果讀不到,就讀數(shù)據(jù)庫(kù)隘膘,并將數(shù)據(jù)會(huì)寫到緩存疑故,并返回。

2. 需要更新數(shù)據(jù)時(shí)弯菊,先更新數(shù)據(jù)庫(kù)纵势,然后把緩存里對(duì)應(yīng)的數(shù)據(jù)失效掉(刪掉)。

讀的邏輯大家都很容易理解管钳,談?wù)劯虑仗H绻徊扇∥姨岬降倪@種更新方法,你還能想到什么更新方法呢才漆?大概會(huì)是:先刪除緩存牛曹,然后再更新數(shù)據(jù)庫(kù)。這么做引發(fā)的問(wèn)題是醇滥,如果A,B兩個(gè)線程同時(shí)要更新數(shù)據(jù)黎比,并且A,B已經(jīng)都做完了刪除緩存這一步,接下來(lái)鸳玩,A先更新了數(shù)據(jù)庫(kù)阅虫,C線程讀取數(shù)據(jù),由于緩存沒(méi)有不跟,則查數(shù)據(jù)庫(kù)颓帝,并把A更新的數(shù)據(jù),寫入了緩存窝革,最后B更新數(shù)據(jù)庫(kù)购城。那么緩存和數(shù)據(jù)庫(kù)的值就不一致了。另外有人會(huì)問(wèn)聊闯,如果采用你提到的方法工猜,為什么最后是把緩存的數(shù)據(jù)刪掉,而不是把更新的數(shù)據(jù)寫到緩存里菱蔬。這么做引發(fā)的問(wèn)題是篷帅,如果A,B兩個(gè)線程同時(shí)做數(shù)據(jù)更新,A先更新了數(shù)據(jù)庫(kù)拴泌,B后更新數(shù)據(jù)庫(kù)魏身,則此時(shí)數(shù)據(jù)庫(kù)里存的是B的數(shù)據(jù)。而更新緩存的時(shí)候蚪腐,是B先更新了緩存箭昵,而A后更新了緩存,則緩存里是A的數(shù)據(jù)回季。這樣緩存和數(shù)據(jù)庫(kù)的數(shù)據(jù)也不一致家制。按照我提到的這種更新緩存的策略正林,理論上也是有不一致的風(fēng)險(xiǎn)的,之前在其他的博客文章有看到過(guò)颤殴,只不過(guò)概率很小觅廓,我們暫時(shí)可以不考慮,后面我們有其他手段來(lái)補(bǔ)救涵但。討論完使用緩存的策略杈绸,我們?cè)賮?lái)看這三種不一致的情況。

1. 對(duì)于第一種矮瘟,在讀數(shù)據(jù)的時(shí)候瞳脓,會(huì)自動(dòng)把數(shù)據(jù)庫(kù)的數(shù)據(jù)寫到緩存,因此不一致自動(dòng)消除.

2. 對(duì)于第二種澈侠,數(shù)據(jù)最終變成了不相等劫侧,但他們之前在某一個(gè)時(shí)間點(diǎn)一定是相等的(不管你使用懶加載還是預(yù)加載的方式,在緩存加載的那一刻埋涧,它一定和數(shù)據(jù)庫(kù)一致)板辽。這種不一致,一定是由于你更新數(shù)據(jù)所引發(fā)的棘催。前面我們講了更新數(shù)據(jù)的策略劲弦,先更新數(shù)據(jù)庫(kù),然后刪除緩存醇坝。因此邑跪,不一致的原因,一定是數(shù)據(jù)庫(kù)更新了呼猪,但是刪除緩存失敗了画畅。

3. 對(duì)于第三種,情況和第二種類似宋距,你把數(shù)據(jù)庫(kù)的數(shù)據(jù)刪了轴踱,但是刪除緩存的時(shí)候失敗了。

因此谚赎,最終的結(jié)論是淫僻,需要解決的不一致,產(chǎn)生的原因是更新數(shù)據(jù)庫(kù)成功壶唤,但是刪除緩存失敗雳灵。

解決方案大概有以下幾種:

1. 對(duì)刪除緩存進(jìn)行重試,數(shù)據(jù)的一致性要求越高闸盔,我越是重試得快悯辙。

2. 定期全量更新,簡(jiǎn)單地說(shuō),就是我定期把緩存全部清掉躲撰,然后再全量加載针贬。

3. 給所有的緩存一個(gè)失效期。

第三種方案可以說(shuō)是一個(gè)大殺器拢蛋,任何不一致坚踩,都可以靠失效期解決,失效期越短瓤狐,數(shù)據(jù)一致性越高。但是失效期越短批幌,查數(shù)據(jù)庫(kù)就會(huì)越頻繁础锐。因此失效期應(yīng)該根據(jù)業(yè)務(wù)來(lái)定。

并發(fā)不高的情況:

讀:讀redis->沒(méi)有荧缘,讀mysql->把mysql數(shù)據(jù)寫回redis皆警,有的話直接從redis中取截粗;

寫:寫mysql->成功信姓,再寫redis;

并發(fā)高的情況:

讀:讀redis->沒(méi)有绸罗,讀mysql->把mysql數(shù)據(jù)寫回redis意推,有的話直接從redis中取珊蟀;

寫:異步話菊值,先寫入redis的緩存,就直接返回育灸;定期或特定動(dòng)作將數(shù)據(jù)保存到mysql腻窒,可以做到多次更新,一次保存磅崭;

7儿子、如何應(yīng)對(duì)緩存穿透和緩存雪崩等問(wèn)題

說(shuō)句實(shí)在話,一般中小型傳統(tǒng)軟件企業(yè)砸喻,很難碰到這個(gè)問(wèn)題柔逼。如果有大并發(fā)的項(xiàng)目,流量有幾百萬(wàn)左右恩够。這幾個(gè)問(wèn)題一定要深刻考慮卒落。

?緩存穿透

緩存穿透是指用戶查詢數(shù)據(jù),在數(shù)據(jù)庫(kù)沒(méi)有蜂桶,自然在緩存中也不會(huì)有儡毕。這樣就導(dǎo)致用戶查詢的時(shí)候,在緩存中找不到,每次都要去數(shù)據(jù)庫(kù)再查詢一遍腰湾,然后返回空(相當(dāng)于進(jìn)行了兩次無(wú)用的查詢)雷恃。這樣請(qǐng)求就繞過(guò)緩存直接查數(shù)據(jù)庫(kù),這也是經(jīng)常提的緩存命中率問(wèn)題费坊。

解決方案:

1.布隆過(guò)濾

有很多種方法可以有效地解決緩存穿透問(wèn)題倒槐,最常見(jiàn)的則是采用布隆過(guò)濾器,將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中附井,一個(gè)一定不存在的數(shù)據(jù)會(huì)被這個(gè)bitmap攔截掉讨越,從而避免了對(duì)底層存儲(chǔ)系統(tǒng)的查詢壓力。

2. 緩存空對(duì)象.將 null 變成一個(gè)值.

也可以采用一個(gè)更為簡(jiǎn)單粗暴的方法永毅,如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù) 據(jù)不存在把跨,還是系統(tǒng)故障),我們?nèi)匀话堰@個(gè)空結(jié)果進(jìn)行緩存沼死,但它的過(guò)期時(shí)間會(huì)很短着逐,最長(zhǎng)不超過(guò)五分鐘。

通過(guò)這個(gè)直接設(shè)置的默認(rèn)值存放到緩存意蛀,這樣第二次到緩沖中獲取就有值了耸别,而不會(huì)繼續(xù)訪問(wèn)數(shù)據(jù)庫(kù)沸手,這種辦法最簡(jiǎn)單粗暴睹限!

緩存空對(duì)象會(huì)有兩個(gè)問(wèn)題:

第一错览,空值做了緩存搏色,意味著緩存層中存了更多的鍵山上,需要更多的內(nèi)存空間 ( 如果是攻擊昔案,問(wèn)題更嚴(yán)重)瓤荔,比較有效的方法是針對(duì)這類數(shù)據(jù)設(shè)置一個(gè)較短的過(guò)期時(shí)間婴洼,讓其自動(dòng)剔除兜看。

第二锥咸,緩存層和存儲(chǔ)層的數(shù)據(jù)會(huì)有一段時(shí)間窗口的不一致,可能會(huì)對(duì)業(yè)務(wù)有一定影響细移。例如過(guò)期時(shí)間設(shè)置為 5分鐘搏予,如果此時(shí)存儲(chǔ)層添加了這個(gè)數(shù)據(jù),那此段時(shí)間就會(huì)出現(xiàn)緩存層和存儲(chǔ)層數(shù)據(jù)的不一致弧轧,此時(shí)可以利用消息系統(tǒng)或者其他方式清除掉緩存層中的空對(duì)象雪侥。

緩存雪崩

緩存雪崩我們可以簡(jiǎn)單的理解為:由于原有緩存失效,新緩存未到期間(例如:我們?cè)O(shè)置緩存時(shí)采用了相同的過(guò)期時(shí)間精绎,在同一時(shí)刻出現(xiàn)大面積的緩存過(guò)期)速缨,所有原本應(yīng)該訪問(wèn)緩存的請(qǐng)求都去查詢數(shù)據(jù)庫(kù)了,而對(duì)數(shù)據(jù)庫(kù)CPU和內(nèi)存造成巨大壓力代乃,嚴(yán)重的會(huì)造成數(shù)據(jù)庫(kù)宕機(jī)旬牲。從而形成一系列連鎖反應(yīng)仿粹,造成整個(gè)系統(tǒng)崩潰。

解決方案:

(1)給緩存的失效時(shí)間原茅,加上一個(gè)隨機(jī)值吭历,避免集體失效。

(2)加鎖排隊(duì)擂橘,在緩存失效后晌区,通過(guò)加鎖或者隊(duì)列來(lái)控制讀數(shù)據(jù)庫(kù)寫緩存的線程數(shù)量。比如對(duì)某個(gè)key只允許一個(gè)線程查詢數(shù)據(jù)和寫緩存通贞,其他線程等待朗若。

加鎖排隊(duì)只是為了減輕數(shù)據(jù)庫(kù)的壓力,并沒(méi)有提高系統(tǒng)吞吐量昌罩。假設(shè)在高并發(fā)下捡偏,緩存重建期間key是鎖著的,這是過(guò)來(lái)1000個(gè)請(qǐng)求999個(gè)都在阻塞的峡迷。同樣會(huì)導(dǎo)致用戶等待超時(shí),這是個(gè)治標(biāo)不治本的方法你虹!

(3)雙緩存绘搞。我們有兩個(gè)緩存,緩存A和緩存B傅物。緩存A的失效時(shí)間為20分鐘夯辖,緩存B不設(shè)失效時(shí)間。自己做緩存預(yù)熱操作董饰。然后細(xì)分以下幾個(gè)小點(diǎn)

I 從緩存A讀數(shù)據(jù)庫(kù)蒿褂,有則直接返回

II A沒(méi)有數(shù)據(jù),直接從B讀數(shù)據(jù)卒暂,直接返回啄栓,并且異步啟動(dòng)一個(gè)更新線程。

III 更新線程同時(shí)更新緩存A和緩存B也祠。

(4)還有一個(gè)解決辦法解決方案是:給每一個(gè)緩存數(shù)據(jù)增加相應(yīng)的緩存標(biāo)記昙楚,記錄緩存的是否失效,如果緩存標(biāo)記失效诈嘿,則更新數(shù)據(jù)緩存堪旧。

解釋說(shuō)明:

1、緩存標(biāo)記:記錄緩存數(shù)據(jù)是否過(guò)期奖亚,如果過(guò)期會(huì)觸發(fā)通知另外的線程在后臺(tái)去更新實(shí)際key的緩存淳梦;

2、緩存數(shù)據(jù):它的過(guò)期時(shí)間比緩存標(biāo)記的時(shí)間延長(zhǎng)1倍昔字,例:標(biāo)記緩存時(shí)間30分鐘爆袍,數(shù)據(jù)緩存設(shè)置為60分鐘。這樣,當(dāng)緩存標(biāo)記key過(guò)期后螃宙,實(shí)際緩存還能把舊數(shù)據(jù)返回給調(diào)用端蛮瞄,直到另外的線程在后臺(tái)更新完成后,才會(huì)返回新緩存谆扎。

緩存降級(jí)

就是在高并發(fā)高負(fù)載情況下挂捅,選擇動(dòng)態(tài)的關(guān)閉一下不重要的服務(wù),拒絕訪問(wèn)等堂湖,來(lái)為重要的服務(wù)節(jié)省資源闲先,比如電商平臺(tái)秒殺當(dāng)天可關(guān)閉推薦等功能。

當(dāng)訪問(wèn)量劇增无蜂、服務(wù)出現(xiàn)問(wèn)題(如響應(yīng)時(shí)間慢或不響應(yīng))或非核心服務(wù)影響到核心流程的性能時(shí)伺糠,仍然需要保證服務(wù)還是可用的,即使是有損服務(wù)斥季。系統(tǒng)可以根據(jù)一些關(guān)鍵數(shù)據(jù)進(jìn)行自動(dòng)降級(jí)训桶,也可以配置開(kāi)關(guān)實(shí)現(xiàn)人工降級(jí)。

降級(jí)的最終目的是保證核心服務(wù)可用酣倾,即使是有損的舵揭。而且有些服務(wù)是無(wú)法降級(jí)的(如加入購(gòu)物車、結(jié)算)躁锡。

在進(jìn)行降級(jí)之前要對(duì)系統(tǒng)進(jìn)行梳理午绳,看看系統(tǒng)是不是可以丟卒保帥;從而梳理出哪些必須誓死保護(hù)映之,哪些可降級(jí)拦焚;比如可以參考日志級(jí)別設(shè)置預(yù)案:

(1)一般:比如有些服務(wù)偶爾因?yàn)榫W(wǎng)絡(luò)抖動(dòng)或者服務(wù)正在上線而超時(shí),可以自動(dòng)降級(jí)杠输;

(2)警告:有些服務(wù)在一段時(shí)間內(nèi)成功率有波動(dòng)(如在95~100%之間)赎败,可以自動(dòng)降級(jí)或人工降級(jí),并發(fā)送告警蠢甲;

(3)錯(cuò)誤:比如可用率低于90%螟够,或者數(shù)據(jù)庫(kù)連接池被打爆了,或者訪問(wèn)量突然猛增到系統(tǒng)能承受的最大閥值峡钓,此時(shí)可以根據(jù)情況自動(dòng)降級(jí)或者人工降級(jí)妓笙;

(4)嚴(yán)重錯(cuò)誤:比如因?yàn)樘厥庠驍?shù)據(jù)錯(cuò)誤了,此時(shí)需要緊急人工降級(jí)能岩。

緩存無(wú)底洞現(xiàn)象

該問(wèn)題由facebook 的工作人員提出的寞宫, facebook 在 2010 年左右,memcached 節(jié)點(diǎn)就已經(jīng)達(dá)3000 個(gè)拉鹃,緩存數(shù)千G 內(nèi)容辈赋。

他們發(fā)現(xiàn)了一個(gè)問(wèn)題---memcached連接頻率鲫忍,效率下降了,于是加 memcached 節(jié)點(diǎn)钥屈,

添加了后悟民,發(fā)現(xiàn)因?yàn)檫B接頻率導(dǎo)致的問(wèn)題,仍然存在篷就,并沒(méi)有好轉(zhuǎn)射亏,稱之為”無(wú)底洞現(xiàn)象”。

目前主流的數(shù)據(jù)庫(kù)竭业、緩存智润、Nosql、搜索中間件等技術(shù)棧中未辆,都支持“分片”技術(shù)窟绷,來(lái)滿足“高性能、高并發(fā)咐柜、高可用兼蜈、可擴(kuò)展”等要求。有些是在client端通過(guò)Hash取模(或一致性Hash)將值映射到不同的實(shí)例上拙友,有些是在client端通過(guò)范圍取值的方式映射的饭尝。當(dāng)然,也有些是在服務(wù)端進(jìn)行的献宫。但是,每一次操作都可能需要和不同節(jié)點(diǎn)進(jìn)行網(wǎng)絡(luò)通信來(lái)完成实撒,實(shí)例節(jié)點(diǎn)越多姊途,則開(kāi)銷會(huì)越大,對(duì)性能影響就越大知态。

主要可以從如下幾個(gè)方面避免和優(yōu)化:

數(shù)據(jù)分布方式

有些業(yè)務(wù)數(shù)據(jù)可能適合Hash分布捷兰,而有些業(yè)務(wù)適合采用范圍分布,這樣能夠從一定程度避免網(wǎng)絡(luò)IO的開(kāi)銷负敏。

IO優(yōu)化

可以充分利用連接池贡茅,NIO等技術(shù)來(lái)盡可能降低連接開(kāi)銷,增強(qiáng)并發(fā)連接能力其做。

數(shù)據(jù)訪問(wèn)方式

一次性獲取大的數(shù)據(jù)集顶考,會(huì)比分多次去獲取小數(shù)據(jù)集的網(wǎng)絡(luò)IO開(kāi)銷更小。

當(dāng)然妖泄,緩存無(wú)底洞現(xiàn)象并不常見(jiàn)驹沿。在絕大多數(shù)的公司里可能根本不會(huì)遇到。

8蹈胡、如何解決redis的并發(fā)競(jìng)爭(zhēng)key問(wèn)題

這個(gè)問(wèn)題大致就是渊季,同時(shí)有多個(gè)子系統(tǒng)去set一個(gè)key朋蔫。這個(gè)時(shí)候要注意什么呢?大家思考過(guò)么却汉。需要說(shuō)明一下驯妄,博主提前百度了一下,發(fā)現(xiàn)答案基本都是推薦用redis事務(wù)機(jī)制合砂。博主不推薦使用redis的事務(wù)機(jī)制青扔。因?yàn)槲覀兊纳a(chǎn)環(huán)境,基本都是redis集群環(huán)境既穆,做了數(shù)據(jù)分片操作赎懦。你一個(gè)事務(wù)中有涉及到多個(gè)key操作的時(shí)候,這多個(gè)key不一定都存儲(chǔ)在同一個(gè)redis-server上幻工。因此励两,redis的事務(wù)機(jī)制,十分雞肋囊颅。

(1)如果對(duì)這個(gè)key操作当悔,不要求順序

這種情況下,準(zhǔn)備一個(gè)分布式鎖踢代,大家去搶鎖盲憎,搶到鎖就做set操作即可,比較簡(jiǎn)單胳挎。

(2)如果對(duì)這個(gè)key操作饼疙,要求順序

假設(shè)有一個(gè)key1,系統(tǒng)A需要將key1設(shè)置為valueA,系統(tǒng)B需要將key1設(shè)置為valueB,系統(tǒng)C需要將key1設(shè)置為valueC.

期望按照key1的value值按照valueA–>valueB–>valueC的順序變化。這種時(shí)候我們?cè)跀?shù)據(jù)寫入數(shù)據(jù)庫(kù)的時(shí)候慕爬,需要保存一個(gè)時(shí)間戳窑眯。假設(shè)時(shí)間戳如下

系統(tǒng)Akey 1 {valueA? 3:00}

系統(tǒng)Bkey 1 {valueB? 3:05}

系統(tǒng)Ckey 1 {valueC? 3:10}

那么,假設(shè)這會(huì)系統(tǒng)B先搶到鎖医窿,將key1設(shè)置為{valueB

3:05}磅甩。接下來(lái)系統(tǒng)A搶到鎖,發(fā)現(xiàn)自己的valueA的時(shí)間戳早于緩存中的時(shí)間戳姥卢,那就不做set操作了卷要。以此類推。

其他方法独榴,比如利用隊(duì)列僧叉,將set方法變成串行訪問(wèn)也可以」桌疲總之彪标,靈活變通。

9掷豺、Redis

Pipeline原理分析

為什么會(huì)出現(xiàn)Pipeline捞烟?

Redis本身是基于Request/Response協(xié)議的薄声,正常情況下,客戶端發(fā)送一個(gè)命令题画,等待Redis應(yīng)答默辨,Redis在接收到命令,處理后應(yīng)答苍息。在這種情況下缩幸,如果同時(shí)需要執(zhí)行大量的命令,那就是等待上一條命令應(yīng)答后再執(zhí)行竞思,這中間不僅僅多了RTT(Round Time Trip)表谊,而且還頻繁的調(diào)用系統(tǒng)IO,發(fā)送網(wǎng)絡(luò)請(qǐng)求盖喷。如下圖:

為了提升效率爆办,這時(shí)候Pipeline出現(xiàn)了,它允許客戶端可以一次發(fā)送多條命令课梳,而不等待上一條命令執(zhí)行的結(jié)果距辆,這和網(wǎng)絡(luò)的Nagel算法有點(diǎn)像(TCP_NODELAY選項(xiàng))。不僅減少了RTT暮刃,同時(shí)也減少了IO調(diào)用次數(shù)(IO調(diào)用涉及到用戶態(tài)到內(nèi)核態(tài)之間的切換)跨算。如下圖:

客戶端這邊首先將執(zhí)行的命令寫入到緩沖中,最后再一次性發(fā)送Redis椭懊。但是有一種情況就是诸蚕,緩沖區(qū)的大小是有限制的,比如Jedis氧猬,限制為8192背犯,超過(guò)了,則刷緩存狂窑,發(fā)送到Redis,但是不去處理Redis的應(yīng)答桑腮,如上圖所示那樣泉哈。

實(shí)現(xiàn)原理

要支持Pipeline,其實(shí)既要服務(wù)端的支持破讨,也要客戶端支持丛晦。對(duì)于服務(wù)端來(lái)說(shuō),所需要的是能夠處理一個(gè)客戶端通過(guò)同一個(gè)TCP連接發(fā)來(lái)的多個(gè)命令提陶,可以理解為烫沙,這里將多個(gè)命令切分,和處理單個(gè)命令一樣(之前老生常談的黏包現(xiàn)象)隙笆,Redis就是這樣處理的锌蓄。而客戶端升筏,則是要將多個(gè)命令緩存起來(lái),緩沖區(qū)滿了就發(fā)送瘸爽,然后再寫緩沖您访,最后才處理Redis的應(yīng)答,如Jedis剪决。

從哪個(gè)方面提升性能

正如上面所說(shuō)的灵汪,一個(gè)是RTT,節(jié)省往返時(shí)間柑潦,但是另一個(gè)原因也很重要享言,就是IO系統(tǒng)調(diào)用。一個(gè)read系統(tǒng)調(diào)用渗鬼,需要從用戶態(tài)览露,切換到內(nèi)核態(tài)。

總結(jié)redis節(jié)約內(nèi)存的方法:

1乍钻,使用對(duì)象共享池優(yōu)化小整數(shù)對(duì)象肛循。

2,數(shù)據(jù)優(yōu)先使用整數(shù)银择,比字符串更節(jié)省空間多糠。

3,操作優(yōu)化浩考。盡量避免字符串的追加操作夹孔,因?yàn)樽址嬖陬A(yù)分配機(jī)制。追加操作后字符串對(duì)象會(huì)分配一倍的容量作為于預(yù)留空間析孽。

4搭伤,編碼優(yōu)化。list袜瞬,hash怜俐,set,zset盡可能使用ziplist編碼邓尤。好處是內(nèi)存下降拍鲤,壞處是操作變慢。一般大小不超過(guò)1000汞扎。

5季稳,控制鍵的數(shù)量,100萬(wàn)idfa映射到1000個(gè)hash中澈魄,每個(gè)hash保存1000個(gè)元素景鼠。因?yàn)橥瑯拥臄?shù)據(jù)使用ziplist編碼比存儲(chǔ)string類型節(jié)約空間。

10痹扇、如何獲取Redis里所有的keys

(可以先說(shuō)下keys *命令,然后再介紹可能導(dǎo)致系統(tǒng)阻塞無(wú)法對(duì)外響應(yīng),再介紹更好的scan命令)

由于KEYS命令一次性返回所有匹配的key铛漓,所以溯香,當(dāng)redis中的key非常多時(shí),對(duì)于內(nèi)存的消耗和redis服務(wù)器都是一個(gè)隱患票渠,

對(duì)于Redis

2.8以上版本給我們提供了一個(gè)更好的遍歷key的命令 SCAN 該命令的基本格式:

SCAN cursor [MATCH pattern] [COUNTcount]

SCAN 每次執(zhí)行都只會(huì)返回少量元素逐哈,所以可以用于生產(chǎn)環(huán)境,而不會(huì)出現(xiàn)像 KEYS 或者 SMEMBERS 命令帶來(lái)的可能會(huì)阻塞服務(wù)器的問(wèn)題问顷。

SCAN命令是一個(gè)基于游標(biāo)的迭代器昂秃。這意味著命令每次被調(diào)用都需要使用上一次這個(gè)調(diào)用返回的游標(biāo)作為該次調(diào)用的游標(biāo)參數(shù),以此來(lái)延續(xù)之前的迭代過(guò)程

當(dāng)SCAN命令的游標(biāo)參數(shù)(即cursor)被設(shè)置為 0 時(shí)杜窄, 服務(wù)器將開(kāi)始一次新的迭代肠骆, 而當(dāng)服務(wù)器向用戶返回值為0 的游標(biāo)時(shí), 表示迭代已結(jié)束塞耕。

redis 127.0.0.1:6379> scan 0

1) "17"

2)?1) "key:12"

???2) "key:8"

???3) "key:4"

???4) "key:14"

???5) "key:16"

???6) "key:17"

???7) "key:15"

???8) "key:10"

???9) "key:3"

??10) "key:7"

??11) "key:1"

redis 127.0.0.1:6379> scan 17

1) "0"

2) 1) "key:5"

??2) "key:18"

??3) "key:0"

??4) "key:2"

??5) "key:19"

??6) "key:13"

??7) "key:6"

??8) "key:9"

??9) "key:11"

在上面這個(gè)例子中蚀腿,第一次迭代使用 0 作為游標(biāo), 表示開(kāi)始一次新的迭代扫外。第二次迭代使用的是第一次迭代時(shí)返回的游標(biāo) 17 莉钙,作為新的迭代參數(shù) 。

顯而易見(jiàn)筛谚,SCAN命令的返回值 是一個(gè)包含兩個(gè)元素的數(shù)組磁玉, 第一個(gè)數(shù)組元素是用于進(jìn)行下一次迭代的新游標(biāo),而第二個(gè)數(shù)組元素則又是一個(gè)數(shù)組驾讲, 這個(gè)數(shù)組中包含了所有被迭代的元素蚊伞。

在第二次調(diào)用SCAN 命令時(shí), 命令返回了游標(biāo)0 吮铭, 這表示迭代已經(jīng)結(jié)束时迫,整個(gè)數(shù)據(jù)集已經(jīng)被完整遍歷過(guò)了。

COUNT選項(xiàng)

對(duì)于增量式迭代命令不保證每次迭代所返回的元素?cái)?shù)量谓晌,我們可以使用COUNT選項(xiàng)掠拳,對(duì)命令的行為進(jìn)行一定程度上的調(diào)整。COUNT 選項(xiàng)的作用就是讓用戶告知迭代命令纸肉, 在每次迭代中應(yīng)該從數(shù)據(jù)集里返回多少元素溺欧。使用COUNT 選項(xiàng)對(duì)于對(duì)增量式迭代命令相當(dāng)于一種提示, 大多數(shù)情況下這種提示都比較有效的控制了返回值的數(shù)量毁靶。

注意:COUNT選項(xiàng)并不能嚴(yán)格控制返回的key數(shù)量胧奔,只能說(shuō)是一個(gè)大致的約束逊移。并非每次迭代都要使用相同的 COUNT 值预吆,用戶可以在每次迭代中按自己的需要隨意改變 COUNT 值,只要記得將上次迭代返回的游標(biāo)用到下次迭代里面就可以了胳泉。

MATCH 選項(xiàng)

MATCH 選項(xiàng)對(duì)元素的模式匹配工作是在命令從數(shù)據(jù)集中取出元素后和向客戶端返回元素前的這段時(shí)間內(nèi)進(jìn)行的拐叉, 所以如果被迭代的數(shù)據(jù)集中只有少量元素和模式相匹配岩遗, 那么迭代命令或許會(huì)在多次執(zhí)行中都不返回任何元素。

redis 127.0.0.1:6379> scan 0MATCH *11*

1) "288"

2) 1) "key:911"

redis 127.0.0.1:6379> scan 288MATCH *11*

1) "224"

2) (empty list or set)

基于SCAN的這種安全性凤瘦,建議大家在生產(chǎn)環(huán)境都使用SCAN命令來(lái)代替KEYS宿礁,不過(guò)注意,該命令是在2.8.0版本之后加入的蔬芥,如果你的Redis低于這個(gè)版本梆靖,則需要升級(jí)Redis。

11笔诵、Redis適用的場(chǎng)景:

(1)返吻、會(huì)話緩存(Session Cache)

最常用的一種使用Redis的情景是會(huì)話緩存(session cache)。用Redis緩存會(huì)話比其他存儲(chǔ)(如Memcached)的優(yōu)勢(shì)在于:Redis提供持久化乎婿。當(dāng)維護(hù)一個(gè)不是嚴(yán)格要求一致性的緩存時(shí)测僵,如果用戶的購(gòu)物車信息全部丟失,大部分人都會(huì)不高興的谢翎,現(xiàn)在捍靠,他們還會(huì)這樣嗎?

幸運(yùn)的是森逮,隨著Redis 這些年的改進(jìn)榨婆,很容易找到怎么恰當(dāng)?shù)氖褂肦edis來(lái)緩存會(huì)話的文檔。甚至廣為人知的商業(yè)平臺(tái)Magento也提供Redis的插件吊宋。

(2)纲辽、全頁(yè)緩存(FPC)

除基本的會(huì)話token之外,Redis還提供很簡(jiǎn)便的FPC平臺(tái)璃搜⊥虾穑回到一致性問(wèn)題,即使重啟了Redis實(shí)例这吻,因?yàn)橛写疟P的持久化吊档,用戶也不會(huì)看到頁(yè)面加載速度的下降,這是一個(gè)極大改進(jìn)唾糯,類似PHP本地FPC怠硼。

再次以Magento為例,Magento提供一個(gè)插件來(lái)使用Redis作為全頁(yè)緩存后端移怯。

此外香璃,對(duì)WordPress的用戶來(lái)說(shuō),Pantheon有一個(gè)非常好的插件?wp-redis舟误,這個(gè)插件能幫助你以最快速度加載你曾瀏覽過(guò)的頁(yè)面葡秒。

(3)、隊(duì)列

Reids在內(nèi)存存儲(chǔ)引擎領(lǐng)域的一大優(yōu)點(diǎn)是提供 list 和 set 操作,這使得Redis能作為一個(gè)很好的消息隊(duì)列平臺(tái)來(lái)使用眯牧。Redis作為隊(duì)列使用的操作蹋岩,就類似于本地程序語(yǔ)言(如Python)對(duì) list 的 push/pop 操作。

如果你快速的在Google中搜索“Redis queues”学少,你馬上就能找到大量的開(kāi)源項(xiàng)目剪个,這些項(xiàng)目的目的就是利用Redis創(chuàng)建非常好的后端工具,以滿足各種隊(duì)列需求版确。例如扣囊,Celery有一個(gè)后臺(tái)就是使用Redis作為broker,你可以從這里去查看绒疗。

(4)如暖,排行榜/計(jì)數(shù)器

Redis在內(nèi)存中對(duì)數(shù)字進(jìn)行遞增或遞減的操作實(shí)現(xiàn)的非常好。集合(Set)和有序集合(Sorted

Set)也使得我們?cè)趫?zhí)行這些操作的時(shí)候變的非常簡(jiǎn)單忌堂,Redis只是正好提供了這兩種數(shù)據(jù)結(jié)構(gòu)盒至。所以,我們要從排序集合中獲取到排名最靠前的10個(gè)用戶–我們稱之為“user_scores”士修,我們只需要像下面一樣執(zhí)行即可:

當(dāng)然枷遂,這是假定你是根據(jù)你用戶的分?jǐn)?shù)做遞增的排序。如果你想返回用戶及用戶的分?jǐn)?shù)棋嘲,你需要這樣執(zhí)行:

ZRANGE user_scores 0 10 WITHSCORES

Agora Games就是一個(gè)很好的例子酒唉,用Ruby實(shí)現(xiàn)的,它的排行榜就是使用Redis來(lái)存儲(chǔ)數(shù)據(jù)的沸移,你可以在這里看到痪伦。

(5)、發(fā)布/訂閱

最后(但肯定不是最不重要的)是Redis的發(fā)布/訂閱功能雹锣。發(fā)布/訂閱的使用場(chǎng)景確實(shí)非常多网沾。我已看見(jiàn)人們?cè)谏缃痪W(wǎng)絡(luò)連接中使用,還可作為基于發(fā)布/訂閱的腳本觸發(fā)器蕊爵,甚至用Redis的發(fā)布/訂閱功能來(lái)建立聊天系統(tǒng);愿纭(不,這是真的攒射,你可以去核實(shí))醋旦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市会放,隨后出現(xiàn)的幾起案子饲齐,更是在濱河造成了極大的恐慌,老刑警劉巖咧最,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捂人,死亡現(xiàn)場(chǎng)離奇詭異甩骏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)先慷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咨察,“玉大人论熙,你說(shuō)我怎么就攤上這事∩阌” “怎么了脓诡?”我有些...
    開(kāi)封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)媒役。 經(jīng)常有香客問(wèn)我祝谚,道長(zhǎng),這世上最難降的妖魔是什么酣衷? 我笑而不...
    開(kāi)封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任交惯,我火速辦了婚禮,結(jié)果婚禮上穿仪,老公的妹妹穿的比我還像新娘席爽。我一直安慰自己,他們只是感情好啊片,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布只锻。 她就那樣靜靜地躺著,像睡著了一般紫谷。 火紅的嫁衣襯著肌膚如雪齐饮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天笤昨,我揣著相機(jī)與錄音祖驱,去河邊找鬼。 笑死瞒窒,一個(gè)胖子當(dāng)著我的面吹牛羹膳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播根竿,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼陵像,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了寇壳?” 一聲冷哼從身側(cè)響起醒颖,我...
    開(kāi)封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎壳炎,沒(méi)想到半個(gè)月后泞歉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體逼侦,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年腰耙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了榛丢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挺庞,死狀恐怖晰赞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情选侨,我是刑警寧澤掖鱼,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站援制,受9級(jí)特大地震影響戏挡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晨仑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一褐墅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洪己,春花似錦掌栅、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至噪珊,卻和暖如春晌缘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痢站。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工磷箕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阵难。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓岳枷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親呜叫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子空繁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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