14.事件驅(qū)動(dòng)的架構(gòu)(譯)

原文:https://herbertograca.com/2017/10/05/event-driven-architecture/

這篇文章是軟件架構(gòu)編年史()的一部分,這部編年史由一系列關(guān)于軟件架構(gòu)的文章組成。在這一系列文章中,我將寫下我對(duì)軟件架構(gòu)的學(xué)習(xí)和思考,以及我是如何運(yùn)用這些知識(shí)的。如果你閱讀了這個(gè)系列中之前的文章,本篇文章的的內(nèi)容將更有意義。

使用事件來設(shè)計(jì)應(yīng)用似乎是上個(gè)世紀(jì)八十年代后期的實(shí)踐。我們可以在前端后端任何地方使用事件。當(dāng)按鈕被按下時(shí),當(dāng)數(shù)據(jù)變化時(shí),又或是后端操作執(zhí)行時(shí)。

但事件的準(zhǔn)確定義是什么?我們何時(shí)該使用它?又該如何使用它?它的缺點(diǎn)又是什么?

何物/何時(shí)/何因

和類、組件應(yīng)該保持相互之間的低耦合與自身內(nèi)部的高內(nèi)聚一樣。當(dāng)組件需要協(xié)作時(shí),比如組件“A”需要觸發(fā)組件“B”中的某段邏輯,自然而然的方法就是簡單地讓組件 A 調(diào)用組件 B 的一個(gè)對(duì)象的方法。然而,如果 A 知道了 B 的存在,那么它們就產(chǎn)生了耦合,即 A 依賴 B,這讓系統(tǒng)更難變化和維護(hù)。而事件可以用來避免耦合

而且,由于事件的使用和解耦組件帶來的副作用,如果有團(tuán)隊(duì)只在組件 B 上工作,他們甚至不用和負(fù)責(zé)組件 A 的團(tuán)隊(duì)商量就可以改變組件 B 對(duì)組件 A 中的邏輯的響應(yīng)。組件可以獨(dú)立地演進(jìn):我們的應(yīng)用變得更有機(jī)(??)了

即便是在同一個(gè)組件中,有時(shí)我們也會(huì)有一段需要作為操作結(jié)果執(zhí)行的代碼,但是不需要在同一次請(qǐng)求/響應(yīng)回合中立即執(zhí)行。最明顯的例子就是發(fā)郵件了。在這種情況下,我們可以立即向用戶返回一個(gè)響應(yīng),并在稍后以異步方式發(fā)送電子郵件,從而避免用戶等待電子郵件的發(fā)送。

然而,這里也有不少坑。如果我們不假思索地使用事件,就會(huì)產(chǎn)生風(fēng)險(xiǎn),最終概念上高度內(nèi)聚的邏輯流程卻使用了事件來串聯(lián),而這本該是一種解耦的機(jī)制。換句話說,本應(yīng)放在一起的代碼被分開了,脈絡(luò)很難理清(這和goto語句很像),理解和推斷都很難:代碼將變成意大利面!

要防止我們的代碼庫變成一坨意大利面代碼,我們應(yīng)該只在明確識(shí)別出來的情況下使用事件。根據(jù)我的經(jīng)驗(yàn),有以下三種情形需要使用事件:

  1. 解耦組件
  2. 執(zhí)行異步任務(wù)
  3. 跟蹤狀態(tài)變化(審計(jì)日志)

1. 解耦組件

當(dāng)組件 A 執(zhí)行的邏輯需要觸發(fā)組件 B 的邏輯時(shí),它會(huì)觸發(fā)一個(gè)事件發(fā)送給事件派發(fā)器,而不是直接調(diào)用 B 的邏輯。組件 B 會(huì)監(jiān)聽事件派發(fā)器中這個(gè)特殊的事件,在該事件發(fā)生時(shí)做出響應(yīng)。

這意味著 A 和 B 都將依賴派發(fā)器和事件,但它們卻互不知曉:它們是解耦的。

