MQ消息最終一致性解決方案

隨著分布式服務(wù)架構(gòu)的流行與普及,原來(lái)在單體應(yīng)用中執(zhí)行的多個(gè)邏輯操作,現(xiàn)在被拆分成了多個(gè)服務(wù)之間的遠(yuǎn)程調(diào)用。雖然服務(wù)化為我們的系統(tǒng)帶來(lái)了水平伸縮的能力,然而隨之而來(lái)挑戰(zhàn)就是分布式事務(wù)問(wèn)題蜻牢,多個(gè)服務(wù)之間使用自己?jiǎn)为?dú)維護(hù)的數(shù)據(jù)庫(kù)烤咧,它們彼此之間不在同一個(gè)事務(wù)中,假如A執(zhí)行成功了抢呆,B執(zhí)行卻失敗了煮嫌,而A的事務(wù)此時(shí)已經(jīng)提交,無(wú)法回滾抱虐,那么最終就會(huì)導(dǎo)致兩邊數(shù)據(jù)不一致性的問(wèn)題昌阿;盡管很早之前就有基于兩階段提交的XA分布式事務(wù),但是這類(lèi)方案因?yàn)樾枰Y源的全局鎖定恳邀,導(dǎo)致性能極差懦冰;因此后面就逐漸衍生出了消息最終一致性、TCC等柔性事務(wù)的分布式事務(wù)方案谣沸,本文主要分析的是基于消息的最終一致性方案刷钢。

普通消息的處理流程

image
  1. 消息生成者發(fā)送消息
  2. MQ收到消息,將消息進(jìn)行持久化乳附,在存儲(chǔ)中新增一條記錄
  3. 返回ACK給生產(chǎn)者
  4. MQ push 消息給對(duì)應(yīng)的消費(fèi)者内地,然后等待消費(fèi)者返回ACK
  5. 如果消息消費(fèi)者在指定時(shí)間內(nèi)成功返回ack,那么MQ認(rèn)為消息消費(fèi)成功赋除,在存儲(chǔ)中刪除消息阱缓,即執(zhí)行第6步;如果MQ在指定時(shí)間內(nèi)沒(méi)有收到ACK举农,則認(rèn)為消息消費(fèi)失敗荆针,會(huì)嘗試重新push消息,重復(fù)執(zhí)行4、5并蝗、6步驟
  6. MQ刪除消息

普通消息處理存在的一致性問(wèn)題

我們以訂單創(chuàng)建為例祭犯,訂單系統(tǒng)先創(chuàng)建訂單(本地事務(wù))秸妥,再發(fā)送消息給下游處理滚停;如果訂單創(chuàng)建成功,然而消息沒(méi)有發(fā)送出去粥惧,那么下游所有系統(tǒng)都無(wú)法感知到這個(gè)事件键畴,會(huì)出現(xiàn)臟數(shù)據(jù);

public void processOrder() {
    // 訂單處理(業(yè)務(wù)操作) 
    orderService.process();
    // 發(fā)送訂單處理成功消息(發(fā)送消息) 
    sendBizMsg ();
}

如果先發(fā)送訂單消息突雪,再創(chuàng)建訂單起惕;那么就有可能消息發(fā)送成功,但是在訂單創(chuàng)建的時(shí)候卻失敗了咏删,此時(shí)下游系統(tǒng)卻認(rèn)為這個(gè)訂單已經(jīng)創(chuàng)建惹想,也會(huì)出現(xiàn)臟數(shù)據(jù)。

public void processOrder() {
   // 發(fā)送訂單處理成功消息(發(fā)送消息) 
    sendBizMsg ();
    // 訂單處理(業(yè)務(wù)操作) 
    orderService.process();
}

一個(gè)錯(cuò)誤的想法

此時(shí)可能有同學(xué)會(huì)想督函,我們可否將消息發(fā)送和業(yè)務(wù)處理放在同一個(gè)本地事務(wù)中來(lái)進(jìn)行處理嘀粱,如果業(yè)務(wù)消息發(fā)送失敗激挪,那么本地事務(wù)就回滾,這樣是不是就能解決消息發(fā)送的一致性問(wèn)題呢?

@Transactionnal
public void processOrder() {
    try{
        // 訂單處理(業(yè)務(wù)操作) 
        orderService.process(); 
        // 發(fā)送訂單處理成功消息(發(fā)送消息) 
        sendBizMsg ();
    }catch(Exception e){
         事務(wù)回滾;   
    }
}

