精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

支持10X增長(zhǎng),攜程機(jī)票訂單庫(kù)Sharding實(shí)踐

移動(dòng)開(kāi)發(fā) 移動(dòng)應(yīng)用 數(shù)據(jù)庫(kù)
項(xiàng)目的成功上線離不開(kāi)每一個(gè)成員的努力。在實(shí)施過(guò)程中,遇到的問(wèn)題比這篇文章列舉的問(wèn)題多得多,很多都是一些非常瑣碎的事情。特別是項(xiàng)目初期,我們往往是解決了一個(gè),冒出了更多的問(wèn)題。但是每次遇到問(wèn)題后,團(tuán)隊(duì)的成員都積極思考,集思廣益,攻破了一個(gè)又一個(gè)的技術(shù)問(wèn)題和業(yè)務(wù)問(wèn)題。

作者 | 初八,攜程資深研發(fā)經(jīng)理,專注于訂單后臺(tái)系統(tǒng)架構(gòu)優(yōu)化工作;JefferyXin,攜程高級(jí)后端開(kāi)發(fā)專家,專注系統(tǒng)性能、業(yè)務(wù)架構(gòu)等領(lǐng)域。

 一、背景

隨著機(jī)票訂單業(yè)務(wù)的不斷增長(zhǎng),當(dāng)前訂單處理系統(tǒng)的架構(gòu)已經(jīng)不能滿足日益增長(zhǎng)的業(yè)務(wù)需求,系統(tǒng)性能捉襟見(jiàn)肘,主要體現(xiàn)在以下方面:

  • 數(shù)據(jù)庫(kù)CPU資源在業(yè)務(wù)高峰期經(jīng)常達(dá)到50%以上,運(yùn)行狀況亮起了黃燈
  • 磁盤(pán)存儲(chǔ)空間嚴(yán)重不足,需要經(jīng)常清理磁盤(pán)數(shù)據(jù)騰挪可用空間
  • 系統(tǒng)擴(kuò)容能力不足,如果需要提升處理能力只能更換配置更好的硬件資源

因此我們迫切需要調(diào)整和優(yōu)化機(jī)票訂單數(shù)據(jù)庫(kù)的架構(gòu),從而提升訂單系統(tǒng)的處理性能。通過(guò)建立良好的水平擴(kuò)展能力,來(lái)滿足日益增長(zhǎng)的業(yè)務(wù)需求,為后續(xù)系統(tǒng)優(yōu)化和支撐10x訂單量的增長(zhǎng)打下良好基礎(chǔ)。

1.1 存儲(chǔ)架構(gòu)的演進(jìn)

我們選擇一個(gè)新的系統(tǒng)架構(gòu),應(yīng)該基于當(dāng)下面臨的問(wèn)題,綜合成本、風(fēng)險(xiǎn)、收益等多方面因素,選擇出的最合適的方案。機(jī)票訂單庫(kù)的架構(gòu)演進(jìn)也不例外。

我們最開(kāi)始接觸機(jī)票訂單數(shù)據(jù)庫(kù)時(shí),它是一個(gè)非常龐大的數(shù)據(jù)集合,所有的訂單業(yè)務(wù)全部都集中一個(gè)數(shù)據(jù)庫(kù)上,因此整體BR非常高。同時(shí),我們的SQL語(yǔ)句也非常復(fù)雜,混雜著很多歷史遺留下來(lái)的存儲(chǔ)過(guò)程。可想而之,整個(gè)數(shù)據(jù)庫(kù)當(dāng)時(shí)的壓力巨大,維護(hù)成本居高不下。DBA每天的工作也非常忙碌,想方設(shè)法降高頻,解決慢SQL等線上問(wèn)題。生產(chǎn)偶爾也會(huì)因?yàn)槟承](méi)有review的SQL導(dǎo)致數(shù)據(jù)庫(kù)短暫的停止服務(wù)。

初期,我們采用了最常見(jiàn)的幾種手段進(jìn)行優(yōu)化,包括:

  • 索引優(yōu)化
  • 讀寫(xiě)分離
  • 降高頻

雖然手段比較常規(guī),通過(guò)一段時(shí)間的治理,訂單庫(kù)的穩(wěn)定性也得到了一定的增強(qiáng)。總體實(shí)施成本較低,效果也是立竿見(jiàn)影的。

隨著時(shí)間的推移和數(shù)據(jù)的積累,新的性能瓶頸逐漸顯露。我們?cè)俅螌?duì)系統(tǒng)進(jìn)行了升級(jí),對(duì)數(shù)據(jù)庫(kù)架構(gòu)做了改進(jìn)。主要包括以下幾個(gè)方面:

垂直拆分

 訂單庫(kù)根據(jù)業(yè)務(wù)屬性拆分成了多個(gè)數(shù)據(jù)庫(kù)

基于業(yè)務(wù)對(duì)數(shù)據(jù)庫(kù)進(jìn)行垂直拆分在很大程度上提高了系統(tǒng)的可靠性和可維護(hù)性。一個(gè)上百人的團(tuán)隊(duì),同時(shí)對(duì)一套數(shù)據(jù)庫(kù)進(jìn)行維護(hù),對(duì)于發(fā)布變更來(lái)說(shuō)是一種煎熬,同時(shí)也存在很大的風(fēng)險(xiǎn)。當(dāng)一個(gè)非核心鏈路上的發(fā)布出現(xiàn)了問(wèn)題,例如某些操作導(dǎo)致了鎖表或者占用過(guò)多的系統(tǒng)資源,其他關(guān)鍵鏈路的數(shù)據(jù)庫(kù)訪問(wèn)都會(huì)因此受到影響。

我們根據(jù)不同的業(yè)務(wù)場(chǎng)景,例如:訂單管理系統(tǒng)、出票、退票、改簽等業(yè)務(wù),將數(shù)據(jù)庫(kù)進(jìn)行垂直拆分。使各自業(yè)務(wù)系統(tǒng)數(shù)據(jù)隔離,減少相互的影響。這些拆分的數(shù)據(jù)庫(kù),可以根據(jù)不同性能要求,靈活調(diào)整數(shù)據(jù)庫(kù)的部署方式,來(lái)降低總體成本。

水平拆分(冷熱數(shù)據(jù)分離)

通常來(lái)說(shuō),當(dāng)航班過(guò)了起飛時(shí)間并且用戶已經(jīng)使用了當(dāng)前機(jī)票,那么我們認(rèn)為該訂單服務(wù)已經(jīng)完成,后續(xù)訂單數(shù)據(jù)發(fā)生改變的可能性很小,于是會(huì)將該數(shù)據(jù)遷移到一個(gè)具有相同結(jié)構(gòu)的冷數(shù)據(jù)庫(kù)中。該數(shù)據(jù)庫(kù)僅提供查詢功能,不提供修改功能。但是我們發(fā)現(xiàn)少數(shù)場(chǎng)景仍然需要對(duì)這些數(shù)據(jù)進(jìn)行修改。于是我們開(kāi)發(fā)了一套數(shù)據(jù)還原功能,將處于冷數(shù)據(jù)庫(kù)中的數(shù)據(jù),還原到熱數(shù)據(jù)庫(kù)中,然后再進(jìn)行操作。

注:我們當(dāng)時(shí)采用的數(shù)據(jù)庫(kù)和數(shù)據(jù)結(jié)構(gòu)是完全一致的,這樣做備份和還原、查詢會(huì)比較方便。其實(shí)也可以采用其他類型的數(shù)據(jù)庫(kù),例如Mongo等。在讀取性能和使用成本等方面可能會(huì)更具優(yōu)勢(shì)。

這次升級(jí)同樣解決了不少問(wèn)題,使數(shù)據(jù)庫(kù)的穩(wěn)定性得到了很大的增強(qiáng)。

1.2 基于冷熱數(shù)據(jù)分離的適用性

雖然基于冷熱數(shù)據(jù)的分庫(kù)方案,在目前來(lái)看遇到了瓶頸。但是我認(rèn)為它是一個(gè)非常值得借鑒的方案。我們現(xiàn)在仍然有大量的業(yè)務(wù)系統(tǒng)數(shù)據(jù)庫(kù)采用這種方案對(duì)數(shù)據(jù)進(jìn)行拆分。它不僅實(shí)施簡(jiǎn)單,同時(shí)運(yùn)維成本也相對(duì)較低。

優(yōu)勢(shì):

  • 功能簡(jiǎn)單
  • 實(shí)施成本低

局限性:

  • 數(shù)據(jù)冷處理的規(guī)則應(yīng)該相對(duì)簡(jiǎn)單,不應(yīng)該經(jīng)常發(fā)生變化
  • 熱數(shù)據(jù)的膨脹需要受到限制,否則熱數(shù)據(jù)的量一旦累積過(guò)多,性能瓶頸仍然會(huì)出現(xiàn)
  • 需要額外的查詢來(lái)找到訂單所處的位置(冷/熱數(shù)據(jù)庫(kù))
  • 因?yàn)槔鋽?shù)據(jù)量龐大,冷數(shù)據(jù)的查詢能力、表結(jié)構(gòu)調(diào)整能力都收到了限制,不能進(jìn)行復(fù)雜的業(yè)務(wù)查詢操作

根據(jù)我們的系統(tǒng)規(guī)劃,在當(dāng)下或者可預(yù)見(jiàn)的未來(lái)滿足以上提到原因中的多個(gè),那么就得謹(jǐn)慎選擇采用此方案。或者在改方案的基礎(chǔ)上進(jìn)行優(yōu)化。

正是由于我們目前的業(yè)務(wù)場(chǎng)景恰好命中了上面列舉的所有問(wèn)題,我們才需要對(duì)這個(gè)架構(gòu)進(jìn)行進(jìn)一步調(diào)整,選擇一個(gè)更好的水平擴(kuò)展的方式,解決當(dāng)前系統(tǒng)面臨的問(wèn)題。

1.3 當(dāng)時(shí)面臨的主要問(wèn)題

從2019年開(kāi)始,我們就開(kāi)始著手研究和規(guī)劃訂單數(shù)據(jù)庫(kù)sharding項(xiàng)目。當(dāng)時(shí)主要面臨如下問(wèn)題:

1.3.1 訂單的存儲(chǔ)要求

受制于當(dāng)前訂單數(shù)據(jù)庫(kù)架構(gòu)的限制以及機(jī)票業(yè)務(wù)的特殊性(通常不超過(guò)2年的處理生命周期),改造前的訂單數(shù)據(jù)庫(kù)僅能夠支持2年的訂單存儲(chǔ)。超過(guò)2年,我們會(huì)將數(shù)據(jù)進(jìn)行歸檔。用戶和員工都無(wú)法通過(guò)在線查詢的方式獲取訂單信息。