理想情況下,派發(fā)器和事件不應(yīng)該屬于任何組件:

  • 派發(fā)器應(yīng)該是一個(gè)完全獨(dú)立于應(yīng)用的庫,因此使用依賴管理系統(tǒng)安裝在
    一個(gè)通用的地址。在 PHP 中,它使用 Composer 安裝在 vendor 目錄中的東西。

  • 然而,事件卻是應(yīng)用的一部分,但是為了讓組件互相無感,它應(yīng)該不屬于任何組件。事件就是 DDD 中稱為共享內(nèi)核的部分。這樣一來,兩個(gè)組件都依賴共享內(nèi)核但仍然互相無感。
    但是在單體應(yīng)用中,為了方便,將事件放在觸發(fā)它的組件中也是可以接收的。

共享內(nèi)核

[…] 團(tuán)隊(duì)就要共享的領(lǐng)域模型中的子集達(dá)成一致,用一條清晰的邊界將其標(biāo)明。保持內(nèi)核小巧。[…] 這些顯式共享的東西有著特殊的狀態(tài),沒有和其它團(tuán)隊(duì)溝通的情況下不應(yīng)該修改。

Eric Evans 2014, Domain-Driven Design Reference

2. 執(zhí)行異步任務(wù)

有時(shí)候我們有一段想要執(zhí)行的邏輯,但它可能需要一些時(shí)間來執(zhí)行,而我們又不希望讓用戶等它執(zhí)行完成。這種情況下,人們希望將它作為一個(gè)異步的工作執(zhí)行,并立即返回一條消息給用戶,通知他他的請(qǐng)求將在稍后異步執(zhí)行。

例如,在網(wǎng)店上下單可以同步完成,而發(fā)送郵件通知用戶可以異步完成。

這些情況下,我們能做的是觸發(fā)一個(gè)事件放到隊(duì)列中,事件將在隊(duì)列中等待直到有程序在系統(tǒng)有資源的時(shí)候能接收并執(zhí)行它。

這些情形下,相關(guān)邏輯是否屬于同一個(gè)上下文無關(guān)緊要,邏輯是解耦的。

3. 跟蹤狀態(tài)變化(審計(jì)日志)

用傳統(tǒng)方式保存數(shù)據(jù)時(shí),我們用實(shí)體持有某些數(shù)據(jù)。當(dāng)這些實(shí)體之中的數(shù)據(jù)變化時(shí),我們簡單地將數(shù)據(jù)庫表中的行更新成新的值。

這里的問題是,我們沒有保存是什么發(fā)生了變化以及何時(shí)發(fā)生的變化。

我們可以用一種審計(jì)日志的結(jié)構(gòu)保存包含變化的事件。

稍后介紹事件溯源時(shí)還有更多詳細(xì)解釋。

監(jiān)聽器 vs. 訂閱者

