部署的模式(四):Serverless和Lambda

前一部分我們提到使用Docker部署應(yīng)用同時(shí)采用12factors原則時(shí)攒钳,服務(wù)器對(duì)于容器來(lái)講,只依賴(lài)它的計(jì)算資源雷滋,那么如果有一個(gè)提供運(yùn)行時(shí)環(huán)境的黑盒功能不撑,讓我們可以直接部署代碼去運(yùn)行文兢,那敢情真的太棒了。其次焕檬,從資源利用率的角度來(lái)說(shuō)姆坚,在單塊架構(gòu)下,如果應(yīng)用的某個(gè)功能模塊需要水平擴(kuò)展实愚,那么整個(gè)應(yīng)用都得和它一起水平擴(kuò)展兼呵,這是一種資源的浪費(fèi)。微服務(wù)架構(gòu)各個(gè)功能模塊分解成單獨(dú)的微服務(wù)腊敲,每個(gè)服務(wù)獨(dú)立部署击喂,獨(dú)立擴(kuò)展,一定程度上降低了資源的浪費(fèi)碰辅。但是我們仔細(xì)看一下懂昂,假設(shè)一個(gè)用戶(hù)管理的微服務(wù),請(qǐng)求/login的endpoint要比請(qǐng)求register的endpoint多的多没宾,如果因?yàn)?code>/login的請(qǐng)求數(shù)量多需要擴(kuò)展凌彬,那么/register得和它一起擴(kuò)展,那/login不禁就要發(fā)問(wèn)榕吼,你/register有何德何能去承受這些資源饿序。同樣的道理可以延伸到Restful endpoint的每個(gè)函數(shù)。

|Restful API | RPM |
|---|---|---|
| /login | 1000 |
| /register | 100 |

如果你的微服務(wù)粒度很小羹蚣,容器和PaaS一定程度上可以解決上面的問(wèn)題,如果我完全不想關(guān)心應(yīng)用服務(wù)器或者容器乱凿,怎么辦顽素。Here comes Serverless。

什么是Serverless


鑒于這是一個(gè)比較新鮮的事物徒蟆,喜歡定義的我司給出了Serverless的概念胁出,Serverless原本有兩個(gè)概念:

  1. 描述嚴(yán)重或完全依賴(lài)第三方應(yīng)用程序/服務(wù)(比如在云平臺(tái))管理服務(wù)器端邏輯和狀態(tài)的應(yīng)用程序。舉個(gè)例子段审,對(duì)于Single Page的應(yīng)用全蝶,我們一般都是使用前后端分離的架構(gòu),前端部署在AWS S3上面寺枉,你可以把S3看成一個(gè)文件存儲(chǔ)服務(wù)抑淫,對(duì)于前端應(yīng)用的部署,只是上傳新的文件而已姥闪,同時(shí)S3的服務(wù)器對(duì)我們來(lái)說(shuō)都是不可見(jiàn)的始苇,我們也不用擔(dān)心任何的維護(hù)壓力,也不用擔(dān)心如何擴(kuò)展(大多數(shù)情況下)筐喳。
  2. 開(kāi)發(fā)者實(shí)現(xiàn)的服務(wù)器端的應(yīng)用邏輯(微服務(wù)甚至粒度更小的服務(wù))以event trigger的方式運(yùn)行在無(wú)狀態(tài)的臨時(shí)的容器中催式,并且這些容器函喉、計(jì)算資源都是由第三方去管理。你也可以稱(chēng)之為FaaS(函數(shù)即服務(wù))荣月,AWS的Lambda服務(wù)就是目前最好的一個(gè)實(shí)現(xiàn)管呵。

所以可以看到核心的點(diǎn)在于

  1. 除了代碼外全托管,無(wú)任何服務(wù)器維護(hù)哺窄、擴(kuò)展的負(fù)擔(dān)
  2. 更小的實(shí)現(xiàn)粒度撇寞,微服務(wù)粒度可以小到一個(gè)Restful的endpoint,這里可以小到一個(gè)函數(shù)

Serverless的使用場(chǎng)景


Serverless的使用場(chǎng)景堂氯,目前能看到的有三種:

  1. 傳統(tǒng)的MVC架構(gòu)的應(yīng)用
  2. 事件驅(qū)動(dòng)的應(yīng)用
  3. 定時(shí)任務(wù)

