OLTP場景數(shù)據(jù)分布式實(shí)踐-設(shè)計(jì)篇

最近幾年做分布式項(xiàng)目要出,很多工作是關(guān)于OLTP(聯(lián)機(jī)交易系統(tǒng))場景下數(shù)據(jù)分布式架構(gòu)的,疫情期間正好整理下這方面的一些設(shè)計(jì)與實(shí)踐。為避免篇幅太長出革,本文分為設(shè)計(jì)篇和技術(shù)篇,設(shè)計(jì)篇主要偏向數(shù)據(jù)拆分的理論與方法渡讼,還有一些原則與經(jīng)驗(yàn)骂束。技術(shù)篇?jiǎng)t主要會(huì)介紹分庫分表中間件的設(shè)計(jì)與使用實(shí)踐,以及如何構(gòu)建一個(gè)完整的分布式數(shù)據(jù)服務(wù)平臺(tái)成箫。

一般來說做分布式架構(gòu)展箱,應(yīng)用層是好做分布式的,因?yàn)橥际菬o狀態(tài)的(或者通過將數(shù)據(jù)轉(zhuǎn)移到DB蹬昌、緩存混驰、MQ等方式來實(shí)現(xiàn)無狀態(tài)),只需在流量入口、即在應(yīng)用前面加一個(gè)負(fù)載均衡即可(例如Nginx栖榨、HAProxy昆汹、F5),這在大單體架構(gòu)也多已具備婴栽。所以一般我們說分布式架構(gòu)满粗,一個(gè)重要的部分就是要做數(shù)據(jù)的分布式化。

傳統(tǒng)單體集中式架構(gòu)

數(shù)據(jù)的分布式不像應(yīng)用那么簡單居夹,因?yàn)楦鞴?jié)點(diǎn)的數(shù)據(jù)可能是不一樣的败潦,需要進(jìn)行路由、解決多副本一致性准脂,甚至多寫沖突等問題劫扒。雖然實(shí)現(xiàn)方案復(fù)雜,不過數(shù)據(jù)的分布式本質(zhì)上就兩種樸素思想:復(fù)制分片狸膏。復(fù)制技術(shù)在傳統(tǒng)關(guān)系數(shù)據(jù)庫中也很常見沟饥,主要用來做主備、雙活湾戳,例如 MySQL Replication贤旷、Oracle DataGuard等。分片在數(shù)據(jù)庫里也有對應(yīng)產(chǎn)品砾脑。例如 MySQL Fabric幼驶、Oracle Sharding,但與復(fù)制相比韧衣,這些數(shù)據(jù)庫廠商對應(yīng)的分片方案卻一直沒有被大眾廣泛接受盅藻。

在NewSQL數(shù)據(jù)庫中往往都內(nèi)置了sharding機(jī)制,而且都基于paxos畅铭、raft算法來保證復(fù)制一致性氏淑,關(guān)于分庫分表與NewSQL方案對比選型,可參見我之前一篇文章分庫分表 vs NewSQL數(shù)據(jù)庫

在OLTP場景下硕噩,復(fù)制和分片思想應(yīng)用在傳統(tǒng)關(guān)系數(shù)據(jù)庫上假残,有兩個(gè)更為人熟知的名字,分庫分表讀寫分離炉擅。
分庫分表辉懒,就是對原來單一數(shù)據(jù)庫表進(jìn)行拆分,是基于傳統(tǒng)關(guān)系數(shù)據(jù)庫實(shí)現(xiàn)分布式架構(gòu)轉(zhuǎn)型的一個(gè)主要方式谍失,因此首先第一個(gè)問題:

為什么拆分耗帕?什么時(shí)候需要拆分?

容量袱贮、性能仿便、橫向擴(kuò)展体啰、微服務(wù)

單機(jī)數(shù)據(jù)庫的存儲(chǔ)、CPU嗽仪、內(nèi)存等資源都存在上限瓶頸荒勇,當(dāng)數(shù)據(jù)量、訪問量到達(dá)一定量級后闻坚,性能則會(huì)急劇下降沽翔,也就是說通過scale up這種垂直擴(kuò)展的方式是一個(gè)上限的,而且成本是較高的窿凤。
如果要實(shí)現(xiàn)scale out橫向擴(kuò)展仅偎,就需要把原來一張表的數(shù)據(jù)拆分到多張物理庫表中存儲(chǔ)(水平拆分)。
另外如果是微服務(wù)架構(gòu)雳殊,拆分后的服務(wù)歸屬不同的系統(tǒng)橘沥,對應(yīng)不同的數(shù)據(jù)庫,其實(shí)就已經(jīng)進(jìn)行了垂直拆分夯秃。

