框架相關(2)-- 分布式事務

分布式事務解決的用戶最本質訴求是什么?數據一致。

大中企業有一個共同的訴求是數據一致,幾乎覆蓋到各個行業。

比如說零售行業,庫存與出貨的數據需要保持一致,出貨量與庫存數據不匹配,顯而易見會出問題,拿到訂單卻沒貨了,或者有貨卻下不了訂單。

比如說金融行業,轉賬數據搞錯了,A扣款了,B沒加上,馬上該用戶投訴了;A沒扣款,B卻加上了,產生資損。又比如從總賬戶中買了基金、股票后余額不對了,等等,都會導致嚴重問題。

以前多數企業的數據規模相對較小,很多操作是單機完成,數據庫本地事務可以搞定,所以數據一致問題不那么明顯。隨著互聯網技術快速發展,數據規模增大,分布式系統越來越普及,采用分布式數據庫或者跨多個數據庫的應用在中大規模企業普遍存在,服務化也是廣泛應用,由于網絡的不可靠和機器不可靠,數據不一致問題很容易出現。

?

事務的基本特性:

Atomicity(原子性):是指事務是一個不可分割的整體,所有操作要么全做,要么全不做;只要事務中有一個操作出錯,回滾到事務開始前的狀態,那么之前已經執行的所有操作都是無效的,都應該會滾到開始前的狀態。

Consistency(一致性):指事務執行前后,數據從一個狀態到另一個狀態必須是一致的,比如A向B轉賬(A、B的總金額就是一個一致性狀態),不可能出現A扣了錢, B卻沒收到的情況。

Isolation(隔離性):多個并發事務之間相互隔離,不能相互干擾。這里的并發事務指的是兩個事務操作了同一份數據的情況,要求不能出現臟讀、幻讀的情況。常用手段就是通過數據庫的相關鎖機制來保證。

Durablity(持久性):事務完成后,對數據庫的更改是永久保存的,不能回滾。


分布式事務

分布式事務是為了解決微服務架構(形式都是分布式系統)中不同節點之間的數據一致性的問題。這個一致性問題本質上解決的也是傳統事務需要解決的問題,即一個請求在多個微服務調用鏈中,所有服務的數據處理要么全部成功,要么全部回滾。當然分布式事務問題的形式可能與傳統事務會有比較大的差異,但是問題本質是一致的,都是要求解決數據的一致性問題,并且滿足事務的基本特性(ACID)。

舉個例子,在一個JVM進程中如果需要同時操作數據庫的多條記錄,而這些操作需要在一個事務中,那么我們就可以通過數據庫提供的事務機制(一般是數據庫鎖)來實現。而隨著這個JVM進程(應用)被拆分成了微服務架構,原本一個本地邏輯執行單元被分到了多個獨立的微服務中,這些微服務又分別操作不同的數據庫和表,服務之間通過網絡調用。

在微服務中的分布式事務問題

讓我門想象一下一個傳統的單體應用,它的業務由三個模塊構建而成,他們使用了一個單一本地數據源。很顯然,本地事務可以保證整個業務過程的數據一致性。

微服務架構中一些事情需要被改變,這三個被上文提及到的模塊,被設計為三個不同數據源之上的三個服務,業務過程將由3個服務的調用來完成。

此時,每個服務內部的數據一致性仍由本地事務來保證。而整個業務層面的全局數據一致性要如何保障呢?這就是微服務架構下面臨的,典型的分布式事務需求:我們需要一個分布式事務的解決方案保障業務全局的數據一致性。

關于分布式事務,工程領域主要討論的是強一致性和最終一致性的解決方案。典型方案包括,對業務無侵入的和對業務有侵入的兩類:

業務無侵入方案

? ? * 兩階段提交(2PC, Two-phase Commit)、三階段提交(3PC)

業務侵入方案

? ? * TCC 補償模式

? ? * 基于消息的最終一致性

?

