消息隊列雜談

本篇文章聊聊消息隊列相關(guān)的東西愈污,內(nèi)容局限于我們?yōu)槭裁匆孟㈥犃械』蹋㈥犃芯烤菇鉀Q了什么問題涨缚,消息隊列的選型。

為了更容易的理解消息隊列甚疟,我們首先通過一個開發(fā)場景來切入仗岖。

不使用消息隊列的場景

首先逃延,我們假設(shè)A同學(xué)負(fù)責(zé)訂單系統(tǒng)的開發(fā)览妖,B、C同學(xué)負(fù)責(zé)開發(fā)積分系統(tǒng)揽祥、倉儲系統(tǒng)讽膏。我們知道,在一般的購物電商平臺上拄丰,我們下單完成后府树,積分系統(tǒng)會給下單的用戶增加積分,然后倉儲系統(tǒng)會按照下單時填寫的信息料按,發(fā)出用戶購買的商品奄侠。

那問題來了,積分系統(tǒng)载矿、倉儲系統(tǒng)如何感知到用戶的下單操作垄潮?

你可能會說,當(dāng)然是訂單系統(tǒng)在創(chuàng)建完訂單之后調(diào)用積分系統(tǒng)闷盔、倉儲系統(tǒng)的接口了

image-20210202211720981

OK弯洗,直接調(diào)用接口的方式在目前來看沒有什么問題。于是B逢勾、C同學(xué)就找到A同學(xué)牡整,說讓他在訂單完成后,調(diào)用一下他們的接口來通知一下積分系統(tǒng)和倉儲系統(tǒng)溺拱,來給用戶增加積分逃贝、發(fā)貨。A同學(xué)想著迫摔,就這兩個系統(tǒng)秋泳,應(yīng)該還好,OK我給你加了攒菠。

但是隨著系統(tǒng)的迭代迫皱,需要感知訂單操作的系統(tǒng)也越來越多,從之前的積分系統(tǒng)、倉儲系統(tǒng)2個系統(tǒng)卓起,擴(kuò)充到了5個和敬。每個系統(tǒng)的負(fù)責(zé)同學(xué)都需要去找A同學(xué),讓他人肉的把對應(yīng)系統(tǒng)的通知接口加上戏阅。然后就因為加了這一個接口昼弟,又需要把訂單重新發(fā)布一遍。

這對A同學(xué)來說實際上是很痛苦的一件事情奕筐,因為A同學(xué)有自己的任務(wù)舱痘、排期,一有新系統(tǒng)就需要去添加通知接口离赫,發(fā)布服務(wù)芭逝,會打亂A的開發(fā)計劃,增加開發(fā)量渊胸。同時還需要去梳理在開發(fā)期間旬盯,新增的代碼到底能不能夠上線。一旦不能上線翎猛,但是又沒有檢查到胖翰,上線就直接炸了。

image-20210202212157459

而且切厘,如果5個系統(tǒng)如果有哪個需要額外的字段萨咳,或者是更新了接口什么的,都需要麻煩A同學(xué)修改疫稿。5個系統(tǒng)就這樣跟A系統(tǒng)強(qiáng)耦合在了一起培他。

除此之外,整個創(chuàng)建訂單的調(diào)用鏈因為同步調(diào)用這5個系統(tǒng)的通知接口而加長而克,這減慢了接口的響應(yīng)速度靶壮,降低了用戶側(cè)的購物、下單體驗员萍。前面的至少影響的還是內(nèi)部的員工腾降,但是現(xiàn)在直接是影響到了用戶,明顯是不可取的方案碎绎。

image-20210203113301066

可以看到螃壤,整個的調(diào)用鏈路加長了,更別提筋帖,在同步調(diào)用中奸晴,如果其余的系統(tǒng)發(fā)生了錯誤,或者是調(diào)用其他系統(tǒng)的時候出現(xiàn)了網(wǎng)絡(luò)抖動日麸,核心的下單流程就會被阻塞住寄啼,甚至?xí)谙聠蔚慕缑嫣崾咎崾居脩舫鲥e逮光,整個的購物體驗又被拉低了一個檔次。更何況墩划,在實際的業(yè)務(wù)中涕刚,調(diào)用鏈比這個長的多。

可能有人會說了乙帮, 這不就是個同步調(diào)用問題嘛杜漠?訂單系統(tǒng)的核心邏輯,我還是采用同步來處理察净,但是后續(xù)的通知我采用異步的方式驾茴,用線程池去處理,這樣調(diào)用鏈路不就恢復(fù)正常了氢卡?