拆分方式有哪些座咆?

1. 垂直拆分

垂直拆分一般更加貼近業(yè)務(wù)的拆分方式,在做微服務(wù)時(shí)使用最多的就是這種方式仓洼,具體會(huì)根據(jù)DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))技術(shù)或者業(yè)務(wù)能力進(jìn)行拆分介陶,一般有界上下文確定了,拆分規(guī)則也就比較明確了色建。
這種方式對應(yīng)用侵入性較小哺呜,往往只需要配置各自獨(dú)立數(shù)據(jù)庫(可能是物理機(jī),也可能只是不同的實(shí)列)即可箕戳,最多做一個(gè)多數(shù)據(jù)源選擇的數(shù)據(jù)訪問層某残。
另外還有一種垂直拆分的場景是由于冷熱數(shù)據(jù),同一行數(shù)據(jù)的不同列訪問頻率差別很大漂羊,或者是有些Text、Blob等大字段影響讀寫效率卸留,這時(shí)也會(huì)將這些列拆分到不同表中走越。這種方式一般不常見,很多時(shí)候是在做性能優(yōu)化時(shí)會(huì)考慮耻瑟。

垂直拆分

垂直拆分的優(yōu)點(diǎn):

  • 拆分后業(yè)務(wù)清晰旨指,拆分規(guī)則明確。 往往是按照系統(tǒng)或者交易的
  • 系統(tǒng)之間整合或擴(kuò)展容易
  • 數(shù)據(jù)維護(hù)簡單喳整、架構(gòu)復(fù)雜度低

垂直拆分的缺點(diǎn):

  • 部分業(yè)務(wù)表無法join谆构,只能在應(yīng)用層通過接口方式解決
  • 受每種業(yè)務(wù)不同的限制存在單庫性能瓶頸。
  • 往往會(huì)產(chǎn)生分布式事務(wù)場景

由于垂直切分是按照業(yè)務(wù)的分類將表分散到不同的庫框都,所以有些業(yè)務(wù)表會(huì)過于龐大,存在單庫讀寫與存儲(chǔ)瓶頸,這時(shí)就需要水平拆分來做解決忽刽。

2. 水平拆分

水平拆分更加技術(shù)化,將一張表的數(shù)據(jù)分布到多張庫與表中摸屠,具體方式可分為:只分庫、只分表粱哼、分庫又分表季二。例如order表,只分庫(ds1.order揭措、ds2.order…dsk.order)胯舷,只分表(ds.order_0、ds.order_1…ds.order_n)绊含,分庫又分表(ds1.order_0桑嘶、ds2.order_1…dsk.order_n)。

水平拆分

水平拆分的優(yōu)點(diǎn):

  • 如果操作數(shù)據(jù)分布在同一庫中艺挪, 可以支持join不翩、子查詢等復(fù)雜SQL
  • 解決了單庫性能瓶頸,支持橫向擴(kuò)展
  • 由于應(yīng)用未拆分麻裳,如果有分布式數(shù)據(jù)訪問層口蝠,則應(yīng)用改造較少

水平拆分的缺點(diǎn):

  • 拆分規(guī)則、分庫分表數(shù)量需要精心設(shè)計(jì)
  • 如果涉及多個(gè)庫津坑,會(huì)產(chǎn)生分布式事務(wù)場景
  • 數(shù)據(jù)擴(kuò)容時(shí)數(shù)據(jù)遷移工作量較大
  • 跨庫join往往需要應(yīng)用實(shí)現(xiàn)妙蔗,性能較差
  • 數(shù)據(jù)合并、聚合疆瑰、分頁等無法由數(shù)據(jù)庫直接支持

數(shù)據(jù)庫有分區(qū)表還要分庫分表嗎眉反?
傳統(tǒng)關(guān)系數(shù)據(jù)庫的分區(qū)表本質(zhì)上還是共享cpu、內(nèi)存穆役,所以仍然面臨著scale up的問題寸五,而且分區(qū)表支持的分區(qū)鍵往往也不夠靈活。但新的一些NewSQL分布式數(shù)據(jù)庫耿币,如OceanBase的分區(qū)表分散在不同的存儲(chǔ)節(jié)點(diǎn)上梳杏,從而避免單機(jī)性能瓶頸問題。