分布式事務解決方案

(1)2PC方案——強一致性

基于XA協議的兩階段提交方案

交易中間件與數據庫通過 XA 接口規范,使用兩階段提交來完成一個全局事務, XA 規范的基礎是兩階段提交協議。

第一階段是表決階段,所有參與者都將本事務能否成功的信息反饋發給協調者;第二階段是執行階段,協調者根據所有參與者的反饋,通知所有參與者,步調一致地在所有分支上提交或者回滾。

兩階段提交方案應用非常廣泛,幾乎所有商業OLTP數據庫都支持XA協議。但是兩階段提交方案鎖定資源時間長,對性能影響很大,基本不適合解決微服務事務問題。

2PC

階段1:請求階段(commit-request phase,或稱表決階段,voting phase)

協調者節點向所有參與者節點詢問是否可以執行提交操作,并開始等待各參與者節點的響應。

參與者節點執行詢問發起為止的所有事務操作,并將Undo信息和Redo信息寫入日志。

各參與者節點響應協調者節點發起的詢問。如果參與者節點的事務操作實際執行成功,則它返回一個”同意”消息;如果參與者節點的事務操作實際執行失敗,則它返回一個”中止”消息。 有時候,第一階段也被稱作投票階段,即各參與者投票是否要繼續接下來的提交操作。。

階段2:提交階段(commit phase)

在該階段,協調者將基于第一個階段的投票結果進行決策:提交或取消。當且僅當所有的參與者同意提交事務協調者才通知所有的參與者提交事務,否則協調者將通知所有的參與者取消事務。參與者在接收到協調者發來的消息后將執行響應的操作。

成功:

1)當協調者節點從所有參與者節點獲得的相應消息都為”同意”時:

2)協調者節點向所有參與者節點發出”正式提交”的請求。

3)參與者節點正式完成操作,并釋放在整個事務期間內占用的資源。

4)參與者節點向協調者節點發送”完成”消息。

5)協調者節點收到所有參與者節點反饋的”完成”消息后,完成事務。

失敗:

1)如果任一參與者節點在第一階段返回的響應消息為”終止”,或者 協調者節點在第一階段的詢問超時之前無法獲取所有參與者節點的響應消息時:

2)協調者節點向所有參與者節點發出”回滾操作”的請求。

3)參與者節點利用之前寫入的Undo信息執行回滾,并釋放在整個事務期間內占用的資源。

4)參與者節點向協調者節點發送”回滾完成”消息。

5)協調者節點收到所有參與者節點反饋的”回滾完成”消息后,取消事務。

有時候,第二階段也被稱作完成階段,因為無論結果怎樣,協調者都必須在此階段結束當前事務。

2PC 存在的問題

1)同步阻塞問題

它的執行過程中間,節點都處于阻塞狀態。即節點之間在等待對方的相應消息時,它將什么也做不了。特別是,當一個節點在已經占有了某項資源的情況下,為了等待其他節點的響應消息而陷入阻塞狀態時,當第三個節點嘗試訪問該節點占有的資源時,這個節點也將連帶陷入阻塞狀態

2)單點故障

由于協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤其在第二階段,協調者發生故障,那么所有的參與者還都處于鎖定事務資源的狀態中,而無法繼續完成事務操作。(如果是協調者掛掉,可以重新選舉一個協調者,但是無法解決因為協調者宕機導致的參與者處于阻塞狀態的問題)

3)數據不一致

如果出現協調者和參與者都掛了的情況,有可能導致數據不一致。


3PC

三階段提交(Three-phase commit),也叫三階段提交協議(Three-phase commit protocol),是二階段提交(2PC)的改進版本。

與兩階段提交不同的是,三階段提交有兩個改動點。

? ? # 引入超時機制。同時在協調者和參與者中都引入超時機制。

? ? # 在第一階段和第二階段中插入一個準備階段。保證了在最后提交階段之前各參與節點的狀態是一致的。