其中我們目前主要在2蔑担、3的業(yè)務(wù)場(chǎng)景下使用Serverless也就是AWS Lambda。而對(duì)于1咽白,我們很早就進(jìn)行了嘗試啤握,但是更多的是純技術(shù)性質(zhì)、或者是hackday的spike晶框,并沒(méi)有在核心業(yè)務(wù)上去使用排抬。最主要的考慮是它耦合了太多的AWS服務(wù),失去了可移植性授段,而非不能做蹲蒲。

MVC架構(gòu)的應(yīng)用


假設(shè)我們有一個(gè)MVC架構(gòu)的單頁(yè)面應(yīng)用寵物店,那么當(dāng)瀏覽器請(qǐng)求服務(wù)器時(shí)侵贵,它會(huì)按照請(qǐng)求届搁,從數(shù)據(jù)庫(kù)讀取具體的數(shù)據(jù),render html窍育,然后返回給瀏覽器卡睦。


pet store
pet store

在這個(gè)例子中,我們需要維護(hù)寵物店應(yīng)用的服務(wù)器以及數(shù)據(jù)庫(kù)服務(wù)器漱抓,以及其對(duì)應(yīng)代碼表锻、schema等,還有如何部署乞娄、擴(kuò)展的問(wèn)題瞬逊。但是如果切換到Serverless架構(gòu)的話,在AWS上看起來(lái)會(huì)是這樣的:

multi tier arch
  1. 對(duì)寵物店應(yīng)用做前后端分離仪或,前端部署在S3上面确镊,后端的邏輯拆分到函數(shù)級(jí)別,部署在Lambda上溶其,狀態(tài)和數(shù)據(jù)保存在Dynamodb中(Dynamodb是一個(gè)全托管的NoSQL數(shù)據(jù)庫(kù))骚腥,API-Gateway可以作為http proxy以及single sign-on驗(yàn)證入口
  2. 有請(qǐng)求到來(lái)時(shí),首先返回前端的靜態(tài)頁(yè)面瓶逃,然后根據(jù)其中請(qǐng)求的API束铭,由API-Gateway經(jīng)過(guò)驗(yàn)證(Oauth或者SAML)后trigger對(duì)應(yīng)的Lambda函數(shù)廓块,比如/search對(duì)應(yīng)的是Search Function。在函數(shù)中它會(huì)訪問(wèn)Dynamodb契沫,獲取數(shù)據(jù)并返回带猴。

我們?cè)谶@里面用到的服務(wù)API-GatewayS3懈万、Lambda以及Dynamodb都是全托管的服務(wù)拴清,他們基本上都可以通過(guò)Cloudformation實(shí)現(xiàn)基礎(chǔ)設(shè)施即代碼以及部署(某些region不能使用cloudformation去管理API-Gateway,S3的部署不需要使用cloudformation会通,同樣Dynamodb也不存在部署的問(wèn)題)口予,維護(hù)的壓力很小,水平擴(kuò)展由這些服務(wù)自己實(shí)現(xiàn)涕侈,比如Dynamodb沪停、以及Lambda,S3可以通過(guò)CDN去提升性能裳涛。同時(shí)我們可以發(fā)現(xiàn)木张,原本單塊架構(gòu)下部署在一起的三層結(jié)構(gòu),現(xiàn)在被徹底的分開(kāi)部署了端三。是不是非常的amazing(Amazoning)舷礼。

這樣的好處在于:

  1. 不需要為一個(gè)一直在線的服務(wù)器付費(fèi),以及維護(hù)郊闯、升級(jí)
  2. 按照請(qǐng)求的數(shù)量去付費(fèi)妻献,而不是服務(wù)器類(lèi)型、運(yùn)行的數(shù)量去付費(fèi)
  3. 完全不用去關(guān)心水平擴(kuò)展的問(wèn)題
  4. 部署非常簡(jiǎn)單虚婿,而且速度都比較快

使用這樣的架構(gòu)對(duì)于我們這種長(zhǎng)期堅(jiān)持微服務(wù)(粒度到一個(gè)Restful endpoint一個(gè)API)旋奢、前后端分離、持續(xù)交付等實(shí)踐的公司來(lái)講是比較容易的然痊,到現(xiàn)在還沒(méi)有在核心業(yè)務(wù)上使用主要是個(gè)政治問(wèn)題,也許過(guò)幾個(gè)月CTO覺(jué)得這個(gè)也不錯(cuò)屉符,可能立馬就采用了剧浸。
同時(shí),我們可以發(fā)現(xiàn)矗钟,傳統(tǒng)的只做部署或者服務(wù)器維護(hù)的運(yùn)維人員在這個(gè)架構(gòu)下已經(jīng)沒(méi)有什么位置了……唆香。所以也許DevOps的下一步就真的是NoOps了……。