但是基于以下幾個(gè)方面原因,原本2年的存儲(chǔ)和處理周期已經(jīng)不能滿足客戶和業(yè)務(wù)的需要:

  • 從客戶的角度出發(fā),仍然有查詢歷史訂單的需求;
  • 業(yè)務(wù)場(chǎng)景的拓展導(dǎo)致機(jī)票訂單整個(gè)服務(wù)周期變長(zhǎng);

原先機(jī)票使用完成(出行)一段時(shí)間后就可以視為服務(wù)結(jié)束,大部分訂單3個(gè)月后就不會(huì)發(fā)生變化,但是由于新業(yè)務(wù)的推出,熱點(diǎn)數(shù)據(jù)查詢和處理周期明顯變長(zhǎng)。

1.3.2 系統(tǒng)架構(gòu)瓶頸

1)熱數(shù)據(jù)膨脹

熱數(shù)據(jù)原本僅千萬(wàn)級(jí)別,由于業(yè)務(wù)的變化熱數(shù)據(jù)數(shù)量不斷膨脹。

2)冷數(shù)據(jù)量龐大

由于訂單存儲(chǔ)周期拉長(zhǎng)和訂單量的增長(zhǎng),冷數(shù)據(jù)的數(shù)量也不斷攀升。冷數(shù)據(jù)庫(kù)查詢性能不斷下降;索引調(diào)整也變得非常困難,經(jīng)常出現(xiàn)修改失敗的場(chǎng)景。

3)數(shù)據(jù)庫(kù)高峰期BR達(dá)到了10w+;

4)系統(tǒng)存儲(chǔ)了20TB的數(shù)據(jù),磁盤(pán)使用率達(dá)到80%以上,經(jīng)常觸發(fā)使用容量告警;

5)主庫(kù)的CPU使用率高峰期接近50%;

6)由于采用了讀寫(xiě)分離的架構(gòu),當(dāng)主庫(kù)的服務(wù)器的性能受到影響的時(shí)候,AG延遲變得非常高,偶爾達(dá)到分鐘級(jí),有的時(shí)候甚至更長(zhǎng)。

主從同步的延遲,導(dǎo)致了數(shù)據(jù)新鮮度的降低。我們之前的ORM層封裝了一個(gè)新鮮容忍度的參數(shù)。當(dāng)不能滿足新鮮度要求的時(shí)候,讀取會(huì)切換到主庫(kù),從而進(jìn)一步加重了主庫(kù)的負(fù)擔(dān)。

因此,訂單庫(kù)的整體性能壓力非常大,如果想快速解決性能問(wèn)題,只能對(duì)機(jī)器進(jìn)行擴(kuò)容。但是由于數(shù)據(jù)庫(kù)本身就是消耗資源大戶,CPU和內(nèi)存消耗非常高,只能通過(guò)進(jìn)一步提高數(shù)據(jù)庫(kù)的硬件配置來(lái)解決問(wèn)題,因此整體升級(jí)的成本居高不下。另外硬件升級(jí)完成后,SQLServer的授權(quán)成本可能也會(huì)進(jìn)一步提升。

為了徹底解決以上問(wèn)題,我們計(jì)劃通過(guò)優(yōu)化架構(gòu)來(lái)提升系統(tǒng)的水平擴(kuò)展能力,從而進(jìn)一步提升我們系統(tǒng)的性能和服務(wù)水平。

二、項(xiàng)目目標(biāo)和實(shí)施方案

2.1 目標(biāo)

基于上文提到的這些問(wèn)題,為了確保系統(tǒng)能夠長(zhǎng)久持續(xù)的穩(wěn)定運(yùn)行并且提升訂單系統(tǒng)的處理能力,我們計(jì)劃對(duì)數(shù)據(jù)庫(kù)的架構(gòu)進(jìn)行升級(jí),總體實(shí)現(xiàn)以下目標(biāo):

  • 訂單的存儲(chǔ)和處理周期至少達(dá)到5年
  • 提升訂單系統(tǒng)的處理能力,支撐訂單QPS10倍的規(guī)模增長(zhǎng)
  • 在提升系統(tǒng)性能的前提下,降低總體成本
  • 提高系統(tǒng)的水平擴(kuò)展能力,通過(guò)簡(jiǎn)便的操作可以快速擴(kuò)容以應(yīng)對(duì)長(zhǎng)期的業(yè)務(wù)增長(zhǎng)

我們希望通過(guò)1-2年的時(shí)間,實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)架構(gòu)升級(jí)改造以及完成SQLServer遷移到MySQL的目標(biāo)。

2.2 架構(gòu)改造

2.2.1 新舊架構(gòu)的對(duì)比

舊系統(tǒng)架構(gòu):

架構(gòu)說(shuō)明:

1)訂單數(shù)據(jù)庫(kù)

為熱數(shù)據(jù)的主庫(kù),提供讀寫(xiě)功能

2)訂單數(shù)據(jù)庫(kù) Slave

為熱數(shù)據(jù)的從庫(kù),在保障新鮮度的前提下提供只讀功能,采用SQLServer的AlwaysOn技術(shù)

3)訂單備份數(shù)據(jù)庫(kù)

為冷數(shù)據(jù)庫(kù)(沒(méi)有主從之分),僅提供只讀功能

4)Archive

備份機(jī)制,根據(jù)業(yè)務(wù)的需要,為了緩解主數(shù)據(jù)庫(kù)的壓力,對(duì)于符合條件的訂單(通常時(shí)起飛后+已出行)定時(shí)遷移到冷數(shù)據(jù)庫(kù)的操作

5)Restore

還原機(jī)制。在一些特殊情況下,需要對(duì)已經(jīng)備份的數(shù)據(jù)進(jìn)行修改,我們需要將數(shù)據(jù)從冷數(shù)據(jù)庫(kù)中恢復(fù)到熱數(shù)據(jù)庫(kù)然后才能進(jìn)行操作。這個(gè)操作叫做還原。

當(dāng)前存在的問(wèn)題:

1)數(shù)據(jù)量變多,業(yè)務(wù)場(chǎng)景變得復(fù)雜后,主庫(kù)的數(shù)據(jù)量從千萬(wàn)級(jí)增長(zhǎng)到億級(jí)別,對(duì)數(shù)據(jù)庫(kù)的性能產(chǎn)生明顯影響

2)冷數(shù)據(jù)庫(kù)的歷史累計(jì)數(shù)據(jù)量也在不斷膨脹,受到本地SSD磁盤(pán)容量的限制,磁盤(pán)空間使用率達(dá)到了80%以上

3)冷數(shù)據(jù)庫(kù)的訂單數(shù)量達(dá)到了十億級(jí),數(shù)據(jù)庫(kù)索引調(diào)整,結(jié)構(gòu)調(diào)整變得較為困難;查詢性能也受到了很大的影響

4)備份和還原邏輯需要根據(jù)業(yè)務(wù)要求不斷調(diào)整

新系統(tǒng)架構(gòu):

架構(gòu)說(shuō)明:

1)訂單數(shù)據(jù)庫(kù)Shard Cluster

新數(shù)據(jù)庫(kù)基于訂單號(hào)將數(shù)據(jù)水平拆分成64個(gè)分片,目前部署在16臺(tái)物理機(jī)上

2)訂單聚合數(shù)據(jù)庫(kù)

針對(duì)熱點(diǎn)數(shù)據(jù),通過(guò)Binglog和有序消息隊(duì)列同步到訂單聚合數(shù)據(jù)庫(kù),方便數(shù)據(jù)監(jiān)控,并且用于提高數(shù)據(jù)聚合查詢的性能

2.2.2 新舊架構(gòu)的差異

新舊系統(tǒng)的主要差別包括:

  • 新數(shù)據(jù)庫(kù)的拆分維度從冷熱數(shù)據(jù)變更成了根據(jù)訂單號(hào)進(jìn)行水平拆分
  • 數(shù)據(jù)庫(kù)從1拆2,變成了1拆64解決了磁盤(pán)存儲(chǔ)空間不足的問(wèn)題
  • 新數(shù)據(jù)庫(kù)的部署方式更加靈活,如果16臺(tái)物理機(jī)器資源不足時(shí),可以通過(guò)增加服務(wù)器的數(shù)量快速提高數(shù)據(jù)庫(kù)的處理性能
  • 如果64個(gè)分片的數(shù)量不足時(shí),可以通過(guò)調(diào)整分片計(jì)算的組件功能,擴(kuò)展分片數(shù)量
  • 原先的SQLServer采用的一主多從一DR的模式進(jìn)行配置。當(dāng)前系統(tǒng)每個(gè)分片物理服務(wù)器采用一主一從一DR的模式進(jìn)行配置
  • 通過(guò)增加訂單聚合數(shù)據(jù)庫(kù)將部分跨分片的數(shù)據(jù)通過(guò)Binglog+有序消息的方式聚合到新的數(shù)據(jù)庫(kù)上,降低跨分片查詢帶來(lái)的性能損失

2.3 技術(shù)方案

在項(xiàng)目執(zhí)行過(guò)程中有非常多的技術(shù)細(xì)節(jié)問(wèn)題需要分析和解決。我們列舉一些在項(xiàng)目過(guò)程中可能會(huì)遇到的問(wèn)題:

  • 如何選擇分片鍵
  • 如何解決跨分片查詢性能的損失
  • 如何提高開(kāi)發(fā)效率,降低項(xiàng)目風(fēng)險(xiǎn)
  • 全鏈路的灰度切換方案
  • 分片故障的處理方案

下面我們就選擇幾個(gè)典型的例子,來(lái)說(shuō)明我們?cè)陧?xiàng)目過(guò)程中遇到的問(wèn)題,以及解決這些問(wèn)題的方案。

2.3.1 分片鍵選擇

分庫(kù)的第一步也是最重要的一步,就是選擇分片鍵。選擇的原則是:

  • 分片鍵必須是不會(huì)被更新的字段
  • 各個(gè)分庫(kù)的數(shù)據(jù)量和讀寫(xiě)壓力要均勻,避免熱點(diǎn)分庫(kù)
  • 要盡量減少單次查詢涉及的分庫(kù)數(shù)量,降低DB壓力