也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段

CanCommit階段

3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。

1.事務詢問協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然后開始等待參與者的響應。

2.響應反饋參與者接到CanCommit請求之后,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,并進入預備狀態。否則反饋No

PreCommit階段

協調者根據參與者的反應情況來決定是否可以記性事務的PreCommit操作。根據響應情況,有以下兩種可能。

假如協調者從所有的參與者獲得的反饋都是Yes響應,那么就會執行事務的預執行。

1.發送預提交請求協調者向參與者發送PreCommit請求,并進入Prepared階段。

2.事務預提交參與者接收到PreCommit請求后,會執行事務操作,并將undo和redo信息記錄到事務日志中。

3.響應反饋如果參與者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。

假如有任何一個參與者向協調者發送了No響應,或者等待超時之后,協調者都沒有接到參與者的響應,那么就執行事務的中斷。

1.發送中斷請求協調者向所有參與者發送abort請求。

2.中斷事務參與者收到來自協調者的abort請求之后(或超時之后,仍未收到協調者的請求),執行事務的中斷。

doCommit階段

該階段進行真正的事務提交,也可以分為以下兩種情況。

執行提交

1.發送提交請求協調接收到參與者發送的ACK響應,那么他將從預提交狀態進入到提交狀態。并向所有參與者發送doCommit請求。

2.事務提交參與者接收到doCommit請求之后,執行正式的事務提交。并在完成事務提交之后釋放所有事務資源。

3.響應反饋事務提交完之后,向協調者發送Ack響應。

4.完成事務協調者接收到所有參與者的ack響應之后,完成事務。

中斷事務協調者沒有接收到參與者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那么就會執行中斷事務。

1.發送中斷請求協調者向所有參與者發送abort請求

2.事務回滾參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執行事務的回滾操作,并在完成回滾之后釋放所有的事務資源。

3.反饋結果參與者完成事務回滾之后,向協調者發送ACK消息

4.中斷事務協調者接收到參與者反饋的ACK消息之后,執行事務的中斷。


2TCC Try-Confirm-Cancel)補償模式——最終一致性

有些場景下,我們根據自己的真實需要,并不需要純的2PC,比如你只關心數據的原子性與最終一致性,那2PC階段的阻塞是你不能忍受的,那就有聰明的人想到了一種新的辦法。就是我們今天要說的柔性事務TCC。「柔」主要是相對于「傳統」ACID的剛而言,柔性事務只需要遵循BASE原則。而TCC是柔性事務的一種實現。TCC是三個首字母,Try-Confirm-Cancel,具體描述是將整個操作分為上面這三步。兩個微服務間同時進行Try,在Try的階段會進行數據的校驗,檢查,資源的預創建,如果都成功就會分別進行Confirm,如果兩者都成功則整個TCC事務完成。如果Confirm時有一個服務有問題,則會轉向Cancel,相當于進行Confirm的逆向操作。

TCC方案在電商、金融領域落地較多。TCC方案其實是兩階段提交的一種改進。其將整個業務邏輯的每個分支顯式的分成了Try、Confirm、Cancel三個操作。Try部分完成業務的準備工作,confirm部分完成業務的提交,cancel部分完成事務的回滾。基本原理如下圖所示。

事務開始時,業務應用會向事務協調器注冊啟動事務。之后業務應用會調用所有服務的try接口,完成一階段準備。之后事務協調器會根據try接口返回情況,決定調用confirm接口或者cancel接口。如果接口調用失敗,會進行重試。

其核心思想是:針對每個操作,都要注冊一個與其對應的確認和補償(撤銷)操作。它分為三個階段:

? ? # Try階段主要是對業務系統做檢測及資源預留

? ? # Confirm階段主要是對業務系統做確認提交,Try階段執行成功并開始執行 Confirm階段時,默認 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。

? ? # Cancel階段主要是在業務執行錯誤,需要回滾的狀態下執行的業務取消,預留資源釋放。

