15 - 架構(gòu)設(shè)計(jì) - 數(shù)據(jù)庫之分庫分表

分庫分表

讀寫分離分散了數(shù)據(jù)庫讀寫操作的壓力钉迷,但沒有分散存儲(chǔ)壓力,當(dāng)數(shù)據(jù)量達(dá)到千萬甚至上億條的時(shí)候,單臺(tái)數(shù)據(jù)庫服務(wù)器的存儲(chǔ)能力會(huì)成為系統(tǒng)的瓶頸,主要體現(xiàn)在這幾個(gè)方面:

  • 數(shù)據(jù)量太大殴蹄,讀寫的性能會(huì)下降,即使有索引猾担,索引也會(huì)變得很大饶套,性能同樣會(huì)下降
  • 數(shù)據(jù)文件會(huì)變得很大,數(shù)據(jù)庫備份和恢復(fù)需要耗費(fèi)很長時(shí)間
  • 數(shù)據(jù)文件越大垒探,極端情況下丟失數(shù)據(jù)的風(fēng)險(xiǎn)越高(例如,機(jī)房火災(zāi)導(dǎo)致數(shù)據(jù)庫主備機(jī)都發(fā)生故障)

基于上述原因怠李,單個(gè)數(shù)據(jù)庫服務(wù)器存儲(chǔ)的數(shù)據(jù)量不能太大圾叼,需要控制在一定的范圍內(nèi)。為了滿足業(yè)務(wù)數(shù)據(jù)存儲(chǔ)的需求捺癞,就需要將存儲(chǔ)分散到多臺(tái)數(shù)據(jù)庫服務(wù)器上

  • 常見的分散存儲(chǔ)的方法“分庫分表”夷蚊,其中包括“分庫”和“分表”兩大類