分片鍵的選擇,是需要根據(jù)具體的業(yè)務(wù)場(chǎng)景來(lái)確定。對(duì)于訂單數(shù)據(jù)的拆分,常見(jiàn)的選擇是訂單ID和用戶ID兩個(gè)維度,這也是業(yè)內(nèi)最常用的兩個(gè)分片鍵。我們最終采用的是主訂單ID,主要是基于四個(gè)因素:

  • 90%的請(qǐng)求都是基于訂單ID進(jìn)行查詢
  • 主訂單ID是對(duì)應(yīng)于用戶的一個(gè)訂單,包含多個(gè)行程和貴賓休息室等附加產(chǎn)品,后臺(tái)會(huì)可能將這些拆分為多個(gè)子訂單,而子訂單之間會(huì)做Join等關(guān)聯(lián)處理,所以不能選擇子訂單維度
  • 一個(gè)主訂單可能關(guān)聯(lián)多個(gè)用戶ID,比如用戶A為用戶B購(gòu)買機(jī)票,用戶B又可以自己為這個(gè)訂單添加值機(jī)的功能。一個(gè)訂單ID關(guān)聯(lián)了兩個(gè)用戶ID,從而使用用戶ID用作分片鍵會(huì)導(dǎo)致訂單分布在不同的分片
  • 分銷商的訂單量非常大,按用戶ID分庫(kù)會(huì)導(dǎo)致數(shù)據(jù)不均衡

我們決定采用主訂單號(hào)作為分片鍵后,進(jìn)行了下列改造,用于實(shí)現(xiàn)并且加速分片選擇的過(guò)程。

1)訂單ID索引表

【問(wèn)題】:如何獲取主子訂單對(duì)應(yīng)的分片ID?

按主訂單ID分庫(kù),首先產(chǎn)生的問(wèn)題是子訂單ID如何計(jì)算分庫(kù),需要查詢所有分庫(kù)么?我們是采用異構(gòu)索引表的方式,即創(chuàng)建一個(gè)訂單ID到主訂單ID的索引表,并且索引表是按訂單ID進(jìn)行分庫(kù)。每次查詢訂單ID查詢時(shí),從索引表中獲取對(duì)應(yīng)的主訂單ID,計(jì)算出分庫(kù),再進(jìn)行業(yè)務(wù)查詢,避免查詢所有分庫(kù)。

2)索引表多級(jí)緩存

【問(wèn)題】:通過(guò)索引表查詢分片ID會(huì)增加了查詢的二次開(kāi)銷,使查詢性能損失嚴(yán)重,如何減少數(shù)據(jù)庫(kù)二次查詢的開(kāi)銷來(lái)提高查詢性能呢?

訂單ID的二次查詢,仍然會(huì)帶來(lái)數(shù)據(jù)庫(kù)的壓力明顯上升,實(shí)際上訂單ID是不會(huì)更新的,訂單ID和主訂單ID的映射關(guān)系也是不會(huì)發(fā)生變化的,完全可以把訂單ID索引表的信息緩存起來(lái),每次查詢時(shí)從緩存中就可以獲取主訂單ID。

我們?cè)O(shè)計(jì)了多級(jí)緩存來(lái)實(shí)現(xiàn)查詢加速,所有的緩存和分庫(kù)邏輯都封裝在組件中,提供給各個(gè)客戶端使用。三級(jí)緩存結(jié)構(gòu)如下:

注:圖下方的數(shù)字代表在當(dāng)前緩存和它的所有上級(jí)緩存命中率的總和。例如Redis的99.5%代表1000個(gè)訂單有995個(gè)在本地緩存或者是Redis緩存中命中了。

客戶端本地緩存:將最熱門(mén)的訂單ID索引存放在應(yīng)用的本地內(nèi)存中,只需要一次內(nèi)存操作就能獲取主訂單號(hào),不需要進(jìn)行額外的網(wǎng)絡(luò)IO

Redis分布式緩存:將大量的索引信息存放在Redis中,并且所有客戶端可以共用Redis緩存,命中率超過(guò)99%,并且由于訂單的映射關(guān)系是不會(huì)發(fā)生變化的,因此可以在生成訂單號(hào)的階段對(duì)緩存進(jìn)行預(yù)填充

服務(wù)端本地緩存:對(duì)DB索引表的讀取,都是在特定的應(yīng)用中實(shí)現(xiàn),未命中緩存時(shí)客戶端是通過(guò)服務(wù)端獲取索引信息。服務(wù)端也有本地緩存,使用Guava實(shí)現(xiàn)用于減緩熱點(diǎn)key的流量尖刺避免緩存擊穿

3)本地緩存的內(nèi)存優(yōu)化

【問(wèn)題】:使用本地緩存可以減少索引表查詢開(kāi)銷,如果需要提高緩存命中率,就需要消耗更多的內(nèi)存使用,那么如何減少內(nèi)存占用的問(wèn)題呢?

本地緩存的效率是最高的,存儲(chǔ)在本地的索引信息自然是越多越好。但本地內(nèi)存是寶貴而有限的,我們需要盡量減少單個(gè)索引占用的內(nèi)存。訂單ID都是Long類型,每個(gè)Long類型占用24個(gè)字節(jié),通常情況下,單個(gè)索引中包含兩個(gè)Long類型, 還需要緩存內(nèi)部的多層Node節(jié)點(diǎn),最終單個(gè)索引大約需要100個(gè)字節(jié)。

我們主要是結(jié)合業(yè)務(wù)場(chǎng)景來(lái)改進(jìn)內(nèi)存的使用。訂單ID是有序的,而且主子訂單ID的生成時(shí)間是非常接近的,大部分情況下,主訂單ID和子訂單ID的數(shù)值差異是很小的。對(duì)于連續(xù)的數(shù)字,數(shù)組的方式是非常節(jié)省空間的,100個(gè)Long類型占用2400個(gè)字節(jié),而一個(gè)長(zhǎng)度為100的long數(shù)組,則只占用824個(gè)字節(jié)。同時(shí)不直接存儲(chǔ)主訂單ID,而是只存儲(chǔ)主子訂單ID的差值,從long類型縮減為short類型,可以進(jìn)一步減少內(nèi)存占用。

最終的緩存結(jié)構(gòu)為:Map<Long, short[]>。從而使整體的內(nèi)存占用減少了大約93%的存儲(chǔ)空間。也就意味著我們可以適當(dāng)增加本地緩存的容量,同時(shí)減少內(nèi)存的消耗。

改造后:

  • 【Key】表示訂單ID所在的桶,計(jì)算方式為訂單ID對(duì) 64(數(shù)組長(zhǎng)度)取模
  • 【下標(biāo)】表示訂單ID的具體位置,計(jì)算方式為訂單ID對(duì) 64(數(shù)組長(zhǎng)度)取余數(shù),即【KEY】和【下標(biāo)】合計(jì)起來(lái)表示訂單ID
  • 【偏移量】表示主訂單ID的信息,計(jì)算方式是主訂單ID減去訂單ID

最優(yōu)情況下,存儲(chǔ)64個(gè)索引只需要一個(gè)Long類型、一個(gè)長(zhǎng)度64的short數(shù)組和約50個(gè)字節(jié)的輔助空間,總計(jì)200個(gè)字節(jié),平均每個(gè)索引3個(gè)字節(jié),占用的內(nèi)存縮減到原來(lái)的100個(gè)字節(jié)的3%。

值得注意的是對(duì)于偏移量的設(shè)計(jì)仍然有一定的講究。我們需要分析主子訂單的差異區(qū)間范圍。Short的取值范圍是-32768 ~ 32767,首先將-32768定義為非法值。我們還發(fā)現(xiàn)大部分的訂單分布區(qū)間其實(shí)并沒(méi)有和這個(gè)取值范圍重疊,因此需要額外再給偏移量增加二次偏移量來(lái)優(yōu)化這個(gè)問(wèn)題,實(shí)際的取值范圍是:-10000 ~ 55534,進(jìn)一步提高了short偏移量的覆蓋面。

4)主子訂單ID同余

【問(wèn)題】我們對(duì)訂單ID索引做了各種改進(jìn),使它運(yùn)行的越來(lái)越順暢,但三級(jí)緩存的引入,也使得我們的系統(tǒng)結(jié)構(gòu)變復(fù)雜,是否有辦法跳過(guò)索引表呢?

我們將未使用的訂單ID按余數(shù)分成多個(gè)桶,新增訂單在拆分訂單時(shí),子訂單ID不再是隨機(jī)生成,而是按照主訂單ID的余數(shù)確定對(duì)應(yīng)的桶,然后只允許使用這個(gè)桶內(nèi)的訂單ID,即保證主訂單ID和子訂單ID的余數(shù)是相同的。在查詢時(shí),子訂單ID直接取余數(shù)就能確定對(duì)應(yīng)的分庫(kù),不需要讀取訂單索引。

再進(jìn)一步,生成主訂單ID時(shí)也不再是隨機(jī)選擇,而是基于用戶ID來(lái)分桶和選擇,做到一個(gè)UID下的訂單會(huì)盡量集中到單一分庫(kù)中。

用戶ID/主訂單ID/子訂單ID三者同余

2.3.2 跨分片查詢優(yōu)化

數(shù)據(jù)分庫(kù)后,當(dāng)查詢條件不是分片鍵時(shí),例如使用用戶ID、更新時(shí)間等作為查詢條件,都需要對(duì)所有分片進(jìn)行查詢,在DB上的執(zhí)行次數(shù)會(huì)變?yōu)樵瓉?lái)的64倍,消耗的CPU資源也會(huì)急劇放大。這是所有分庫(kù)分表都會(huì)遇到的問(wèn)題,也是一個(gè)分庫(kù)項(xiàng)目最具有技術(shù)挑戰(zhàn)的環(huán)節(jié)。我們針對(duì)各種場(chǎng)景,采取多種方式來(lái)進(jìn)行優(yōu)化。

1)UID索引表

【問(wèn)題】UID是除了訂單號(hào)以外消耗資源最多的查詢之一,大約占用大約8%的數(shù)據(jù)庫(kù)使用資源。使用allShards查詢會(huì)消耗非常多的資源,嚴(yán)重降低查詢性能。那么我們?nèi)绾螌?duì)UID查詢進(jìn)行優(yōu)化,從而提升查詢效率呢?