在實(shí)現(xiàn)事件驅(qū)動(dòng)架構(gòu)時(shí)常見的爭(zhēng)論就是使用事件監(jiān)聽器還是事件訂閱者,所以在這里澄清一下我的觀點(diǎn):

  1. 事件監(jiān)聽器只會(huì)響應(yīng)一種事件,并有多個(gè)方法來響應(yīng)它。因此我們應(yīng)該根據(jù)事件名稱來命名監(jiān)聽器,例如,如果我們有一個(gè)“UserRegisteredEvent”事件,那么就會(huì)一個(gè)“UserRegisteredEventListener”監(jiān)聽器。這樣,即使不查看文件內(nèi)部,也可以很容易地知道監(jiān)聽器正在監(jiān)聽什么事件。響應(yīng)事件的方法(反應(yīng))應(yīng)該體現(xiàn)出該方法做了什么,例如,“notifyNewUserAboutHisAccount()”和“notifyAdminThatNewUserHasRegistered()”。大多數(shù)情況下這應(yīng)該是常規(guī)做法,因?yàn)樗3直O(jiān)聽器的小巧并讓監(jiān)聽器專注于單一職責(zé),即響應(yīng)特定的事件。還有,如果我們采用了組件化架構(gòu),每個(gè)組件可以擁有自己的監(jiān)聽器,它監(jiān)聽的事件可能從多個(gè)地方觸發(fā)。

  2. 事件訂閱者可以響應(yīng)多種事件,并有多個(gè)方法來響應(yīng)它。訂閱者的命名更加困難,因?yàn)樗荒芴赜兴福欢嗛喺呷匀恍枰裱瓎我宦氊?zé)原則,訂閱者的名字需要體現(xiàn)出它的單一意圖。事件訂閱者的使用應(yīng)該是更少見的方式,尤其是在組件內(nèi)部,因?yàn)閱我宦氊?zé)原則很容易被破壞。正確使用事件訂閱者的一個(gè)例子是事務(wù)管理,具體點(diǎn)說就是我們可以采用一個(gè)名為“RequestTransactionSubscriber”的事件訂閱者,它要響應(yīng)“RequestReceivedEvent”、“ResponseSentEvent”和“KernelExceptionEvent”三個(gè)事件,分別綁定到事務(wù)的開始、結(jié)束和回滾,每個(gè)操作都有各自的方法,如“startTransaction()”, “finishTransaction()”和“rollbackTransaction()”。這個(gè)事件訂閱者可以響應(yīng)多種事件但仍然專注于管理請(qǐng)求事務(wù)的單一職責(zé)。

模式

Martin Fowler 識(shí)別出了三種不同類型的事件模式:

  • 事件通知
  • 事件攜帶的狀態(tài)轉(zhuǎn)換
  • 事件溯源

這些模式有著同樣的關(guān)鍵概念:

  1. 事件表達(dá)了某事已經(jīng)發(fā)生(它們?cè)谀呈潞蟀l(fā)生);
  2. 事件被廣播給監(jiān)聽它的任意代碼(一個(gè)事件能被多個(gè)代碼單元響應(yīng))。

事件通知

假設(shè)我們有一個(gè)應(yīng)用核心,其組件定義清晰。理想情況下,這些組件之間是完全解耦的,但是,它們的某些功能需要執(zhí)行其它組件中的邏輯

這是最典型的情況,之前已經(jīng)描述過:當(dāng)組件 A 執(zhí)行的邏輯需要觸發(fā)組件 B 的邏輯時(shí),它會(huì)觸發(fā)一個(gè)事件發(fā)送給事件派發(fā)器,而不是直接調(diào)用 B 的邏輯。組件 B 會(huì)監(jiān)聽事件派發(fā)器中這個(gè)特殊的事件,在該事件發(fā)生時(shí)做出響應(yīng)。

有一點(diǎn)要特別指出,這種模式有一個(gè)特點(diǎn),事件只會(huì)攜帶最少的數(shù)據(jù)。它只會(huì)攜帶足夠讓監(jiān)聽器能知道發(fā)生了什么并能執(zhí)行它們的代碼的數(shù)據(jù),通常就只有實(shí)體 ID(可以是多個(gè))以及事件發(fā)生的日期和時(shí)間。

優(yōu)點(diǎn)

  • 更好的可恢復(fù)性,如果事件被放入了隊(duì)列,即便第二段邏輯因?yàn)槌霈F(xiàn)問題不能在當(dāng)下執(zhí)行,來源組件依然可以執(zhí)行它自己的邏輯(因?yàn)槭录环湃腙?duì)列,它們可以稍后在問題修復(fù)后再執(zhí)行)。
  • 低延遲,如果事件被放入了隊(duì)列,用戶就不用等著邏輯執(zhí)行完成;
  • 團(tuán)隊(duì)可以獨(dú)立地演進(jìn)組件,他們的工作更簡單、完成得更快、問題更少、更有機(jī)(??)。

