分布式系統(tǒng)中接口的冪等性

轉(zhuǎn)載:https://testerhome.com/articles/29399
欲野發(fā)表于 TesterHome 」
原文:

因果

最近在做公司接口測(cè)試的時(shí)候符喝,發(fā)現(xiàn)公司接口在應(yīng)對(duì)重復(fù)請(qǐng)求的懊渡,處理不是很好轴猎,當(dāng)時(shí)問(wèn)研發(fā)小哥腮郊,研發(fā)小哥說(shuō)對(duì)重復(fù)提交請(qǐng)求的深啤,當(dāng)前項(xiàng)目沒(méi)有做接口的冪等性的校驗(yàn)巍沙,說(shuō)實(shí)話神凑,當(dāng)時(shí)感覺(jué)一臉懵逼谈山,就在思考什么是冪等性,自己一直在做接口測(cè)試忆某,都不知道有這個(gè)東西点待,于是就自己換位思考,決定站在研發(fā)的角度弃舒,去學(xué)習(xí)以下如何實(shí)現(xiàn)冪等性的實(shí)現(xiàn)癞埠,畢竟知道如何實(shí)現(xiàn)的,在接下來(lái)的業(yè)務(wù)場(chǎng)景中聋呢,才能更好的測(cè)試苗踪。

業(yè)務(wù)場(chǎng)景

公司有個(gè)借貸的項(xiàng)目,具體業(yè)務(wù)類似于阿里的螞蟻借唄削锰,用戶在平臺(tái)上借款徒探,然后規(guī)定一個(gè)到期時(shí)間,在該時(shí)間內(nèi)用戶需將借款還清并收取一定的手續(xù)費(fèi)喂窟,如果規(guī)定時(shí)間逾期未還上,則會(huì)產(chǎn)生滯納金央串。
用戶發(fā)起借款因此會(huì)產(chǎn)生一筆借款訂單磨澡,用戶可通過(guò)支付寶或在系統(tǒng)中綁定銀行卡到期自動(dòng)扣款等方式進(jìn)行還款。還款流程都走支付系統(tǒng)质和,因此用戶還款是否逾期以及逾期天數(shù)稳摄、逾期費(fèi)等都通過(guò)系統(tǒng)來(lái)計(jì)算。
但是在做訂單系統(tǒng)的時(shí)候饲宿,遇到這樣一個(gè)業(yè)務(wù)場(chǎng)景厦酬,由于業(yè)務(wù)原因允許用戶通過(guò)線下支付寶還款,即我們提供一個(gè)公司官方的支付寶二維碼瘫想,用戶掃碼還款仗阅,然后財(cái)務(wù)不定期的去拉取該支付寶賬戶下的還款清單并生成規(guī)范化的 Excel 表格錄入到支付系統(tǒng)。
支付系統(tǒng)將這些支付信息生成對(duì)應(yīng)的支付訂單并落庫(kù)国夜,同時(shí)針對(duì)每筆還款記錄生產(chǎn)一個(gè)消息信息到消息系統(tǒng)减噪,消息的消費(fèi)者就是訂單系統(tǒng)。訂單系統(tǒng)接受到消息后去結(jié)算當(dāng)前用戶的金額清算:先還本金车吹,本金還清再還滯納金筹裕,都還清則該筆訂單結(jié)清并提升可借貸額度,……窄驹,整個(gè)流程大致如下:

  • 從上面的流程描述可以知道朝卒,相當(dāng)于原來(lái)線上的支付現(xiàn)在轉(zhuǎn)移到線下進(jìn)行,這會(huì)產(chǎn)生一個(gè)問(wèn)題:支付結(jié)算的不及時(shí)乐埠。例如用戶的訂單在今天 19-05-27 到期抗斤,但是用戶在 19-05-26 還清囚企,財(cái)務(wù)在 19-05-27 甚至更晚的時(shí)候從支付寶拉取清單錄入支付系統(tǒng)。這樣就造成了實(shí)際上用戶是未逾期還清借款而我們這邊卻記錄的是用戶未還清且產(chǎn)生了滯納金豪治。
  • 當(dāng)然以上的是業(yè)務(wù)范疇的問(wèn)題洞拨,我們今天要說(shuō)的是支付系統(tǒng)發(fā)送消息到訂單系統(tǒng)的環(huán)節(jié)中的一個(gè)問(wèn)題。大家都知道為了避免消息丟失或者訂單系統(tǒng)處理異掣耗猓或者網(wǎng)絡(luò)問(wèn)題等問(wèn)題烦衣,我們?cè)O(shè)計(jì)消息系統(tǒng)的時(shí)候都需要考慮消息持久化和消息的失敗重試機(jī)制。
  • 對(duì)于重試機(jī)制掩浙,假如訂單系統(tǒng)消費(fèi)了消息花吟,但是由于網(wǎng)絡(luò)等問(wèn)題消息系統(tǒng)未收到反饋是否已成功處理。這時(shí)消息系統(tǒng)會(huì)根據(jù)配置的規(guī)則隔段時(shí)間就 retry 一次厨姚。你 retry 一次沒(méi)錯(cuò)衅澈,是為了保證系統(tǒng)的處理正常性,但是如果這時(shí)網(wǎng)絡(luò)恢復(fù)正常谬墙,我第一次收到的消息成功處理了今布,這時(shí)我又收到了一條消息,如果沒(méi)有做一些防護(hù)措施拭抬,會(huì)產(chǎn)生如下情況:用戶付款一次但是訂單系統(tǒng)計(jì)算了兩次部默,這樣會(huì)造成財(cái)務(wù)賬單異常對(duì)不上賬的情況發(fā)生。那就可能用戶笑呵呵老板哭兮兮了造虎。