索引表是一種常見(jiàn)的解決方案,需要滿足三個(gè)條件:

  • 索引字段不允許更新訂單庫(kù)中用戶ID是不會(huì)被更新的
  • 單個(gè)字段值關(guān)聯(lián)的數(shù)據(jù)要少,或者關(guān)聯(lián)的分庫(kù)數(shù)量少關(guān)聯(lián)的數(shù)據(jù)過(guò)多,最終還是到所有分庫(kù)中獲取數(shù)據(jù),也就失去了索引表的意義。對(duì)于我們的業(yè)務(wù)場(chǎng)景來(lái)說(shuō),用戶購(gòu)買機(jī)票是一種較為低頻的行為。因此,大部分用戶的訂單數(shù)量相對(duì)有限,平均每個(gè)用戶的訂單涉及的分庫(kù)數(shù)量遠(yuǎn)小于所有分庫(kù)數(shù)量。
  • 查詢頻率要足夠高索引表本質(zhì)上是一個(gè)“空間換時(shí)間”的思路,只有足夠高的查詢頻率,有足夠的收益,才值的實(shí)現(xiàn)索引表。

以用戶ID作為條件的查詢,是業(yè)務(wù)中非常重要的一類查詢,也是排除訂單ID查詢后,最多的一類查詢。基于業(yè)務(wù)和現(xiàn)有數(shù)據(jù)來(lái)分析,由于單個(gè)用戶購(gòu)買機(jī)票的總數(shù)并不是很多,用戶ID分布在了有限的分庫(kù)上。我們?cè)黾右粋€(gè)用戶ID索引表,存儲(chǔ)用戶ID與訂單ID的映射信息,并按照用戶ID進(jìn)行分庫(kù)存儲(chǔ)。如下圖,每次用戶ID的查詢,會(huì)先查詢索引,獲取包含此用戶訂單的所有分庫(kù)列表,通過(guò)一次額外的查詢,能夠快速排除大量無(wú)關(guān)的分庫(kù)。再結(jié)合前面提及的用戶ID與訂單ID同余的策略,單個(gè)用戶ID的新增訂單會(huì)集中存儲(chǔ)在單一分庫(kù)中,隨著歷史數(shù)據(jù)的逐步歸檔,單個(gè)用戶查詢的分庫(kù)數(shù)量會(huì)越來(lái)越少。

UserIDIndex表結(jié)構(gòu)

2)鏡像庫(kù)

【問(wèn)題】:并不是所有的查詢都可以像用戶ID一樣,通過(guò)建立一個(gè)二級(jí)索引表來(lái)優(yōu)化查詢問(wèn)題,而且建立二級(jí)索引表的代價(jià)比較大,我們需要一個(gè)更通用的方案解決這些查詢問(wèn)題。

AllShards查詢中的另一類查詢就是時(shí)間戳的查詢,尤其是大量的監(jiān)控查詢,大部分請(qǐng)求是可以接受一定的延遲,同時(shí)這些請(qǐng)求只是關(guān)注熱點(diǎn)數(shù)據(jù),比如尚未被使用的訂單。

我們新建了一套MySQL數(shù)據(jù)庫(kù),作為鏡像庫(kù),將64個(gè)分庫(kù)中的熱點(diǎn)數(shù)據(jù),集中存儲(chǔ)到單一數(shù)據(jù)庫(kù)中,相關(guān)的查詢直接在鏡像庫(kù)中執(zhí)行,避免分庫(kù)的問(wèn)題。

鏡像庫(kù)的數(shù)據(jù)同步,則是通過(guò)Canal+QMQ的方式來(lái)實(shí)現(xiàn),并定時(shí)對(duì)比數(shù)據(jù),業(yè)務(wù)應(yīng)用上則是只讀不寫(xiě),嚴(yán)格保證雙邊數(shù)據(jù)一致性。

3)ES/MySQL對(duì)比

【問(wèn)題】:鏡像庫(kù)存在多種實(shí)現(xiàn)方案,很多系統(tǒng)采用了ES作為查詢引擎,我們?cè)撊绾芜x擇?

ES也是解決復(fù)雜查詢場(chǎng)景的一種常見(jiàn)方案,我們?cè)?jīng)考慮采用ES來(lái)提升查詢性能,并且進(jìn)行了詳細(xì)的評(píng)估和測(cè)試,但最終放棄了ES方案,主要考慮到以下幾點(diǎn)原因:

  • 項(xiàng)目前期對(duì)所有的查詢進(jìn)行了充分簡(jiǎn)化和規(guī)整,目前所有的查詢使用MySQL都可以很好的運(yùn)行。
  • 在已經(jīng)正確建立索引和優(yōu)化SQL語(yǔ)句的情況下,MySQL消耗的CPU可能遠(yuǎn)小于ES,尤其是訂單ID、時(shí)間戳等數(shù)字類型的查詢,MySQL消耗的CPU只是ES消耗的20%甚至更低。
  • ES并不擅長(zhǎng)數(shù)字查詢,而是更合適索引字段多變的場(chǎng)景。

因此具體采用ES還是MySQL,或者是其他數(shù)據(jù)庫(kù)來(lái)建立鏡像數(shù)據(jù)庫(kù),最重要的一點(diǎn)還是要基于現(xiàn)有的業(yè)務(wù)場(chǎng)景和實(shí)際生產(chǎn)上的需求進(jìn)行綜合分析和驗(yàn)證后,找出一個(gè)最適合自己當(dāng)前情況的方案。

2.3.3 雙寫(xiě)組件設(shè)計(jì)

因?yàn)榧夹g(shù)棧的問(wèn)題,目前我們的ORM采用的是公司的DAL組件。這個(gè)組件本身對(duì)公司的環(huán)境支持較好,而且該組件對(duì)于Sharding數(shù)據(jù)庫(kù)也提供了良好的支持。因此我們?cè)谠擁?xiàng)目上仍然使用DAL作為我們數(shù)據(jù)庫(kù)的訪問(wèn)組件。

但是原生的DAL并不支持雙寫(xiě)的功能,不支持讀寫(xiě)的切換。針對(duì)項(xiàng)目的特性,我們需要盡可能的讓開(kāi)發(fā)少感知或者不感知底層數(shù)據(jù)庫(kù)的雙寫(xiě)和讀寫(xiě)切換的操作。一切對(duì)于用戶來(lái)說(shuō)變得更簡(jiǎn)單、更透明。另一方面,我們打算優(yōu)化組件本身的使用接口,讓用戶使用起來(lái)更傻瓜化。

組件的升級(jí)改造需要符合以下原則:

  • 對(duì)業(yè)務(wù)代碼侵入少
  • 改造少,降低工作量
  • 使用簡(jiǎn)單
  • 符合直覺(jué)

這些改造的意義是非常重大的,它是我們能夠高質(zhì)量上線的關(guān)鍵。于是我們對(duì)組件進(jìn)行了一些封裝和優(yōu)化。

1)業(yè)務(wù)層對(duì)象和數(shù)據(jù)庫(kù)層對(duì)象進(jìn)行隔離

為了統(tǒng)一維護(hù)方便我們將團(tuán)隊(duì)內(nèi)所有的數(shù)據(jù)庫(kù)對(duì)象(Pojo)都維護(hù)在了公共組件中。因此,在公共jar包中生成的對(duì)象通常是一個(gè)大而全的數(shù)據(jù)庫(kù)實(shí)體。這種大而全的實(shí)體信息存在以下幾個(gè)問(wèn)題:

  • 單表查詢時(shí)直接只用pojo返回了全量信息,影響查詢性能
  • 直接在代碼中使用pojo帶來(lái)了大量無(wú)用的字段,不符合按需使用的原則
  • 很難統(tǒng)計(jì)應(yīng)用對(duì)于數(shù)據(jù)庫(kù)字段的依賴的問(wèn)題
  • 數(shù)據(jù)庫(kù)字段和代碼直接耦合,在代碼編寫(xiě)期間不能對(duì)字段的命名等問(wèn)題進(jìn)行優(yōu)化

為了解決以上問(wèn)題,我們中間新增了Model層,實(shí)現(xiàn)數(shù)據(jù)庫(kù)pojo和業(yè)務(wù)代碼的隔離。例如我們的航班信息表(Flight)有200多個(gè)字段,但是實(shí)際在代碼中僅需要使用航班號(hào)和起飛時(shí)間。我們可以在業(yè)務(wù)代碼中定義一個(gè)新的FlightModel,如下圖所示:

@Builder
public class Flight implements DalDto {
/**
* 訂單號(hào)
*/
private Long orderId;


/**
* 航班號(hào)
*/
[DalField=”flight”]
private String flightNo;
}

擴(kuò)展組件將該對(duì)象映射到數(shù)據(jù)庫(kù)的Pojo上,并且可以改變字段的命名甚至類型從而優(yōu)化代碼的可讀性。在數(shù)據(jù)庫(kù)查詢時(shí)也進(jìn)行了優(yōu)化,僅僅查詢必要字段,減少了開(kāi)銷。

2)雙寫(xiě)功能

我們實(shí)現(xiàn)的雙寫(xiě)方案是先寫(xiě)SQLServer再寫(xiě)MySQL,同時(shí)也實(shí)現(xiàn)了失敗處理相關(guān)的策略。

雙寫(xiě)模式包括:

異步雙寫(xiě)

這個(gè)主要是在雙寫(xiě)功能實(shí)現(xiàn)的初期,我們會(huì)使用隊(duì)列+異步線程的方式將數(shù)據(jù)寫(xiě)入到MySQL。采取這種方式的數(shù)據(jù)一致性是比較差的,之所以采用這種方式也是在初期我們對(duì)數(shù)據(jù)庫(kù)處于探索階段,避免MySQL數(shù)據(jù)庫(kù)故障對(duì)當(dāng)前系統(tǒng)產(chǎn)生影響。

同步雙寫(xiě)

當(dāng)SQLServer寫(xiě)入成功后,在相同的線程中對(duì)MySQL進(jìn)行寫(xiě)入。這種模式相對(duì)來(lái)說(shuō)數(shù)據(jù)一致性會(huì)比較好,但是在極端情況下仍然可能存在數(shù)據(jù)不一致的情況。

如下圖所示。當(dāng)任務(wù)1更新MySQL數(shù)據(jù)庫(kù)之前,如果有別的任務(wù)搶先更新了相同的數(shù)據(jù)字段就有可能產(chǎn)生臟寫(xiě)的問(wèn)題。

我們可以通過(guò)以下手段減少數(shù)據(jù)不一致的問(wèn)題:

  • 數(shù)據(jù)表的讀寫(xiě)盡可能收口
  • 訪問(wèn)收口以后,通過(guò)對(duì)業(yè)務(wù)系統(tǒng)增加分布式鎖等手段緩解此類問(wèn)題的產(chǎn)生?
  • 可以增加數(shù)據(jù)比對(duì)的工具,主動(dòng)發(fā)現(xiàn)數(shù)據(jù)的不一致并進(jìn)行修復(fù),通過(guò)一個(gè)異步的掃描時(shí)間戳的工具來(lái)主動(dòng)進(jìn)行數(shù)據(jù)對(duì)比注和修復(fù)
  • 寫(xiě)入失敗需要根據(jù)當(dāng)前的模式觸發(fā)自動(dòng)補(bǔ)償?shù)牟呗裕@個(gè)可以參考下文提到的數(shù)據(jù)雙寫(xiě)異常的補(bǔ)償方案