缺點(diǎn)

  • 如果不能規(guī)范地使用,有可能把代碼庫變成一堆意大利面代碼。

事件攜帶的狀態(tài)轉(zhuǎn)換

我們還是以前面這個(gè)有著清晰定義的組件的應(yīng)用核心為例。這一次,它們有些功能需要其它組件的數(shù)據(jù)。獲取這些數(shù)據(jù)最順其自然的方式就是問其它的組件要,但這意味著發(fā)起查詢的組件將知道被查詢的組件的信息:這兩個(gè)組件耦合在了一起!

另一種分享數(shù)據(jù)的方式是使用擁有該數(shù)據(jù)的組件在數(shù)據(jù)變化時(shí)所觸發(fā)的事件。這些事件會(huì)攜帶完整的新版本數(shù)據(jù)。對(duì)該數(shù)據(jù)有興趣的組件會(huì)監(jiān)聽這些事件并通過在保存該數(shù)據(jù)的本地副本來響應(yīng)它們。這樣,當(dāng)它們需要外部數(shù)據(jù)時(shí),它們可以在本地找到,就不用向其他組件查詢了。

優(yōu)點(diǎn)

  • 更好的可恢復(fù)性,因?yàn)榧幢惚徊樵兊慕M件不可用(不管是出現(xiàn)問題還是遠(yuǎn)程服務(wù)器無法訪問),發(fā)起查詢的組件依然可以工作。
  • 低延遲,因?yàn)闆]有遠(yuǎn)程調(diào)用(如果被查詢的組件是遠(yuǎn)程組件);
  • 我們不用擔(dān)心被查詢組件的負(fù)載是否可以支撐全部發(fā)起查詢的組件的查詢(特別是這些組件是遠(yuǎn)程組件的話)。

缺點(diǎn)

  • 同樣的數(shù)據(jù)存在多個(gè)副本,即便都是只讀的,即便數(shù)據(jù)存儲(chǔ)現(xiàn)在不再是問題了。
  • 發(fā)起查詢的組件復(fù)雜性更高,因?yàn)樗枰壿媮砭S的護(hù)外部數(shù)據(jù)在本地的拷貝,盡管這些邏輯相當(dāng)?shù)臉?biāo)準(zhǔn)。

如果兩個(gè)組件都在同一個(gè)進(jìn)程中執(zhí)行(這讓組件間的通信比較迅速),這種模式可能是不必要的,但即便是這樣,為了追求解耦和可維護(hù)性或是為了準(zhǔn)備好在未來某個(gè)時(shí)間點(diǎn)將這些組件解耦成微服務(wù),這種模式仍然是有吸引力的。這完全取決于我們現(xiàn)在和未來的需要,以及我們期望/需要多大程度的解耦。

事件溯源

假設(shè)一個(gè)實(shí)體處于初始狀態(tài)。作為一個(gè)實(shí)體,它有自己的身份標(biāo)識(shí),它是應(yīng)用要建模的真實(shí)世界中的一個(gè)特定事物。伴隨著它的生命周期,實(shí)體數(shù)據(jù)不斷變化,而傳統(tǒng)的做法是,將實(shí)體的當(dāng)前狀態(tài)簡單地保存為數(shù)據(jù)庫中一行。

事務(wù)日志

上面這種方法大多數(shù)情況下都可以工作得很好,但是如果我們想要知道實(shí)體是如何到達(dá)這個(gè)狀態(tài)的呢(比如,我們想知道銀行賬號(hào)得貸項(xiàng)和借項(xiàng))?這種方法就做不到了,因?yàn)槲覀冎4媪水?dāng)前狀態(tài)!

如果使用事件溯源,而不是保存實(shí)體狀態(tài),我們就能專注于保存實(shí)體的狀態(tài)變化并根據(jù)這些變化計(jì)算出實(shí)體狀態(tài)。每一次狀態(tài)變化都是一個(gè)事件,保存在事件流中(比如,關(guān)系型數(shù)據(jù)庫中的一張表)。當(dāng)我們需要實(shí)體的當(dāng)前狀態(tài),我們將根據(jù)事件流中它的全部事件計(jì)算出來。