接口冪等性

為了防止上述情況的發(fā)生傅蹂,我們需要提供一個(gè)防護(hù)措施,對(duì)于同一筆支付信息如果我其中某一次處理成功了算凿,我雖然又接收到了消息份蝴,但是這時(shí)我不處理了,即保證接口的 冪等性氓轰。

維基百科上的定義:

冪等(idempotent婚夫、idempotence)是一個(gè)數(shù)學(xué)與計(jì)算機(jī)學(xué)概念,常見(jiàn)于抽象代數(shù)中署鸡。
在編程中一個(gè)冪等操作的特點(diǎn)是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同请敦。冪等函數(shù),或冪等方法储玫,是指可以使用相同參數(shù)重復(fù)執(zhí)行侍筛,并能獲得相同結(jié)果的函數(shù)。這些函數(shù)不會(huì)影響系統(tǒng)狀態(tài)撒穷,也不用擔(dān)心重復(fù)執(zhí)行會(huì)對(duì)系統(tǒng)造成改變匣椰。例如,“setTrue()” 函數(shù)就是一個(gè)冪等函數(shù),無(wú)論多次執(zhí)行端礼,其結(jié)果都是一樣的禽笑,更復(fù)雜的操作冪等保證是利用唯一交易號(hào) (流水號(hào)) 實(shí)現(xiàn).

任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同入录,這是冪等性的核心特點(diǎn)。其實(shí)在我們編程中主要操作就是 CURD佳镜,其中讀攘鸥濉(Retrieve)操作和刪除(Delete)操作是天然冪等的,受影響的就是創(chuàng)建(Create)蟀伸、更新(Update)蚀同。
對(duì)于業(yè)務(wù)中需要考慮冪等性的地方一般都是接口的重復(fù)請(qǐng)求,重復(fù)請(qǐng)求是指同一個(gè)請(qǐng)求因?yàn)槟承┰虮欢啻翁峤话√汀?dǎo)致這個(gè)情況會(huì)有幾種場(chǎng)景:

  • 前端重復(fù)提交:提交訂單蠢络,用戶快速重復(fù)點(diǎn)擊多次,造成后端生成多個(gè)內(nèi)容重復(fù)的訂單迟蜜。
  • 接口超時(shí)重試:對(duì)于給第三方調(diào)用的接口刹孔,為了防止網(wǎng)絡(luò)抖動(dòng)或其他原因造成請(qǐng)求丟失,這樣的接口一般都會(huì)設(shè)計(jì)成超時(shí)重試多次娜睛。
  • 消息重復(fù)消費(fèi):MQ 消息中間件髓霞,消息重復(fù)消費(fèi)。 對(duì)于一些業(yè)務(wù)場(chǎng)景影響比較大的畦戒,接口的冪等性是個(gè)必須要考慮的問(wèn)題方库,例如金錢的交易方面的接口。否則一個(gè)錯(cuò)誤的兢交、考慮不周的接口可能會(huì)給公司帶來(lái)巨額的金錢損失,那么背鍋的肯定是程序員自己了笼痹。

冪等性實(shí)現(xiàn)方式

對(duì)于和 web 端交互的接口配喳,我們可以在前端攔截一部分,例如防止表單重復(fù)提交凳干,按鈕置灰晴裹、隱藏、不可點(diǎn)擊等方式救赐。
但是前端做控制實(shí)際效益不是很高涧团,懂點(diǎn)技術(shù)的都會(huì)模擬請(qǐng)求調(diào)用你的服務(wù),所以安全的策略還是需要從后端的接口層來(lái)做经磅。
那么后端要實(shí)現(xiàn)分布式接口的冪等性有哪些策略方式呢泌绣?主要可以從以下幾個(gè)方面來(lái)考慮實(shí)現(xiàn):