事件驅(qū)動(dòng)的架構(gòu)(Event-Driven Architecture)


事件驅(qū)動(dòng)的架構(gòu)比如Serverless Architectures提到的這個(gè)應(yīng)用場(chǎng)景吨艇,廣告服務(wù)收集到信息發(fā)布到消息系統(tǒng)中牵舱,然后通知注冊(cè)到具體topic下的click processor服務(wù)著觉,進(jìn)行處理,保存到數(shù)據(jù)庫(kù)校坑。

event driven
event driven

其中,click processor服務(wù)可能得運(yùn)行在單獨(dú)的服務(wù)器上谒撼,需要一直在線,同時(shí),如果系統(tǒng)中的消息/事件數(shù)量突然增多凸舵,對(duì)于click processor的水平擴(kuò)展,只能是基于容器甚至于服務(wù)器級(jí)別失尖,雖然這是可以做到的啊奄,但是對(duì)于整個(gè)架構(gòu)的設(shè)計(jì)和使用的服務(wù)要求比較高,同時(shí)掀潮,資源浪費(fèi)也是不可避免的(比如菇夸,可以通過(guò)ASG去監(jiān)控click processor的服務(wù)器,如果它的CPU使用率過(guò)高仪吧,就啟動(dòng)新的click processor服務(wù)器去處理庄新,使用容器的方式同理)。
使用無(wú)服務(wù)架構(gòu)的方式的話就變成下面這樣:

kinesis and lambda
  1. 用戶(hù)點(diǎn)擊廣告服務(wù)時(shí)邑商,其請(qǐng)求通過(guò)http proxy寫(xiě)入到Kinesis中
  2. click processor的Lambda函數(shù)上可以綁定要處理的Kinesis Stream摄咆,同時(shí)指定每次處理的batch大小
  3. Lambda輪詢(xún)Kinesis Stream,有新的事件則立即啟動(dòng)click processor函數(shù)去處理人断。

這樣就不需要一個(gè)一直在線的服務(wù)去poll消息系統(tǒng)吭从,同樣如果需要擴(kuò)展的話可以通過(guò)在click processor函數(shù)中去invoke其它的click process函數(shù)來(lái)實(shí)現(xiàn)。

Lambda也可以用push模型恶迈,比如涩金,它可以將事件源設(shè)定為S3的某個(gè)bucket中文件上傳,當(dāng)有新的文件上傳暇仲,它會(huì)觸發(fā)該Lambda函數(shù)來(lái)處理新上傳的文件步做。除了S3之外,SNS奈附、Congnito等服務(wù)也可以以push的方式去觸發(fā)Lambda函數(shù)全度。

定時(shí)任務(wù)(Scheduled Job)


定時(shí)任務(wù)在企業(yè)里面是一個(gè)很常見(jiàn)的場(chǎng)景,我見(jiàn)過(guò)有一些把定時(shí)任務(wù)和應(yīng)用放在一個(gè)服務(wù)器上跑的斥滤,恕我直言這種方式不可取将鸵,首先它無(wú)法自己scale,處理的不好還會(huì)導(dǎo)致服務(wù)器的性能問(wèn)題佑颇,同時(shí)你在部署的時(shí)候可能還得考慮它顶掉。比如每周一企業(yè)都會(huì)給用戶(hù)發(fā)一個(gè)簡(jiǎn)報(bào)郵件,如果你用定時(shí)任務(wù)來(lái)跑挑胸,可能得像下面這樣:

sheduled email sending with cron
  1. Cron服務(wù)器上郵件發(fā)送cron定時(shí)運(yùn)行痒筒,首先從數(shù)據(jù)庫(kù)獲取要發(fā)送的郵箱的list
  2. 該cron將郵箱發(fā)送給郵件發(fā)送服務(wù)集群,集群前面的loadbalancer可以將請(qǐng)求平均到每個(gè)服務(wù)器上

