Dubbo調(diào)用過程分析

文檔目的:

公司目前使用的dubbo版本是2.6.2鹤竭,看完dubbo官方文檔中的一些功能,所以就想知道dubbo調(diào)用過程大概是怎么樣的藕漱。

看源碼前提:

一定要先看DUBBO SPI的相關(guān)知識(shí):

內(nèi)容:

一上來必須是看看官方的這三張圖

服務(wù)引用過程:

取名:圖1


1.jpeg

調(diào)用過程的兩張:

取名:圖2


image.png

取名:圖3


image.png

圖2中紅色部分為dubbo服務(wù)調(diào)用過程欲侮。

下面將分析各層的實(shí)現(xiàn)和功能點(diǎn)

  1. Interface:為開發(fā)定義的接口,使用@Reference引用服務(wù)

  2. Proxy:dubbo為我們生成的代理類肋联,應(yīng)該是在引用過程創(chuàng)建的威蕉,這個(gè)之后再分析具體實(shí)現(xiàn)-ReferenceConfig.get()->ReferenceConfig.createProxy()->ProxyFactory.getProxy(Invoker)

  3. Invoker:屬于Cluster層,從圖中得到的信息時(shí)橄仍,先從Register層獲得注冊(cè)了的服務(wù)韧涨,然后通過路由規(guī)則過濾(Router),之后再通過LoadBalance算法得到實(shí)際調(diào)用服務(wù)侮繁。具體邏輯定位:com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker虑粥。dubbo在外層還是用了com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper來做包裝,支持服務(wù)降級(jí)功能(dubbo中SPI相關(guān)的功能XxxWrapper)宪哩∶浯看默認(rèn)的cluster是failover(失敗重試其他,默認(rèn)重試次數(shù):2锁孟,失敗之后重試兩次彬祖,參見:com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker

相關(guān)功能使用說明:

  1. Filter:Protocol層茁瘦。定義暴露服務(wù)和引用服務(wù)的邏輯。dubbo定義了三個(gè)Protocol的包裝類储笑,分別是:
  • com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper : protocol類型非registry時(shí)甜熔,使用責(zé)任鏈模式,將定義的com.alibaba.dubbo.rpc.Filter集成到Invoker中
  • com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper:protocol類型非registry時(shí)突倍,在暴露服務(wù)和引用服務(wù)時(shí)腔稀,將ExporterListener和InvokerListener嵌入到ListenerExporterWrapper和ListenerInvokerWrapper以實(shí)現(xiàn)對(duì)服務(wù)暴露,服務(wù)撤銷和服務(wù)引用羽历,引用銷毀的事件監(jiān)聽烧颖。
  • com.alibaba.dubbo.qos.protocol.QosProtocolWrapper:protocol類型為registry時(shí),開啟qos服務(wù)窄陡。詳情參見:https://blog.csdn.net/yuanshangshenghuo/article/details/107563319

我們目前關(guān)注的是ProtocolFilterWrappe中的邏輯,定義的Filter已經(jīng)被放入Invoker的執(zhí)行鏈中拆火。

  1. Invoker: 取決于具體配置的dubbo的protocol跳夭,圖中使用的protocol是dubbo,對(duì)應(yīng)的Invoker是com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.

我之前使用過injvm的protocol们镜,其暴露服務(wù)直接維護(hù)在內(nèi)存map中币叹,引用的時(shí)候直接從map中取。他在filter層之下模狭,說明使用injvm協(xié)議也會(huì)走Filter邏輯颈抚。

  1. ExchangeClient:封裝請(qǐng)求響應(yīng)模式,同步轉(zhuǎn)異步

其下之后都是數(shù)據(jù)傳輸相關(guān)嚼鹉,看可以參見各層功能及可擴(kuò)展點(diǎn)


這里分析將protocol使用dubbo時(shí)的請(qǐng)求和返回的處理流程:

1. 部分初始化邏輯

DubboProtocol在引用服務(wù)的時(shí)候贩汉,會(huì)初始化ExchangeClient,

1.1 當(dāng)url中沒有指定:連接數(shù)-Constants.CONNECTIONS_KEY或該值為0時(shí)锚赤,則該應(yīng)用對(duì)提供服務(wù)的應(yīng)用(服務(wù)ip+port)會(huì)使用同一個(gè)ExchangeClient匹舞,參見:DubboProtocol.getClients。這種情況會(huì)在外層包裝一層ReferenceCountExchangeClient线脚,用與計(jì)數(shù)該ExchangeClient被多少Invoker使用赐稽,在銷毀的時(shí)候來判斷最后一個(gè)引用執(zhí)行destory的時(shí)候才真正close掉ExchangeClient。

1.2 其中調(diào)用Exchanges.connect時(shí)浑侥,如果url中沒有Constants.CODEC_KEY時(shí)姊舵,則指定Constants.CODEC_KEY=exchange。但是因?yàn)镈ubboProtocol.initClient時(shí)已經(jīng)指定了Constants.CODEC_KEY=dubbo寓落,所以最終的加解密方式為dubbo括丁,對(duì)應(yīng)DubboCodec。這里的說明是為了之后請(qǐng)求和返回值的加解密邏輯進(jìn)行說明零如,不然無法確定加解密邏輯躏将。參見:DubboProtocol.initClient锄弱。