Token 機(jī)制

針對(duì)前端重復(fù)連續(xù)多次點(diǎn)擊的情況,例如用戶購(gòu)物提交訂單预厌,提交訂單的接口就可以通過(guò) Token 的機(jī)制實(shí)現(xiàn)防止重復(fù)提交阿迈。

主要流程就是:
服務(wù)端提供了發(fā)送 token 的接口。我們?cè)诜治鰳I(yè)務(wù)的時(shí)候轧叽,哪些業(yè)務(wù)是存在冪等問(wèn)題的苗沧,就必須在執(zhí)行業(yè)務(wù)前刊棕,先去獲取 token,服務(wù)器會(huì)把 token 保存到 redis 中待逞。(微服務(wù)肯定是分布式了甥角,如果單機(jī)就適用 jvm 緩存)。
然后調(diào)用業(yè)務(wù)接口請(qǐng)求時(shí)识樱,把 token 攜帶過(guò)去嗤无,一般放在請(qǐng)求頭部。
服務(wù)器判斷 token 是否存在 redis 中牺荠,存在表示第一次請(qǐng)求翁巍,這時(shí)把 redis 中的 token 刪除,繼續(xù)執(zhí)行業(yè)務(wù)休雌。
如果判斷 token 不存在 redis 中灶壶,就表示是重復(fù)操作,直接返回重復(fù)標(biāo)記給 client杈曲,這樣就保證了業(yè)務(wù)代碼驰凛,不被重復(fù)執(zhí)行。

數(shù)據(jù)庫(kù)去重表

往去重表里插入數(shù)據(jù)的時(shí)候担扑,利用數(shù)據(jù)庫(kù)的唯一索引特性恰响,保證唯一的邏輯。唯一序列號(hào)可以是一個(gè)字段涌献,例如訂單的訂單號(hào)胚宦,也可以是多字段的唯一性組合。例如設(shè)計(jì)如下的數(shù)據(jù)庫(kù)表燕垃。