注:數(shù)據(jù)對(duì)比和補(bǔ)償需要注意熱點(diǎn)數(shù)據(jù)頻繁更新和由于讀取時(shí)間差導(dǎo)致的不一致的問(wèn)題

剛才提到我們抽象了Model層的數(shù)據(jù),在此基礎(chǔ)上,我們的雙寫(xiě)改造對(duì)用戶來(lái)說(shuō)非常的容易。

@DalEntity(primaryTypeName = "com.ctrip.xxx.dal.sqlserver.entity.FlightPojo",
secondaryTypeName = "com.ctrip.xxx.dal.mysql.entity.FlightPojo")
@Builder
public class Flight implements DalDto {

我們僅需在Model對(duì)象上增加DalEntity注解實(shí)現(xiàn)數(shù)據(jù)庫(kù)Pojo的雙邊映射。除此之外,開(kāi)發(fā)人員不需要對(duì)業(yè)務(wù)代碼做其他調(diào)整,即可以通過(guò)配置實(shí)現(xiàn)雙寫(xiě)、數(shù)據(jù)源切換等操作。

3)雙寫(xiě)異常處理模式

雙寫(xiě)時(shí),我們需要盡可能保證數(shù)據(jù)的一致性,對(duì)于MySQL數(shù)據(jù)寫(xiě)入異常時(shí),我們提供了多種異常處理模式。

  • AC

異步雙寫(xiě)時(shí),如果從庫(kù)發(fā)生異常進(jìn)行數(shù)據(jù)捕獲,不拋出異常,僅輸出告警信息

  • SC

同步雙寫(xiě)時(shí),如果從庫(kù)發(fā)生異常進(jìn)行數(shù)據(jù)捕獲,不拋出異常,僅輸出告警信息

  • ST

同步雙寫(xiě)時(shí),如果從庫(kù)發(fā)生異常,拋出異常,中斷處理流程

4)雙讀功能

雙寫(xiě)功能相對(duì)比較好理解。在灰度切換過(guò)程中,假如存在灰度控制的訂單A以SQLServer為主,訂單B以MySQL為主。但是我們查詢到結(jié)果中同時(shí)包含了訂單A和訂單B的場(chǎng)景。這個(gè)時(shí)候我們希望的是,同時(shí)查詢SQLServer和MySQL的數(shù)據(jù)源,并且從不同數(shù)據(jù)源中獲取相應(yīng)的訂單數(shù)據(jù),然后進(jìn)行組合、排序、拼接。這些篩選邏輯由我們的組件來(lái)自動(dòng)完成,從而實(shí)現(xiàn)了更加精細(xì)的灰度控制。

值得注意的是allShard查詢的結(jié)果在部分情況下(例如分頁(yè)查詢)和單庫(kù)查詢的結(jié)果存在較大的差異,也需要組件的支持。

5)數(shù)據(jù)寫(xiě)入異常的補(bǔ)償方案

我們需要在不同階段設(shè)計(jì)不同的補(bǔ)償方案。初期MySQL的數(shù)據(jù)并不會(huì)對(duì)外提供服務(wù),即使數(shù)據(jù)寫(xiě)入失敗,也不能影響系統(tǒng)流程的正常運(yùn)行,同時(shí)也要保證數(shù)據(jù)寫(xiě)入的準(zhǔn)確性。因此,我們采用了SC的異常處理模式,并且增加了主動(dòng)和被動(dòng)的數(shù)據(jù)補(bǔ)償。

但是我們的目標(biāo)是使用MySQL的數(shù)據(jù)。因此,當(dāng)主數(shù)據(jù)源需要SQLServer切換到MySQL后,雖然數(shù)據(jù)庫(kù)寫(xiě)入的順序仍然保持先寫(xiě)SQLServer再寫(xiě)MySQL,但是數(shù)據(jù)寫(xiě)入失敗的處理模式需要發(fā)生變化。

這里先插播一個(gè)問(wèn)題,就是為什么不能先寫(xiě)MySQL然后同步更新SQLServer。主要考慮到以下兩個(gè)因素:

1)數(shù)據(jù)庫(kù)主鍵生成的歷史遺留原因

由于MySQL是Sharding數(shù)據(jù)庫(kù),如果先插入該數(shù)據(jù)庫(kù),默認(rèn)情況下會(huì)通過(guò)雪花算法生成主鍵。寫(xiě)入完成后,我們將該主鍵同步給SQLServer。

但是受到公司ORM框架和歷史遺留的技術(shù)限制,SQLServer不會(huì)使用該數(shù)據(jù),仍然采用自增的方式生成主鍵。導(dǎo)致數(shù)據(jù)嚴(yán)重不一致。

2)數(shù)據(jù)雙向同步的復(fù)雜度問(wèn)題

當(dāng)我們以SQLServer作為主數(shù)據(jù)庫(kù)時(shí),如果數(shù)據(jù)不一致需要同步給MySQL(異步存在延時(shí));當(dāng)以MySQL作為主數(shù)據(jù)庫(kù)時(shí),如果發(fā)生數(shù)據(jù)不一致,需要進(jìn)行反向同步。

一來(lái),數(shù)據(jù)補(bǔ)償程序復(fù)雜度很高。二來(lái),如果我們?nèi)绻贛ySQL和SQLServer數(shù)據(jù)庫(kù)誰(shuí)作為主庫(kù)之間切換頻繁,數(shù)據(jù)同步程序就會(huì)變得非常迷茫,到底誰(shuí)該同步給誰(shuí)?

那么如何提高在以MySQL為主的情況下,雙邊數(shù)據(jù)庫(kù)的一致性呢?

首先,我們得關(guān)閉自動(dòng)補(bǔ)償功能,異常處理模式需要從SC切換到ST,遇到MySQL失敗直接拋出數(shù)據(jù)庫(kù)異常,然后基于系統(tǒng)的業(yè)務(wù)場(chǎng)景進(jìn)行如下操作:

1)依賴業(yè)務(wù)系統(tǒng)的自動(dòng)補(bǔ)償

對(duì)于訂單處理系統(tǒng),大部分的流程其實(shí)具備了自動(dòng)補(bǔ)償?shù)哪芰Γ虼四呐耂QLServer更新成功,MySQL未成功。下次補(bǔ)償程序仍然讀取MySQL,SQLServer會(huì)被二次更新,從而達(dá)到最終一致性。

這個(gè)時(shí)候,需要考慮的SQLServer的可重入性。

2)無(wú)法自動(dòng)補(bǔ)償?shù)膱?chǎng)景,提供手工數(shù)據(jù)補(bǔ)償?shù)墓δ?/strong>

因?yàn)榇藭r(shí)MySQL已經(jīng)作為主要數(shù)據(jù)源,如果SQLServer存在不一致的場(chǎng)景可以提供手工的方式將數(shù)據(jù)補(bǔ)償回SQLServer。這邊沒(méi)有實(shí)現(xiàn)自動(dòng)補(bǔ)償,因?yàn)槔碚撋现挥性跀?shù)據(jù)不一致的場(chǎng)景,并且發(fā)生了回切才會(huì)產(chǎn)生影響。

3)數(shù)據(jù)的比對(duì)功能仍然正常開(kāi)啟,及時(shí)發(fā)現(xiàn)數(shù)據(jù)的不一致

6) 組件設(shè)計(jì)的功能和策略分離

我們整體的功能都整合在名為Dal-Extension的系統(tǒng)組件里,主要分為功能實(shí)現(xiàn)和策略兩大部分。

功能就是前面提到的例如雙寫(xiě),讀切換,異常處理模式切換等。策略就是引擎,它實(shí)現(xiàn)了功能和功能間的聯(lián)動(dòng)。例如上文提到的,如果以SQLServer作為主數(shù)據(jù)源,那么系統(tǒng)自動(dòng)采用SC的異常處理模式,并且主動(dòng)調(diào)用數(shù)據(jù)補(bǔ)償功能。如果是以MySQL作為主數(shù)據(jù)源,那么系統(tǒng)自動(dòng)切換到ST的異常處理模式。

相較于基于應(yīng)用、表維度的切換策略。我們提供了維度更豐富的切換組合策略。

  • 應(yīng)用/IP地址
  • 讀/寫(xiě)
  • 訂單區(qū)間

通過(guò)對(duì)以上維度的配置進(jìn)行靈活調(diào)整,我們即可以實(shí)現(xiàn)單表,單機(jī)器的試驗(yàn)性切換控制,也可以進(jìn)行全鏈路的灰度切換,確保一個(gè)訂單在整個(gè)訂單處理生命周期使用相同的數(shù)據(jù)源,從而避免因?yàn)閿?shù)據(jù)雙寫(xiě)或者同步導(dǎo)致的數(shù)據(jù)讀取結(jié)果不一致的問(wèn)題。整體的數(shù)據(jù)切換操作由配置中心統(tǒng)一托管。

2.3.4 分片故障處理

原先的數(shù)據(jù)庫(kù)如果發(fā)生了故障,會(huì)導(dǎo)致整個(gè)系統(tǒng)不可用。但是新的數(shù)據(jù)庫(kù)擴(kuò)展成64個(gè)分片后,其實(shí)相對(duì)來(lái)說(shuō)故障概率提高了64倍。因此,我們需要避免部分分片故障導(dǎo)致整個(gè)系統(tǒng)失效的情況。另外增加故障轉(zhuǎn)移和隔離功能,避免故障擴(kuò)散,減少損失也是我們重點(diǎn)關(guān)注的功能。

(當(dāng)然,如果發(fā)生分片故障,首選的故障恢復(fù)方案是數(shù)據(jù)庫(kù)的主從切換)。

1)返回僅包含查詢成功分片的部分?jǐn)?shù)據(jù)

【問(wèn)題】針對(duì)跨分片查詢的場(chǎng)景,如果一個(gè)分片故障默認(rèn)情況下會(huì)導(dǎo)致整個(gè)查詢失敗,那么如何提高查詢成功率呢?