消息發(fā)送的異常情況分析

可能的情況 一致性
訂單處理成功锋叨,然后突然宕機(jī)编矾,事務(wù)未提交扮授,消息沒(méi)有發(fā)送出去 一致
訂單處理成功,由于網(wǎng)絡(luò)原因或者M(jìn)Q宕機(jī),消息沒(méi)有發(fā)送出去履植,事務(wù)回滾 一致
訂單處理成功,消息發(fā)送成功熔吗,但是MQ由于其他原因姆钉,導(dǎo)致消息存儲(chǔ)失敗,事務(wù)回滾 一致
訂單處理成功听诸,消息存儲(chǔ)成功炉奴,但是MQ處理超時(shí),從而ACK確認(rèn)失敗蛇更,導(dǎo)致發(fā)送方本地事務(wù)回滾 不一致

從上面的情況分析瞻赶,我們可以看到,使用普通的處理方式派任,無(wú)論如何砸逊,都無(wú)法保證業(yè)務(wù)處理與消息發(fā)送兩邊的一致性,其根本的原因就在于:遠(yuǎn)程調(diào)用掌逛,結(jié)果最終可能為成功师逸、失敗、超時(shí)豆混;而對(duì)于超時(shí)的情況篓像,處理方最終的結(jié)果可能是成功,也可能是失敗皿伺,調(diào)用方是無(wú)法知曉的员辩。 筆者就曾經(jīng)在項(xiàng)目中出現(xiàn)類(lèi)似的情況,調(diào)用方先在本地寫(xiě)數(shù)據(jù)鸵鸥,然后發(fā)起RPC服務(wù)調(diào)用奠滑,但是處理方由于DB數(shù)據(jù)量比較大,導(dǎo)致處理超時(shí)妒穴,調(diào)用方在出現(xiàn)超時(shí)異常后宋税,直接回滾本地事務(wù),從而導(dǎo)致調(diào)用方這邊沒(méi)數(shù)據(jù)讼油,而處理方那邊數(shù)據(jù)卻已經(jīng)寫(xiě)入了杰赛,最終導(dǎo)致兩邊業(yè)務(wù)數(shù)據(jù)的不一致。為了保證兩邊數(shù)據(jù)的一致性矮台,我們只能從其他地方尋找新的突破口乏屯。

事務(wù)消息

由于傳統(tǒng)的處理方式無(wú)法解決消息生成者本地事務(wù)處理成功消息發(fā)送成功兩者的一致性問(wèn)題阔墩,因此事務(wù)消息就誕生了,它實(shí)現(xiàn)了消息生成者本地事務(wù)與消息發(fā)送的原子性瓶珊,保證了消息生成者本地事務(wù)處理成功與消息發(fā)送成功的最終一致性問(wèn)題啸箫。

事務(wù)消息處理的流程

image
  1. 事務(wù)消息與普通消息的區(qū)別就在于消息生產(chǎn)環(huán)節(jié),生產(chǎn)者首先預(yù)發(fā)送一條消息到MQ(這也被稱(chēng)為發(fā)送half消息)

  2. MQ接受到消息后伞芹,先進(jìn)行持久化忘苛,則存儲(chǔ)中會(huì)新增一條狀態(tài)為待發(fā)送的消息

  3. 然后返回ACK給消息生產(chǎn)者,此時(shí)MQ不會(huì)觸發(fā)消息推送事件

  4. 生產(chǎn)者預(yù)發(fā)送消息成功后唱较,執(zhí)行本地事務(wù)

  5. 執(zhí)行本地事務(wù)扎唾,執(zhí)行完成后,發(fā)送執(zhí)行結(jié)果給MQ

  6. MQ會(huì)根據(jù)結(jié)果刪除或者更新消息狀態(tài)為可發(fā)送

  7. 如果消息狀態(tài)更新為可發(fā)送南缓,則MQ會(huì)push消息給消費(fèi)者胸遇,后面消息的消費(fèi)和普通消息是一樣的

注意點(diǎn):由于MQ通常都會(huì)保證消息能夠投遞成功,因此汉形,如果業(yè)務(wù)沒(méi)有及時(shí)返回ACK結(jié)果纸镊,那么就有可能造成MQ的重復(fù)消息投遞問(wèn)題。因此概疆,對(duì)于消息最終一致性的方案逗威,消息的消費(fèi)者必須要對(duì)消息的消費(fèi)支持冪等,不能造成同一條消息的重復(fù)消費(fèi)的情況岔冀。

