分布式id生成器:徹底解決雪花算法時間回撥問題

Butterfly 簡介

雪花算法是twitter提出的分布式id生成器方案蒂培,但是有三個問題击胜,其中前兩個問題在業(yè)內(nèi)很常見:

  • 時間回撥問題
  • 機器id的分配和回收問題
  • 機器id的上限問題

Butterfly(蝴蝶)是一個超高性能的發(fā)號器框架捌浩。起名Butterfly是用世界上沒有完全相同的蝴蝶翅膀來表示該算法的唯一性快集〖识龋框架通過引入多種新的方案不僅解決了雪花算法存在的所有問題葵袭,而且還能夠提供比雪花算法更高的性能。在單機版QPS理論值為51.2(w/s)這種情況下乖菱,新的方案在一些機器上可達 1200(w/s) 甚至更高坡锡。

其中業(yè)內(nèi)針對前兩個問題都有個自己的解決方式蓬网,但是都不是很完美,或者說沒有完全解決鹉勒。我們這里從新的思路出發(fā)帆锋,通過改造雪花算法以及其他相關(guān)方式徹底解決了以上的三個問題。該方案算是對雪花算法比較完美的一種實現(xiàn)方式贸弥。方案請見方案介紹

框架說明文檔:https://www.yuque.com/simonalong/butterfly/tul824
源碼:https://github.com/SimonAlong/Butterfly

以下內(nèi)容摘自框架說明文檔

二窟坐、方案介紹

雪花算法

雪花算法是twitter提出的分布式id生成器方案,也叫發(fā)號器方案绵疲。這里簡單介紹下雪花算法
image.png
image.png

這個就是原生的雪花算法分配

  • 41bit時間戳:這里采用的就是當(dāng)前系統(tǒng)的具體時間哲鸳,單位為毫秒
  • 10bit工作機器ID(workerId):每臺機器分配一個id,這樣可以標示不同的機器盔憨,但是上限為1024徙菠,標示一個集群某個業(yè)務(wù)最多部署的機器個數(shù)上限
  • 12bit序列號(自增域):表示在某一毫秒下,這個自增域最大可以分配的bit個數(shù)郁岩,在當(dāng)前這種配置下婿奔,每一毫秒可以分配2^12個數(shù)據(jù),也就是說QPS可以到 409.6 w/s问慎。

存在的問題

  • 時間回撥問題:由于機器的時間是動態(tài)的調(diào)整的萍摊,有可能會出現(xiàn)時間跑到之前幾毫秒,如果這個時候獲取到了這種時間如叼,則會出現(xiàn)數(shù)據(jù)重復(fù)
  • 機器id分配及回收問題:目前機器id需要每臺機器不一樣冰木,這樣的方式分配需要有方案進行處理,同時也要考慮笼恰,如果改機器宕機了踊沸,對應(yīng)的workerId分配后的回收問題
  • 機器id上限:機器id是固定的bit,那么也就是對應(yīng)的機器個數(shù)是有上限的社证,在有些業(yè)務(wù)場景下逼龟,需要所有機器共享同一個業(yè)務(wù)空間,那么10bit表示的1024臺機器是不夠的追葡。

業(yè)內(nèi)方案

業(yè)內(nèi)的方案中對以上三個問題有這么幾種處理腺律,但是都沒有徹底解決,我們這里表述下

1.時間回撥問題:

  • 采用直接拋異常方式:這種很不友好辽俗,太粗暴
  • 采用等待跟上次時間的一段范圍:這種算是簡單解決疾渣,可以接受,但是如果等待一段時間后再出現(xiàn)回撥崖飘,則拋異常,可接受杈女,但是不算徹底解決

2.機器id分配及回收:

  • 采用zookeeper的順序節(jié)點分配:解決了分配吊圾,回收可采用zookeeper臨時節(jié)點回收,但是臨時節(jié)點不可靠翰蠢,存在無故消失問題项乒,因此也不可靠
  • 采用DB中插入數(shù)據(jù)作為節(jié)點值:解決了分配,沒有解決回收

3.機器id上限