業(yè)務(wù)分庫

  • 業(yè)務(wù)分庫指的是按照業(yè)務(wù)模塊將數(shù)據(jù)分散到不同的數(shù)據(jù)庫服務(wù)器

    • 一個(gè)簡單的電商網(wǎng)站,包括用戶髓介、商品惕鼓、訂單三個(gè)業(yè)務(wù)模塊
    • 我們可以將用戶數(shù)據(jù)、商品數(shù)據(jù)唐础、訂單數(shù)據(jù)分開放到三臺(tái)不同的數(shù)據(jù)庫服務(wù)器上箱歧,而不是將所有數(shù)據(jù)都放在一臺(tái)數(shù)據(jù)庫服務(wù)器上
    業(yè)務(wù)分庫
  • 雖然業(yè)務(wù)分庫能夠分散存儲(chǔ)和訪問壓力,但同時(shí)也帶來了新的問題:

  1. join 操作問題
    • 業(yè)務(wù)分庫后一膨,原本在同一個(gè)數(shù)據(jù)庫中的表分散到不同數(shù)據(jù)庫中呀邢,導(dǎo)致無法使用 SQL 的 join 查詢
    • 例如:“查詢購買了化妝品的用戶中女性用戶的列表”這個(gè)功能,雖然訂單數(shù)據(jù)中有用戶的 ID 信息豹绪,但是用戶的性別數(shù)據(jù)在用戶數(shù)據(jù)庫中
    • 如果在同一個(gè)庫中价淌,簡單的 join 查詢就能完成
    • 現(xiàn)在數(shù)據(jù)分散在兩個(gè)不同的數(shù)據(jù)庫中,無法做 join 查詢,只能采取先從訂單數(shù)據(jù)庫中查詢購買了化妝品的用戶 ID 列表蝉衣,然后再到用戶數(shù)據(jù)庫中查詢這批用戶 ID 中的女性用戶列表括尸,這樣實(shí)現(xiàn)就比簡單的 join 查詢要復(fù)雜一些
  2. 事務(wù)問題
    • 原本在同一個(gè)數(shù)據(jù)庫中不同的表可以在同一個(gè)事務(wù)中修改,業(yè)務(wù)分庫后病毡,表分散到不同的數(shù)據(jù)庫中濒翻,無法通過事務(wù)統(tǒng)一修改。雖然數(shù)據(jù)庫廠商提供了一些分布式事務(wù)的解決方案(例如剪验,MySQL 的 XA)肴焊,但性能實(shí)在太低,與高性能存儲(chǔ)的目標(biāo)是相違背的
    • 例如功戚,用戶下訂單的時(shí)候需要扣商品庫存娶眷,如果訂單數(shù)據(jù)和商品數(shù)據(jù)在同一個(gè)數(shù)據(jù)庫中,我們可以使用事務(wù)來保證扣減商品庫存和生成訂單的操作要么都成功要么都失敗啸臀,但分庫后就無法使用數(shù)據(jù)庫事務(wù)了届宠,需要業(yè)務(wù)程序自己來模擬實(shí)現(xiàn)事務(wù)的功能
    • 例如,先扣商品庫存乘粒,扣成功后生成訂單豌注,如果因?yàn)橛唵螖?shù)據(jù)庫異常導(dǎo)致生成訂單失敗,業(yè)務(wù)程序又需要將商品庫存加上灯萍;而如果因?yàn)闃I(yè)務(wù)程序自己異常導(dǎo)致生成訂單失敗轧铁,則商品庫存就無法恢復(fù)了,需要人工通過日志等方式來手工修復(fù)庫存異常
  3. 成本問題
    • 業(yè)務(wù)分庫同時(shí)也帶來了成本的代價(jià)旦棉,本來 1 臺(tái)服務(wù)器搞定的事情齿风,現(xiàn)在要 3 臺(tái),如果考慮備份绑洛,那就是 2 臺(tái)變成了 6 臺(tái)
    • 基于上述原因救斑,對(duì)于小公司初創(chuàng)業(yè)務(wù),并不建議一開始就這樣拆分真屯,主要有幾個(gè)原因:
    • 初創(chuàng)業(yè)務(wù)存在很大的不確定性脸候,業(yè)務(wù)不一定能發(fā)展起來,業(yè)務(wù)開始的時(shí)候并沒有真正的存儲(chǔ)和訪問壓力绑蔫,業(yè)務(wù)分庫并不能為業(yè)務(wù)帶來價(jià)值
    • 業(yè)務(wù)分庫后运沦,表之間的 join 查詢、數(shù)據(jù)庫事務(wù)無法簡單實(shí)現(xiàn)了
    • 業(yè)務(wù)分庫后配深,因?yàn)椴煌臄?shù)據(jù)要讀寫不同的數(shù)據(jù)庫茶袒,代碼中需要增加根據(jù)數(shù)據(jù)類型映射到不同數(shù)據(jù)庫的邏輯,增加了工作量凉馆。而業(yè)務(wù)初創(chuàng)期間最重要的是快速實(shí)現(xiàn)薪寓、快速驗(yàn)證亡资,業(yè)務(wù)分庫會(huì)拖慢業(yè)務(wù)節(jié)奏

分表

將不同業(yè)務(wù)數(shù)據(jù)分散存儲(chǔ)到不同的數(shù)據(jù)庫服務(wù)器,能夠支撐百萬甚至千萬用戶規(guī)模的業(yè)務(wù)向叉,但如果業(yè)務(wù)繼續(xù)發(fā)展锥腻,同一業(yè)務(wù)的單表數(shù)據(jù)也會(huì)達(dá)到單臺(tái)數(shù)據(jù)庫服務(wù)器的處理瓶頸。例如母谎,淘寶的幾億用戶數(shù)據(jù)瘦黑,如果全部存放在一臺(tái)數(shù)據(jù)庫服務(wù)器的一張表中,肯定是無法滿足性能要求的奇唤,此時(shí)就需要對(duì)單表數(shù)據(jù)進(jìn)行拆分

  • 單表數(shù)據(jù)拆分有兩種方式:垂直分表和水平分表