1.3 創(chuàng)建ExchangerCliient邏輯:DubboProtocol.initClient中Exchanges.connect中有確定Exchanger的邏輯,默認(rèn)使用的是header祸憋,對(duì)應(yīng)HeaderExchangeClient会宪。handler參數(shù)是DubboProtocol.requestHandler, 包裝成HeaderExchangeHandler后又被包裝成DecodeHandler(類型為ChannelHandler)粗恢。后續(xù)在Transporter.connect創(chuàng)建client時(shí)准验,又被自適應(yīng)的AllDispatcher.dispatch方法包裝成AllChannelHandler->HeartbeatHandler->MultiMessageHandler笑诅,參見:new NettyClient()疫稿。

1.3.1 在創(chuàng)建AllChannelHandler時(shí)损话,其父類WrappedChannelHandler會(huì)使用自適應(yīng)的ThreadPool來創(chuàng)建線程池敬惦,然后放進(jìn)自適應(yīng)的DataStore中(componentName=consumer,key=url.getPort())韧掩,該線程池在之后的new AbstractClient()時(shí)取出賦值給AbstractClient.executor后從DataStore移出象浑。線程名稱在new NettyClinet()中的wrapChannelHandler中執(zhí)行了線程名為:DubboClientHandler-ip:port-xxxx荷鼠,線程池類型默認(rèn)值被設(shè)置為cached句携。


image.png

1.3.2 創(chuàng)建HeaderExchangeClient時(shí),會(huì)創(chuàng)建維持心跳的定時(shí)任務(wù)放在HeaderExchangeClient.scheduled的靜態(tài)線程池中執(zhí)行允乐。線程名:dubbo-remoting-client-heartbeat-xxxx(核心線程數(shù)=2)

1.4 創(chuàng)建Client邏輯:HeaderExchanger.connect中會(huì)創(chuàng)建client矮嫉,先選擇自適應(yīng)的Transporter,默認(rèn)的是com.alibaba.dubbo.remoting.transport.netty.NettyTransporter牍疏。NettyTransporter中創(chuàng)建的是NettyClient蠢笋。

2. DubboInvoker

經(jīng)過Cluster層和Filter的處理邏輯之后(ClusterInvoker->filter->Invoker),請(qǐng)求來到了DubboInvoker鳞陨。AbstractInvoker.invoke處理了一些通用邏輯之后昨寞,交由DubboInvoker.doInvoke. 其中主要是處理三種調(diào)用方式:return=false,async=true以及正常的調(diào)用厦滤。


image.png

3. HeaderExchangeClient

接下來將從正常調(diào)用方式來分析援岩,從1.3可知,默認(rèn)使用的是HeaderExchangeClient馁害。其成員變量channel使用的是HeaderExchangeChannel窄俏,HeaderExchangeChannel中的channel是NettyClient。

HeaderExchangeClient.request


image.png

HeaderExchangeChannel.request:封裝Request碘菜,構(gòu)建DefaultFuture返回凹蜈。同步轉(zhuǎn)異步。DefaultFuture起了一個(gè)守護(hù)線程來處理超時(shí)的future忍啸。詳見:DefaultFuture


image.png

4. NettyClient

. 1.4中說過使用的client是NettyClient仰坦,現(xiàn)在看看NettyClient的創(chuàng)建過程:


image.png