我們調(diào)研了數(shù)據(jù)使用端,發(fā)現(xiàn)有很多場(chǎng)景,例如人工訂單處理的環(huán)節(jié),是可以接受部分?jǐn)?shù)據(jù)的返回。也就是說(shuō)有查詢出盡可能多符合條件的訂單,放入人工待處理列表中。我們?cè)黾恿薱ontinueOnError參數(shù)來(lái)表示當(dāng)前查詢可以接受部分分片失效的場(chǎng)景。并且,系統(tǒng)返回了查詢結(jié)果后,如果存在分片查詢失敗的場(chǎng)景,系統(tǒng)會(huì)提供了錯(cuò)誤分片的信息。這樣業(yè)務(wù)上不僅能夠確保了很多業(yè)務(wù)環(huán)節(jié)處理不中斷,同時(shí)針對(duì)它提供的錯(cuò)誤分片信息可以讓我們快速感知失效的分片,以便系統(tǒng)自動(dòng)或者人工對(duì)這些分片進(jìn)行干預(yù)。

2)故障分片隔離

【問(wèn)題】當(dāng)故障分片出現(xiàn)大規(guī)模錯(cuò)誤后,如果是因?yàn)轫憫?yīng)時(shí)間長(zhǎng)會(huì)導(dǎo)致大量線程block,從而拖累整個(gè)應(yīng)用服務(wù)器。那么如何解決此類問(wèn)題呢?

當(dāng)分片發(fā)生故障時(shí),有可能我們的數(shù)據(jù)庫(kù)請(qǐng)求被hang住。我們allShards查詢的底層實(shí)現(xiàn)是基于共享線程池。當(dāng)部分分片的響應(yīng)慢時(shí),會(huì)拖累整個(gè)線程池。另外單表查詢時(shí),也可能會(huì)因?yàn)閿?shù)據(jù)庫(kù)響應(yīng)時(shí)間的問(wèn)題導(dǎo)致工作線程數(shù)量上漲的問(wèn)題。

我們?yōu)榇嗽黾恿朔制帘蔚膮?shù)。當(dāng)我們啟用分片臨時(shí)屏蔽功能后,底層數(shù)據(jù)庫(kù)查詢時(shí),發(fā)現(xiàn)該分片被屏蔽直接拋出異常,讓?xiě)?yīng)用程序能夠得到快速響應(yīng)。從而避免了網(wǎng)絡(luò)和數(shù)據(jù)庫(kù)訪問(wèn)時(shí)間消耗,提高了異常執(zhí)行的效率,避免問(wèn)題擴(kuò)散到正常的分片的數(shù)據(jù)處理。

3)故障訂單轉(zhuǎn)移

【問(wèn)題】根據(jù)之前的介紹,用戶訂單號(hào)是根據(jù)UID的哈希值進(jìn)行分配的。也就是說(shuō)同一個(gè)用戶分配的分片是固定的。如果該分片故障時(shí),用戶就無(wú)法提交訂單。那么如何避免或者減少此類問(wèn)題呢? 

如上圖所示,用戶ID_1和用戶ID_2根據(jù)哈希算法,原先會(huì)在分片1上生成訂單。但是如果發(fā)生了分片1故障時(shí),我們的UID分片計(jì)算組件會(huì)將分片1標(biāo)記為不可用,然后通過(guò)新的Hash算法計(jì)算出新的分片。

這里需要注意的是,新hash算法的選擇。

方法1:

使用同樣的哈希算法,但是生成結(jié)果后取模的值為63(64-1),但是這個(gè)存在的問(wèn)題是用戶ID_1和用戶ID_2計(jì)算出來(lái)的分片結(jié)果是一致的。假如新的分片號(hào)為2的話,如果發(fā)生分片1、分片2同時(shí)失效的情況下。那么仍然有1/64的訂單出現(xiàn)問(wèn)題。

方法2:

采用新的哈希算法,盡量使訂單分布在出了分片1以外的其他分片上。那么這種方法,即使分片1、分片2同時(shí)失效。那么僅僅會(huì)影響到1/64 * 1/63的訂單。受影響的訂單量大幅降低。

三、項(xiàng)目規(guī)劃

除了以上提到的技術(shù)問(wèn)題以外,我們?cè)僬務(wù)勴?xiàng)目的管理和規(guī)劃問(wèn)題。首先,圈定合理的項(xiàng)目范圍,劃清項(xiàng)目邊界是項(xiàng)目順利實(shí)施的重要前提。這個(gè)項(xiàng)目的范圍包括兩個(gè)重要的屬性:數(shù)據(jù)和團(tuán)隊(duì)。

數(shù)據(jù)范圍

1)劃定數(shù)據(jù)表范圍,先進(jìn)行表結(jié)構(gòu)優(yōu)化的工作

我們需要在項(xiàng)目初期明確數(shù)據(jù)表的范圍,針對(duì)一些可以下線的表或者字段,先完成合并和下線的工作,來(lái)縮小項(xiàng)目范圍。避免表結(jié)構(gòu)的變化和該項(xiàng)目耦合在一起,造成不必要的困擾。

2)相關(guān)數(shù)據(jù)表中哪些數(shù)據(jù)需要被遷移

我們?cè)谔幚磉@個(gè)問(wèn)題上,有一些反復(fù)。

方案1:僅遷移熱數(shù)據(jù)

因?yàn)橛唵螖?shù)據(jù)分為冷熱數(shù)據(jù),所以我們最開(kāi)始考慮是不是只要遷移熱數(shù)據(jù)就好了,冷數(shù)據(jù)僅保留查詢功能。

但是,這個(gè)方案有兩個(gè)很大的問(wèn)題:一是存在冷數(shù)據(jù)需要被還原到熱數(shù)據(jù)的場(chǎng)景,增加了系統(tǒng)實(shí)現(xiàn)的復(fù)雜度。二是冷數(shù)據(jù)保留時(shí)限的問(wèn)題,無(wú)法在短時(shí)間內(nèi)下線這個(gè)數(shù)據(jù)庫(kù)。

方案2:部分?jǐn)?shù)據(jù)自然消亡的表和字段不進(jìn)行遷移

針對(duì)有一些表由于業(yè)務(wù)或者系統(tǒng)改造的原因,可能后續(xù)數(shù)據(jù)不會(huì)更新了,或者在新的訂單上這些字段已經(jīng)廢棄了。大家在設(shè)計(jì)新表的時(shí)候其實(shí)往往很不喜歡把這些已經(jīng)廢棄了的信息加到新設(shè)計(jì)的表中。但是,我們需要面臨的問(wèn)題是,舊數(shù)據(jù)如何兼容是一個(gè)非常現(xiàn)實(shí)的問(wèn)題。

因此,當(dāng)我們開(kāi)發(fā)到中間的過(guò)程中,還是將部分表和字段重新加了回來(lái)。來(lái)確保舊數(shù)據(jù)庫(kù)盡快下線以及歷史邏輯保持兼容。

方案3:保留當(dāng)前所有的表結(jié)構(gòu)和信息

我們最后采用了這個(gè)方案,哪怕這個(gè)數(shù)據(jù)表或者字段未來(lái)不會(huì)做任何修改。

團(tuán)隊(duì)范圍

確定好數(shù)據(jù)范圍后,我們需要根據(jù)這些數(shù)據(jù),確定我們需要做的工作以及找到完成這些工作的相關(guān)團(tuán)隊(duì)并提前安排好資源。整個(gè)項(xiàng)目的資源分為核心成員和相關(guān)配合改造的團(tuán)隊(duì)。

核心成員需要做到組織分工明確,并且需要經(jīng)常一起頭腦風(fēng)暴,提出問(wèn)題,解決問(wèn)題,消除隱患。核心成員的另一個(gè)職責(zé)是幫助配合改造的團(tuán)隊(duì),協(xié)調(diào)并且解決技術(shù)問(wèn)題、資源問(wèn)題等等。特別是涉及到的改動(dòng)點(diǎn)較多、改造難度較大的團(tuán)隊(duì),需要提前介入,在適當(dāng)?shù)臅r(shí)候提供更多的幫助。

3.1 規(guī)劃

確定了項(xiàng)目的目標(biāo)和范圍后,我們?yōu)轫?xiàng)目設(shè)計(jì)了6個(gè)里程碑,來(lái)幫助我們更好的完成這個(gè)項(xiàng)目。

階段1:通過(guò)API對(duì)讀取進(jìn)行收口

這個(gè)階段雖然難度并不大,但是周期很長(zhǎng),溝通成本較高。在這個(gè)階段重點(diǎn)在于任務(wù)的協(xié)調(diào)和跟進(jìn)。DBA幫助我們研發(fā)了生產(chǎn)Trace查詢的工具,能夠準(zhǔn)實(shí)時(shí)的知道數(shù)據(jù)表的訪問(wèn)情況,幫我們快速驗(yàn)收并且圈定改造范圍。

我們建立了任務(wù)的看板,為每一個(gè)任務(wù)設(shè)定了負(fù)責(zé)人以及預(yù)期解決的時(shí)間,定期對(duì)任務(wù)進(jìn)行進(jìn)行跟蹤。項(xiàng)目的負(fù)責(zé)人也作為驗(yàn)收人,確認(rèn)每個(gè)任務(wù)的完成情況。

通過(guò)一段時(shí)間的努力,數(shù)據(jù)庫(kù)的訪問(wèn)收口在極少數(shù)內(nèi)部應(yīng)用當(dāng)中。實(shí)現(xiàn)了數(shù)據(jù)訪問(wèn)的收口。

階段2:開(kāi)發(fā)雙讀/雙寫(xiě)功能來(lái)實(shí)現(xiàn)平滑的數(shù)據(jù)切換

這個(gè)階段需要將整個(gè)項(xiàng)目的技術(shù)點(diǎn)、難點(diǎn)都逐一的找到,并且給出解決方案。如何提高效率和質(zhì)量也是這個(gè)階段重點(diǎn)關(guān)注的話題,我們盡量把這些雙寫(xiě)、切換的功能進(jìn)行封裝,讓業(yè)務(wù)邏輯層盡可能少感知,或者不感知這些底層邏輯。降低代碼開(kāi)發(fā)量,不僅能提高效率,還能提升質(zhì)量。

總的來(lái)說(shuō),這個(gè)階段需要提升開(kāi)發(fā)效率,提高開(kāi)發(fā)質(zhì)量并且降低項(xiàng)目風(fēng)險(xiǎn)。

階段3:驗(yàn)證數(shù)據(jù)一致性

這個(gè)是對(duì)階段2的驗(yàn)證工作,需要注意的是在驗(yàn)證中減少噪音,提高驗(yàn)證的自動(dòng)化率,能有效的提升項(xiàng)目的開(kāi)發(fā)質(zhì)量。