該問題在業(yè)內(nèi)都沒有處理梁沧,也就是說如果采用雪花算法檀何,則必定會存在該問題,但是該問題也只有需要大量的業(yè)務(wù)機器共享的場景才會出現(xiàn)廷支,這種情況频鉴,采用客戶端雙Buffer+DB這種非雪花算法的方案也未嘗不可。


Butterfly方案

對于以上三個問題恋拍,我們這里簡述下我們的方案垛孔。

1.時間回撥問題

這里采用新的方案:大概思路是:啟動時間戳采用的是“歷史時間”,每次請求只增序列值施敢,序列值增滿周荐,然后“歷史之間”增1,序列值重新計算僵娃。具體方案請見后面

2.機器id分配和回收

這里機器id分配和回收具體有兩種方案:zookeeper和db概作。理論上分配方案zk是通過哈希和擴容機器,而db是通過查找機制默怨⊙堕牛回收方案,zk采用的是永久節(jié)點先壕,節(jié)點中存儲下次過期時間瘩扼,客戶端定時上報(設(shè)置心跳),db是添加過期時間字段垃僚,查找時候判斷過期字段集绰。

3.機器id上限

這個采用將改造版雪花+zookeeper分配id方案作為服務(wù)端的節(jié)點,客戶端采用雙Buffer+異步獲取提高性能谆棺,服務(wù)端采用每次請求時間戳增1栽燕。
以上是方案的簡述,對于方案的具體實現(xiàn)請看下面

改進版雪花設(shè)計

前面我們已經(jīng)知道雪花算法的三個問題改淑,以及也簡述了我們針對雪花算法的幾個方案碍岔。這里詳細描述針對雪花算法的調(diào)整和我們自己的方案。

bit劃分調(diào)整

我們對雪花算法的bit劃分做了調(diào)整朵夏,將“機器id(workerId)”從高位置換到了地位蔼啦,同時將bit也邊更為了13bit,同時縮減了序列號(自增域)的bit為9bit仰猖。
image.png
image.png

將其中“機器id”調(diào)整到最后捏肢,是為了避免“序列號”增1導(dǎo)致的整體數(shù)據(jù)增1的問題奈籽,這樣可以在一定程度上規(guī)避外部數(shù)據(jù)對id的猜測,以防止惡意爬取鸵赫。

時間獲取處理

采用“歷史時間”:

這里是我們方案的核心衣屏,我們這里采用的不是實際時間,而是歷史時間辩棒,在進程啟動后狼忱,我們會將當(dāng)前時間(實際處理采用了延遲10ms啟動)作為該業(yè)務(wù)這臺機器進程的時間戳中的起始時間字段。后續(xù)的自增是在序列號自增到最大值時候時間戳增1一睁,而序列號重新歸為0钻弄,算是將時間戳和序列號作為一個大值進行自增,只是初始化不同卖局。

序列號自增:

每次有數(shù)據(jù)請求斧蜕,直接對序列號增加即可,序列號從0增加到最大砚偶,到達最大時批销,時間戳字段增加1,其實是時間增加1毫秒染坯,序列號重0計算均芽。

機器id分配和回收:

對機器的分配和回收這里有三種默認方式,不過也支持用戶自定義實現(xiàn)

(單機版)zookeeper分配和回收:

分配采用哈希方式在預(yù)分配的一些空置永久節(jié)點中進行分配单鹿,節(jié)點后綴是有編號的掀宋,查找其中節(jié)點沒有被占用,或者被占用但是占用超時的節(jié)點進行分配仲锄,其中分配的編號就是WorkerId劲妙。分配完畢,定期更新節(jié)點中的超時時間儒喊,超時后下次節(jié)點分配時會判斷超時镣奋。這里初始節(jié)點默認設(shè)置為16個節(jié)點,如果節(jié)點都被占用(占用或者沒占用但是超時時間沒過)怀愧,則模仿HashMap方式進行2倍擴容侨颈,然后重新分配。

(單機版)db分配和回收:

先看過期的里面最小的id芯义,找到了則當(dāng)前workerId就是機器id哈垢。如果過期中沒找到,則查看其中最大的workerId并增1扛拨,然后新增耘分,當(dāng)前增1后的workerId就是分配的workerId。

(分布式版)集群分配workerId:

客戶端的wokerId是每次Buffer請求中攜帶過來的,這樣對客戶端而言就沒有workerId上限問題陶贼,因為workerId是服務(wù)端節(jié)點分配的啤贩。由于采用了網(wǎng)絡(luò)傳輸待秃,為了提高性能拜秧,客戶端這里采用雙Buffer+異步刷新方式,server端這里采用改進版的雪花+zookeeper分配和回收wokerId方式章郁。

問題解決方式

1.時間回撥問題:

采用歷史時間則天然的不存在時間回撥問題枉氮。但是在超高并發(fā)情況下,歷史的時間很快用完暖庄,時間一直保持在最新時間的話聊替,這個時候出現(xiàn)時間回撥,則采用業(yè)界對于時間回撥的處理方式(首次等待培廓,即等待一段回撥時間)

2.機器id分配及回收:

機器的id分配和回收惹悄,我們這里采用zookeeper和db兩種方式分配,這兩種方式肩钠,均只有在進程啟動的時候生效泣港,后續(xù)就不再跟客戶端有更多交互,唯一的是有個定時上報過期時間的任務(wù)价匠。該過期時間為24小時当纱,因此zookeeper或者db宕機一天,該發(fā)號器都不會有任何問題踩窖∑侣龋回收這里采用的是上報的過期時間,過期了洋腮,則下次分配可以直接使用箫柳。

3.機器id上限

其中單機版的zookeeper和 DB均不是解決這個問題而存在的,其中(分布式版)distribute分配workerId是采用服務(wù)端方式啥供,用服務(wù)端方式啟動作為workerId的分配者悯恍,客戶端使用的時候每次Buffer請求中服務(wù)端會將那一次的workerId和時間戳返回過來。這樣雖然服務(wù)集群的workerId上限(即服務(wù)集群節(jié)點個數(shù)上限)是有的滤灯,但是對于客戶端擁有的集群而言坪稽,理論上無上限,因為一個服務(wù)端節(jié)點就可以服務(wù)一個業(yè)務(wù)集群中的許多節(jié)點鳞骤。

超高性能

由于時間戳采用的是過去時間窒百,我們這樣來看,如果實際QPS小于理論值(我們這里是9bit豫尽,理論值就是51.2w/s)篙梢,那么一段時間后,產(chǎn)生的最新的全局id中包含的時間跟當(dāng)前實際時間就有一定的時間差美旧,那么這個時間差我們可以稱之為“時間緩存”渤滞,而每一毫秒對應(yīng)的都是0~最大值的這么多個數(shù)據(jù)贬墩,隨著時間的積累,這里可以有海量的“邏輯上”的數(shù)據(jù)緩存妄呕。我們想象這樣一個場景陶舞,如果通常情況下業(yè)務(wù)的場景QPS是小于51.2w/s的,那么這個緩存就會越來越大绪励,那么如果一瞬間有大量的請求過來的時候肿孵,由于我們有大量的緩存,我們這里就可以產(chǎn)生更多不重復(fù)的id疏魏,將QPS提高到幾十倍甚至更多停做,自己的小本測試中可以達到1200w/s。如果QPS一直是高位的話比51.2w/s高的話大莫,那么這種其實業(yè)務(wù)方面就可以通過業(yè)務(wù)集群化擴容蛉腌,將單個業(yè)務(wù)節(jié)點性能降低,不過就發(fā)號器技術(shù)上來說的話只厘,目前單機版的這個在這種持續(xù)高并發(fā)情況下烙丛,經(jīng)過測試理論上會保持在53w/s左右,更高則暫時無法支持懈凹。

workerId分配

分配workerId我們這里有三種默認方式蜀变,用戶也可以自定義自己的默認實現(xiàn)方式

1.(單機版)zookeeper分配和回收
2.(單機版)db分配和回收
3.(分布式版)通過服務(wù)端分配和回收

其中單機版都是直接提供jar包就可以直接使用,只是根據(jù)采用的方式不同介评,進行不同的配置即可

zookeeper分配

哈希分配