拆分具體步驟

(一) 確定拆分方式
根據(jù)業(yè)務(wù)特性選擇合適的拆分方式淹接,一般結(jié)合使用

  1. 垂直拆分
    場景:字段長度十性、訪問頻率差別較大字段表、微服務(wù)化
    注意:需要在同事務(wù)中操作的表盡量不要做拆分
  2. 水平拆分
    場景:數(shù)據(jù)量較大塑悼,超過單表劲适、單庫性能
    注意:是否有跨庫事務(wù),是否有非分片鍵操作表的場景厢蒜,會(huì)涉及到庫表掃描交易

(二) 確定拆分字段

  1. 垂直拆分表霞势、字段
    按照功能模塊進(jìn)行拆分直接按表即可烹植,如果是拆分部分列,則需添加關(guān)聯(lián)列甚至冗余列支示。
  2. 水平拆分字段
    確保 拆分表都有分片鍵刊橘,多為主鍵或唯一索引,這些列中需包含分片信息颂鸿。如果請求中未包含分片信息促绵,則需要一個(gè)全局的路由表。

(三) 確定拆分規(guī)則

  1. 范圍Range
    適合按照一定規(guī)律有序遞增的業(yè)務(wù)字段嘴纺,例如日期败晴、流水ID等,這種方式栽渴,例如0-9999->庫1尖坤,10000~19999->庫2 …;20150101-20161231->庫1,20170101-20171231->庫2…;
    這種方式天然支持水平擴(kuò)展闲擦,方便進(jìn)行冷熱分離慢味、歸檔,按需擴(kuò)容方便墅冷,但負(fù)載容易不均衡纯路,如果單庫壓力大,則也需數(shù)據(jù)遷移寞忿。
  2. 哈希Hash
    數(shù)據(jù)分布比較均衡驰唬,一般通過mod庫/表數(shù)量計(jì)算路由,本質(zhì)上一種預(yù)分配腔彰,因此擴(kuò)容時(shí)需要進(jìn)行數(shù)據(jù)遷移叫编,通常有一致性哈希、成倍擴(kuò)容法霹抛。
  3. 應(yīng)用自定義
    由應(yīng)用自定義路由規(guī)則搓逾,配置有分片ID對應(yīng)的庫表序號,可以通過路由表杯拐、配置文件或其它自定義算法霞篡。這種方式靈活度最高,容易實(shí)現(xiàn)動(dòng)態(tài)改變藕施。
    在我們項(xiàng)目中是1寇损、2凸郑、3方式都有使用裳食。

(四) 確定拆分?jǐn)?shù)量

  1. 假設(shè)目標(biāo)數(shù)據(jù)量為T(根據(jù)業(yè)務(wù)發(fā)展需求預(yù)估)
  2. 單表數(shù)據(jù)量建議P(例如MySQL 為500w),分表數(shù)量=T/P
  3. 目前配置典型業(yè)務(wù)場景下芙沥,單庫性能穩(wěn)定前提下對應(yīng)的數(shù)據(jù)容量上限L

單庫性能可以根據(jù)cpu(80% 以上)诲祸、磁盤IO(磁盤使用率100% iowait出現(xiàn)并逐步增大)浊吏、交易tps穩(wěn)定性(出現(xiàn)tps大幅度波動(dòng))等系統(tǒng)指標(biāo)確定其瓶頸狀態(tài)從而得到容量上限的評估

  1. 分庫數(shù)量=T/L

庫表的數(shù)量關(guān)系到未來擴(kuò)容、以及運(yùn)維需求救氯,不宜太多也不宜太少找田,以上主要是從容量角度去計(jì)算,實(shí)際場景下還需要結(jié)合硬件成本預(yù)算着憨、數(shù)據(jù)清理歸檔策略等因素綜合考慮墩衙。

拆分后怎么擴(kuò)容?

垂直擴(kuò)容

垂直拆分后甲抖,如果某個(gè)應(yīng)用的數(shù)據(jù)庫壓力太大漆改,可通過增加其資源配置(CPU、內(nèi)存准谚、PCIE)進(jìn)行垂直擴(kuò)容挫剑。