image-20210203125117084

就單純對于減少鏈路來說锈至,的確可行。但是如果某一個流程失敗了呢异吻?難道失敗就失敗了嗎裹赴?我下單成功了不漲積分喜庞?該給我發(fā)的貨甚至沒有發(fā)貨诀浪?這合理嗎?

unnamed

同時延都,失敗了訂單系統(tǒng)是不是要去處理呢雷猪?否則因為其他的系統(tǒng)拉垮了整個主流程,誰還來你這買東西呢晰房?

那有什么辦法求摇,既能夠減少調(diào)用的鏈路,又能夠在發(fā)生錯誤的時候重試呢殊者?歸根結(jié)底与境,核心思想就是像增加積分、返優(yōu)惠券的流程不應(yīng)該和主流程耦和在一起猖吴,更不應(yīng)該影響主流程摔刁。

試想,我們能不能在訂單系統(tǒng)完成自己的核心邏輯之后海蔽,把訂單創(chuàng)建的消息放到一個隊列中去共屈,然后訂單系統(tǒng)就返回給用戶下單成功的結(jié)果了。然后其他的系統(tǒng)從這個隊列中收到了下單成功的消息党窜,就各自的去執(zhí)行各自的操作拗引,例如增加積分、返優(yōu)惠券等等操作幌衣。

后續(xù)如果有新的系統(tǒng)需要感知訂單創(chuàng)建的消息矾削,直接去訂閱這個隊列,消費里面的消息就好了?這雖然跟真實的消息的隊列有些出入哼凯,但其思路是完成吻合的垦细。

為什么需要消息隊列

通過上面的例子,我們大致就能夠理解為什么要引入消息隊列了挡逼,這里簡單總結(jié)一下括改。

異步

對于實時性不是很高的業(yè)務(wù),例如給用戶發(fā)送短信家坎、郵件通知嘱能,調(diào)用第三方的接口,都可以放到消息隊列里去虱疏。因為相對于核心訂單流程來說惹骂,短信、郵件晚一些發(fā)送做瞪,對用戶來說影響不是很大对粪。同時還可以提升整個鏈路的響應(yīng)時間。

削峰

假設(shè)我們有服務(wù)A装蓬,是個無狀態(tài)的服務(wù)著拭。通過橫向擴(kuò)展,它可以輕松抗住1w的并發(fā)量牍帚,但是這N個服務(wù)實例儡遮,底層訪問的都是同一個數(shù)據(jù)庫。數(shù)據(jù)庫能抗住的并發(fā)量是有限的暗赶,如果你的機(jī)器足夠好的話鄙币,可能能夠抗住5000的并發(fā),如果服務(wù)A的所有請求全部打向數(shù)據(jù)庫蹂随,會直接把數(shù)據(jù)打掛十嘿。

image-20210203143713003

解耦

像上文舉的例子,訂單系統(tǒng)在創(chuàng)建了訂單之后需要通知其他的所有系統(tǒng)岳锁,這樣一來就把訂單系統(tǒng)和其余的系統(tǒng)強(qiáng)耦合在了一起绩衷。后續(xù)的可維護(hù)性、擴(kuò)展性都大大降低了浸锨。

而通過消息隊列來關(guān)聯(lián)所有系統(tǒng)唇聘,可以達(dá)到解耦的目的。

image-20210203144922191

像上圖這種模式柱搜,如果后續(xù)再有新系統(tǒng)需要感知訂單創(chuàng)建的消息迟郎,只需要去消費「訂單系統(tǒng)」發(fā)送到MQ中的消息即可。同樣聪蘸,訂單系統(tǒng)如果需要感知其余系統(tǒng)的某些事件宪肖,也只是從MQ中消費即可表制。

通過MQ,達(dá)成服務(wù)之間的松耦合控乾,服務(wù)內(nèi)的高內(nèi)聚么介,提升了服務(wù)的自治性。

消息隊列選型

已知的消息隊列有Kafka蜕衡、RocketMQ壤短、RabbitMQ和ActiveMQ。但是由于ActiveMQ現(xiàn)在用的公司比較少了慨仿,這里就不做討論久脯,我們著重討論前三種。

Kafka

Kafka最初來自于LinkedIn镰吆,是用于做日志收集的工具帘撰,采用Java和Scala開發(fā)。其實那個時候已經(jīng)有ActiveMQ了万皿,但是在當(dāng)時ActiveMQ沒有辦法滿足LinkedIn的需求摧找,于是Kafka就應(yīng)運而生。