采用zookeeper進行分配workerId的時候库北,首先生成一個uuid,然后根據(jù)這個uuid進行對當(dāng)前的命名空間的當(dāng)前已分配的空間最大值進行取余们陆,得到一個臨時的下標寒瓦,然后查看以這個下標作為的zookeeper節(jié)點,查看該節(jié)點是否被占用坪仇,也就是該節(jié)點中的過期時間是否過期杂腰,如果過期,則該下標就是對應(yīng)的workerId椅文。如果沒有過期喂很,則該下標+1,并重新判斷皆刺,如果到達當(dāng)前分配空間的最大值少辣,則從0開始繼續(xù)查找可用節(jié)點,如果最后找到最開始的節(jié)點羡蛾,則說明當(dāng)前空間中已經(jīng)沒有可用節(jié)點漓帅,則進入到擴容模塊。這里的擴容其實很簡單,其實就是在初始節(jié)點個數(shù)(初始節(jié)點個數(shù)為16)上乘2忙干,然后重新哈希器予。
其中數(shù)據(jù)在zookeeper中的節(jié)點分配如下

/butterfly/sequence
|
|_bizType_1
|    |
|    |_config
|    |
|    |_worker_0
|    |     |
|    |     |_session
|    |      
|    |_worker_1
|    |     |
|    |     |_session
|    |
|    ...
|
|_bizType_2
|    |
|    |_config
|    |
|    |_worker_0
|    |     |
|    |     |_session
|    |      
|    |_worker_1
|    |     |
|    |     |_session
|    ...

節(jié)點解釋:

butterfly/sequence:zookeeper中的固定節(jié)點bizType_1/bizType_2:為對應(yīng)的命名空間,實際命名空間為用戶自定義config:為每個命名空間中放置配置的節(jié)點捐迫,其中存儲的信息如下乾翔,一個是bit分配值,一個是命名空間中節(jié)點對應(yīng)的值弓乙,bit分配的值末融,是為了以后擴展使用,這里先放置在這里

{
    "currentMaxMachine":16,
    "sequenceBits":9,
    "timestampBits":41,
    "workerBits":13
}

worker_x:對應(yīng)的workerId對應(yīng)的節(jié)點暇韧,其中有個session節(jié)點,該節(jié)點為臨時節(jié)點浓瞪,如果不存在session節(jié)點,則表示當(dāng)前節(jié)點沒被占用,則不判斷過期時間越走,直接在內(nèi)部創(chuàng)建session節(jié)點蛉迹,并將下標x作為workerId返回。如果被占用英岭,采用的是查看worker_x中存儲的下次過期時間湾盒,如果當(dāng)前過期時間小于當(dāng)前時間,則認為已經(jīng)過期诅妹,即創(chuàng)建session節(jié)點罚勾,并更新worker_x中的信息,并將x作為workerId使用吭狡。

{
    "ip":"127.0.0.1",
    "lastExpireTime":1588955705615,
    "processId":"90705",
    "uidKey":"762bcaad-48f5-4da9-8a1d-5e05a28add18"
}

定時上報

沒起對應(yīng)的進程啟動尖殃,并分配好workerId之后,就會啟動每5s向該worker_x節(jié)點上報一次下次過期的時間划煮,目前采用的下次過期時間為24小時送丰,也就是說一個進程一旦占用一個節(jié)點,一般情況下都是異常斷開后最多占用24小時弛秋。但是正常斷開的話器躏,會自動將這些信息清理掉。

db分配

查找數(shù)據(jù)

采用db方式的話蟹略,需要先保證已經(jīng)創(chuàng)建了butterfly_uuid_generator登失,如果沒有則先在對應(yīng)的庫中創(chuàng)建