new NettyClinet()的時(shí)候會(huì)執(zhí)行父類AbstractClient以及父類的父類AbstractEndpoint,基本的邏輯是:

4.1. 確定加解密codec的方式(參見:AbstractEndpoint.getChannelCodec)计雌,1.2中說過了codec使用的是DubboCodec悄晃。

4.2. 斷線重連的線程池DubboClientReconnectTimer-xxxx 靜態(tài)的線程池(核心線程數(shù)為2,定時(shí)檢查channel是否連接正常,不正常時(shí)重新連接創(chuàng)建新的channel)妈橄,所有繼承了AbstractClient的client共享(參見:AbstractClient.initConnectStatusCheckCommand)庶近,

4.3. doOpen中主要是初始化ClientBootstrap,學(xué)過netty的都基本能看懂眷蚓,設(shè)置一些連接參數(shù)及pipeline中加入ChannelHandler,分別是讀取時(shí)的解密的decoder鼻种,寫入時(shí)的加密的encoder,數(shù)據(jù)寫入和處理都會(huì)處理的handler-NettyHandler沙热。

4.4. doConnect中與url對(duì)應(yīng)的服務(wù)端建立channel叉钥,賦值給NettyClient.channel,細(xì)節(jié):NettyClient.channel被定義成volatile篙贸,因?yàn)檫B接斷開后重連會(huì)重新賦值投队。

4.5 此處還需要關(guān)注channelFactory的創(chuàng)建,具體參數(shù)理解參照:https://jianshu.com/p/eec2677651c0 中Netty3.x的線程模型爵川。

5. 加密和解密

下面就再看看DubboCodec的加密和解密的邏輯:

5.1 加密:詳細(xì)說明可以參見:http://www.reibang.com/p/99a0bc93eeb6

image.png

簡(jiǎn)單描述就是:

5.1.1 先確定序列化的方式敷鸦,還是通過Dubbo SPI的自適應(yīng),找到對(duì)應(yīng)的Serialization寝贡,此處默認(rèn)的是hessian2轧膘。

5.1.2 發(fā)送的數(shù)據(jù)結(jié)構(gòu)是header+body的形式,header中會(huì)保存body的長(zhǎng)度兔甘,序列化方式,及其他信息鳞滨。

header信息洞焙,長(zhǎng)度為16的byte數(shù)組,

  • 頭兩個(gè)字節(jié)存魔數(shù)

  • 第三個(gè)字節(jié):存是否是Request拯啦,是否是Event澡匪,是否是FLAG_TWOWAY(需要等待返回值),以及序列化的方式serialization.getContentTypeId (后五位褒链,也就是能支持32中序列化方式)

  • 第四個(gè)字節(jié):從解密代碼來看唁情,是用來返回時(shí)存放狀態(tài)碼的。

  • 從第五個(gè)字節(jié)開始甫匹,用8個(gè)字節(jié)存request的id(Long)

  • 從第13個(gè)字節(jié)開始甸鸟,用四個(gè)字節(jié)將body的長(zhǎng)度(Integer)

將request對(duì)象序列化后寫入ChannelBuffer,還有個(gè)校驗(yàn)是檢查body長(zhǎng)度不能超過8M

Body:其中就包含客戶端jar包版本號(hào)兵迅,調(diào)用的服務(wù)名抢韭,調(diào)用服務(wù)的version,調(diào)用的方法名恍箭,參數(shù)類型刻恭,參數(shù)值,attachments扯夭。參見下圖代碼:參見:DubboCodec.encodeRequestData


image.png

5.2 解密:

解密的邏輯基本就是按加密的結(jié)構(gòu)來解析就行了鳍贾。
[圖片上傳中...(image-b73439-1629862540429-0)]

從header中解析出序列化的方式鞍匾,request的id,以及狀態(tài)碼骑科。

根據(jù)header中的序列化方式解析出返回值橡淑,就是反序列化過程。

其中重要的一點(diǎn)就是纵散,真正的反序列化的調(diào)用并不是在netty的worker線程梳码,而是在DubboCodec.decodeBody的時(shí)候,被包裝成DecodeableRpcResult返回了伍掀,并在7中的線程池中執(zhí)行DecodeHandler.received(Channel, message)時(shí)掰茶,才真正的執(zhí)行返回值的反序列化。


image.png

6. HeaderExchangeClient到加密邏輯

下面要整理出3中HeaderExchangeChannel.request是如何走到5.1的加密邏輯的:

