基于Redis的分布式鎖——antirez

原文地址:https://redis.io/topics/distlock

在不同進(jìn)程必須以互斥的方式操作共享資源的環(huán)境中宜鸯,分布式鎖是一種非常有用的機(jī)制。

有很多代碼庫(kù)和博客描述了如何基于Redis實(shí)現(xiàn)DLM(Distributed Lock Manager)蠢箩,但是每個(gè)庫(kù)使用方式都不同,并且很多人使用了比復(fù)雜設(shè)計(jì)實(shí)現(xiàn)保證較低的簡(jiǎn)單方法肥荔。

本文旨在提供一種更權(quán)威的算法加酵,用于實(shí)現(xiàn)基于Redis的分布式鎖。我們提供的算法叫做RedLock姜盈,實(shí)現(xiàn)了一種比vanilla單例方式更安全的DLM低千。我們希望社區(qū)可以分析它并進(jìn)行反饋。

實(shí)現(xiàn)

在描述算法之前馏颂,下面有幾個(gè)可用的實(shí)現(xiàn)鏈接用于參考示血。

Redlock-rb?(Ruby).

Redlock-py?(Python)

Aioredlock?(Asyncio Python)

Redlock-php?(PHP)

PHPRedisMutex?(further PHP)

cheprasov/php-redis-lock?(PHP)

Redsync.go?(Go)

Redisson?(Java)

Redis::DistLock?(Perl)

Redlock-cpp?(C++)

Redlock-cs?(C#/.NET)

RedLock.net?(C#/.NET)

ScarletLock?(C# .NET)

node-redlock?(NodeJS)

安全與可用保證

我們用3個(gè)屬性對(duì)設(shè)計(jì)進(jìn)行建模,依我看來(lái)救拉,這些屬性有效使用分布式鎖的最小保證:

1. 安全性:互斥难审。在任一時(shí)刻,只有一個(gè)客戶端可以持有鎖亿絮。

2. 可用性 A:死鎖釋放告喊。即使鎖定資源的客戶端崩潰或者被分區(qū)麸拄,也可以獲取到鎖。

3. 可用性 B:容錯(cuò)黔姜。只要多數(shù)Redis節(jié)點(diǎn)啟動(dòng)拢切,客戶端就可以獲取和釋放鎖。

為什么基于故障轉(zhuǎn)移的實(shí)現(xiàn)還不夠

為了理解我們需要提升的內(nèi)容地淀,我們需要分析基于Redis的分布式鎖庫(kù)的現(xiàn)狀失球。

使用Redis鎖定資源的最簡(jiǎn)單的方式是在實(shí)例中創(chuàng)建一個(gè)Key。Key的創(chuàng)建通常會(huì)有一個(gè)存活時(shí)間帮毁,使用的是Redis的過(guò)期的特性,這樣可以保證鎖始終可以被釋放豺撑。當(dāng)客戶端需要釋放資源時(shí)烈疚,就刪除這個(gè)Key。

表面上看俏扩,這種方式很有效辑舷,但是有一個(gè)問(wèn)題:就是我們架構(gòu)中的單點(diǎn)故障西土。如果Redis主機(jī)故障會(huì)發(fā)生什么?好吧灯抛,我們?cè)黾右粋€(gè)Slave節(jié)點(diǎn)。如果Master不可用音瓷,我們就使用Slave節(jié)點(diǎn)对嚼。很遺憾這不可行。這樣做無(wú)法實(shí)現(xiàn)互斥的安全性绳慎,因?yàn)镽edis復(fù)制是異步的纵竖。

這個(gè)方案中有明顯的競(jìng)爭(zhēng)條件:

1. 客戶端A在Master中獲取鎖。

2. Key同步到Slave節(jié)點(diǎn)前杏愤,主機(jī)發(fā)生崩潰靡砌。

3. Slave被提升到Master。

4. 客戶端B獲取到客戶端A持有的鎖珊楼。違反安全性

如果發(fā)生故障時(shí)允許多個(gè)客戶端同時(shí)持有同一個(gè)鎖通殃,那么就可以使用基于復(fù)制的方案。否則厕宗,建議使用本文所描述的方案画舌。

基于單個(gè)實(shí)例的正確實(shí)現(xiàn)

在嘗試處理單例方案問(wèn)題之前,我們檢查下簡(jiǎn)單的場(chǎng)景中如何正確實(shí)現(xiàn)分布式鎖媳瞪,因?yàn)樵陔S時(shí)出現(xiàn)競(jìng)爭(zhēng)狀態(tài)的應(yīng)用中骗炉,這是一個(gè)可行的解決方案,而且對(duì)于我們將要描述的分布式算法這個(gè)是基礎(chǔ)蛇受。

