隨著樂視硬件搶購的不斷升級,樂視集團(tuán)支付面臨的請求壓力百倍乃至千倍的暴增。作為商品購買的最后一環(huán),保證用戶快速穩(wěn)定的完成支付尤為重要。所以在15年11月,我們對整個支付系統(tǒng)進(jìn)行了全面的架構(gòu)升級,使之具備了每秒穩(wěn)定處理10萬訂單的能力。為樂視生態(tài)各種形式的搶購秒殺活動提供了強(qiáng)有力的支撐。
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:國際域名空間、網(wǎng)絡(luò)空間、營銷軟件、網(wǎng)站建設(shè)、湞江網(wǎng)站維護(hù)、網(wǎng)站推廣。在Redis,memcached等緩存系統(tǒng)盛行的互聯(lián)網(wǎng)時代,構(gòu)建一個支撐每秒十萬只讀的系統(tǒng)并不復(fù)雜,無非是通過一致性哈希擴(kuò)展緩存節(jié)點(diǎn),水平擴(kuò)展web服務(wù)器等。支付系統(tǒng)要處理每秒十萬筆訂單,需要的是每秒數(shù)十萬的數(shù)據(jù)庫更新操作(insert加update),這在任何一個獨(dú)立數(shù)據(jù)庫上都是不可能完成的任務(wù),所以我們首先要做的是對訂單表(簡稱order)進(jìn)行分庫與分表。
在進(jìn)行數(shù)據(jù)庫操作時,一般都會有用戶ID(簡稱uid)字段,所以我們選擇以uid進(jìn)行分庫分表。
分庫策略我們選擇了“二叉樹分庫”,所謂“二叉樹分庫”指的是:我們在進(jìn)行數(shù)據(jù)庫擴(kuò)容時,都是以2的倍數(shù)進(jìn)行擴(kuò)容。比如:1臺擴(kuò)容到2臺,2臺擴(kuò)容到4臺,4臺擴(kuò)容到8臺,以此類推。這種分庫方式的好處是,我們在進(jìn)行擴(kuò)容時,只需DBA進(jìn)行表級的數(shù)據(jù)同步,而不需要自己寫腳本進(jìn)行行級數(shù)據(jù)同步。
光是有分庫是不夠的,經(jīng)過持續(xù)壓力測試我們發(fā)現(xiàn),在同一數(shù)據(jù)庫中,對多個表進(jìn)行并發(fā)更新的效率要遠(yuǎn)遠(yuǎn)大于對一個表進(jìn)行并發(fā)更新,所以我們在每個分庫中都將order表拆分成10份:order_0,order_1,….,order_9。
最后我們把order表放在了8個分庫中(編號1到8,分別對應(yīng)DB1到DB8),每個分庫中10個分表(編號0到9,分別對應(yīng)order_0到order_9),部署結(jié)構(gòu)如下圖所示:
根據(jù)uid計算數(shù)據(jù)庫編號:
數(shù)據(jù)庫編號 = (uid / 10) % 8 + 1
根據(jù)uid計算表編號:
表編號 = uid % 10
當(dāng)uid=9527時,根據(jù)上面的算法,其實(shí)是把uid分成了兩部分952和7,其中952模8加1等于1為數(shù)據(jù)庫編號,而7則為表編號。所以uid=9527的訂單信息需要去DB1庫中的order_7表查找。具體算法流程也可參見下圖:
有了分庫分表的結(jié)構(gòu)與算法最后就是尋找分庫分表的實(shí)現(xiàn)工具,目前市面上約有兩種類型的分庫分表工具:
客戶端分庫分表,在客戶端完成分庫分表操作,直連數(shù)據(jù)庫
使用分庫分表中間件,客戶端連分庫分表中間件,由中間件完成分庫分表操作
這兩種類型的工具市面上都有,這里不一一列舉,總的來看這兩類工具各有利弊??蛻舳朔謳旆直碛捎谥边B數(shù)據(jù)庫,所以性能比使用分庫分表中間件高15%到20%。而使用分庫分表中間件由于進(jìn)行了統(tǒng)一的中間件管理,將分庫分表操作和客戶端隔離,模塊劃分更加清晰,便于DBA進(jìn)行統(tǒng)一管理。
我們選擇的是在客戶端分庫分表,因?yàn)槲覀冏约洪_發(fā)并開源了一套數(shù)據(jù)層訪問框架,它的代號叫“芒果”,芒果框架原生支持分庫分表功能,并且配置起來非常簡單。
芒果主頁:mango.jfaster.org
芒果源碼:github.com/jfaster/mango
訂單系統(tǒng)的ID必須具有全局唯一的特征,最簡單的方式是利用數(shù)據(jù)庫的序列,每操作一次就能獲得一個全局唯一的自增ID,如果要支持每秒處理10萬訂單,那每秒將至少需要生成10萬個訂單ID,通過數(shù)據(jù)庫生成自增ID顯然無法完成上述要求。所以我們只能通過內(nèi)存計算獲得全局唯一的訂單ID。
Java領(lǐng)域最著名的唯一ID應(yīng)該算是UUID了,不過UUID太長而且包含字母,不適合作為訂單ID。通過反復(fù)比較與篩選,我們借鑒了Twitter的Snowflake算法,實(shí)現(xiàn)了全局唯一ID。下面是訂單ID的簡化結(jié)構(gòu)圖:
上圖分為3個部分:
時間戳
這里時間戳的粒度是毫秒級,生成訂單ID時,使用System.currentTimeMillis()
作為時間戳。
機(jī)器號
每個訂單服務(wù)器都將被分配一個唯一的編號,生成訂單ID時,直接使用該唯一編號作為機(jī)器號即可。
自增序號
當(dāng)在同一服務(wù)器的同一毫秒中有多個生成訂單ID的請求時,會在當(dāng)前毫秒下自增此序號,下一個毫秒此序號繼續(xù)從0開始。比如在同一服務(wù)器同一毫秒有3個生成訂單ID的請求,這3個訂單ID的自增序號部分將分別是0,1,2。
上面3個部分組合,我們就能快速生成全局唯一的訂單ID。不過光全局唯一還不夠,很多時候我們會只根據(jù)訂單ID直接查詢訂單信息,這時由于沒有uid,我們不知道去哪個分庫的分表中查詢,遍歷所有的庫的所有表?這顯然不行。所以我們需要將分庫分表的信息添加到訂單ID上,下面是帶分庫分表信息的訂單ID簡化結(jié)構(gòu)圖:
我們在生成的全局訂單ID頭部添加了分庫與分表的信息,這樣只根據(jù)訂單ID,我們也能快速的查詢到對應(yīng)的訂單信息。
分庫分表信息具體包含哪些內(nèi)容?第一部分有討論到,我們將訂單表按uid維度拆分成了8個數(shù)據(jù)庫,每個數(shù)據(jù)庫10張表,最簡單的分庫分表信息只需一個長度為2的字符串即可存儲,第1位存數(shù)據(jù)庫編號,取值范圍1到8,第2位存表編號,取值范圍0到9。
還是按照第一部分根據(jù)uid計算數(shù)據(jù)庫編號和表編號的算法,當(dāng)uid=9527時,分庫信息=1,分表信息=7,將他們進(jìn)行組合,兩位的分庫分表信息即為”17”。具體算法流程參見下圖:
上述使用表編號作為分表信息沒有任何問題,但使用數(shù)據(jù)庫編號作為分庫信息卻存在隱患,考慮未來的擴(kuò)容需求,我們需要將8庫擴(kuò)容到16庫,這時取值范圍1到8的分庫信息將無法支撐1到16的分庫場景,分庫路由將無法正確完成,我們將上訴問題簡稱為分庫信息精度丟失。
為解決分庫信息精度丟失問題,我們需要對分庫信息精度進(jìn)行冗余,即我們現(xiàn)在保存的分庫信息要支持以后的擴(kuò)容。這里我們假設(shè)最終我們會擴(kuò)容到64臺數(shù)據(jù)庫,所以新的分庫信息算法為:
分庫信息 = (uid / 10) % 64 + 1
當(dāng)uid=9527時,根據(jù)新的算法,分庫信息=57,這里的57并不是真正數(shù)據(jù)庫的編號,它冗余了最后擴(kuò)展到64臺數(shù)據(jù)庫的分庫信息精度。我們當(dāng)前只有8臺數(shù)據(jù)庫,實(shí)際數(shù)據(jù)庫編號還需根據(jù)下面的公式進(jìn)行計算:
實(shí)際數(shù)據(jù)庫編號 = (分庫信息 - 1) % 8 + 1
當(dāng)uid=9527時,分庫信息=57,實(shí)際數(shù)據(jù)庫編號=1,分庫分表信息=”577”。
由于我們選擇模64來保存精度冗余后的分庫信息,保存分庫信息的長度由1變?yōu)榱?,最后的分庫分表信息的長度為3。具體算法流程也可參見下圖:
如上圖所示,在計算分庫信息的時候采用了模64的方式冗余了分庫信息精度,這樣當(dāng)我們的系統(tǒng)以后需要擴(kuò)容到16庫,32庫,64庫都不會再有問題。
當(dāng)577找不到訂單時,采用(分庫信息57 - 1) % 8 + 1,就能找到實(shí)際數(shù)據(jù)庫編號。
可以這么理解:
1、擴(kuò)容后的分庫分表信息,可以兼容擴(kuò)容之前的分庫分表信息,也就是說:9527這個uid本來按照64庫設(shè)計是在57庫1表中,由于目前最多只有8庫,所有只能讓它分配在8庫中,經(jīng)換算是在7庫1表中。
9527按照8庫設(shè)計也是在7庫1表中。這種算法在某中程度上換算結(jié)果是一致的。
2、擴(kuò)容后的分庫分表,后續(xù)的用戶會均勻分布在擴(kuò)容的庫和表中;之前的庫表信息存在就存在了,所以說擴(kuò)容前的已存在的庫表中存的訂單信息(如8庫9表)要比后面擴(kuò)容后(如64庫9表)存在的訂單信息(9-64庫9表) 要多。也就是說擴(kuò)容前的庫表信息冗余了。
上面的訂單ID結(jié)構(gòu)已經(jīng)能很好的滿足我們當(dāng)前與之后的擴(kuò)容需求,但考慮到業(yè)務(wù)的不確定性,我們在訂單ID的最前方加了1位用于標(biāo)識訂單ID的版本,這個版本號屬于冗余數(shù)據(jù),目前并沒有用到。下面是最終訂單ID簡化結(jié)構(gòu)圖:
Snowflake算法:github.com/twitter/snowflake
到目前為止,我們通過對order表uid維度的分庫分表,實(shí)現(xiàn)了order表的超高并發(fā)寫入與更新,并能通過uid和訂單ID查詢訂單信息。但作為一個開放的集團(tuán)支付系統(tǒng),我們還需要通過業(yè)務(wù)線ID(又稱商戶ID,簡稱bid)來查詢訂單信息,所以我們引入了bid維度的order表集群,將uid維度的order表集群冗余一份到bid維度的order表集群中,要根據(jù)bid查詢訂單信息時,只需查bid維度的order表集群即可。
上面的方案雖然簡單,但保持兩個order表集群的數(shù)據(jù)一致性是一件很麻煩的事情。兩個表集群顯然是在不同的數(shù)據(jù)庫集群中,如果在寫入與更新中引入強(qiáng)一致性的分布式事務(wù),這無疑會大大降低系統(tǒng)效率,增長服務(wù)響應(yīng)時間,這是我們所不能接受的,所以我們引入了消息隊列進(jìn)行異步數(shù)據(jù)同步,來實(shí)現(xiàn)數(shù)據(jù)的最終一致性。當(dāng)然消息隊列的各種異常也會造成數(shù)據(jù)不一致,所以我們又引入了實(shí)時監(jiān)控服務(wù),實(shí)時計算兩個集群的數(shù)據(jù)差異,并進(jìn)行一致性同步。
下面是簡化的一致性同步圖:
沒有任何機(jī)器或服務(wù)能保證在線上穩(wěn)定運(yùn)行不出故障。比如某一時間,某一數(shù)據(jù)庫主庫宕機(jī),這時我們將不能對該庫進(jìn)行讀寫操作,線上服務(wù)將受到影響。
所謂數(shù)據(jù)庫高可用指的是:當(dāng)數(shù)據(jù)庫由于各種原因出現(xiàn)問題時,能實(shí)時或快速的恢復(fù)數(shù)據(jù)庫服務(wù)并修補(bǔ)數(shù)據(jù),從整個集群的角度看,就像沒有出任何問題一樣。需要注意的是,這里的恢復(fù)數(shù)據(jù)庫服務(wù)并不一定是指修復(fù)原有數(shù)據(jù)庫,也包括將服務(wù)切換到另外備用的數(shù)據(jù)庫。
數(shù)據(jù)庫高可用的主要工作是數(shù)據(jù)庫恢復(fù)與數(shù)據(jù)修補(bǔ),一般我們以完成這兩項(xiàng)工作的時間長短,作為衡量高可用好壞的標(biāo)準(zhǔn)。這里有一個惡性循環(huán)的問題,數(shù)據(jù)庫恢復(fù)的時間越長,不一致數(shù)據(jù)越多,數(shù)據(jù)修補(bǔ)的時間就會越長,整體修復(fù)的時間就會變得更長。所以數(shù)據(jù)庫的快速恢復(fù)成了數(shù)據(jù)庫高可用的重中之重,試想一下如果我們能在數(shù)據(jù)庫出故障的1秒之內(nèi)完成數(shù)據(jù)庫恢復(fù),修復(fù)不一致的數(shù)據(jù)和成本也會大大降低。
下圖是一個最經(jīng)典的主從結(jié)構(gòu):
上圖中有1臺web服務(wù)器和3臺數(shù)據(jù)庫,其中DB1是主庫,DB2和DB3是從庫。我們在這里假設(shè)web服務(wù)器由項(xiàng)目組維護(hù),而數(shù)據(jù)庫服務(wù)器由DBA維護(hù)。
當(dāng)從庫DB2出現(xiàn)問題時,DBA會通知項(xiàng)目組,項(xiàng)目組將DB2從web服務(wù)的配置列表中刪除,重啟web服務(wù)器,這樣出錯的節(jié)點(diǎn)DB2將不再被訪問,整個數(shù)據(jù)庫服務(wù)得到恢復(fù),等DBA修復(fù)DB2時,再由項(xiàng)目組將DB2添加到web服務(wù)。
當(dāng)主庫DB1出現(xiàn)問題時,DBA會將DB2切換為主庫,并通知項(xiàng)目組,項(xiàng)目組使用DB2替換原有的主庫DB1,重啟web服務(wù)器,這樣web服務(wù)將使用新的主庫DB2,而DB1將不再被訪問,整個數(shù)據(jù)庫服務(wù)得到恢復(fù),等DBA修復(fù)DB1時,再將DB1作為DB2的從庫即可。
上面的經(jīng)典結(jié)構(gòu)有很大的弊?。翰还苤鲙旎驈膸斐霈F(xiàn)問題,都需要DBA和項(xiàng)目組協(xié)同完成數(shù)據(jù)庫服務(wù)恢復(fù),這很難做到自動化,而且恢復(fù)工程也過于緩慢。
我們認(rèn)為,數(shù)據(jù)庫運(yùn)維應(yīng)該和項(xiàng)目組分開,當(dāng)數(shù)據(jù)庫出現(xiàn)問題時,應(yīng)由DBA實(shí)現(xiàn)統(tǒng)一恢復(fù),不需要項(xiàng)目組操作服務(wù),這樣便于做到自動化,縮短服務(wù)恢復(fù)時間。
先來看從庫高可用結(jié)構(gòu)圖:
如上圖所示,web服務(wù)器將不再直接連接從庫DB2和DB3,而是連接LVS負(fù)載均衡,由LVS連接從庫。這樣做的好處是LVS能自動感知從庫是否可用,從庫DB2宕機(jī)后,LVS將不會把讀數(shù)據(jù)請求再發(fā)向DB2。同時DBA需要增減從庫節(jié)點(diǎn)時,只需獨(dú)立操作LVS即可,不再需要項(xiàng)目組更新配置文件,重啟服務(wù)器來配合。
再來看主庫高可用結(jié)構(gòu)圖:
如上圖所示,web服務(wù)器將不再直接連接主庫DB1,而是連接KeepAlive虛擬出的一個虛擬ip,再將此虛擬ip映射到主庫DB1上,同時添加DB_bak從庫,實(shí)時同步DB1中的數(shù)據(jù)。正常情況下web還是在DB1中讀寫數(shù)據(jù),當(dāng)DB1宕機(jī)后,腳本會自動將DB_bak設(shè)置成主庫,并將虛擬ip映射到DB_bak上,web服務(wù)將使用健康的DB_bak作為主庫進(jìn)行讀寫訪問。這樣只需幾秒的時間,就能完成主數(shù)據(jù)庫服務(wù)恢復(fù)。
組合上面的結(jié)構(gòu),得到主從高可用結(jié)構(gòu)圖:
數(shù)據(jù)庫高可用還包含數(shù)據(jù)修補(bǔ),由于我們在操作核心數(shù)據(jù)時,都是先記錄日志再執(zhí)行更新,加上實(shí)現(xiàn)了近乎實(shí)時的快速恢復(fù)數(shù)據(jù)庫服務(wù),所以修補(bǔ)的數(shù)據(jù)量都不大,一個簡單的恢復(fù)腳本就能快速完成數(shù)據(jù)修復(fù)。
支付系統(tǒng)除了最核心的支付訂單表與支付流水表外,還有一些配置信息表和一些用戶相關(guān)信息表。如果所有的讀操作都在數(shù)據(jù)庫上完成,系統(tǒng)性能將大打折扣,所以我們引入了數(shù)據(jù)分級機(jī)制。
我們簡單的將支付系統(tǒng)的數(shù)據(jù)劃分成了3級:
第1級:訂單數(shù)據(jù)和支付流水?dāng)?shù)據(jù);這兩塊數(shù)據(jù)對實(shí)時性和精確性要求很高,所以不添加任何緩存,讀寫操作將直接操作數(shù)據(jù)庫。
第2級:用戶相關(guān)數(shù)據(jù);這些數(shù)據(jù)和用戶相關(guān),具有讀多寫少的特征,所以我們使用redis進(jìn)行緩存。
第3級:支付配置信息;這些數(shù)據(jù)和用戶無關(guān),具有數(shù)據(jù)量小,頻繁讀,幾乎不修改的特征,所以我們使用本地內(nèi)存進(jìn)行緩存。
使用本地內(nèi)存緩存有一個數(shù)據(jù)同步問題,因?yàn)榕渲眯畔⒕彺嬖趦?nèi)存中,而本地內(nèi)存無法感知到配置信息在數(shù)據(jù)庫的修改,這樣會造成數(shù)據(jù)庫中數(shù)據(jù)和本地內(nèi)存中數(shù)據(jù)不一致的問題。
為了解決此問題,我們開發(fā)了一個高可用的消息推送平臺,當(dāng)配置信息被修改時,我們可以使用推送平臺,給支付系統(tǒng)所有的服務(wù)器推送配置文件更新消息,服務(wù)器收到消息會自動更新配置信息,并給出成功反饋。
***,前端重試等一些原因會造成請求量的暴漲,如果我們的服務(wù)被激增的請求給一波打死,想要重新恢復(fù),就是一件非常痛苦和繁瑣的過程。
舉個簡單的例子,我們目前訂單的處理能力是平均10萬下單每秒,峰值14萬下單每秒,如果同一秒鐘有100萬個下單請求進(jìn)入支付系統(tǒng),毫無疑問我們的整個支付系統(tǒng)就會崩潰,后續(xù)源源不斷的請求會讓我們的服務(wù)集群根本啟動不起來,唯一的辦法只能是切斷所有流量,重啟整個集群,再慢慢導(dǎo)入流量。
我們在對外的web服務(wù)器上加一層“粗細(xì)管道”,就能很好的解決上面的問題。高并發(fā)下的數(shù)據(jù)庫設(shè)計高并發(fā)下的數(shù)據(jù)庫設(shè)計高并發(fā)下的數(shù)據(jù)庫設(shè)計高并發(fā)下的數(shù)據(jù)庫設(shè)計高并發(fā)下的數(shù)據(jù)庫設(shè)計高并發(fā)下的數(shù)據(jù)庫設(shè)計高并發(fā)下的數(shù)據(jù)庫設(shè)計高并發(fā)下的數(shù)據(jù)庫設(shè)計
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
當(dāng)前名稱:高并發(fā)下的數(shù)據(jù)庫設(shè)計-創(chuàng)新互聯(lián)
文章轉(zhuǎn)載:http://jinyejixie.com/article48/ddeihp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、自適應(yīng)網(wǎng)站、企業(yè)網(wǎng)站制作、商城網(wǎng)站、域名注冊、品牌網(wǎng)站設(shè)計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容