水平擴(kuò)容

水平拆分下可以通過增加數(shù)據(jù)庫服務(wù)器進(jìn)行擴(kuò)容。這種方式需要進(jìn)行數(shù)據(jù)遷移柱衔,如果一致性哈希則遷移就近節(jié)點(diǎn)數(shù)據(jù)樊破,如果是成倍擴(kuò)容時(shí)則需遷移所有節(jié)點(diǎn)一半數(shù)據(jù)。

一致性哈希模式雖然遷移的數(shù)據(jù)量較小唆铐,但容易造成數(shù)據(jù)的冷熱不均哲戚,因此我們項(xiàng)目中采用的成倍擴(kuò)容方式,具體方式是提前將表分出來或链,例如分成128張表惫恼,項(xiàng)目初期將這些表均勻分布在4臺(tái)數(shù)據(jù)庫服務(wù)器,隨著業(yè)務(wù)增加數(shù)據(jù)量增長澳盐,擴(kuò)容到8臺(tái)數(shù)據(jù)庫祈纯,只需要將原4臺(tái)數(shù)據(jù)庫各自一半數(shù)量的表遷出到新增的4臺(tái)服務(wù)器,然后修改SQL路由即可叼耙。

成倍擴(kuò)容:應(yīng)對整體數(shù)據(jù)量增長腕窥,擴(kuò)容后物理機(jī)是原有2倍

如果是單臺(tái)數(shù)據(jù)庫有熱點(diǎn)數(shù)據(jù)壓力,也可以只將該庫一部分?jǐn)?shù)據(jù)遷移出新擴(kuò)容的庫筛婉。


單庫擴(kuò)容:應(yīng)對某個(gè)切片數(shù)據(jù)增長過快簇爆,擴(kuò)容到獨(dú)立的物理機(jī)

拆分后面臨的問題

  • 引入分布式事務(wù)的問題
  • 跨庫Join的問題
  • 多庫合并排序分頁問題
  • SQL路由、重寫問題
  • 多數(shù)據(jù)源管理問題
  • 多維度拆分后帶來的數(shù)據(jù)匯總查詢等操作問題

解決方式:

  1. 盡可能避免分布式事務(wù)爽撒、跨節(jié)點(diǎn)join入蛆、排序場景
  2. 避免使用數(shù)據(jù)庫分布式事務(wù),提供柔性事務(wù)支持(冪等硕勿、沖正哨毁、可靠性消息、TCC)
  3. 由應(yīng)用層解決join問題
  4. 提供分布式數(shù)據(jù)訪問層
  5. 匯總庫源武、二級索引庫扼褪、小表廣播

關(guān)于分布式數(shù)據(jù)訪問層在技術(shù)篇進(jìn)行詳細(xì)介紹想幻。

讀寫分離

在實(shí)際業(yè)務(wù)場景中,對數(shù)據(jù)庫的讀寫頻率是不一樣的话浇。有的是寫多讀少脏毯,例如交易流水表幔崖;有的是讀寫均衡食店,例如訂單表;有的則是讀多寫少叛买,如客戶蹋订、信息以及配置等信息表。

數(shù)據(jù)分片解決的是單點(diǎn)性能瓶頸和橫向擴(kuò)展能力动漾,適合寫壓力比較大的場景。而讀多寫少的這類場景荠锭,如果單庫容量可以滿足证九,則可通過讀寫分離來解決讀壓力大的問題删豺。具體可以把寫操作路由到主庫,讀操作按照權(quán)重愧怜、機(jī)房等分散在主庫和各個(gè)從庫呀页。

讀寫分離