CREATE TABLE `t_idempotent` (
  `id` int(11) NOT NULL COMMENT 'ID',
  `serial_no` varchar(255)  NOT NULL COMMENT '唯一序列號(hào)',
  `source_type` varchar(255)  NOT NULL COMMENT '資源類型',
  `status` int(4) DEFAULT NULL COMMENT '狀態(tài)',
  `remark` varchar(255)  NOT NULL COMMENT '備注',
  `create_by` bigint(20) DEFAULT NULL COMMENT '創(chuàng)建人',
  `create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
  `modify_by` bigint(20) DEFAULT NULL COMMENT '修改人',
  `modify_time` datetime DEFAULT NULL COMMENT '修改時(shí)間',
  PRIMARY KEY (`id`)
  UNIQUE KEY `key_s` (`serial_no`,`source_type`, `remark`)  COMMENT '保證業(yè)務(wù)唯一性'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='冪等性校驗(yàn)表';

我們注意看如下這幾個(gè)關(guān)鍵性字段枢劝,

serial_no:唯一序列號(hào)的值治专,在這里我設(shè)置的是通過(guò)注解@IdempotentKey來(lái)標(biāo)識(shí)請(qǐng)求對(duì)象中的字段怖辆,通過(guò)對(duì)他們 MD5 加密獲取對(duì)應(yīng)的值剃氧。
source_type:業(yè)務(wù)類型脂崔,區(qū)分不同的業(yè)務(wù)餐曹,訂單憋他,支付等国章。
remark:是由標(biāo)識(shí)字段的拼接成的字符串褐隆,拼接符為 “|”侦副。
由于數(shù)據(jù)建立了 serial_no,source_type, remark 三個(gè)字段組合構(gòu)成的唯一索引侦锯,所以可以通過(guò)這個(gè)來(lái)去重達(dá)到接口的冪等性,具體的代碼設(shè)計(jì)如下秦驯,

public class PaymentOrderReq {

    /** * 支付寶流水號(hào) */
    @IdempotentKey(order=1)
    private String alipayNo;

    /** * 支付訂單ID */
    @IdempotentKey(order=2)
    private String paymentOrderNo;

    /** * 支付金額 */
    private Long amount;
}

因?yàn)橹Ц秾毩魉?hào)和訂單號(hào)在系統(tǒng)中是唯一的率触,所以唯一序列號(hào)可由他們組合 MD5 生成,具體的生成方式如下:

private void getIdempotentKeys(Object keySource, Idempotent idempotent) {
    TreeMap<Integer, Object> keyMap = new TreeMap<Integer, Object>();
    for (Field field : keySource.getClass().getDeclaredFields()) {
        if (field.isAnnotationPresent(IdempotentKey.class)) {
            try {
                field.setAccessible(true);
                keyMap.put(field.getAnnotation(IdempotentKey.class).order(),
                        field.get(keySource));
            } catch (IllegalArgumentException | IllegalAccessException e) {
                logger.error("", e);
                return;
            }
        }
    }
    generateIdempotentKey(idempotent, keyMap.values().toArray());
}

生成冪等 Key,如果有多個(gè) key 可以通過(guò)分隔符 "|" 連接葱蝗,

private void generateIdempotentKey(Idempotent idempotent, Object... keyObj) {
     if (keyObj.length == 0) {
         logger.info("idempotentkey is empty,{}", keyObj);
         return;
     }
     StringBuilder serialNo= new StringBuilder();
     for (Object key : keyObj) {
         serialNo.append(key.toString()).append("|");
     }
     idempotent.setRemark(serialNo.toString());
     idempotent.setSerialNo(md5(serialNo));
 }

一切準(zhǔn)備就緒穴张,則可對(duì)外提供冪等性校驗(yàn)的接口方法,接口方法為:

public <T> void idempotentCheck(IdempotentTypeEnum idempotentType, T keyObj) throws IdempotentException {
    Idempotent idempotent = new Idempotent();
    getIdempotentKeys(keyObj, idempotent );
    if (StringUtils.isBlank(idempotent.getSerialNo())) {
        throw new ServiceException("fail to get idempotentkey");
    }
    idempotentEvent.setSourceType(idempotentType.name());
    try {
        idempotentMapper.saveIdempotent(idempotent);
    } catch (DuplicateKeyException e) {
        logger.error("idempotent check fail", e);
        throw new IdempotentException(idempotent);
    }
}

當(dāng)然這個(gè)接口的方法具體在項(xiàng)目中合理的使用就看項(xiàng)目要求了两曼,可以通過(guò)@Autowired注解注入到需要使用的地方皂甘,但是缺點(diǎn)就是每個(gè)地方都需要調(diào)用。我個(gè)人推薦的是自定義一個(gè)注解悼凑,在需要冪等性保證的接口上加上該注解偿枕,然后通過(guò)攔截器方法攔截使用。這樣簡(jiǎn)單便不會(huì)造成代碼侵入和污染户辫。

另外渐夸,使用數(shù)據(jù)庫(kù)防重表的方式它有個(gè)嚴(yán)重的缺點(diǎn),那就是系統(tǒng)容錯(cuò)性不高渔欢,如果冪等表所在的數(shù)據(jù)庫(kù)連接異衬顾或所在的服務(wù)器異常,則會(huì)導(dǎo)致整個(gè)系統(tǒng)冪等性校驗(yàn)出問(wèn)題奥额。如果做數(shù)據(jù)庫(kù)備份來(lái)防止這種情況苫幢,又需要額外忙碌一通了啊。

Redis 實(shí)現(xiàn)

上面介紹過(guò)防重表的設(shè)計(jì)方式和偽代碼垫挨,也說(shuō)過(guò)它的一個(gè)很明顯的缺點(diǎn)韩肝。所以我另外介紹一個(gè) Redis 的實(shí)現(xiàn)方式。

Redis 實(shí)現(xiàn)的方式就是將唯一序列號(hào)作為 Key九榔,唯一序列號(hào)的生成方式和上面介紹的防重表的一樣哀峻,value 可以是你想填的任何信息。唯一序列號(hào)也可以是一個(gè)字段哲泊,例如訂單的訂單號(hào)剩蟀,也可以是多字段的唯一性組合。當(dāng)然這里需要設(shè)置一個(gè) key 的過(guò)期時(shí)間攻旦,否則 Redis 中會(huì)存在過(guò)多的 key喻旷。具體校驗(yàn)流程如下圖所示生逸,實(shí)現(xiàn)代碼也很簡(jiǎn)單這里就不寫(xiě)了牢屋。

由于企業(yè)如果考慮在項(xiàng)目中使用 Redis,因?yàn)榇蟛糠謺?huì)拿它作為緩存來(lái)使用槽袄,那么一般都會(huì)是集群的方式出現(xiàn)烙无,至少肯定也會(huì)部署兩臺(tái) Redis 服務(wù)器。所以我們使用 Redis 來(lái)實(shí)現(xiàn)接口的冪等性是最適合不過(guò)的了遍尺。

狀態(tài)機(jī)

對(duì)于很多業(yè)務(wù)是有一個(gè)業(yè)務(wù)流轉(zhuǎn)狀態(tài)的截酷,每個(gè)狀態(tài)都有前置狀態(tài)和后置狀態(tài),以及最后的結(jié)束狀態(tài)乾戏。例如流程的待審批迂苛,審批中三热,駁回,重新發(fā)起三幻,審批通過(guò)就漾,審批拒絕。訂單的待提交念搬,待支付抑堡,已支付,取消朗徊。

以訂單為例首妖,已支付的狀態(tài)的前置狀態(tài)只能是待支付,而取消狀態(tài)的前置狀態(tài)只能是待支付爷恳,通過(guò)這種狀態(tài)機(jī)的流轉(zhuǎn)我們就可以控制請(qǐng)求的冪等有缆。

public enum OrderStatusEnum {

    UN_SUBMIT(0, 0, "待提交"),
    UN_PADING(0, 1, "待支付"),
    PAYED(1, 2, "已支付待發(fā)貨"),
    DELIVERING(2, 3, "已發(fā)貨"),
    COMPLETE(3, 4, "已完成"),
    CANCEL(0, 5, "已取消"),
    ;

    //前置狀態(tài)
    private int preStatus;

    //狀態(tài)值
    private int status;

    //狀態(tài)描述
    private String desc;

    OrderStatusEnum(int preStatus, int status, String desc) {
        this.preStatus = preStatus;
        this.status = status;
        this.desc = desc;
    }

    //...
}

假設(shè)當(dāng)前狀態(tài)是已支付,這時(shí)候如果支付接口又接收到了支付請(qǐng)求舌仍,則會(huì)拋異扯拭玻或拒絕此次處理。

總結(jié)

通過(guò)以上的了解我們可以知道铸豁,針對(duì)不同的業(yè)務(wù)場(chǎng)景我們需要靈活的選擇冪等性的實(shí)現(xiàn)方式灌曙。
例如防止類似于前端重復(fù)提交、重復(fù)下單的場(chǎng)景就可以通過(guò) Token 的機(jī)制實(shí)現(xiàn)节芥,而那些有狀態(tài)前置和后置轉(zhuǎn)換的場(chǎng)景則可以通過(guò)狀態(tài)機(jī)的方式實(shí)現(xiàn)冪等性在刺,對(duì)于那些重復(fù)消費(fèi)和接口重試的場(chǎng)景則使用數(shù)據(jù)庫(kù)唯一索引的方式實(shí)現(xiàn)更合理。

我個(gè)人讀后感:測(cè)試做久了头镊,文中提到訂單蚣驼,支付業(yè)務(wù)中涉及到的 重復(fù)請(qǐng)求,消息重復(fù)消費(fèi)相艇,重試機(jī)制颖杏,唯一性機(jī)制,這些功能點(diǎn)都知道需要去處理坛芽,但是重來(lái)沒(méi)有發(fā)現(xiàn)這些都屬于一種類型的問(wèn)題留储。知道冪等性后,才知道這些可以歸類一個(gè)問(wèn)題去處理咙轩,并且給出了 Token 機(jī)制获讳,數(shù)據(jù)庫(kù)去重表,Redis 實(shí)現(xiàn)活喊,狀態(tài)機(jī) 等方式具體落實(shí)丐膝。很棒

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子帅矗,更是在濱河造成了極大的恐慌偎肃,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浑此,死亡現(xiàn)場(chǎng)離奇詭異软棺,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)尤勋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)喘落,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人最冰,你說(shuō)我怎么就攤上這事瘦棋。” “怎么了暖哨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵赌朋,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我篇裁,道長(zhǎng)沛慢,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任达布,我火速辦了婚禮团甲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黍聂。我一直安慰自己躺苦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布产还。 她就那樣靜靜地躺著匹厘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脐区。 梳的紋絲不亂的頭發(fā)上愈诚,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音牛隅,去河邊找鬼炕柔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛倔叼,可吹牛的內(nèi)容都是我干的汗唱。 我是一名探鬼主播宫莱,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丈攒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起巡验,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤际插,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后显设,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體框弛,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年捕捂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑟枫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡指攒,死狀恐怖慷妙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情允悦,我是刑警寧澤膝擂,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站隙弛,受9級(jí)特大地震影響架馋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜全闷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一叉寂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧总珠,春花似錦办绝、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至腌逢,卻和暖如春降淮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搏讶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工佳鳖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人媒惕。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓系吩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親妒蔚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子穿挨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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