3中說明了HeaderExchangeChannel中的channel是NettyClient蜜笤,NettyClient.send(Request)->AbstractClient(NettyClient).send(Request, sent)->NettyChannel.write(Message),至此就會(huì)走到NettyClient初始化時(shí)設(shè)置的channelPipeline了濒蒋,先執(zhí)行NettyHandler.writeRequested


image.png

NettyHandler.writeRequested

->OneToOneEncoder(NettyCodecAdapter).handleDownstream

->OneToOneEncoder(NettyCodecAdapter).doEncode

->NettyCodecAdapter$InternalEncoder.encode 之后請(qǐng)求就發(fā)出去了。

之后又處理dubbo自己的邏輯:看完好像是發(fā)送請(qǐng)求之后把兔,回寫一些數(shù)據(jù)

調(diào)用鏈:

NettyHandler.writeRequested

->AbstractPeer(NettyClient).sent(Channel, msg)

->AbstractChannelHandlerDelegate(MultiMessageHandler).sent(Channel, message)

->HeartBeatHandler.sent(Channel, message)

->WrappedChannelHandler(AllChannelHandler).sent(Channel, message)

->AbstractChannelHandlerDelegate(DecodeHandler).sent(Channel, message)

->HeaderExchangeHandler.sent(Channel, message)

->DubboProtocol.requestHandler.sent:空方法

HeartBeatHandler中增加了更新NettyChannel中的發(fā)送數(shù)據(jù)的時(shí)間戳邏輯

HeaderExchangeHandler中調(diào)用DefaultFuture.sent方法更HeaderExchangeChannel.request時(shí)創(chuàng)建的DefaultFuture中的發(fā)送請(qǐng)求時(shí)間沪伙。

7. 數(shù)據(jù)解密之后如何將結(jié)果返回給調(diào)用線程:

可以debug,也可以通過之前梳理的創(chuàng)建過程來查看調(diào)用鏈县好。

NettyHandler.messageReceived

->AbstractPeer(NettyClient).received(Channel, msg)

->MultiMessageHandler.received(Channel, msg)

->HeartBeatHandler.received(Channel, msg)

->AllChannelHandler.received(Channel, msg){用1.3.1中創(chuàng)建的線程池執(zhí)行ChannelEventRunnable任務(wù)}

->之后的流程只在線程池中執(zhí)行的:

DecodeHandler.received(Channel, message) :反序列化RpcResult

->HeaderExchangeHandler.received(Channel, msg)

->DefaultFuture.received(Channel, Response)

->DefaultFuture.doReceived(Response){設(shè)置結(jié)果围橡,喚醒調(diào)用線程,如果有callback缕贡,執(zhí)行callback邏輯}

->之后是調(diào)用線程邏輯:(Invoker->filter->ClusterInvoker)

DubboInvoker.doInvoke()

->Filter邏輯

->FailoverClusterInvoker.doInvoke(){處理重試邏輯}

->MockClusterInvoker.invoke(){之前說過的mock邏輯}

HeaderExchangeHandler.received:


image.png

HeaderExchangeHandler.handleResponse:


image.png

DefaultFuture.received: 可以看出Request和Response是通過Request.id來對(duì)應(yīng)起來的翁授,生成規(guī)則參見:com.alibaba.dubbo.remoting.exchange.Request.newId()


image.png

8. 部分Filter邏輯:

具體Cosumer端配置了哪些Filter,可以看platform-util的jar中META-INFO/dubbo/com.alibaba.dubbo.rpc.Filter和 dubbo的jar中META-INFO/dubbo/internal/com.alibaba.dubbo.rpc.Filter中的配置

  • ConsumerRecordFilter

  • ResponseConsumerFilter (cat打點(diǎn):Consumer.code和Consumer.success)

  • DubboAppContextFilter (dubboApplication參數(shù)放入attachment)

  • SentinelDubboConsumerFilter(sentinel 限流)

  • MonitorFilter: 如果url中帶了monitor參數(shù)晾咪,則上報(bào)調(diào)用和返回值信息

  • FutureFilter: 支持下面三種事件的觸發(fā)收擦, url中指定的onthrow.method,onreturn.method,oninvoke.method ,具體使用待確定

  • CatTransaction:PigeonCall的cat數(shù)據(jù)上報(bào),接口調(diào)用信息

  • ConsumerContextFilter:調(diào)用前初始化RpcContext中部分信息谍倦,調(diào)用后清理掉RpcContext中的attachments

  • com.yupaopao.platform.util.dubbo.filter.ExceptionFilter:對(duì)返回值類型是Response的調(diào)用塞赂,統(tǒng)一處理異常