CREATE TABLE `butterfly_uuid_generator` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `namespace` varchar(128) DEFAULT '' COMMENT '命名空間',
  `work_id` int(16) COMMENT '工作id',
  `last_expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下次失效時間',
  `uid` varchar(128) DEFAULT '0' COMMENT '本次啟動唯一id',
  `ip` bigint(20) NOT NULL DEFAULT '0' COMMENT 'ip',
  `process_id` varchar(128) NOT NULL DEFAULT '0' COMMENT '進程id',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_name_work` (`namespace`,`work_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='發(fā)號器表';

查找數(shù)據(jù),這里首先是在對應(yīng)的命名空間中科乎,查找過期的數(shù)據(jù)壁畸,如果能找到其中id最小的,則該id對應(yīng)中的workerId就是我們要分配的workerId,如果都沒有過期捏萍,這塊有問題太抓,明天繼續(xù)看,那么就找最小的workerId令杈,并將workerId+1走敌,重新插入到數(shù)據(jù)庫中(之前查詢其實是采用事務(wù)中添加悲觀鎖方式,保證這里不會有多個重復(fù)的插入)逗噩,其中對應(yīng)的workerId+1就是我們要分配的workerId掉丽。

定時更新(心跳)

workerId分配之后,每次都要定時(每5秒)刷新當(dāng)前命名空間中對應(yīng)workerId中對應(yīng)的下次過期時間(就是當(dāng)前時間往后推24小時)异雁。如果對應(yīng)的進程異常宕機了捶障,則該數(shù)據(jù)對應(yīng)的這個數(shù)據(jù)會存在,只是過期時間不會再更新纲刀,而如果改進程是正常宕機项炼,則會正常將db中的對應(yīng)的那條數(shù)據(jù)刪除掉。方便下次分配者繼續(xù)分配workerId示绊。

我建議锭部,在表中增加一個status字段,在數(shù)據(jù)正常宕機之后面褐,就是將當(dāng)前的這條數(shù)據(jù)狀態(tài)設(shè)置為不可用拌禾,在下次數(shù)據(jù)請求的時候,首先查找狀態(tài)為不可用的數(shù)據(jù)展哭,如果找到湃窍,則分配
1.找狀態(tài)為關(guān)閉的2.找已經(jīng)過期的3.找workerId最大的,并將workerId+1插入到DB中
這樣就可以得到最新的workerId了

分布式模式

客戶端

一摄杂、代理獲取數(shù)據(jù)二坝咐、雙Buffer+異步獲取數(shù)據(jù)

服務(wù)端

采用改進版雪花+zookeeper模式

一、使用示例

目前框架理論完成析恢,還有待驗證和考驗墨坚,暫時還未發(fā)布到中央倉庫,有意向同學(xué)可以先基于源碼自行編譯在內(nèi)部使用
該框架中“機器id(workerId)”的分配方式默認有三種方式

1.(單機版)zookeeper分配
2.(單機版)db分配
3.(分布式版)通過服務(wù)端分配

不同的分配方式就是不同的用法映挂,依賴也是不同泽篮。其中單機版和分布式版也要根據(jù)場景來自行選擇:

  • 單機版:無網(wǎng)絡(luò)消耗,高可靠性柑船,超高性能(單機可達1200w/s)帽撑,有workerId上限問題
  • 分布式版:有網(wǎng)絡(luò)消耗,高可用鞍时,高性能(集群單節(jié)點可達100w/s)亏拉,無雪花算法任何問題扣蜻,但是需要維護服務(wù)端

zookeeper分配workerId

1.添加依賴

<dependency>
  <groupId>com.github.simonalong</groupId>
  <artifactId>butterfly-zookeeper-allocator</artifactId>
  <!--替換為具體版本號-->
  <version>${last.version.release}</version>
</dependency>

2.使用示例

@Test
public void test(){
    ZkButterflyConfig config = new ZkButterflyConfig();
    config.setHost("localhost:2181");

    ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config);
    // 設(shè)置起始時間,如果不設(shè)置及塘,則默認從2020年2月22日開始
    generator.setStartTime(2020, 5, 1, 0, 0, 0);
    // 添加業(yè)務(wù)空間莽使,如果業(yè)務(wù)空間不存在,則會注冊
    generator.addNamespaces("test1", "test2");
    Long uuid = generator.getUUid("test1");
    System.out.println(uuid);
}

db分配workerId

1.添加依賴

<dependency>
  <groupId>com.github.simonalong</groupId>
  <artifactId>butterfly-db-allocator</artifactId>
  <!--替換為具體版本號-->
  <version>${last.version.release}</version>
</dependency>

2.建表

對應(yīng)的庫中需要包含如下的表笙僚,沒有則創(chuàng)建

CREATE TABLE `butterfly_uuid_generator` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `namespace` varchar(128) DEFAULT '' COMMENT '命名空間',
  `work_id` int(16) COMMENT '工作id',
  `last_expire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '下次失效時間',
  `uid` varchar(128) DEFAULT '0' COMMENT '本次啟動唯一id',
  `ip` bigint(20) NOT NULL DEFAULT '0' COMMENT 'ip',
  `process_id` varchar(128) NOT NULL DEFAULT '0' COMMENT '進程id',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_name_work` (`namespace`,`work_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='發(fā)號器表';

3.使用示例

@Test
public void test(){
    DbButterflyConfig config = new DbButterflyConfig();
    config.setUrl("jdbc:mysql://127.0.0.1:3306/neo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&&allowPublicKeyRetrieval=true");
    config.setUserName("neo_test");
    config.setPassword("neo@Test123");

    ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config);
    // 設(shè)置起始時間芳肌,如果不設(shè)置,則默認從2020年2月22日開始
    generator.setStartTime(2020, 5, 1, 0, 0, 0);
    // 添加業(yè)務(wù)空間肋层,如果業(yè)務(wù)空間不存在亿笤,則會注冊
    generator.addNamespaces("test1", "test2");
    Long uuid = generator.getUUid("test1");
    System.out.println(uuid);
}