要獲得鎖句葵,可以采用下面的方式:

SET resource_name my_random_value NX PX 30000

只有Key不存在時(shí)(NX 選項(xiàng)),這條命令才會(huì)設(shè)置Key,并且30000毫秒到期(PX 選項(xiàng))乍丈。Key設(shè)置為“myrandomvalue”剂碴。這個(gè)值必須要在所有的客戶端以及加鎖的請(qǐng)求中保證唯一。使用隨機(jī)值本質(zhì)上是為了以安全的方式釋放鎖轻专,通過(guò)腳本告訴Redis:只有Key存在并且Key對(duì)應(yīng)的Value是期望值忆矛,才能移除Key。這是通過(guò)以下Lua腳本完成的:

if redis.call("get",KEYS[1]) == ARGV[1] then

? ? return redis.call("del",KEYS[1]);

else?

? ? return 0;

end

避免移除其他客戶端的鎖是很重要的请垛。例如客戶端A獲取到鎖以后催训,鎖失效后才執(zhí)行完成,當(dāng)客戶端釋放鎖時(shí)鎖已經(jīng)被其他的客戶端獲取宗收。因此客戶端只用DEL命令是不安全的漫拭,也許會(huì)移除其他客戶端的鎖。相反混稽,使用上述的腳本采驻,每把鎖都有一個(gè)隨機(jī)的“簽名”,因此匈勋,只有設(shè)置鎖的客戶端嘗試刪除時(shí)礼旅,這把鎖才能被刪除。

隨機(jī)字符串應(yīng)該是什么呢洽洁?我采取的是 /dev/urandom 生成的20個(gè)字節(jié)痘系,但是你可以找到一種成本更低的方式,確保它在你的任務(wù)中是唯一的诡挂。例如一個(gè)安全的選擇是利用RC4算法從/dev/urandom生成一個(gè)偽隨機(jī)流碎浇。還有一種簡(jiǎn)單的方案是使用unix時(shí)間戳加上客戶端ID,雖然不夠安全璃俗,但是在大多數(shù)環(huán)境中可能足夠了奴璃。

我們用作Key存活的時(shí)間,也叫做“鎖可用時(shí)間”城豁。它既是鎖自動(dòng)釋放時(shí)間苟穆,也是其他客戶端搶占鎖之前客戶端用來(lái)執(zhí)行任務(wù)的時(shí)間,在技術(shù)上不違反互斥的原則唱星,他保證了在獲取鎖以后限定的時(shí)間內(nèi)保持互斥雳旅。

現(xiàn)在我們有了一個(gè)獲取釋放鎖的好的方法。由單一间聊、持續(xù)可用的實(shí)例組成的非分布式系統(tǒng)是安全的攒盈。讓我們將這一概念擴(kuò)展到分布式系統(tǒng),分布式系統(tǒng)沒(méi)有這么多保證哎榴。

Redlock算法