讀寫分離模式下需要注意幾點(diǎn):

  1. 主從延遲。在從庫上讀比主庫數(shù)據(jù)有一定時(shí)延(一般在毫秒級別拥坛,寫壓力大時(shí)可能在秒級別)蓬蝶,所以選擇這種方式時(shí)業(yè)務(wù)上要允許一定的數(shù)據(jù)時(shí)延,例如一般對外查詢類交易都使用這種方式猜惋。
  2. 同一事務(wù)中丸氛,不能在從庫讀取數(shù)據(jù),因?yàn)榭赡苡捎跀?shù)據(jù)延時(shí)讀取到臟數(shù)據(jù)著摔,違背事務(wù)的一致性缓窜,所以必須在主庫讀取。在實(shí)際開發(fā)時(shí),數(shù)據(jù)訪問層可根據(jù)是否關(guān)閉事務(wù)自動(dòng)提交來自動(dòng)判斷是否必須在主庫讀雹洗。
  3. 對于數(shù)據(jù)延遲容忍度很低的查詢交易,可以在開發(fā)時(shí)單獨(dú)再封裝一個(gè)從主庫查詢的接口卧波,或者在入?yún)⒃黾印笆欠裥枰獜?qiáng)一致”標(biāo)志时肿,交易實(shí)現(xiàn)時(shí)根據(jù)該標(biāo)志選擇從主庫還是從庫讀。

在實(shí)際項(xiàng)目中分庫分表和讀寫分離方式都有場景在用港粱,但注意一般情況下避免使用分庫分表+讀寫分離這種復(fù)雜方案螃成,因?yàn)榉謳旆直砗笞x寫壓力也不會(huì)太大了。

原則與經(jīng)驗(yàn)

數(shù)據(jù)分布式是個(gè)系統(tǒng)工程查坪,需要從領(lǐng)域建模寸宏、場景劃分、數(shù)據(jù)訪問偿曙、數(shù)據(jù)遷移擴(kuò)容等多方面綜合考慮氮凝,在落地實(shí)現(xiàn)前要從全局做好設(shè)計(jì),這里簡單列下我們的一些設(shè)計(jì)原則與經(jīng)驗(yàn):

  1. 用簡單的方案解決問題望忆。能不切分盡量不要切分罩阵,切莫為了分布式而拆分。讀寫分離能解決問題启摄,就不分庫分表稿壁。
  2. 切分一定要選擇合適切分規(guī)則(能保證90%交易不會(huì)跨分片), 梳理好所有場景歉备,提前規(guī)劃好再實(shí)施傅是。
  3. 數(shù)據(jù)訪問層設(shè)計(jì)上功能要強(qiáng)大,但一定明確使用場景蕾羊,切忌無腦濫用喧笔。比如我們項(xiàng)目中數(shù)據(jù)訪問中間件雖然支持分布式事務(wù)XA,但一般并不推薦使用龟再;支持DDL溃斋,但聯(lián)機(jī)交易時(shí)禁止使用;支持多庫鏈?zhǔn)绞聞?wù)提交吸申,但默認(rèn)只支持嚴(yán)格單庫事務(wù)梗劫。
  4. 制定應(yīng)用開發(fā)規(guī)范,明確SQL使用限制與要求截碴,SQL要盡量簡單梳侨。例如我們項(xiàng)目使用MySQL,部署在PC Server上日丹,單機(jī)性能相比小型機(jī)上DB2走哺、Oracle差很多,因此禁止使用觸發(fā)器哲虾、外鍵丙躏、join择示,SQL操作必須攜帶索引與拆分列(數(shù)據(jù)訪問層也會(huì)校驗(yàn)),主鍵必須是自增等等晒旅。
  5. 盡量使用柔性事務(wù)解決跨庫與跨系統(tǒng)事務(wù)問題栅盲。能用MQ最終一致性就別用Saga、TCC废恋。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谈秫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鱼鼓,更是在濱河造成了極大的恐慌拟烫,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迄本,死亡現(xiàn)場離奇詭異硕淑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嘉赎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門喜颁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曹阔,你說我怎么就攤上這事半开。” “怎么了赃份?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵寂拆,是天一觀的道長。 經(jīng)常有香客問我抓韩,道長纠永,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任谒拴,我火速辦了婚禮尝江,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘英上。我一直安慰自己炭序,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布苍日。 她就那樣靜靜地躺著惭聂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪相恃。 梳的紋絲不亂的頭發(fā)上辜纲,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼耕腾。 笑死见剩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扫俺。 我是一名探鬼主播苍苞,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼牵舵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起倦挂,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤畸颅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后方援,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體没炒,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年犯戏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了送火。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡先匪,死狀恐怖种吸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呀非,我是刑警寧澤坚俗,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站岸裙,受9級特大地震影響猖败,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜降允,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一恩闻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剧董,春花似錦幢尚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至犁嗅,卻和暖如春边涕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工功蜓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留园爷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓式撼,卻偏偏與公主長得像童社,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子著隆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

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