參考知識(shí):

  • XxxWrapper裝飾類在dubbo中的實(shí)現(xiàn)方式:https://www.cnblogs.com/killbug/p/7341968.html

  • 各層說明:(來自官網(wǎng))

    • config 配置層:對(duì)外配置接口,以 ServiceConfig, ReferenceConfig 為中心昼蛀,可以直接初始化配置類宴猾,也可以通過 spring 解析配置生成配置類

    • proxy 服務(wù)代理層:服務(wù)接口透明代理,生成服務(wù)的客戶端 Stub 和服務(wù)器端 Skeleton, 以 ServiceProxy 為中心叼旋,擴(kuò)展接口為 ProxyFactory

    • registry 注冊(cè)中心層:封裝服務(wù)地址的注冊(cè)與發(fā)現(xiàn)鳍置,以服務(wù) URL 為中心,擴(kuò)展接口為 RegistryFactory, Registry, RegistryService

    • cluster 路由層:封裝多個(gè)提供者的路由及負(fù)載均衡送淆,并橋接注冊(cè)中心税产,以 Invoker 為中心,擴(kuò)展接口為 Cluster, Directory, Router, LoadBalance

    • monitor 監(jiān)控層:RPC 調(diào)用次數(shù)和調(diào)用時(shí)間監(jiān)控,以 Statistics 為中心辟拷,擴(kuò)展接口為 MonitorFactory, Monitor, MonitorService

    • protocol 遠(yuǎn)程調(diào)用層:封裝 RPC 調(diào)用撞羽,以 Invocation, Result 為中心,擴(kuò)展接口為 Protocol, Invoker, Exporter

    • exchange 信息交換層:封裝請(qǐng)求響應(yīng)模式衫冻,同步轉(zhuǎn)異步诀紊,以 Request, Response 為中心,擴(kuò)展接口為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer

    • transport 網(wǎng)絡(luò)傳輸層:抽象 mina 和 netty 為統(tǒng)一接口隅俘,以 Message 為中心邻奠,擴(kuò)展接口為 Channel, Transporter, Client, Server, Codec

    • serialize 數(shù)據(jù)序列化層:可復(fù)用的一些工具,擴(kuò)展接口為 Serialization, ObjectInput, ObjectOutput, ThreadPool

  • 2.6.2 Dubbo線程模型:


    image.png

參見:

https://segmentfault.com/a/1190000021627474

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末为居,一起剝皮案震驚了整個(gè)濱河市碌宴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒙畴,老刑警劉巖贰镣,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異膳凝,居然都是意外死亡碑隆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蹬音,熙熙樓的掌柜王于貴愁眉苦臉地迎上來上煤,“玉大人,你說我怎么就攤上這事著淆÷ト耄” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵牧抽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我遥赚,道長(zhǎng)扬舒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任凫佛,我火速辦了婚禮讲坎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘愧薛。我一直安慰自己晨炕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布毫炉。 她就那樣靜靜地躺著瓮栗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上费奸,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天弥激,我揣著相機(jī)與錄音,去河邊找鬼愿阐。 笑死微服,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缨历。 我是一名探鬼主播以蕴,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼辛孵!你這毒婦竟也來了丛肮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤觉吭,失蹤者是張志新(化名)和其女友劉穎腾供,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲜滩,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伴鳖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了徙硅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片榜聂。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嗓蘑,靈堂內(nèi)的尸體忽然破棺而出须肆,到底是詐尸還是另有隱情,我是刑警寧澤桩皿,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布豌汇,位于F島的核電站,受9級(jí)特大地震影響泄隔,放射性物質(zhì)發(fā)生泄漏拒贱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一佛嬉、第九天 我趴在偏房一處隱蔽的房頂上張望逻澳。 院中可真熱鬧,春花似錦暖呕、人聲如沸斜做。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓤逼。三九已至笼吟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抛姑,已是汗流浹背赞厕。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留定硝,地道東北人皿桑。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蔬啡,于是被迫代替她去往敵國(guó)和親诲侮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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