事務(wù)消息異常情況分析

異常情況 一致性 處理異常方法
消息未存儲(chǔ)凯旭,業(yè)務(wù)操作未執(zhí)行 一致 無(wú)
存儲(chǔ)待發(fā)送消息成功,但是ACK失敗使套,導(dǎo)致業(yè)務(wù)未執(zhí)行(可能是MQ處理超時(shí)罐呼、網(wǎng)絡(luò)抖動(dòng)等原因) 不一致 MQ確認(rèn)業(yè)務(wù)操作結(jié)果,處理消息(刪除消息)
存儲(chǔ)待發(fā)送消息成功侦高,ACK成功嫉柴,業(yè)務(wù)執(zhí)行(可能成功也可能失敗),但是MQ沒(méi)有收到生產(chǎn)者業(yè)務(wù)處理的最終結(jié)果 不一致 MQ確認(rèn)業(yè)務(wù)操作結(jié)果矫膨,處理消息(根據(jù)就業(yè)務(wù)處理結(jié)果差凹,更新消息狀態(tài)期奔,如果業(yè)務(wù)執(zhí)行成功侧馅,則投遞消息,失敗則刪除消息)
業(yè)務(wù)處理成功呐萌,并且發(fā)送結(jié)果給MQ馁痴,但是MQ更新消息失敗,導(dǎo)致消息狀態(tài)依舊為待發(fā)送 不一致 同上

支持事務(wù)消息的MQ

現(xiàn)在目前較為主流的MQ肺孤,比如ActiveMQ罗晕、RabbitMQ济欢、Kafka、RocketMQ等小渊,只有RocketMQ支持事務(wù)消息法褥。據(jù)筆者了解,早年阿里對(duì)MQ增加事務(wù)消息也是因?yàn)橹Ц秾毮沁呉驗(yàn)闃I(yè)務(wù)上的需求而產(chǎn)生的酬屉。因此半等,如果我們希望強(qiáng)依賴(lài)一個(gè)MQ的事務(wù)消息來(lái)做到消息最終一致性的話(huà),在目前的情況下呐萨,技術(shù)選型上只能去選擇RocketMQ來(lái)解決杀饵。上面我們也分析了事務(wù)消息所存在的異常情況,即MQ存儲(chǔ)了待發(fā)送的消息谬擦,但是MQ無(wú)法感知到上游處理的最終結(jié)果切距。對(duì)于RocketMQ而言,它的解決方案非常的簡(jiǎn)單惨远,就是其內(nèi)部實(shí)現(xiàn)會(huì)有一個(gè)定時(shí)任務(wù)谜悟,去輪訓(xùn)狀態(tài)為待發(fā)送的消息,然后給producer發(fā)送check請(qǐng)求北秽,而producer必須實(shí)現(xiàn)一個(gè)check監(jiān)聽(tīng)器赌躺,監(jiān)聽(tīng)器的內(nèi)容通常就是去檢查與之對(duì)應(yīng)的本地事務(wù)是否成功(一般就是查詢(xún)DB),如果成功了羡儿,則MQ會(huì)將消息設(shè)置為可發(fā)送礼患,否則就刪除消息。