這種方式除了資源的浪費(fèi)還有水平擴(kuò)展成本高之外,還有就是得維護(hù)額外的cron服務(wù)器以及代碼簿透。以前的慣例是把所有的cron任務(wù)放在專(zhuān)門(mén)的一臺(tái)/多臺(tái)機(jī)器上運(yùn)行移袍,維護(hù)的成本很高,而且服務(wù)器出問(wèn)題萎战,很難定位到原因咐容,即便你把它們的運(yùn)行日志都上傳到中心化服務(wù)器中。
如果使用Lambda的話就簡(jiǎn)單了很多:

sheduling with lambda
  1. 首先email functionCloudWatchCloudWatch Events - Schedule作為事件源蚂维,并選定執(zhí)行該函數(shù)的時(shí)間戳粒,可以用cron的標(biāo)準(zhǔn)格式去定義
  2. 其次在email function運(yùn)行時(shí),它會(huì)從數(shù)據(jù)庫(kù)中獲取列表虫啥,從水平擴(kuò)展的角度考慮蔚约,你可以把列表分為n份,在這個(gè)函數(shù)中去invoke多個(gè)(成千上萬(wàn)個(gè))真正的郵件發(fā)送函數(shù)去處理涂籽。實(shí)際上要做的事情很簡(jiǎn)單苹祟,像下面這樣:
    var aws = require('aws-sdk');
    var lambda = new aws.Lambda({
        region: 'ap-southeast-2' 
    });
    #fake the mail list
    var mailers = {mailer1: "someone@gmail.com", mailer2: "somebody@qq.com"};

    console.log("call email sending function");
    lambda.invoke({
        FunctionName: 'arn:aws:lambda:ap-southeast-2:AWS_ACCOUNT_ID:function:email-sending-function',
        Payload: JSON.stringify(mailers) 
            }, 
        function(error, data) {
                if (error) {
                        context.done('error', error);
                        console.log("invoke failed");
                        }
                    if(data.Payload) {
             context.succeed(data.Payload);
             console.log("invoke success");
        }
    });

這樣前面所說(shuō)的痛點(diǎn)基本都解決了,沒(méi)記錯(cuò)的話我們目前使用Lambda最多的場(chǎng)景就是定時(shí)任務(wù)评雌,比如ETL树枫。

關(guān)于AWS Lambda


目前Lambda只支持三種語(yǔ)言的運(yùn)行時(shí)環(huán)境:

  1. java
  2. nodejs
  3. python

其中nodejs和python可以直接部署代碼,而java是部署編譯后的jar包景东。雖然看上去支持的語(yǔ)言不多砂轻,但實(shí)際上如Ruby語(yǔ)言,可以通過(guò)JRuby去開(kāi)發(fā)部署斤吐,同理還有基于JVM的scala等語(yǔ)言搔涝。

Lambda函數(shù)運(yùn)行的容器最多只能保留5分鐘,所以一定要保證函數(shù)在5分鐘內(nèi)執(zhí)行完畢和措。同時(shí)不要在上保存狀態(tài)庄呈,狀態(tài)可以保存在S3或者數(shù)據(jù)庫(kù)中。

Lambda的部署方式有三種

  1. 如果沒(méi)有很多外部依賴(lài)派阱,直接上傳代碼即可
  2. 如果有外部依賴(lài)诬留,可以打包成zip文件上傳
  3. 將打包后的文件上傳到s3上,通過(guò)cloudformation去做部署

同時(shí)贫母,Lambda支持的Alias功能可以很容易讓我們實(shí)現(xiàn)藍(lán)綠部署故响。

Lambda的監(jiān)控比較粗粒度,AWS沒(méi)有提供它在容器中運(yùn)行的狀況監(jiān)控颁独,雖然這點(diǎn)沒(méi)有太大的必要,你也可以通過(guò)比較hack的方式伪冰,比如用newrelic來(lái)實(shí)現(xiàn)對(duì)它的監(jiān)控誓酒。

出錯(cuò)報(bào)警也是需要注意的地方,可以通過(guò)創(chuàng)建cloudwatch alarm監(jiān)控錯(cuò)誤數(shù)量,出問(wèn)題時(shí)報(bào)警靠柑,當(dāng)然對(duì)應(yīng)的你代碼得在錯(cuò)誤時(shí)拋出異常寨辩。