階段4:通過(guò)壓測(cè),故障模擬等手段驗(yàn)證系統(tǒng)性能。在數(shù)據(jù)庫(kù)故障時(shí),提供可靠的系統(tǒng)的災(zāi)備和故障隔離能力。

階段5:數(shù)據(jù)讀取從SQLServer切換到MySQL

這個(gè)階段可能不需要有太多的資源投入,但是風(fēng)險(xiǎn)卻是最大的。這個(gè)階段是對(duì)前面所有階段成果的驗(yàn)收。做好數(shù)據(jù)監(jiān)控、制定良好的切換方案、出現(xiàn)問(wèn)題時(shí)能夠回退是這個(gè)階段順利實(shí)施的重點(diǎn)。

階段6:停止SQLServer寫(xiě)入并且下線相關(guān)數(shù)據(jù)表

相比起階段5,階段6沒(méi)有后悔藥。一旦停止了SQLServer的寫(xiě)入,就非常難進(jìn)行回切的操作。所以得仔細(xì)做好白名單的驗(yàn)證,并且及時(shí)響應(yīng)和解決相關(guān)問(wèn)題。

3.2 原則

整個(gè)項(xiàng)目周期較長(zhǎng),我們需要制定好每個(gè)階段的目標(biāo),每個(gè)任務(wù)的目標(biāo)。由于數(shù)據(jù)庫(kù)承載了非常核心的業(yè)務(wù),因此整個(gè)階段、所有任務(wù)以及技術(shù)方案其實(shí)圍繞著一個(gè)原則展開(kāi),就是降低風(fēng)險(xiǎn)。

所以我們?cè)谠O(shè)計(jì)每個(gè)技術(shù)方案的時(shí)候,盡可能考慮這點(diǎn)。例如在數(shù)據(jù)源切換的開(kāi)關(guān)雖然涉及較多的服務(wù)實(shí)例,但是我們通過(guò)一個(gè)集中控制的平臺(tái),來(lái)實(shí)現(xiàn)全鏈路的切換和灰度控制。

四、經(jīng)驗(yàn)分享

該項(xiàng)目整體的周期較長(zhǎng),每個(gè)階段的挑戰(zhàn)不盡相同。為了確保項(xiàng)目的上線質(zhì)量,后續(xù)在讀切換、寫(xiě)切換兩個(gè)流程的灰度時(shí)間比較久。項(xiàng)目大約在2021年下半年順利完成。

實(shí)現(xiàn)了以下主要目標(biāo)和功能:

系統(tǒng)的水平擴(kuò)展能力得到大幅提升

系統(tǒng)分片數(shù)量為64,部署在16臺(tái)物理機(jī)上。后續(xù)根據(jù)業(yè)務(wù)需要機(jī)器的部署方式和分片數(shù)量可以進(jìn)行靈活調(diào)整。

數(shù)據(jù)庫(kù)資源利用率大幅下降,可靠性提升

數(shù)據(jù)庫(kù)服務(wù)器的CPU利用率從高峰期40%下降到目前的3%-5%之間。

訂單處理能力提升和存儲(chǔ)能力提升

原先區(qū)分冷熱數(shù)據(jù),熱數(shù)據(jù)大約僅能支持3個(gè)月的訂單,按照現(xiàn)在硬件資源推算,系統(tǒng)可以處理至少5年以上的訂單。

數(shù)據(jù)訪問(wèn)收口

原先近200個(gè)應(yīng)用直接訪問(wèn)數(shù)據(jù)庫(kù),給我們的改造帶來(lái)很大的不便。目前僅有限的內(nèi)部應(yīng)用允許直接訪問(wèn)訂單庫(kù)。

整體成本下降

原先主從服務(wù)器的CPU為128核,內(nèi)存256G;現(xiàn)在服務(wù)器縮減為40核心的標(biāo)準(zhǔn)配置。

在項(xiàng)目過(guò)程中也積累了不少的經(jīng)驗(yàn),例如:

項(xiàng)目的規(guī)劃要清晰,任務(wù)要明確,跟蹤要及時(shí)

整個(gè)項(xiàng)目中大約建立了數(shù)百個(gè)子任務(wù),每一個(gè)任務(wù)需要落實(shí)負(fù)責(zé)人以及上線時(shí)間,并對(duì)上線結(jié)果進(jìn)行驗(yàn)收。才能確保整個(gè)項(xiàng)目的周期不至于拉的非常長(zhǎng),減少后續(xù)的項(xiàng)目返工和風(fēng)險(xiǎn)。

減少例外情況的發(fā)生

當(dāng)一個(gè)大型的項(xiàng)目存在非常多的例外情況,這些特殊情況就得特殊處理,那么到最后總會(huì)有一些沒(méi)有處理干凈的尾巴。這些問(wèn)題都是項(xiàng)目的潛在隱患。

減少項(xiàng)目的依賴

這個(gè)和我們?nèi)粘i_(kāi)發(fā)關(guān)系也非常密切,當(dāng)一項(xiàng)任務(wù)有多個(gè)依賴方的時(shí)候,往往項(xiàng)目的進(jìn)展會(huì)大幅超出我們的預(yù)期。因此減少一些前置依賴,在不是非常確定的情況下。我們得先做好最壞的打算。

一次干好一件事

很多時(shí)候我們往往會(huì)高估自己的能力,例如在這次的改造中,我們會(huì)順便優(yōu)化一些表的結(jié)構(gòu)。于是造成了MySQL和SQLServer的數(shù)據(jù)表差異過(guò)大的問(wèn)題。那么這些差異其實(shí)為后面的開(kāi)發(fā)造成了不小的困擾。所有的方案,包括數(shù)據(jù)補(bǔ)償、遷移、數(shù)據(jù)源的切換等等場(chǎng)景都得為這些特殊差異的表單獨(dú)考慮方案,單獨(dú)實(shí)現(xiàn)邏輯。一不留神或者沒(méi)有考慮的很周全的情況下,往往會(huì)漏掉這部分的差異。導(dǎo)致項(xiàng)目返工,甚至出現(xiàn)生產(chǎn)故障。

項(xiàng)目的成功上線離不開(kāi)每一個(gè)成員的努力。在實(shí)施過(guò)程中,遇到的問(wèn)題比這篇文章列舉的問(wèn)題多得多,很多都是一些非常瑣碎的事情。特別是項(xiàng)目初期,我們往往是解決了一個(gè),冒出了更多的問(wèn)題。但是每次遇到問(wèn)題后,團(tuán)隊(duì)的成員都積極思考,集思廣益,攻破了一個(gè)又一個(gè)的技術(shù)問(wèn)題和業(yè)務(wù)問(wèn)題。通過(guò)一年多時(shí)間的鍛煉,團(tuán)隊(duì)成員的項(xiàng)目能力、技術(shù)能力進(jìn)步顯著;發(fā)現(xiàn)問(wèn)題的角度更敏銳,思考的角度更全面;團(tuán)隊(duì)的凝聚力也得到了明顯提升。

責(zé)任編輯:未麗燕 來(lái)源: 攜程技術(shù)
相關(guān)推薦

2022-05-13 09:27:55

Widget機(jī)票業(yè)務(wù)App

2022-06-03 09:21:47

Svelte前端攜程

2023-05-12 10:14:38

APP開(kāi)發(fā)

2020-12-04 14:32:33

AndroidJetpackKotlin

2017-04-11 15:11:52

ABtestABT變量法

2022-05-27 09:52:36

攜程TS運(yùn)營(yíng)AI

2023-11-13 11:27:58

攜程可視化

2025-06-24 09:51:47

2023-08-25 09:51:21

前端開(kāi)發(fā)

2023-01-04 12:17:07

開(kāi)源攜程

2022-08-06 08:27:41

Trace系統(tǒng)機(jī)票前臺(tái)微服務(wù)架構(gòu)

2025-06-24 09:44:41

2022-06-17 09:42:20

開(kāi)源MMKV攜程機(jī)票

2017-04-11 15:34:41

機(jī)票前臺(tái)埋點(diǎn)

2021-03-04 10:19:43

Windows 10Windows微軟

2022-07-15 12:58:02

鴻蒙攜程華為

2020-12-28 12:57:06

Windows 10X微軟Modern Stan

2017-03-15 17:38:19

互聯(lián)網(wǎng)

2022-08-12 08:34:32

攜程數(shù)據(jù)庫(kù)上云

2023-02-08 16:34:05

數(shù)據(jù)庫(kù)工具
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