事件存儲(chǔ)變成了事實(shí)的主要來源,而系統(tǒng)狀態(tài)完全由之推導(dǎo)而來。對(duì)程序員來說,版本管理系統(tǒng)就是最好的例子。所有的提交記錄就是事件存儲(chǔ),而源代碼樹的工作副本就是系統(tǒng)狀態(tài)。

Greg Young 2010, CQRS Documents

刪除

如果有一次狀態(tài)變化(事件)是錯(cuò)誤的,我們不能簡單地刪除該事件,因?yàn)檫@樣做會(huì)改變狀態(tài)變化的歷史,會(huì)違反整個(gè)事件溯源的理念。相反地,我們應(yīng)該在事件流中創(chuàng)建一個(gè)事件,撤銷我們想要?jiǎng)h除的事件。這個(gè)過程被稱作逆轉(zhuǎn)事務(wù),它不僅要將實(shí)體帶回期望的狀態(tài),還要留下展示該對(duì)象在給定時(shí)間點(diǎn)處于該狀態(tài)的軌跡

保留數(shù)據(jù)還會(huì)帶來架構(gòu)上的好處。存儲(chǔ)系統(tǒng)變成了一個(gè)遞增的架構(gòu),眾所周知只能追加的架構(gòu)并一直更新的架構(gòu)更容易變成分布式,因?yàn)橐幚淼逆i會(huì)更少。

Greg Young 2010, CQRS Documents

快照

但是,當(dāng)我們有太多事件流中的事件時(shí),計(jì)算實(shí)體狀態(tài)的代價(jià)很大,性能很差。要解決這個(gè)問題,每 X 個(gè)事件,我們都會(huì)在這個(gè)節(jié)點(diǎn)創(chuàng)建一個(gè)實(shí)體狀態(tài)的快照。這樣的話,當(dāng)我們需要實(shí)體狀態(tài)時(shí),我們只用從最后一個(gè)快照開始計(jì)算。見鬼,我們甚至可以保留一個(gè)永遠(yuǎn)更新的實(shí)體快照,魚與熊掌兼得。

2006-2-event-sourcing.png

投影

在事件溯源中,我們還有一個(gè)概念叫做投影,它是對(duì)事件流中給定起始時(shí)刻之間的事件的計(jì)算。這意味著快照、或者實(shí)體的當(dāng)前狀態(tài),都符合投影的定義。但是,投影概念中最有價(jià)值的理念是我們可以分析特定時(shí)間段內(nèi)實(shí)體的“行為”,讓我們對(duì)未來作出有根據(jù)的猜測(cè)(例如,如果在過去五年中,實(shí)體在八月的活動(dòng)都有所增加,那么很能在接下來的八月中也會(huì)發(fā)生同樣的事情),這種能力對(duì)公司來說非常有價(jià)值。

優(yōu)點(diǎn)和缺點(diǎn)

事件溯源對(duì)業(yè)務(wù)流程和開發(fā)流程都很有幫助:

  • 我們查詢這些事件,有助于從業(yè)務(wù)側(cè)和開發(fā)側(cè)兩方面理解用戶和系統(tǒng)的行為(調(diào)試);
  • 我們還可以使用事件日志來重建過去的狀態(tài),對(duì)業(yè)務(wù)和開發(fā)也都很有幫助;
  • 自動(dòng)調(diào)整狀態(tài),以應(yīng)對(duì)追溯性變化,對(duì)業(yè)務(wù)大有裨益;
  • 在重放時(shí)注入假想事件來探索另一種的歷史,對(duì)業(yè)務(wù)來說太棒了。