服務(wù)端分配workerId

1.服務(wù)端啟動

服務(wù)端和客戶端采用dubbo方式進行通訊,下載server包栋猖,將如下命令中的dubbo注冊中心替換即可啟動净薛。

java -jar -Ddubbo.registry.address=127.0.0.1:2181 butterfly-server-1.0.0.jar

2.客戶端添加依賴

客戶端添加依賴

<dependency>
  <groupId>com.github.simonalong</groupId>
  <artifactId>butterfly-distribute-allocator</artifactId>
  <!--替換為具體版本號-->
  <version>${last.version.release}</version>
</dependency>

3.使用示例

@Test
public void test(){
    DistributeButterflyConfig config = new DistributeButterflyConfig();
    config.setZkHose("localhost:2181");

    ButterflyIdGenerator generator = ButterflyIdGenerator.getInstance(config);
    // 設(shè)置起始時間,如果不設(shè)置掂铐,則默認從2020年2月22日開始
    generator.setStartTime(2020, 5, 1, 0, 0, 0);
    // 添加業(yè)務(wù)空間罕拂,如果業(yè)務(wù)空間不存在,則會注冊
    generator.addNamespaces("test1", "test2");
    Long uuid = generator.getUUid("test1");
    System.out.println(uuid);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末全陨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子衷掷,更是在濱河造成了極大的恐慌辱姨,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戚嗅,死亡現(xiàn)場離奇詭異雨涛,居然都是意外死亡,警方通過查閱死者的電腦和手機懦胞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門替久,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躏尉,你說我怎么就攤上這事蚯根。” “怎么了胀糜?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵颅拦,是天一觀的道長。 經(jīng)常有香客問我教藻,道長距帅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任括堤,我火速辦了婚禮碌秸,結(jié)果婚禮上绍移,老公的妹妹穿的比我還像新娘。我一直安慰自己讥电,他們只是感情好蹂窖,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著允趟,像睡著了一般恼策。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上潮剪,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天涣楷,我揣著相機與錄音,去河邊找鬼抗碰。 笑死狮斗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弧蝇。 我是一名探鬼主播碳褒,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼看疗!你這毒婦竟也來了沙峻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤两芳,失蹤者是張志新(化名)和其女友劉穎摔寨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怖辆,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡是复,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了竖螃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淑廊。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖特咆,靈堂內(nèi)的尸體忽然破棺而出季惩,到底是詐尸還是另有隱情,我是刑警寧澤坚弱,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布蜀备,位于F島的核電站,受9級特大地震影響荒叶,放射性物質(zhì)發(fā)生泄漏碾阁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一些楣、第九天 我趴在偏房一處隱蔽的房頂上張望脂凶。 院中可真熱鬧宪睹,春花似錦、人聲如沸蚕钦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘶居。三九已至罪帖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邮屁,已是汗流浹背整袁。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留佑吝,地道東北人坐昙。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像芋忿,于是被迫代替她去往敵國和親炸客。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345