基本實現原理

這些TCC的框架,基本都是通過「注解」的形式,在注解中聲明Confirm方法與Cancel方法,再通過AOP對帶點該注解的方法統一進行攔截,之后根據結果分別再執行 Confirm 或者 Cancel。

代碼類似這個樣子:

@Compensable(confirmMethod = "confirmRecord",cancelMethod = "cancelRecord", transactionContextEditor =MethodTransactionContextEditor.class)

public String record(TransactionContext transactionContext,CapitalTradeOrderDto tradeOrderDto) {

confirm方法

public void confirmRecord(TransactionContext transactionContext,CapitalTradeOrderDto tradeOrderDto) {

cancel方法:

public void cancelRecord(TransactionContext transactionContext,RedPacketTradeOrderDto tradeOrderDto) {

基于類似的框架,可以比較方便的滿足我們的業務使用場景。

TCC方案讓應用自己定義數據庫操作的粒度,使得降低鎖沖突、提高吞吐量成為可能。 當然TCC方案也有不足之處,集中表現在以下兩個方面:

? ? # 對應用的侵入性強。業務邏輯的每個分支都需要實現try、confirm、cancel三個操作,應用侵入性較強,改造成本高。

? ? # 實現難度較大。需要按照網絡狀態、系統故障等不同的失敗原因實現不同的回滾策略。為了滿足一致性的要求,confirm和cancel接口必須實現冪等。

上述原因導致TCC方案大多被研發實力較強、有迫切需求的大公司所采用。微服務倡導服務的輕量化、易部署,而TCC方案中很多事務的處理邏輯需要應用自己編碼實現,復雜且開發量大。


3)基于消息的最終一致性

消息一致性方案是通過消息中間件保證上、下游應用數據操作的一致性。基本思路是將本地操作和發送消息放在一個事務中,保證本地操作和消息發送要么兩者都成功或者都失敗。下游應用向消息系統訂閱該消息,收到消息后執行相應操作。

所謂的消息事務就是基于消息中間件的兩階段提交,本質上是對消息中間件的一種特殊利用,它是將本地事務和發消息放在了一個分布式事務里,保證要么本地操作成功并且對外發消息成功,要么兩者都失敗,開源的RocketMQ就支持這一特性,具體原理如下:

1、A系統向消息中間件發送一條預備消息

2、消息中間件保存預備消息并返回成功

3、A執行本地事務

4、A發送提交消息給消息中間件

通過以上4步完成了一個消息事務。對于以上的4個步驟,每個步驟都可能產生錯誤,下面一一分析:

步驟一出錯,則整個事務失敗,不會執行A的本地操作

步驟二出錯,則整個事務失敗,不會執行A的本地操作

步驟三出錯,這時候需要回滾預備消息,怎么回滾?答案是A系統實現一個消息中間件的回調接口,消息中間件會去不斷執行回調接口,檢查A事務執行是否執行成功,如果失敗則回滾預備消息

步驟四出錯,這時候A的本地事務是成功的,那么消息中間件要回滾A嗎?答案是不需要,其實通過回調接口,消息中間件能夠檢查到A執行成功了,這時候其實不需要A發提交消息了,消息中間件可以自己對消息進行提交,從而完成整個消息事務

基于消息中間件的兩階段提交往往用在高并發場景下,將一個分布式事務拆成一個消息事務(A系統的本地操作+發消息)+ B系統的本地操作,其中B系統的操作由消息驅動,只要消息事務成功,那么A操作一定成功,消息也一定發出來了,這時候B會收到消息去執行本地操作,如果本地操作失敗,消息會重投,直到B操作成功,這樣就變相地實現了A與B的分布式事務。原理如下:

? ? # 在系統A處理任務A前,首先向消息中間件發送一條消息

? ? # 消息中間件收到后將該條消息持久化,但并不投遞。此時下游系統B仍然不知道該條消息的存在

? ? # 消息中間件持久化成功后,便向系統A返回一個確認應答

? ? # 系統A收到確認應答后,則可以開始處理任務A

? ? # 任務A處理完成后,向消息中間件發送Commit請求。該請求發送完成后,對系統A而言,該事務的處理過程就結束了,? ? ? ? ? ? 此時它可以處理別的任務了

? ? # 消息中間件收到Commit指令后,便向系統B投遞該消息,從而觸發任務B的執行

? ? # 當任務B執行完成后,系統B向消息中間件返回一個確認應答,此時,這個分布式事務完成

如果任務A處理失敗,那么需要進入回滾流程:

? ? # 若系統A在處理任務A時失敗,那么就會向消息中間件發送Rollback請求。系統A發完之后便可以認為回滾已經完成,它? ? ? ? ? 便可以去做其他的事情

? ? # 消息中間件收到回滾請求后,直接將該消息丟棄不投遞給系統B

新的問題

上面所介紹的Commit和Rollback都屬于理想情況,但在實際系統中,Commit和Rollback指令都有可能在傳輸途中丟失。那么當出現這種情況的時候,消息中間件是如何保證數據一致性呢?——答案就是超時詢問機制。

系統A除了實現正常的業務流程外,還需提供一個事務詢問的接口,供消息中間件調用。當消息中間件收到一條事務型消息后便開始計時,如果到了超時時間也沒收到系統A發來的Commit或Rollback指令的話,就會主動調用系統A提供的事務詢問接口詢問該系統目前的狀態。該接口會返回三種結果:

? ? # 提交:若獲得的狀態是“提交”,則將該消息投遞給系統B

? ? # 回滾:若獲得的狀態是“回滾”,則直接將條消息丟棄

? ? # 處理中:若獲得的狀態是“處理中”,則繼續等待

投遞過程的可靠性保證

我們知道當上游系統A發出commit請求之后認為事務已經完成,便可以處理其他的任務了;那么消息中間件是怎么保證消息一定會被下游系統B成功消費呢?這是使用消息中間件投遞過程的可靠性來保證的

消息中間件向系統B投遞完消息后便進入阻塞等待狀態,如果消息在傳遞過程中丟失或者消息的確認應答在返回途中丟失,那么消息中間件在等待超時后會重新投遞直到消息被系統B成功消費為止

為什么是重新投遞而不是回滾?

這就涉及到整套分布式事務系統的實現成本問題。如果回滾的話,系統A就要提供回滾接口,這增加了開發成本,業務系統的復雜度也會隨之提高

異步與同步

上游系統A向消息中間件提交完消息后便可以去做別的事情。然而消息中間件將消息投遞給下游系統B后,它會阻塞等待直到下游系統返回B確認應答。為什么要這么設計呢?

首先,上游系統和消息中間件之間采用異步通信是為了提高系統并發度。業務系統直接和用戶打交道,用戶體驗尤為重要,因此這種異步通信方式能夠極大程度地降低用戶等待時間;

下游系統與消息中間件采用同步雖然降低系統并發度,但實現成本較低。在對并發度要求不是很高或者服務器資源較為充裕的情況下,我們可以選擇使用同步來降低系統的復雜度。

雖然上面的方案能夠完成A和B的操作,但是A和B并不是嚴格一致的,而是最終一致的,我們在這里犧牲了一致性,換來了性能的大幅度提升。當然,這種玩法也是有風險的,如果B一直執行不成功,那么一致性會被破壞,具體要不要玩,還是得看業務能夠承擔多少風險。

消息方案從本質上講是將分布式事務轉換為兩個本地事務,然后依靠下游業務的重試機制達到最終一致性。基于消息的最終一致性方案對應用侵入性也很高,應用需要進行大量業務改造,成本較高。


選擇建議

在面臨數據一致性問題的時候,首先要從業務需求的角度出發,確定我們對于3 種一致性模型的接受程度,再通過具體場景來決定解決方案。

從應用角度看,分布式事務的現實場景常常無法規避,在有能力給出其他解決方案前,2PC也是一個不錯的選擇。

對購物轉賬等電商和金融業務,中間件層的2PC最大問題在于業務不可見,一旦出現不可抗力或意想不到的一致性破壞,如數據節點永久性宕機,業務難以根據2PC的日志進行補償。金融場景下,數據一致性是命根,業務需要對數據有百分之百的掌控力,建議使用TCC這類分布式事務模型,或基于消息隊列的柔性事務框架,這兩種方案都在業務層實現,業務開發者具有足夠掌控力,可以結合SOA框架來架構,包括Dubbo、Spring Cloud等。

侵入式方案的不足:

要求在應用的業務層面把分布式事務技術約束考慮到設計中,通常每一個服務都需要設計實現正向和反向的冪等接口。這樣的設計約束,往往會導致很高的研發和維護成本。


4FESCAR方案Fast & EaSy Commit And Rollback阿里開源高效簡單易用的分布式事務解決方案

一個理想的分布式事務解決方案應該:像使用本地事務一樣簡單,業務邏輯只關注業務層面的需求,不需要考慮機制上的約束。我們要設計一個對業務無侵入的方案,所以從業務無侵入的XA方案來思考:是否可以在XA的基礎上演進,解決調XA方案面臨的問題呢?

首先,如何定義分布式事務?

可以把一個分布式事務理解成一個包含了若干分支事務的全局事務。全局事務的職責是協調其下管轄的分支事務達成一致,要么一起成功提交,要么一起失敗回滾。此外,通常分支事務本身就是一個滿足ACID的本地事務。這是我們對分布式事務結構的解基本認識,與XA是一致的。

其次,與XA的模型類似,FESCAR有3個基本組件來協議分布式事務的處理過程:

? ? # 事務協調器(TC):維護全局事務的運行狀態,負責協調驅動全局事務的提交或回滾。

? ? # Transaction Manager(TM):定義全局事務的范圍:開始全局事務,提交或回滾全局事務。

? ? # 資源管理器(RM):管理分支事務的資源,與TC通信以注冊分支事務和報告分支事務的狀態,并驅動分支事務提交或? ? ? ? ?回滾。

一個典型的分布式事務過程:

(1)TM向TC申請開啟一個全局事務,全局事務創建成功并生成一個全局唯一的XID。

(2)XID在微服務調用鏈路的上下文中傳播。

(3)RM向TC注冊分支事務,將其納入XID對應全局事務的管轄。

(4)TM向TC發起針對XID的全局事務提交或回滾。

(5)TC調度XID下管轄的全部分支事務完成提交或回滾請求。

至此,Fescar的協議機制總體上看與XA是一致的。

Fescar與XA的差別在哪里?

1、架構層次

XA方案的RM實際上是在數據庫層,RM本質上就是數據庫自身(通過提供支持XA的驅動程序來供應用使用)。

而Fescar的RM是以二方包的形式作為中間件層部署在應用程序這一側的,不依賴與數據庫本身對協議的支持,當然也不需要數據庫支持XA協議。這點對于微服務化的架構來說是非常重要的:應用層不需要為本地事務和分布式事務兩類不同場景來適配兩套不同的數據庫驅動。

這個設計剝離了分布式事務方案對數據庫在協議支持上的要求。

2、兩階段提交

先來看一下XA的2PC過程:

無論Phase2的決議是commit還是rollback,事務性資源的鎖都是要保持到Phase2完成才釋放。

設想一個正常運行的業務,大概率是90%以上的事務最終應該是成功提交的,我們是否可以在Phase1就將本地事務提交呢?這樣90%以上的情況下,可以省去Phase2持鎖的時間,整體提高效率。

這個設計,在絕大多數場景減少了事務持鎖的時間, 從而提高了事務的并發度。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容