但也并不是事事順心,要小心潛在的問題:

  • 外部的更新

    如果我們的事件要觸發(fā)外部系統(tǒng)中的更新,當(dāng)我們?yōu)榱藙?chuàng)建投影而重放事件時(shí)我們不希望重新觸發(fā)這些事件。這時(shí),我們可以在“重放模式”中簡單地禁用外部更新,可能會(huì)將這段邏輯封裝在網(wǎng)關(guān)中。
    還有一種解決方法,要依實(shí)際問題而定,可以將對(duì)外部系統(tǒng)的更新緩沖起來,在一段時(shí)間后當(dāng)可以肯定這些事件不會(huì)被重放時(shí)在、再執(zhí)行它們,

  • 外部的查詢

    如果我們的事件要使用對(duì)外部系統(tǒng)的查詢,例如,獲得股票債券評(píng)級(jí),當(dāng)我們?yōu)榱藙?chuàng)建投影而重放事件時(shí)會(huì)發(fā)生什么?我們可能期望得到和第一次執(zhí)行這些事件時(shí)(可能是幾年之前)一樣的評(píng)級(jí)。所以,要么遠(yuǎn)程應(yīng)用可以將這些值給我們,要么我們需要在自己的系統(tǒng)中保存它們,這樣我們就能模擬遠(yuǎn)程查詢,同樣地,這段邏輯將被封裝在網(wǎng)關(guān)里。

  • 代碼變化

    Martin Fowler 識(shí)別出了三種類型的代碼變化:新特性問題修復(fù)以及*臨時(shí)邏輯。當(dāng)應(yīng)該用不同的業(yè)務(wù)邏輯規(guī)則在不同的時(shí)間點(diǎn)播放的事件被重放時(shí),真正的問題就出現(xiàn)了。例如,去年的稅收計(jì)算與今年不同。通常情況下,可以使用條件邏輯,但它會(huì)變得混亂,因此建議使用策略模式。

因此,我建議謹(jǐn)慎使用,只要有可能,我會(huì)遵守以下規(guī)則:

  • 保持事件簡單,只和狀態(tài)變化有關(guān),和變化如何決策無關(guān)。這樣的話我們可以安全地重放任何事件,即使業(yè)務(wù)規(guī)則同一時(shí)間內(nèi)發(fā)生了變化我們也可以期望同樣的結(jié)果(盡管我們要保留遺留的業(yè)務(wù)規(guī)則,我們才能在重放過去的事件時(shí)應(yīng)用它們);

  • 與外部系統(tǒng)的交互不應(yīng)該依賴這些事件,這樣我們就可以安全地重放事件,不存在重新觸發(fā)外部邏輯的危險(xiǎn),而且我們不需要保證外部系統(tǒng)的回復(fù)和一開始播放這些事件時(shí)是一樣的。

而且,當(dāng)然,和其它模式一樣,我們不用在所有地方使用它,我們應(yīng)該在有效的地方使用它,當(dāng)它可以為我們帶來優(yōu)勢(shì)使用它,當(dāng)它解決的問題比帶來的問題更多時(shí)使用它。

總結(jié)

再一次,這主要關(guān)于封裝、低耦合和高內(nèi)聚。

事件可以為可維護(hù)性、性能和代碼庫的擴(kuò)張帶來巨大的好處,但是,要通過事件溯源,它還可以為系統(tǒng)數(shù)據(jù)提供的可靠性和信息帶來巨大的好處。

然而,這是一條布滿荊棘的道路,因?yàn)楦拍顝?fù)雜性和技術(shù)復(fù)雜性都在增加,濫用其中任何一種都可能帶來災(zāi)難性的后果。

引用來源

2005 – Martin Fowler – Event Sourcing
2006 – Martin Fowler – Focusing on Events
2010 – Greg Young – CQRS Documents
2014 – Greg Young – CQRS and Event Sourcing – Code on the Beach 2014
2014 – Eric Evans – Domain-Driven Design Reference
2017 – Martin Fowler – What do you mean by “Event-Driven”?
2017 – Martin Fowler – The Many Meanings of Event-Driven Architecture

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373