在這個(gè)算法的分布式版本中型豁,我們假設(shè)有N個(gè)Redis節(jié)點(diǎn)僵蛛。這些節(jié)點(diǎn)總是相互獨(dú)立的,因此我們不需要同步或者其他的隱式協(xié)調(diào)系統(tǒng)迎变。我們描述了在單節(jié)點(diǎn)環(huán)境中如何安全的獲取與釋放鎖充尉。我們理所當(dāng)然的認(rèn)為這個(gè)算法在每個(gè)節(jié)點(diǎn)中使用相同的方式獲取和釋放鎖。在例子中我們set N=5衣形,這是一個(gè)比較合適的值驼侠,因此我們需要在不同的主機(jī)或者虛擬機(jī)上運(yùn)行5個(gè)Redis主節(jié)點(diǎn),確保他們故障能夠相互隔離谆吴。

為了獲取鎖倒源,客戶端需要執(zhí)行下面的操作:

1. 以毫秒為單位獲取當(dāng)前時(shí)間。

2. 嘗試在所有的N個(gè)實(shí)例中順序的獲取鎖纪铺,在所有實(shí)例中使用相同的Key和Value相速。步驟2中,在每個(gè)實(shí)例中設(shè)置鎖時(shí)鲜锚,為了獲取到鎖,客戶端需要設(shè)置一個(gè)小于鎖自動(dòng)釋放時(shí)間的超時(shí)時(shí)間苫拍。例如如果鎖自動(dòng)釋放時(shí)間是10s芜繁,那么超時(shí)時(shí)間可以設(shè)置成5~50毫秒。避免客戶端長(zhǎng)時(shí)間嘗試與宕掉的Redis的節(jié)點(diǎn)通信造成阻塞:如果一個(gè)實(shí)例不可用绒极,我們應(yīng)該盡快嘗試和下一個(gè)實(shí)例通信骏令。

3. 客戶機(jī)獲取鎖花費(fèi)的時(shí)間,是用當(dāng)前時(shí)間減去步驟1獲取的時(shí)間戳垄提。如果客戶端能夠在多數(shù)實(shí)例(至少3個(gè))中獲取到鎖榔袋,獲取鎖的總時(shí)間少于鎖的有效時(shí)間,就可以認(rèn)為鎖被成功獲取

4. 如果鎖被獲取到铡俐,就可以認(rèn)為鎖的實(shí)際有效時(shí)間就是初始的有效時(shí)間減去步驟3計(jì)算的時(shí)間凰兑。

5. 如果客戶端因?yàn)槟承┰颢@取鎖失敗(不能在N/2+1個(gè)實(shí)例中獲取鎖或者有效時(shí)間是負(fù)數(shù))审丘,那么將會(huì)嘗試在所有實(shí)例中解鎖(包括沒(méi)有成功加鎖的實(shí)例)

算法是異步的嗎吏够?

該算法基于這樣一個(gè)假設(shè),雖然沒(méi)有跨進(jìn)程的同步時(shí)鐘滩报,但每個(gè)進(jìn)程的本地時(shí)鐘會(huì)以近似相同的速率流動(dòng)锅知,誤差小于鎖自動(dòng)釋放的時(shí)間。這一假定與現(xiàn)實(shí)世界的計(jì)算機(jī)類似:每個(gè)計(jì)算機(jī)有本地時(shí)鐘脓钾,我們通常信任不同的計(jì)算機(jī)之間時(shí)間差是很小的售睹。

現(xiàn)在,我們需要細(xì)化互斥規(guī)則:只要獲取到鎖的客戶端能夠在時(shí)間T內(nèi)完成它的工作可训,就能夠保證互斥昌妹,時(shí)間T的計(jì)算規(guī)則是有效時(shí)間(詳見(jiàn)步驟3)減去一些時(shí)間(一般只有幾毫秒捶枢,為了補(bǔ)償不同進(jìn)程之間的時(shí)間差)

想要了解更多關(guān)于時(shí)間差的類似系統(tǒng),可以參考這篇有趣的文章:《Leases: an efficient fault-tolerant mechanism for distributed file cache consistency

失敗重試