在2010年底牢硅,Kakfa的0.7.0被開源到了Github上蹬耘。到了2011年,由于Kafka非常受關(guān)注唤衫,被納入了Apache Incubator婆赠,所有想要成為Apache正式項目的外部項目绵脯,都必須要經(jīng)過Incubator佳励,翻譯過來就是孵化器。旨在將一些項目孵化成完全成熟的Apache開源項目蛆挫。

你也可以把它想象成一個學(xué)校赃承,所有想要成為Apache正式開源項目的外部項目都必須要進(jìn)入Incubator學(xué)習(xí),并且拿到畢業(yè)證悴侵,才能走入社會瞧剖。于是在2012年,Kafka成功從Apache Incubator畢業(yè)可免,正式成為Apache中的一員抓于。

Kafka擁有很高的吞吐量,單機(jī)能夠抗下十幾w的并發(fā)浇借,而且寫入的性能也很高捉撮,能夠達(dá)到毫秒級別。但是有優(yōu)點就有缺點妇垢,能夠達(dá)到這么高的并發(fā)的代價是巾遭,可能會出現(xiàn)消息的丟失肉康。至于具體的丟失場景,我們后續(xù)會討論灼舍。

所以一般Kafka都用于大數(shù)據(jù)的日志收集吼和,這種日志丟個一兩條無傷大雅。

而且Kafka的功能較為簡單骑素,就是簡單的接收生產(chǎn)者的消息炫乓,消費者從Kafka消費消息。

RabbitMQ

RabbitMQ是很多公司對于ActiveMQ的替代方法献丑,現(xiàn)在仍然有很多公司在使用厢岂。其優(yōu)點在于能保證消息不丟失,同Kafka阳距,天平往數(shù)據(jù)的可靠性方向傾斜必然導(dǎo)致其吞吐量下降塔粒。其吞吐量只能夠達(dá)到幾萬,比起Kafka的十萬吞吐來說筐摘,的確是較低的卒茬。如果遇到需要支撐特別高并發(fā)的情況,RabbitMQ可能會無法勝任咖熟。

但是RabbitMQ有比Kafka更多的高級特性圃酵,例如消息重試和死信隊列,而且寫入的延遲能夠降低到微妙級馍管,這也是RabbitMQ一大特點郭赐。

但RabbitMQ還有一個致命的弱點,其開發(fā)語言為Erlang确沸,現(xiàn)在國內(nèi)精通Erlang的人不多捌锭,社區(qū)也不怎么活躍。這也就導(dǎo)致可能公司內(nèi)沒有人能夠去閱讀Erlang的源碼罗捎,更別說基于其源碼進(jìn)行二次開發(fā)或者排查問題了观谦。所以就存在RabbitMQ出了問題可能公司里沒人能夠兜的住,維護(hù)成本非常的高桨菜。

之所以有中小型公司還在使用豁状,是覺得其不會面臨高并發(fā)的場景,RabbitMQ的功能已經(jīng)完全夠用了倒得。

RocketMQ

RocketMQ來自阿里泻红,同Kakfa一樣也是從Apache Incubator出來的頂級項目,用Java語言進(jìn)行開發(fā)霞掺,單機(jī)吞吐量和Kafka一樣谊路,也是十w量級。

RocketMQ的前身是阿里的MetaQ項目根悼,2012年在淘寶內(nèi)部大量的使用凶异,在阿里內(nèi)部迭代到3.0版本之后蜀撑,將MetaQ的核心功能抽離出來,就有了RocketMQ剩彬。RocketMQ整合了Kafka和RabbitMQ的優(yōu)點酷麦,例如較高的吞吐量和通過參數(shù)配置能夠做到消息絕對不丟失。

其底層的設(shè)計參考了Kafka喉恋,具有低延遲沃饶、高性能、高可用的特點轻黑。不同于Kafka的單一日志收集功能糊肤,RocketMQ被廣泛運用于訂單、交易氓鄙、計算馆揉、消息推送、binlog分發(fā)等場景抖拦。