常見(jiàn)的問(wèn)題

  1. 問(wèn):如果預(yù)發(fā)送消息失敗掠归,是不是業(yè)務(wù)就不執(zhí)行了缅叠?

    答:是的,對(duì)于基于消息最終一致性的方案虏冻,一般都會(huì)強(qiáng)依賴(lài)這步肤粱,如果這個(gè)步驟無(wú)法得到保證,那么最終也 就不可能做到最終一致性了厨相。

  2. 問(wèn):為什么要增加一個(gè)消息預(yù)發(fā)送機(jī)制领曼,增加兩次發(fā)布出去消息的重試機(jī)制,為什么不在業(yè)務(wù)成功之后蛮穿,發(fā)送失敗的話(huà)使用一次重試機(jī)制庶骄?

    答:如果業(yè)務(wù)執(zhí)行成功,再去發(fā)消息践磅,此時(shí)如果還沒(méi)來(lái)得及發(fā)消息单刁,業(yè)務(wù)系統(tǒng)就已經(jīng)宕機(jī)了,系統(tǒng)重啟后府适,根本沒(méi)有記錄之前是否發(fā)送過(guò)消息羔飞,這樣就會(huì)導(dǎo)致業(yè)務(wù)執(zhí)行成功肺樟,消息最終沒(méi)發(fā)出去的情況。

  3. 如果consumer消費(fèi)失敗逻淌,是否需要producer做回滾呢么伯?

    答:這里的事務(wù)消息,producer不會(huì)因?yàn)閏onsumer消費(fèi)失敗而做回滾卡儒,采用事務(wù)消息的應(yīng)用蹦狂,其所追求的是高可用最終一致性,消息消費(fèi)失敗的話(huà)朋贬,MQ自己會(huì)負(fù)責(zé)重推消息凯楔,直到消費(fèi)成功。因此锦募,事務(wù)消息是針對(duì)生產(chǎn)端而言的摆屯,而消費(fèi)端,消費(fèi)端的一致性是通過(guò)MQ的重試機(jī)制來(lái)完成的糠亩。

  4. 如果consumer端因?yàn)?strong>業(yè)務(wù)異常而導(dǎo)致回滾虐骑,那么豈不是兩邊最終無(wú)法保證一致性?

    答:基于消息的最終一致性方案必須保證消費(fèi)端在業(yè)務(wù)上的操作沒(méi)障礙,它只允許系統(tǒng)異常的失敗赎线,不允許業(yè)務(wù)上的失敗廷没,比如在你業(yè)務(wù)上拋出個(gè)NPE之類(lèi)的問(wèn)題,導(dǎo)致你消費(fèi)端執(zhí)行事務(wù)失敗垂寥,那就很難做到一致了颠黎。

由于并非所有的MQ都支持事務(wù)消息,假如我們不選擇RocketMQ來(lái)作為系統(tǒng)的MQ滞项,是否能夠做到消息的最終一致性呢狭归?答案是可以的。

基于本地消息的最終一致性

image

基于本地消息的最終一致性方案的最核心做法就是在執(zhí)行業(yè)務(wù)操作的時(shí)候文判,記錄一條消息數(shù)據(jù)到DB过椎,并且消息數(shù)據(jù)的記錄與業(yè)務(wù)數(shù)據(jù)的記錄必須在同一個(gè)事務(wù)內(nèi)完成,這是該方案的前提核心保障戏仓。在記錄完成后消息數(shù)據(jù)后疚宇,后面我們就可以通過(guò)一個(gè)定時(shí)任務(wù)到DB中去輪訓(xùn)狀態(tài)為待發(fā)送的消息,然后將消息投遞給MQ赏殃。這個(gè)過(guò)程中可能存在消息投遞失敗的可能敷待,此時(shí)就依靠重試機(jī)制來(lái)保證,直到成功收到MQ的ACK確認(rèn)之后嗓奢,再將消息狀態(tài)更新或者消息清除讼撒;而后面消息的消費(fèi)失敗的話(huà),則依賴(lài)MQ本身的重試來(lái)完成股耽,其最后做到兩邊系統(tǒng)數(shù)據(jù)的最終一致性根盒。基于本地消息服務(wù)的方案雖然可以做到消息的最終一致性,但是它有一個(gè)比較嚴(yán)重的弊端物蝙,每個(gè)業(yè)務(wù)系統(tǒng)在使用該方案時(shí)炎滞,都需要在對(duì)應(yīng)的業(yè)務(wù)庫(kù)創(chuàng)建一張消息表來(lái)存儲(chǔ)消息。針對(duì)這個(gè)問(wèn)題诬乞,我們可以將該功能單獨(dú)提取出來(lái)册赛,做成一個(gè)消息服務(wù)來(lái)統(tǒng)一處理,因而就衍生出了我們下面將要討論的方案震嫉。

獨(dú)立消息服務(wù)的最終一致性

image