當(dāng)客戶端不能獲取到鎖時(shí)捺宗,應(yīng)該在隨機(jī)延時(shí)后再次嘗試柱蟀,隨機(jī)延時(shí)是為了避免多個(gè)客戶端同步的在同一時(shí)間獲取同一個(gè)資源的鎖(否則會(huì)出現(xiàn)沒(méi)有客戶端能獲取到鎖的腦裂狀態(tài))⊙晾鳎客戶端在多數(shù)Redis實(shí)例中獲取鎖的速度越快长已,需要重試的時(shí)間窗口就越小。因此理想狀態(tài)下昼牛,客戶端應(yīng)該在采用多路復(fù)用的模式同時(shí)向N個(gè)實(shí)例中發(fā)送Set命令

有必要強(qiáng)調(diào)一下术瓮,如果客戶端獲取多數(shù)鎖失敗,應(yīng)盡快的釋放已經(jīng)獲得的鎖贰健,這樣沒(méi)必要等到Key超時(shí)后再獲取鎖(然而出現(xiàn)網(wǎng)絡(luò)分區(qū)和客戶端無(wú)法連接Redis實(shí)例的情況胞四,將失去等待鎖過(guò)期這段時(shí)間的可用性)

鎖釋放

鎖釋放很簡(jiǎn)單,無(wú)論客戶端是否成功的獲取到鎖伶椿,只需要在所有節(jié)點(diǎn)釋放鎖就行辜伟。

安全性論證

算法是否是安全的?我們可以試著思考在不同的場(chǎng)景下會(huì)發(fā)生什么脊另。

開(kāi)始之前导狡,我們假設(shè)客戶端可以在多數(shù)實(shí)例中獲取到鎖。所有的實(shí)例都包含一個(gè)有相同存活時(shí)間的Key偎痛。然而Key是在不同的時(shí)間設(shè)置的旱捧,因此這些key也會(huì)在不同的時(shí)間失效。我們假設(shè)最壞情況下踩麦,第一個(gè)Key設(shè)置的時(shí)間是T1(連接到第一個(gè)服務(wù)的時(shí)間)枚赡,最后一個(gè)Key設(shè)置時(shí)間是T2(最后一個(gè)服務(wù)應(yīng)答的時(shí)間),我們確定第一個(gè)設(shè)置了失效時(shí)間的Key至少存活的時(shí)間為MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT 谓谦,TTL是鎖超時(shí)時(shí)間贫橙,(T2-T1)是獲取鎖的耗時(shí),CLOCK_DRIFT不同進(jìn)程的時(shí)間差茁计。其他Key會(huì)在這個(gè)時(shí)間之后失效料皇,因此我們可以確定,所有的Key在這個(gè)時(shí)間點(diǎn)之前是同時(shí)存在的星压。

在多數(shù)Key被設(shè)置的時(shí)間內(nèi)践剂,其他客戶端無(wú)法獲取到鎖,在N/2+1個(gè)Key存在的情況下娜膘,N/2+1個(gè) SET NX 命令是不會(huì)成功的逊脯。因此一個(gè)鎖被獲取,就不能同時(shí)重新獲取這個(gè)鎖(違反了互斥原則)

當(dāng)然我們需要保證多個(gè)客戶端同時(shí)獲取鎖時(shí)不會(huì)同時(shí)成功竣贪。

如果一個(gè)客戶端鎖定多數(shù)實(shí)例消耗的時(shí)間接近或者超過(guò)鎖最大的有效時(shí)間(為SET使用的TTL)军洼,那么系統(tǒng)會(huì)認(rèn)為鎖失效同時(shí)解鎖這些實(shí)例巩螃,因此我們只需要考慮客戶端在小于鎖有效時(shí)間的范圍內(nèi)鎖定多個(gè)實(shí)例的場(chǎng)景。這種情況我們已經(jīng)在上文進(jìn)行論證匕争,在MIN_VALIDITY時(shí)間范圍內(nèi)避乏,沒(méi)有客戶端能夠重新獲取鎖。因此只有當(dāng)多數(shù)節(jié)點(diǎn)獲取鎖的時(shí)間遠(yuǎn)遠(yuǎn)大于TTL時(shí)甘桑,多個(gè)客戶端才能同時(shí)鎖定N/2+1個(gè)實(shí)例拍皮。