色琪琪综合男人的天堂aⅴ视频| 亚洲乱码中文字幕综合| 国产成人中文字幕| 美女福利视频网| 精品一级视频| 日韩欧美亚洲综合| 久久久成人精品一区二区三区| 亚洲第一天堂在线观看| 老妇喷水一区二区三区| 美日韩精品免费视频| 中文字幕xxx| 国产日韩在线观看视频| 色综合久久99| 轻点好疼好大好爽视频| 久久久久久久久久久国产精品| 精品三区视频| 亚洲国产精品天堂| 亚洲精品国产精品国自产观看| 国产香蕉在线观看| 久久精品国产一区二区三区免费看 | 国产精品人妖ts系列视频| 国产福利久久精品| 国产精品人人爽| 日韩中文字幕区一区有砖一区 | 日韩欧乱色一区二区三区在线| 黑人狂躁日本妞一区二区三区| 影音先锋成人资源网站| 色影视在线观看| 久久久精品2019中文字幕之3| 岛国视频一区免费观看| 91在线视频国产| 石原莉奈一区二区三区在线观看| 91国产在线精品| 久久网一区二区| 香蕉综合视频| 久久精品99无色码中文字幕| 欧美大波大乳巨大乳| 午夜欧洲一区| 日韩激情第一页| 天天躁日日躁狠狠躁av麻豆男男 | 亚洲精品福利视频| 韩国黄色一级片| 日韩精品成人在线观看| 69堂精品视频| 网站在线你懂的| 亚洲精品毛片| 欧美日韩国产成人在线免费| 免费涩涩18网站入口| 日韩网站中文字幕| 在线视频国内自拍亚洲视频| 欧美女人性生活视频| 免费毛片b在线观看| 国产精品主播直播| 国产在线久久久| 国产一区二区女内射| 精品一区二区三区视频| 91精品免费视频| 国产免费高清视频| 国产福利一区在线| 国产精品一区在线观看| 无码精品黑人一区二区三区| 99久久久久免费精品国产| 精品在线观看一区二区| 日韩偷拍自拍| 欧美国产97人人爽人人喊| 色999日韩自偷自拍美女| 91在线不卡| 成人国产在线观看| 久久av一区二区| 黑人与亚洲人色ⅹvideos| 日本一区二区三区在线不卡 | 国产艳俗歌舞表演hd| 蜜桃成人av| 最近中文字幕mv在线一区二区三区四区| 亚洲女人毛茸茸高潮| 亚洲网色网站| 午夜精品视频网站| 波多野结衣视频网址| 精品一区二区日韩| 国产成人精品一区二区三区福利| 四季av日韩精品一区| 国产午夜精品一区二区三区视频 | 1区2区3区在线| 日本乱人伦aⅴ精品| 国内自拍第二页| 在线一区二区三区视频| 亚洲欧美一区二区激情| 天堂av免费在线| 激情久久久久| 国产精品久久久久秋霞鲁丝| www.97av| 国产夜色精品一区二区av| 欧美日韩dvd| 亚洲承认视频| 精品精品国产高清a毛片牛牛 | 日本一二三区在线视频| 国产精品无圣光一区二区| 国产美女永久无遮挡| 国产综合色区在线观看| 日韩精品一区二区三区中文不卡| 久久久久久久久久久国产精品| 小说区亚洲自拍另类图片专区| 91高清视频免费观看| 国产麻豆91视频| 久久视频一区二区| 91亚洲精品国产| 高清亚洲高清| 日韩激情在线视频| 国产精品嫩草影院俄罗斯 | 亚洲制服中文字幕| 国产欧美日韩免费观看 | 久久天堂av| 亚洲成人性视频| 日韩a级片在线观看| 日韩不卡一区二区| 激情久久av| av在线免费网站| 欧洲国内综合视频| 国产熟女高潮一区二区三区| 国产精品99一区二区三区| 日本91av在线播放| 亚洲av电影一区| 亚洲国产中文字幕在线视频综合| 亚洲一二三av| 国产一区日韩| 青青草国产精品一区二区| 日本激情一区二区| 亚洲影院久久精品| 能看毛片的网站| 我不卡神马影院| 国产视频福利一区| jizz在线免费观看| 欧洲人成人精品| 欧美熟妇激情一区二区三区| 亚洲在线成人| 久久国产欧美精品| 国产一二在线播放| 日韩av在线最新| 国产亚洲小视频| 成人网男人的天堂| 美女扒开大腿让男人桶| 日韩区欧美区| 欧美俄罗斯乱妇| www.色亚洲| 亚洲一区中文日韩| 成人做爰www看视频软件| 亚洲欧美文学| 国产精品果冻传媒潘| 日本片在线观看| 精品国产乱码久久久久久图片 | 精品国产乱码久久久久久蜜臀| 免费网站观看www在线观| 欧美在线91| 亚洲精品免费网站| 四虎影院观看视频在线观看| 日韩精品资源二区在线| 久久婷婷国产麻豆91| av动漫一区二区| 久久久久久久久久久久久久国产| 国产探花一区| 国产精品三级在线| 国内精品不卡| 精品久久久久久久久久久久包黑料| 国产精品30p| 97久久精品人人做人人爽50路| 国产成人精品视频免费看| 国产一区二区观看| 国产日韩欧美在线播放| 午夜在线激情影院| 日韩av网址在线| 国产精品露脸视频| 一区二区在线观看免费| 800av在线播放| 日韩不卡一区二区三区| 成年丰满熟妇午夜免费视频| 青草久久视频| 国产欧美一区二区白浆黑人| 青春草在线视频| 国产婷婷97碰碰久久人人蜜臀| www.久久视频| 一区二区在线免费| 亚洲人成人无码网www国产| 久久国产精品99久久久久久老狼| 国产又粗又猛又爽又黄的网站| 网红女主播少妇精品视频| 国产精品自产拍高潮在线观看| 国产区在线观看| 日韩经典中文字幕在线观看| 亚洲一级视频在线观看| 亚洲国产精品精华液网站| 欧美 日韩 国产 成人 在线观看| 国产一区二区三区综合| 国产精品后入内射日本在线观看| 久久国产亚洲| 久久艹中文字幕| 日本一区二区乱| 欧美在线视频免费观看| 在线免费观看的av| 亚洲丝袜av一区| 亚洲欧美另类一区| 欧美日韩久久久久久| 亚洲精品国产精品乱码| 自拍偷拍亚洲综合| 玖玖爱在线观看| 国产在线一区观看| 国产精品69页| 在线欧美亚洲| 国产经典久久久| 欧美色图在线播放| 狠狠爱一区二区三区| 日韩精品一级| 国产美女主播一区| 国产麻豆久久| 日韩av色综合| 国产拍在线视频| 欧美精品久久久久久久免费观看| 永久免费av片在线观看全网站| 日韩精品视频免费| 高清毛片aaaaaaaaa片| 91麻豆精品国产91久久久久久久久| 波多野结衣电车| 黑丝美女久久久| 日韩av电影网| 亚洲专区一二三| 国产精品三区在线观看| 国产精品国产a级| 亚洲а∨天堂久久精品2021| 91小视频免费观看| 天堂www中文在线资源| 丁香六月久久综合狠狠色| 亚洲女人在线观看| 国产在线不卡视频| 免费在线观看污网站| 久久99精品国产麻豆不卡| 亚洲欧美另类动漫| 日韩av一二三| 精品少妇无遮挡毛片| 久久亚洲一区| 亚洲色图38p| 视频一区国产视频| 天天色综合社区| 欧美aaaaaa午夜精品| 伊人国产在线视频| 欧美aⅴ一区二区三区视频| 午夜两性免费视频| 国精产品一区一区三区mba视频 | 成年人视频在线免费看| 富二代精品短视频| 精品不卡一区二区| 色菇凉天天综合网| 这里只有精品免费视频| 欧美性大战xxxxx久久久| 亚洲一级片免费看| 欧美一二三四区在线| 亚洲黄色在线播放| 亚洲激情视频在线| 国产色在线 com| 最近中文字幕mv在线一区二区三区四区| 午夜激情视频在线| 九九热视频这里只有精品| 免费在线播放电影| 欧美亚洲另类视频| 忘忧草在线www成人影院| 成人国产精品一区| 中文字幕一区二区三区四区久久 | 欧美gvvideo网站| 精品一区二区成人免费视频| 欧美日韩18| 播放灌醉水嫩大学生国内精品| 日韩 欧美一区二区三区| 亚洲制服中文字幕| 99久久精品免费看国产| 亚洲精品午夜视频| 玉米视频成人免费看| 婷婷激情五月网| 欧美精品三级在线观看| 内射无码专区久久亚洲| 国产小视频91| 亚洲男同gay网站| 欧美亚洲在线视频| 亚洲欧美综合久久久久久v动漫| 国产福利不卡| 日韩久久综合| 无码中文字幕色专区| 青草国产精品久久久久久| 欧美一级大片免费看| 久久久久99精品国产片| 69xx绿帽三人行| 色婷婷综合五月| 午夜精品一二三区| 在线日韩精品视频| 久久久久黄久久免费漫画| 国产福利成人在线| 亚洲精品观看| 四虎一区二区| 国产日韩亚洲| 久久久久久无码精品人妻一区二区| 97久久超碰精品国产| 999精品视频在线观看播放| 福利视频第一区| 午夜久久久久久久久久| 中文字幕日韩在线视频| freexxx性亚洲精品| 91精品视频网站| 精品国产aⅴ| 精品久久一二三| 国产乱人伦偷精品视频不卡| 国产成人免费观看网站| 午夜精品久久久久久不卡8050| 国产精品久久久久久久成人午夜| 亚洲精品网站在线播放gif| 免费污视频在线观看| 成人激情视频在线观看| 欧美日韩性在线观看| 国产精品va无码一区二区| 国产成人在线视频网址| 天天操夜夜操av| 欧美在线观看一区二区| 欧美白人做受xxxx视频| 91精品国产91久久久久久| 亚洲一区二区免费在线观看| 中日韩在线视频| 麻豆中文一区二区| 少妇精品无码一区二区免费视频| 欧美日韩国产精品一区二区三区四区 | 日韩不卡在线观看日韩不卡视频| 亚洲AV无码国产精品| 亚洲超碰精品一区二区| 亚洲精品久久久久久无码色欲四季 | 欧美精品久久久| 99精品免费网| 男女一区二区三区| 精品久久久久久电影| 手机在线精品视频| 午夜精品理论片| 精品国产一区二区三区不卡蜜臂 | 日韩综合小视频| 精品无码国产污污污免费网站| 色网站国产精品| 国产香蕉在线| 国产精品久久久久久久久免费| 少妇精品久久久一区二区| 久久国产乱子伦免费精品| 久久色成人在线| а中文在线天堂| 国产一区二区三区在线看| 欧美free嫩15| 亚洲欧洲日韩综合二区| 久久国产人妖系列| 亚洲xxxx3d动漫| 日韩欧美国产一区二区在线播放 | 99久久免费看精品国产一区| 久久91精品国产91久久小草| www中文在线| 欧美日韩高清影院| 26uuu亚洲电影在线观看| 亚洲一区二区三区成人在线视频精品| 在线成人激情| 亚洲中文字幕无码一区| 高跟丝袜欧美一区| 国产一级二级三级在线观看| 国产精品亚洲精品| 一区二区三区在线| 午夜福利三级理论电影| 图片区小说区区亚洲影院| 国模精品一区二区| 成人免费自拍视频| 亚洲国产网站| 夜夜春很很躁夜夜躁| 69成人精品免费视频| 国产白丝在线观看| 欧美精品在线一区| 久久99久久99小草精品免视看| 欧美日韩免费一区二区| 亚洲韩国日本中文字幕| 视频一区在线免费看| 中文字幕の友人北条麻妃| 99riav一区二区三区| 一本大道伊人av久久综合| 色与欲影视天天看综合网| 网红女主播少妇精品视频| 亚洲色图偷拍视频| 婷婷开心激情综合| 日韩av中文| 久久精品国产精品国产精品污| 美女一区二区久久| 五月天婷婷丁香| 色婷婷综合久久久久中文字幕1| xxxx日韩| 中文字幕第17页| 激情成人中文字幕| 快射av在线播放一区| 久久精品国产一区二区三区不卡| 久久精品国产99国产| 九九热在线免费观看| 欧美xxxx做受欧美| 欧美美女视频| 老熟妇精品一区二区三区| 欧美剧情片在线观看| 韩日精品一区二区|