獨(dú)立消息服務(wù)最終一致性本地消息服務(wù)最終一致性最大的差異就在于將消息的存儲(chǔ)單獨(dú)地做成了一個(gè)RPC的服務(wù)森瘪,這個(gè)過(guò)程其實(shí)就是模擬了事務(wù)消息的消息預(yù)發(fā)送過(guò)程,如果預(yù)發(fā)送消息失敗票堵,那么生產(chǎn)者業(yè)務(wù)就不會(huì)去執(zhí)行扼睬,因此對(duì)于生產(chǎn)者的業(yè)務(wù)而言,它是強(qiáng)依賴(lài)于該消息服務(wù)的悴势。不過(guò)好在獨(dú)立消息服務(wù)支持水平擴(kuò)容窗宇,因此只要部署多臺(tái),做成HA的集群模式特纤,就能夠保證其可靠性军俊。在消息服務(wù)中,還有一個(gè)單獨(dú)地定時(shí)任務(wù)捧存,它會(huì)定期輪訓(xùn)長(zhǎng)時(shí)間處于待發(fā)送狀態(tài)的消息粪躬,通過(guò)一個(gè)check補(bǔ)償機(jī)制來(lái)確認(rèn)該消息對(duì)應(yīng)的業(yè)務(wù)是否成功,如果對(duì)應(yīng)的業(yè)務(wù)處理成功昔穴,則將消息修改為可發(fā)送短蜕,然后將其投遞給MQ;如果業(yè)務(wù)處理失敗傻咖,則將對(duì)應(yīng)的消息更新或者刪除即可朋魔。因此在使用該方案時(shí),消息生產(chǎn)者必須同時(shí)實(shí)現(xiàn)一個(gè)check服務(wù)卿操,來(lái)供消息服務(wù)做消息的確認(rèn)警检。對(duì)于消息的消費(fèi),該方案與上面的處理是一樣害淤,都是通過(guò)MQ自身的重發(fā)機(jī)制來(lái)保證消息被消費(fèi)扇雕。

總結(jié):上游事務(wù)提交之后,在基于MQ的場(chǎng)景下就不考慮回滾了窥摄。失敗的可能是由于網(wǎng)絡(luò)镶奉、服務(wù)宕機(jī)所導(dǎo)致,文章中提到說(shuō)業(yè)務(wù)上執(zhí)行是無(wú)障礙的。如果下游服務(wù)長(zhǎng)時(shí)間沒(méi)有恢復(fù)哨苛,那么就應(yīng)該設(shè)置告警鸽凶,在這里有幾種機(jī)制來(lái)解決一些牛皮癬類(lèi)型的問(wèn)題,假如上游消息始終發(fā)送失斀ㄇ汀(這種可能性基本不存在除非代碼是假的)這種情況我們可以設(shè)置報(bào)警機(jī)制比如發(fā)生異常時(shí)可以打印日志玻侥,發(fā)送短信,發(fā)送郵件亿蒸,將異常訂單保存到數(shù)據(jù)庫(kù)凑兰,這些措施可以同時(shí)用于下游一些異常訂單,同時(shí)也可以在發(fā)生異常的時(shí)候新建一個(gè)異常Topic的消息提示边锁,讓人工來(lái)介入數(shù)據(jù)訂正姑食。

如果本篇文章對(duì)你有幫助的話(huà)請(qǐng)點(diǎn)個(gè)贊加關(guān)注吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市茅坛,隨后出現(xiàn)的幾起案子音半,更是在濱河造成了極大的恐慌,老刑警劉巖灰蛙,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祟剔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡摩梧,警方通過(guò)查閱死者的電腦和手機(jī)物延,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)仅父,“玉大人叛薯,你說(shuō)我怎么就攤上這事◇舷耍” “怎么了耗溜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)省容。 經(jīng)常有香客問(wèn)我抖拴,道長(zhǎng),這世上最難降的妖魔是什么腥椒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任阿宅,我火速辦了婚禮,結(jié)果婚禮上笼蛛,老公的妹妹穿的比我還像新娘洒放。我一直安慰自己,他們只是感情好滨砍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布往湿。 她就那樣靜靜地躺著妖异,像睡著了一般。 火紅的嫁衣襯著肌膚如雪领追。 梳的紋絲不亂的頭發(fā)上他膳,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音蔓腐,去河邊找鬼矩乐。 笑死龄句,一個(gè)胖子當(dāng)著我的面吹牛回论,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播分歇,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼傀蓉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了职抡?” 一聲冷哼從身側(cè)響起葬燎,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缚甩,沒(méi)想到半個(gè)月后谱净,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡擅威,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年壕探,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郊丛。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡李请,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厉熟,到底是詐尸還是另有隱情导盅,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布揍瑟,位于F島的核電站白翻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绢片。R本人自食惡果不足惜滤馍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杉畜。 院中可真熱鬧纪蜒,春花似錦、人聲如沸此叠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至猬错,卻和暖如春窗看,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背倦炒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工显沈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逢唤。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓拉讯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鳖藕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子魔慷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344