可靠性論證

系統(tǒng)可靠性是基于三個(gè)主要的特性:

1. 鎖自動(dòng)釋放(因?yàn)镵ey失效):最終被鎖定的Key可以再次使用

2. 客戶端通常在鎖獲取失敗或者工作完成后主動(dòng)釋放鎖,我們不需要等待鎖過(guò)期在重新獲得鎖

3. 當(dāng)客戶端重試獲取鎖時(shí)跑杭,客戶端等待時(shí)間遠(yuǎn)遠(yuǎn)大于獲取多數(shù)鎖的時(shí)間铆帽,為了降低資源競(jìng)爭(zhēng)期間腦裂的概率

然而我們?cè)诰W(wǎng)絡(luò)分區(qū)時(shí)需要損失TTL時(shí)間的可用性,如果有持續(xù)的分區(qū)問(wèn)題德谅,那么就會(huì)持續(xù)不可用爹橱。這種情況會(huì)在每次客戶端獲取鎖之后釋放鎖之前被分區(qū)時(shí)發(fā)生。

基本上窄做,如果有持續(xù)的網(wǎng)絡(luò)分區(qū)愧驱,系統(tǒng)在這段時(shí)間內(nèi)就會(huì)持續(xù)不可用。

性能椭盏、故障恢復(fù)和fsync

很多使用Redis作為鎖服務(wù)的用戶冯键,不僅要求獲取釋放鎖的低延時(shí),也要求高吞吐量庸汗,即每秒鐘獲取釋放鎖的數(shù)量。為了滿足這個(gè)要求手报,采用多路復(fù)用的策略與N個(gè)Redis服務(wù)通信以降低延時(shí)(或者使用可憐人的多路復(fù)用蚯舱,即socket設(shè)置成非阻塞模式,發(fā)送所有命令掩蛤,然后讀所有的命令枉昏,假設(shè)客戶端和多個(gè)實(shí)例之間的RTT是相近的)

然而如果我們想要系統(tǒng)故障自動(dòng)回復(fù),還需要考慮持久化揍鸟。我們看下這個(gè)問(wèn)題兄裂,假設(shè)我們配置Redis不持久化⊙粼澹客戶端在3個(gè)實(shí)例中獲取鎖晰奖,然后一個(gè)加鎖的實(shí)例重啟,這時(shí)相同的資源有3個(gè)實(shí)例可以獲取到鎖腥泥,另一個(gè)客戶端就可以再次對(duì)這個(gè)資源加鎖匾南,違反了鎖互斥的安全原則。

如果我們使用AOF持久化蛔外,情況會(huì)好一些蛆楞。例如我們可以通過(guò)SHUTDOWN和重啟升級(jí)一個(gè)服務(wù)溯乒。因?yàn)镽edis失效是語(yǔ)義層面的實(shí)現(xiàn),當(dāng)服務(wù)關(guān)閉時(shí)時(shí)間實(shí)際上仍然在流逝豹爹,我們的需求都能滿足裆悄。只要是正常關(guān)閉,一切都很好臂聋。斷電會(huì)怎么樣呢光稼?如果Redis是默認(rèn)配置,每秒執(zhí)行一次fsync逻住,重啟后我們的key就有可能丟失钟哥。理論上, 如果我們要保證任何實(shí)例重啟的情況下鎖的安全性瞎访,我們需要在持久化設(shè)置中啟用 fsync=always腻贰。反過(guò)來(lái)說(shuō),相比與其他同級(jí)別的實(shí)現(xiàn)安全的分布式鎖的CP系統(tǒng)扒秸,會(huì)損失性能播演。