分表的兩種方式
  • 垂直拆分:示意圖中的垂直切分幸斥,會(huì)把表切分為兩個(gè)表,一個(gè)表包含 ID咬扇、name甲葬、age、sex 列懈贺,另外一個(gè)表包含 ID经窖、nickname、description 列
  • 水平拆分:示意圖中的水平切分梭灿,會(huì)把表分為兩個(gè)表画侣,兩個(gè)表都包含 ID、name堡妒、age配乱、sex、nickname皮迟、description 列搬泥,但是一個(gè)表包含的是 ID 從 1 到 999999 的行數(shù)據(jù),另一個(gè)表包含的是 ID 從 1000000 到 9999999 的行數(shù)據(jù)
  • 架構(gòu)設(shè)計(jì)過程中并不局限切分的次數(shù)万栅,可以切兩次,也可以切很多次西疤,就像切蛋糕一樣烦粒,可以切很多刀
  • 單表進(jìn)行切分后,是否要將切分后的多個(gè)表分散在不同的數(shù)據(jù)庫服務(wù)器中代赁,可以根據(jù)實(shí)際的切分效果來確定扰她,并不強(qiáng)制要求單表切分為多表后一定要分散到不同數(shù)據(jù)庫中
  • 單表切分為多表后,新的表即使在同一個(gè)數(shù)據(jù)庫服務(wù)器中芭碍,也可能帶來可觀的性能提升徒役,如果性能能夠滿足業(yè)務(wù)要求,是可以不拆分到多臺(tái)數(shù)據(jù)庫服務(wù)器的窖壕,畢竟我們?cè)谏厦鏄I(yè)務(wù)分庫的內(nèi)容看到業(yè)務(wù)分庫也會(huì)引入很多復(fù)雜性的問題
  • 如果單表拆分為多表后忧勿,單臺(tái)服務(wù)器依然無法滿足性能要求杉女,那就不得不再次進(jìn)行業(yè)務(wù)分庫的設(shè)計(jì)了
  • 分表能夠有效地分散存儲(chǔ)壓力和帶來性能提升,但和分庫一樣鸳吸,也會(huì)引入各種復(fù)雜性:
  1. 垂直分表
  • 垂直分表適合將表中某些不常用且占了大量空間的列拆分出去
  • 垂直分表引入的復(fù)雜性主要體現(xiàn)在表操作的數(shù)量要增加
  1. 水平分表
  • 水平分表適合表行數(shù)特別大的表熏挎,有的公司要求單表行數(shù)超過 5000 萬就必須進(jìn)行分表,這個(gè)數(shù)字可以作為參考晌砾,但并不是絕對(duì)標(biāo)準(zhǔn)坎拐,關(guān)鍵還是要看表的訪問性能
    • 對(duì)于一些比較復(fù)雜的表,可能超過 1000 萬就要分表了
    • 而對(duì)于一些簡單的表养匈,即使存儲(chǔ)數(shù)據(jù)超過 1 億行哼勇,也可以不分表
  • 水平分表相比垂直分表,會(huì)引入更多的復(fù)雜性呕乎,主要表現(xiàn)在下面幾個(gè)方面:
    • 路由:
    • 水平分表后积担,某條數(shù)據(jù)具體屬于哪個(gè)切分后的子表,需要增加路由算法進(jìn)行計(jì)算楣嘁,這個(gè)算法會(huì)引入一定的復(fù)雜性
    • 常見的路由算法有:
      • 范圍路由:選取有序的數(shù)據(jù)列(例如磅轻,整形、時(shí)間戳等)作為路由的條件逐虚,不同分段分散到不同的數(shù)據(jù)庫表中聋溜。以最常見的用戶 ID 為例,路由算法可以按照 1000000 的范圍大小進(jìn)行分段叭爱,1 ~ 999999 放到數(shù)據(jù)庫 1 的表中撮躁,1000000 ~ 1999999 放到數(shù)據(jù)庫 2 的表中,以此類推
        • 范圍路由設(shè)計(jì)的復(fù)雜點(diǎn)主要體現(xiàn)在分段大小的選取上买雾,分段太小會(huì)導(dǎo)致切分后子表數(shù)量過多把曼,增加維護(hù)復(fù)雜度;分段太大可能會(huì)導(dǎo)致單表依然存在性能問題漓穿,一般建議分段大小在 100 萬至 2000 萬之間嗤军,具體需要根據(jù)業(yè)務(wù)選取合適的分段大小
        • 范圍路由的優(yōu)點(diǎn)是可以隨著數(shù)據(jù)的增加平滑地?cái)U(kuò)充新的表。例如晃危,現(xiàn)在的用戶是 100 萬叙赚,如果增加到 1000 萬,只需要增加新的表就可以了僚饭,原有的數(shù)據(jù)不需要?jiǎng)?/li>
        • 范圍路由的一個(gè)比較隱含的缺點(diǎn)是分布不均勻震叮,假如按照 1000 萬來進(jìn)行分表,有可能某個(gè)分段實(shí)際存儲(chǔ)的數(shù)據(jù)量只有 1000 條鳍鸵,而另外一個(gè)分段實(shí)際存儲(chǔ)的數(shù)據(jù)量有 900 萬條
      • Hash 路由:選取某個(gè)列(或者某幾個(gè)列組合也可以)的值進(jìn)行 Hash 運(yùn)算苇瓣,然后根據(jù) Hash 結(jié)果分散到不同的數(shù)據(jù)庫表中。同樣以用戶 ID 為例偿乖,假如我們一開始就規(guī)劃了 10 個(gè)數(shù)據(jù)庫表击罪,路由算法可以簡單地用 user_id % 10 的值來表示數(shù)據(jù)所屬的數(shù)據(jù)庫表編號(hào)哲嘲,ID 為 985 的用戶放到編號(hào)為 5 的子表中,ID 為 10086 的用戶放到編號(hào)為 6 的字表中
        • Hash 路由設(shè)計(jì)的復(fù)雜點(diǎn)主要體現(xiàn)在初始表數(shù)量的選取上外邓,表數(shù)量太多維護(hù)比較麻煩撤蚊,表數(shù)量太少又可能導(dǎo)致單表性能存在問題。而用了 Hash 路由后损话,增加子表數(shù)量是非常麻煩的侦啸,所有數(shù)據(jù)都要重分布
        • Hash 路由的優(yōu)缺點(diǎn)和范圍路由基本相反,Hash 路由的優(yōu)點(diǎn)是表分布比較均勻丧枪,缺點(diǎn)是擴(kuò)充新的表很麻煩光涂,所有數(shù)據(jù)都要重分布
      • 配置路由:配置路由就是路由表,用一張獨(dú)立的表來記錄路由信息拧烦。同樣以用戶 ID 為例忘闻,我們新增一張 user_router 表,這個(gè)表包含 user_id 和 table_id 兩列恋博,根據(jù) user_id 就可以查詢對(duì)應(yīng)的 table_id
        • 配置路由設(shè)計(jì)簡單齐佳,使用起來非常靈活,尤其是在擴(kuò)充表的時(shí)候债沮,只需要遷移指定的數(shù)據(jù)炼吴,然后修改路由表就可以了
        • 配置路由的缺點(diǎn)就是必須多查詢一次,會(huì)影響整體性能疫衩;而且路由表本身如果太大(例如硅蹦,幾億條數(shù)據(jù)),性能同樣可能成為瓶頸闷煤,如果我們?cè)俅螌⒙酚杀矸謳旆直硗郏瑒t又面臨一個(gè)死循環(huán)式的路由算法選擇問題
    • join 操作:
    • 水平分表后,數(shù)據(jù)分散在多個(gè)表中鲤拿,如果需要與其他表進(jìn)行 join 查詢假褪,需要在業(yè)務(wù)代碼或者數(shù)據(jù)庫中間件中進(jìn)行多次 join 查詢,然后將結(jié)果合并
    • count() 操作
    • 水平分表后近顷,雖然物理上數(shù)據(jù)分散到多個(gè)表中生音,但某些業(yè)務(wù)邏輯上還是會(huì)將這些表當(dāng)作一個(gè)表來處理。例如幕庐,獲取記錄總數(shù)用于分頁或者展示久锥,水平分表前用一個(gè) count() 就能完成的操作家淤,在分表后就沒那么簡單了异剥。常見的處理方式有下面兩種:
      • count() 相加:具體做法是在業(yè)務(wù)代碼或者數(shù)據(jù)庫中間件中對(duì)每個(gè)表進(jìn)行 count() 操作,然后將結(jié)果相加絮重。這種方式實(shí)現(xiàn)簡單冤寿,缺點(diǎn)就是性能比較低歹苦。例如,水平分表后切分為 20 張表督怜,則要進(jìn)行 20 次 count(*) 操作殴瘦,如果串行的話,可能需要幾秒鐘才能得到結(jié)果
      • 記錄數(shù)表:具體做法是新建一張表号杠,假如表名為“記錄數(shù)表”蚪腋,包含 table_name、row_count 兩個(gè)字段姨蟋,每次插入或者刪除子表數(shù)據(jù)成功后屉凯,都更新“記錄數(shù)表”
      • 這種方式獲取表記錄數(shù)的性能要大大優(yōu)于 count() 相加的方式,因?yàn)橹恍枰淮魏唵尾樵兙涂梢垣@取數(shù)據(jù)
      • 缺點(diǎn)是復(fù)雜度增加不少眼溶,對(duì)子表的操作要同步操作“記錄數(shù)表”悠砚,如果有一個(gè)業(yè)務(wù)邏輯遺漏了,數(shù)據(jù)就會(huì)不一致堂飞;且針對(duì)“記錄數(shù)表”的操作和針對(duì)子表的操作無法放在同一事務(wù)中進(jìn)行處理灌旧,異常的情況下會(huì)出現(xiàn)操作子表成功了而操作記錄數(shù)表失敗,同樣會(huì)導(dǎo)致數(shù)據(jù)不一致
      • 記錄數(shù)表的方式也增加了數(shù)據(jù)庫的寫壓力绰筛,因?yàn)槊看吾槍?duì)子表的 insert 和 delete 操作都要 update 記錄數(shù)表枢泰,所以對(duì)于一些不要求記錄數(shù)實(shí)時(shí)保持精確的業(yè)務(wù),也可以通過后臺(tái)定時(shí)更新記錄數(shù)表别智。定時(shí)更新實(shí)際上就是“count() 相加”和“記錄數(shù)表”的結(jié)合宗苍,即定時(shí)通過 count() 相加計(jì)算表的記錄數(shù),然后更新記錄數(shù)表中的數(shù)據(jù)
    • order by 操作
    • 水平分表后薄榛,數(shù)據(jù)分散到多個(gè)子表中讳窟,排序操作無法在數(shù)據(jù)庫中完成,只能由業(yè)務(wù)代碼或者數(shù)據(jù)庫中間件分別查詢每個(gè)子表中的數(shù)據(jù)敞恋,然后匯總進(jìn)行排序