之所以能夠被運用到多種場景升酣,這要歸功于RocketMQ提供的豐富的功能。例如延遲消息态罪、事務(wù)消息噩茄、消息回溯、死信隊列等等复颈。

  • 延遲消息 就是不會立即消費的消息绩聘,例如某個活動開始前15分鐘提醒用戶這樣的場景
  • 事務(wù)消息 其主要解決數(shù)據(jù)庫事務(wù)和MQ消息的數(shù)據(jù)一致性,例如用戶下單耗啦,先發(fā)送消息到MQ凿菩,積分增加了,但是訂單系統(tǒng)在發(fā)出消息之后掛了芹彬。這樣用戶并沒有下單成功蓄髓,但是積分卻增加了,明顯是不符合預(yù)期的
  • 消息回溯 顧名思義舒帮,就是去消費某個Topic下某段時間的歷史消息
  • 死信隊列 沒有被正常消費的消息,首先會按照RocketMQ的重試機(jī)制重試陡叠,當(dāng)達(dá)到了最大的重試次數(shù)之后玩郊,如果消費仍然失敗,RocketMQ不會立即丟掉這條消息枉阵,而是會把消息放入死信隊列中译红。放入死信隊列的消息會在3天后過期,所以需要及時的處理

消息隊列會丟消息嗎

不使用消息隊列的場景中兴溜,我們吹了很多消息隊列的優(yōu)點侦厚,但同時也提到了消息隊列可能會丟失消息耻陕,我們也可以通過參數(shù)的配置來使消息絕對不丟失。

那消息是在什么情況下丟失的呢刨沦?消息隊列中的角色可以分為3類诗宣,分別是生產(chǎn)者、MQ和消費者想诅。一條消息在整個的傳輸鏈路中需要經(jīng)過如下的流程召庞。

image-20210204203711613

生產(chǎn)者將消息發(fā)送給MQ,MQ接收到這條消息后會將消息存儲到磁盤上来破,消費者來消費的時候就會把消息返給消費者篮灼。先給出結(jié)論,在這3種場景下徘禁,消息都有可能會丟失诅诱。

接下來我們一步一步來分析一下。

生產(chǎn)者發(fā)送消息給MQ

生產(chǎn)者在發(fā)送消息的過程中送朱,由于某些意外的情況例如網(wǎng)絡(luò)抖動等逢艘,導(dǎo)致本次網(wǎng)絡(luò)通信失敗,消息并沒有被發(fā)送給MQ骤菠。

MQ存儲消息

當(dāng)MQ接收到了來自生產(chǎn)者的消息之后它改,還沒有來得及處理,MQ就突然宕機(jī)商乎,此時該消息也會丟失央拖。

即使MQ開始處理消息,并且將該消息寫入了磁盤鹉戚,消息仍然可能會丟失鲜戒。因為現(xiàn)代的操作系統(tǒng)都會有自己的OS Cache,因為和磁盤交互是一件代價相當(dāng)大的事情抹凳,所以當(dāng)我們寫入文件的時候會先將數(shù)據(jù)寫入OS Cache中遏餐,然后由OS調(diào)度,根據(jù)策略觸發(fā)真正的I/O操作赢底,將數(shù)據(jù)刷入磁盤失都。

而在刷入磁盤之前,MQ如果宕機(jī)幸冻,在OS Cache中的數(shù)據(jù)就會全部丟失粹庞。

消費者消費消息

當(dāng)消息順利的經(jīng)歷了生產(chǎn)者、MQ之后洽损,消費者拉取到了這條消息庞溜,但是當(dāng)其還沒來得及處理的時候,消費者突然宕機(jī)了碑定,這條消息就丟了(當(dāng)然你如果沒有提交Offset的話流码,重啟之后仍然可以消費到這條消息)

原來我們以為用上了消息隊列又官,就萬無一失了,沒想到逐步分析下來能有這么多坑漫试。任何一個步驟出錯都有可能導(dǎo)致消息丟失六敬。那既然這樣,上文提到的可以通過參數(shù)配置來實現(xiàn)消息不會丟失是怎么一回事呢商虐?

這里我們不去聊具體的MQ是如何實現(xiàn)的觉阅,我們來聊聊消息零丟失的實現(xiàn)思路。

消息最終一致性方案

涉及到的系統(tǒng)有訂單系統(tǒng)秘车、MQ和積分系統(tǒng)典勇,訂單系統(tǒng)為生產(chǎn)者,積分系統(tǒng)為消費者叮趴。

首先訂單系統(tǒng)發(fā)送一個訂單創(chuàng)建的消息給MQ割笙,該消息的狀態(tài)為Prepare狀態(tài),狀態(tài)為Prepare狀態(tài)的消息不會被消費者給消費到眯亦,所以可以放心的發(fā)送伤溉。