然而事情會(huì)比第一眼看起來(lái)的那樣要好,只要實(shí)例崩潰后重啟伴奥,不再參與到當(dāng)前激活的鎖写烤,算法的安全性就能保證。因?yàn)閷?shí)例重啟時(shí)拾徙,當(dāng)前激活的鎖可以被其他實(shí)例獲取洲炊。

為了滿足這一條件,我們需要讓一個(gè)實(shí)例在崩潰后尼啡,不可用的時(shí)間至少要比我們?cè)O(shè)置的TTL多一點(diǎn)暂衡,也就是說(shuō),要大于實(shí)例崩潰時(shí)所有存在的鎖失效或者自動(dòng)釋放所需要的時(shí)間崖瞭。

延遲重啟基本可以保證安全性狂巢,即使沒(méi)有任務(wù)可用的Redis持久化。然而书聚,需要注意的是這也意味著可用性的損失唧领。嫁入多數(shù)實(shí)例崩潰,TTL時(shí)間內(nèi)系統(tǒng)全局不可用(全局意味著在這個(gè)時(shí)間內(nèi)沒(méi)有資源能夠被鎖定)

使算法更可靠:擴(kuò)展鎖

如果客戶端的工作是由小步驟組成的雌续,那么鎖有效時(shí)間可以使用更小的默認(rèn)值斩个,擴(kuò)展算法以實(shí)現(xiàn)鎖的延長(zhǎng)機(jī)制。當(dāng)鎖即將失效時(shí)西雀,如果客戶端處于計(jì)算中萨驶,那么可以通過(guò)向所有實(shí)例發(fā)送延長(zhǎng)TTL的Lua腳本來(lái)延長(zhǎng)鎖,這個(gè)Key必須存在并且和獲取鎖時(shí)客戶端分配的隨機(jī)值一致艇肴。如果可以在多數(shù)實(shí)例中擴(kuò)展鎖并且是在有效期內(nèi)腔呜,那么客戶端只需要考慮鎖的重新獲取叁温。

然而這不是在技術(shù)上改變算法,因此鎖重新獲取的最大次數(shù)應(yīng)該被限制核畴,否則性能會(huì)受到影響膝但。

RedLock分析

Martin Kleppmann 對(duì)RedLock的分析How to do distributed locking

我不同意這篇評(píng)測(cè)并且上傳了我的回復(fù)?Is RedLock Safe?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谤草,隨后出現(xiàn)的幾起案子跟束,更是在濱河造成了極大的恐慌,老刑警劉巖丑孩,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冀宴,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡温学,警方通過(guò)查閱死者的電腦和手機(jī)略贮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)仗岖,“玉大人逃延,你說(shuō)我怎么就攤上這事≡簦” “怎么了揽祥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)檩电。 經(jīng)常有香客問(wèn)我拄丰,道長(zhǎng),這世上最難降的妖魔是什么俐末? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任愈案,我火速辦了婚禮,結(jié)果婚禮上鹅搪,老公的妹妹穿的比我還像新娘。我一直安慰自己遭铺,他們只是感情好丽柿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著魂挂,像睡著了一般甫题。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涂召,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天坠非,我揣著相機(jī)與錄音,去河邊找鬼果正。 笑死炎码,一個(gè)胖子當(dāng)著我的面吹牛盟迟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播潦闲,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼攒菠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了歉闰?” 一聲冷哼從身側(cè)響起辖众,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎和敬,沒(méi)想到半個(gè)月后凹炸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昼弟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年啤它,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片私杜。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚕键,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衰粹,到底是詐尸還是另有隱情锣光,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布铝耻,位于F島的核電站誊爹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瓢捉。R本人自食惡果不足惜频丘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泡态。 院中可真熱鬧搂漠,春花似錦、人聲如沸某弦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)靶壮。三九已至怔毛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腾降,已是汗流浹背拣度。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抗果。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓筋帖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親窖张。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幕随,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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