實(shí)現(xiàn)方法

  • 和數(shù)據(jù)庫讀寫分離類似丽啡,分庫分表具體的實(shí)現(xiàn)方式也是“程序代碼封裝”和“中間件封裝”,但實(shí)現(xiàn)會(huì)更復(fù)雜
  • 讀寫分離實(shí)現(xiàn)時(shí)只要識(shí)別 SQL 操作是讀操作還是寫操作硬猫,通過簡單的判斷 SELECT补箍、UPDATE、INSERT啸蜜、DELETE 幾個(gè)關(guān)鍵字就可以做到坑雅,而分庫分表的實(shí)現(xiàn)除了要判斷操作類型外,還要判斷 SQL 中具體需要操作的表衬横、操作函數(shù)(例如 count 函數(shù))裹粤、order by、group by 操作等
  • 根據(jù)不同的操作進(jìn)行不同的處理蜂林。例如 order by 操作遥诉,需要先從多個(gè)庫查詢到各個(gè)庫的數(shù)據(jù)拇泣,然后再重新 order by 才能得到最終的結(jié)果

小結(jié)

本文講解了高性能數(shù)據(jù)庫集群的分庫分表架構(gòu),包括業(yè)務(wù)分庫產(chǎn)生的問題和分表的兩種方式及其帶來的復(fù)雜度矮锈,希望對(duì)你有所幫助

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末霉翔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子苞笨,更是在濱河造成了極大的恐慌债朵,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瀑凝,死亡現(xiàn)場(chǎng)離奇詭異葱弟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猜丹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門芝加,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人射窒,你說我怎么就攤上這事藏杖。” “怎么了脉顿?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵蝌麸,是天一觀的道長。 經(jīng)常有香客問我艾疟,道長来吩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任蔽莱,我火速辦了婚禮弟疆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盗冷。我一直安慰自己怠苔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布仪糖。 她就那樣靜靜地躺著柑司,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锅劝。 梳的紋絲不亂的頭發(fā)上攒驰,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音故爵,去河邊找鬼玻粪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奶段。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼剥纷,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼痹籍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晦鞋,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤蹲缠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后悠垛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體线定,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年确买,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斤讥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡湾趾,死狀恐怖芭商,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搀缠,我是刑警寧澤铛楣,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站艺普,受9級(jí)特大地震影響簸州,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歧譬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一岸浑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瑰步,春花似錦助琐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舌界,卻和暖如春掘譬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呻拌。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工葱轩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓靴拱,卻偏偏與公主長得像垃喊,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子袜炕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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