然后訂單系統(tǒng)開始執(zhí)行自身的核心邏輯,你可能會說妻率,訂單系統(tǒng)本身的邏輯執(zhí)行失敗了怎么辦乱顾?剛剛的prepare消息不就成了臟數(shù)據(jù)?實際上在訂單系統(tǒng)的事務(wù)失敗之后宫静,就會觸發(fā)回滾操作走净,就會向MQ發(fā)送消息,將該條狀態(tài)為Prepare的數(shù)據(jù)給刪除孤里。

訂單系統(tǒng)核心事務(wù)成功之后伏伯,就會發(fā)送消息給MQ,將狀態(tài)為prepare的消息更新為commit捌袜。沒錯说搅,這就是2PC,一個保證分布式事務(wù)數(shù)據(jù)一致性的協(xié)議虏等。

image-20210207102443940

眼尖的你可能發(fā)現(xiàn)了一個問題弄唧,我發(fā)送了prepare消息之后,還沒來得及執(zhí)行本地事務(wù)博其,訂單系統(tǒng)就掛了怎么辦套才?此時訂單系統(tǒng)即使重啟也不會向MQ發(fā)送刪除操作,這個prepare消息不就是一直存在MQ中了慕淡?

先給出結(jié)論,不會沸毁。

如果訂單系統(tǒng)發(fā)送了prepare消息給MQ之后自己就宕機(jī)了峰髓,MQ確實會存在一條不會被commit的數(shù)據(jù)傻寂。MQ為了解決這個問題,會定時輪詢所有prepare的消息携兵,跟對應(yīng)的系統(tǒng)溝通疾掰,這條prepare消息是要進(jìn)行重試還是回滾。所以prepare消息不會一直存在于MQ中徐紧。這樣一來静檬,就保證了消息對于生產(chǎn)者的DB事務(wù)和MQ中消息的數(shù)據(jù)一致性痴颊。

再來看一種更加極端的情況主之,假設(shè)訂單系統(tǒng)本地事務(wù)執(zhí)行成功之后,發(fā)送了commit消息到MQ又厉,此時MQ突然掛了嘲碧。導(dǎo)致MQ沒有收到該commit消息稻励,在MQ中該消息仍然處于prepare狀態(tài),這怎么辦愈涩?

同樣的望抽,依賴于MQ的輪詢機(jī)制和訂單系統(tǒng)溝通,訂單系統(tǒng)會告訴MQ這個事務(wù)已經(jīng)完成了履婉,MQ就會將這條消息設(shè)置成commit煤篙,消費者就可以消費到該消息了。

接下來的流程就是消息被消費者消費了毁腿,如果消費者消費消息的時候本地事務(wù)失敗了辑奈,則會進(jìn)行重試,再次嘗試消費這條消息狸棍。

好了以上就是本篇博客的全部內(nèi)容了身害,歡迎微信搜索關(guān)注【SH的全棧筆記】,回復(fù)【隊列】獲取MQ學(xué)習(xí)資料草戈,包含基礎(chǔ)概念解析和RocketMQ詳細(xì)的源碼解析塌鸯,持續(xù)更新中。

如果你覺得這篇文章對你有幫助唐片,還麻煩點個贊丙猬,關(guān)個注分個享费韭,留個言茧球。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市星持,隨后出現(xiàn)的幾起案子抢埋,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揪垄,死亡現(xiàn)場離奇詭異穷吮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饥努,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門捡鱼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酷愧,你說我怎么就攤上這事驾诈。” “怎么了溶浴?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵乍迄,是天一觀的道長。 經(jīng)常有香客問我戳葵,道長就乓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任拱烁,我火速辦了婚禮生蚁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戏自。我一直安慰自己邦投,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布擅笔。 她就那樣靜靜地躺著志衣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猛们。 梳的紋絲不亂的頭發(fā)上念脯,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音弯淘,去河邊找鬼绿店。 笑死,一個胖子當(dāng)著我的面吹牛庐橙,可吹牛的內(nèi)容都是我干的假勿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼态鳖,長吁一口氣:“原來是場噩夢啊……” “哼转培!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起浆竭,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤浸须,失蹤者是張志新(化名)和其女友劉穎惨寿,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羽戒,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡缤沦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年虎韵,在試婚紗的時候發(fā)現(xiàn)自己被綠了易稠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡包蓝,死狀恐怖驶社,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情测萎,我是刑警寧澤亡电,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站硅瞧,受9級特大地震影響份乒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腕唧,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一或辖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枣接,春花似錦颂暇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至膀曾,卻和暖如春县爬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背添谊。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工财喳, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碉钠。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓纲缓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喊废。 傳聞我的和親對象是個殘疾皇子祝高,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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