Lambda的日志是傳到cloudwatch中的,想要傳到日志服務(wù)器中得需要些額外的步驟歼冰。還有一種方式就是將日志作為時(shí)間寫(xiě)入到Kinesis Stream中靡狞,以統(tǒng)一的方式寫(xiě)入日志服務(wù)器,同時(shí)可以在檢測(cè)到很多錯(cuò)誤返回時(shí)報(bào)警隔嫡。

目前Lambda最大的問(wèn)題在于測(cè)試甸怕,對(duì)業(yè)務(wù)邏輯做單元測(cè)試自然是沒(méi)有問(wèn)題,但是集成的話會(huì)稍微比較麻煩腮恩,每個(gè)函數(shù)都得有一套對(duì)應(yīng)的測(cè)試環(huán)境梢杭,比如關(guān)聯(lián)的數(shù)據(jù)庫(kù)表、S3 bucket秸滴、Kinesis Stream等武契,維護(hù)的成本比較高。調(diào)試也只能部署上去后在線調(diào)試荡含,想想都覺(jué)得刺激咒唆。有了Lambda,線上改代碼不是夢(mèng)释液。突然間就想起來(lái)P神以前和我結(jié)對(duì)調(diào)試問(wèn)題的時(shí)候說(shuō)的一句話全释,"whenever I debug, I debug in production"。現(xiàn)在想想還是P神高均澳。當(dāng)然這些都是trade off恨溜,看技術(shù)層面的選擇了。

在有一次的AWS Summit上找前,有一位臺(tái)灣的朋友提到說(shuō)Lambda背后實(shí)現(xiàn)的原理主要有兩點(diǎn):

  1. 啟動(dòng)艸快的定制化容器,并且可能會(huì)有部署你代碼的standby的容器
  2. 高速網(wǎng)絡(luò)

實(shí)際上我們回頭看它的使用場(chǎng)景糟袁,用現(xiàn)有的工具,比如Mesos等躺盛,我們?cè)诒镜鼐涂梢詫?shí)現(xiàn)event driven或者定時(shí)任務(wù)的場(chǎng)景项戴,因?yàn)樗鼈儗?duì)于時(shí)效性的要求不高。但是對(duì)于實(shí)時(shí)的業(yè)務(wù)場(chǎng)景槽惫,需要立刻返回的就不好做了周叮。不過(guò)也許等Unikernel成熟了,這點(diǎn)就不再是問(wèn)題了界斜。

總結(jié)


不說(shuō)了仿耽,得趕緊把我創(chuàng)建的lambda函數(shù)刪掉,免的下個(gè)月賬單??各薇。

Reference


  1. Serverless Architectures
  2. AWS Lambda
  3. Event Driven Architecture
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末项贺,一起剝皮案震驚了整個(gè)濱河市君躺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌开缎,老刑警劉巖棕叫,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異奕删,居然都是意外死亡俺泣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)完残,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)伏钠,“玉大人,你說(shuō)我怎么就攤上這事坏怪”慈螅” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵铝宵,是天一觀的道長(zhǎng)打掘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鹏秋,這世上最難降的妖魔是什么尊蚁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮侣夷,結(jié)果婚禮上横朋,老公的妹妹穿的比我還像新娘。我一直安慰自己百拓,他們只是感情好琴锭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著衙传,像睡著了一般决帖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蓖捶,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天地回,我揣著相機(jī)與錄音,去河邊找鬼俊鱼。 笑死刻像,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的并闲。 我是一名探鬼主播细睡,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼帝火!你這毒婦竟也來(lái)了纹冤?” 一聲冷哼從身側(cè)響起洒宝,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萌京,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宏浩,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡知残,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了比庄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片求妹。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖佳窑,靈堂內(nèi)的尸體忽然破棺而出制恍,到底是詐尸還是另有隱情,我是刑警寧澤神凑,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布净神,位于F島的核電站,受9級(jí)特大地震影響溉委,放射性物質(zhì)發(fā)生泄漏鹃唯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一瓣喊、第九天 我趴在偏房一處隱蔽的房頂上張望坡慌。 院中可真熱鬧,春花似錦藻三、人聲如沸洪橘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)熄求。三九已至,卻和暖如春岖寞,著一層夾襖步出監(jiān)牢的瞬間抡四,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工仗谆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留指巡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓隶垮,卻偏偏與公主長(zhǎng)得像藻雪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狸吞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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