沈劍:58同城資料庫架構最佳實踐

錢曙光發表於2016-01-20

宣告:本文首發於CSDN,禁止未經許可的任何形式轉載,可諮詢文末的責編。

資料庫的基本概念

基本概念這一塊,主要是讓大家就一些資料庫方面的概念達成一致。

圖片描述

首先是“單庫”,最初的時候資料庫都是這麼玩的,幾乎所有的業務都有這樣的一個庫。

圖片描述

接下來是“分片”,資料庫的分片是解決資料量大的問題。如果資料量非常大,就要做水平切分,有一些資料庫支援auto sharding。之前58同城也用過兩年mongoDB,後來發現auto sharding功能不太可控,不知道什麼時間進行遷移資料,資料遷移過程中會有大粒度的鎖,讀寫被阻塞,業務會有抖動和毛刺,這些是業務不能接受的,因此現在又遷移回了MySQL。

一旦進行分片,就會面臨“資料路由”的問題:來了一個請求,要將請求路由到對應的資料庫分片上。網際網路常用的資料路由方法有三種:

(1)第一個是按照資料範圍路由,比如有兩個分片,一個範圍是0-1億,一個範圍是1億-2億,這樣來路由。
這個方式的優點是非常的簡單,並且擴充套件性好,假如兩個分片不夠了,增加一個2億-3億的分片即可。
這個方式的缺點是:雖然資料的分佈是均衡的,每一個庫的資料量差不多,但請求的負載會不均衡。例如有一些業務場景,新註冊的使用者活躍度更高,大範圍的分片請求負載會更高。

(2)第二個是按照hash路由,比如有兩個分片,資料模2尋庫即可。
這個方式的優點是路由方式很簡單,資料分佈也是均衡的,請求負載也是均衡的。
這個方式的缺點是如果兩個分片資料量過大,要變成三個分片,資料遷移會比較麻煩,即擴充套件性會受限。

(3)第三個是路由服務。前面兩個資料路由方法均有一個缺點,業務線需要耦合路由規則,如果路由規則發生變化,業務線是需要配合升級的。路由服務可以實現業務線與路由規則的解耦,業務線每次訪問資料庫之前先呼叫路由服務,來知道資料究竟存放在哪個分庫上。

圖片描述

接下來是“分組”“複製”,這解決的是擴充套件讀效能,讀高可用的問題。

根據經驗,大部分網際網路的業務都是讀多寫少。淘寶、京東查詢商品,搜尋商品的請求可能佔了99%,只有下單和支付的時候有寫請求。

58同城搜尋帖子,察看列表頁,檢視詳情頁都是讀請求,釋出帖子是寫請求,寫請求的量也是比較少的。

可見,大部分網際網路的場景都讀多寫少,所以讀效能會最先成為瓶頸,怎麼快速解決這個問題呢?

通常來說,會使用讀寫分離,擴充讀庫的方式來提升讀效能。同時也保證了讀可用性,一臺讀庫掛了,另外一臺讀庫可以持續的提供服務。

圖片描述

常見資料庫的玩法綜合了“分片”和“分組”,資料量大進行分片,為了提高讀效能,保證讀的高可用,進行了分組,80%網際網路公司資料庫都是這種軟體架構

可用性架構實踐

資料庫大家都用,平時除了根據業務設計表結構,根據訪問來設計索引之外,還應該在設計時考慮資料的可用性,可用性又分為讀的高可用寫的高可用

圖片描述

上圖是“讀”高可用的常見玩法。怎麼樣保證資料庫讀庫的高可用呢?解決高可用這個問題的思路是冗餘。

解決站點的可用性問題冗餘多個站點,解決服務的可用性問題冗餘多個服務,解決資料的可用性問題冗餘多份資料。

如果用一個讀庫,保證不了讀高可用,就複製讀庫,一個讀庫掛了另一個仍然可以提供服務,這麼用複製的方式來保證讀的可用性。

資料的冗餘會引發一個副作用,就是一致性的問題。

如果是單庫,讀和寫都落在同一個庫上,每次讀到的都是最新的資料庫,不存在一致性的問題。

但是為了保證可用性將資料複製到多個地方,而這多個地方的資料絕對不是實時同步的,會有同步時延,所以有可能會讀到舊的資料。如何解決主從資料庫一致性問題後面再來闡述。

很多網際網路公司的資料庫軟體架構都是一主兩從或者一主三從,不能夠保證“寫”的高可用,因為寫其實還是隻有一個庫,仍是單點,如果這個庫掛了的話,寫會受影響。那小夥伴們為什麼還使用這個架構呢?

剛才提到大部分網際網路公司99%的業務都是“讀”業務,寫庫不是主要矛盾,寫庫掛了,可能只有1%的使用者會受影響。

如果要做到“寫”的高可用,對資料庫軟體架構的衝擊比較大,不一定值得,為了解決1%的問題引入了80%的複雜度,所以很多網際網路公司都沒有解決寫資料庫的高可用的問題。

圖片描述

怎麼來解決這個問題呢?思路還是冗餘,讀的高可用是冗餘讀庫,寫的高可用是冗餘寫庫。把一個寫變成兩個寫,做一個雙主同步,一個掛了的話,我可以將寫的流量自動切到另外一個,寫庫的高可用性。

用雙主同步的方式保證寫高可用性會存在什麼樣的問題?

上文提到,用冗餘的方式解保證可用性會存在一致性問題。因為兩個主相互同步,這個同步是有時延的,很多公司用到auto-increment-id這樣的一些資料庫的特性,如果用雙組同步的架構,一個主id由10變成11,在沒有同步過去之前,另一個主又來了一個寫請求,也由10變成11,雙向同步會同步失敗,就會有資料丟失。

解決這個雙主同步id衝突的方案有兩種:

(1)一個是雙主使用不同的初始值,相同的步長來生成id,一個庫從0開始(生成02468),一個庫從1開始(生成13579),步長都為2,這樣兩邊同步資料就不會衝突。
(2)另一個方式是不要使用資料庫的auto-increment-id,而由業務層來保證生成的id不衝突。

圖片描述

58同城沒有使用上述兩種方式來保證讀寫的可用性。58同城使用的“雙主”當“主從”的方式來保證資料庫的讀寫可用性。

雖然看上去是雙主同步,但是讀寫都在一個主上,另一個主庫沒有讀寫流量,完全standby。當一個主庫掛掉的時候,流量會自動的切換到另外一個主庫上,這一切對業務線都是透明的,自動完成。

58同城的這種方案,讀寫都在一個主庫上,就不存同步延時而引發的一致性問題了,但缺點有兩個:

第一是資料庫資源的利用率只有50%;
第二是沒有辦法通過增加讀庫的方式來擴充套件系統的讀效能。
58同城的資料庫軟體架構如何來擴充套件讀效能呢,來看下面一章。

讀效能架構實踐

如何增加資料庫的讀效能,先看下傳統的玩法:

圖片描述

(1)第一種玩法是增加從庫,通過增加從庫來提升讀效能,存在的問題是什麼呢?從庫越多,寫的效能越慢,同步的時間越長,不一致的可能性越高。

圖片描述

(2)第二種常見的玩法是增加快取,快取是大家用的非常多的一種提高系統讀效能的一種方法,特別是對於讀多寫少的網際網路的場景非常的有效。常用的快取玩法如上圖,上游是業務線的,底下是讀寫分離主從同步,然後會加一個cache。

對於寫操作:會先淘汰cache,再寫資料庫。

對於讀操作:先讀cache,如果cache hit則返回資料,如果cachemiss則讀從庫,然後把讀出來的資料再入快取。

這是常見的cache玩法。

傳統的cache玩法在一種異常時序下,會引發嚴重的一致性問題,考慮這樣一個特殊的時序:

(1)先來了一個寫請求,淘汰了cache,寫了資料庫;
(2)又來了一個讀請求,讀cache,cache miss了,然後讀從庫,此時寫請求還沒有同步到從庫上,於是讀了一個髒資料,接著髒資料入快取;
(3)最後主從同步完成;

這個時序會導致髒資料一直在快取中沒有辦法被淘汰掉,資料庫和快取中的資料嚴重不一致。

58同城也是採用快取的方式來提升讀效能的,那就會不會有資料一致性問題呢,請往下看。

一致性架構實踐

圖片描述

58同城採用“服務+快取+資料庫”一套的方式來保證資料的一致性,由於58同城使用“雙主當主從用”的資料庫讀寫高可用架構,讀寫都在一個主庫上,不會讀到所謂“讀庫的髒資料”,所以資料庫與快取的不一致情況也不會存在。

傳統玩法中,主從不一致的問題有一些什麼樣的解決方案呢?一起來看一下。

圖片描述

主從為什麼會不一致?上文提到讀寫會有時延,有可能讀到從庫上的舊資料。常見的方法是引入中介軟體,業務層不直接訪問資料庫,而是通過中介軟體訪問資料庫,這個中介軟體會記錄哪一些key上發生了寫請求,在資料主從同步時間視窗之內,如果key上又出了讀請求,就將這個請求也路由到主庫上去(因為此時從庫可能還沒有同步完成,是舊資料),使用這個方法來保證資料的一致性。

中介軟體的方案很理想,那為什麼大部分的網際網路的公司都沒有使用這種方案來保證主從資料的一致性呢?那是因為資料庫中介軟體的技術門檻比較高,有一些大公司,例如百度,騰訊,阿里他們可能有自己的中介軟體,並不是所有的創業公司網際網路公司有自己的中介軟體產品,況且很多網際網路公司的業務對資料一致性的要求並沒有那麼高。比如說同城搜一個帖子,可能5秒鐘之後才搜出來,對使用者的體驗並沒有多大的影響。

圖片描述

除了中介軟體,讀寫都路由到主庫,58同城就是這麼做的,也是一種解決主從不一致的常用方案。

解決完主從不一致,第二個要解決的是資料庫和快取的不一致,剛才提到cache傳統的玩法,髒資料有可能入cache,怎麼解決呢?

兩個實踐:第一個是快取雙淘汰機制,第二個是建議為所有item設定過期時間(前提是允許cache miss)。

(1)快取雙淘汰,傳統的玩法在進行寫操作的時候,先淘汰cache再寫主庫。上文提到,在主從同步時間視窗之內可能有髒資料入cache,此時如果再發起一個非同步的淘汰,即使不一致時間窗內臟資料入了cache,也會再次淘汰掉。
(2)為所有item設定超時時間,例如10分鐘。極限時序下,即使有髒資料入cache,這個髒資料也最多存在十分鐘。帶來的副作用是,可能每十分鐘,這個key上有一個讀請求會穿透到資料庫上,但我們認為這對資料庫的從庫壓力增加是非常小的。

擴充套件性架構實踐

擴充套件性也是架構師在做資料庫架構設計的時候需要考慮的一點。首先分享一個58同城非常帥氣的秒級資料擴容的方案。這個方案解決什麼問題呢?原來資料庫水平切分成N個庫,現在要擴容成2N個庫,解決的就是這個問題。

圖片描述

假設原來分成兩個庫,假設按照hash的方式分片。如上圖,分為奇數庫和偶數庫。

圖片描述

第一個步驟提升從庫,底下一個從庫放到上面來(其實什麼動作都沒有做);
第二個步驟修改配置,此時擴容完成,原來是2個分片,修改配置後變成4個分片,這個過程沒有資料的遷移。原來偶數的那一部分現在變成了兩個部分,一部分是0,一部分是2,奇數的部分現在變成1和3。0庫和2庫沒有資料衝突,只是擴容之後在短時間內雙主的可用性這個特性丟失掉了。

圖片描述

第三個步驟還要做一些收尾操作:把舊的雙主給解除掉,為了保證可用性增加新的雙主同步,原來擁有全部的資料,現在只為一半的資料提供服務了,我們把多餘的資料刪除掉,結尾這三個步驟可以事後慢慢操作。整個擴容在過程在第二步提升從庫,修改配置其實就秒級完成了,非常的帥氣。

這個方案的缺點是只能實現N庫到2N 庫的擴容,2變4、4變8,不能實現2庫變3庫,2庫變5庫的擴容。那麼,如何能夠實現這種擴容呢?

資料庫擴充套件性方面有很多的需求,例如剛才說的2庫擴3庫,2庫擴5庫。產品經理經常變化需求,擴充表的屬性也是經常的事情。今年的資料庫大會同行也介紹了一些使用觸發器來做online schema change的方案,但是觸發器的侷限性在於:

第一、觸發器對資料庫效能的影響比較大;
第二、觸發器只能在同一個庫上才有效,而網際網路的場景特點是資料量非常大,併發量非常大,庫都分佈在不同的物理機器上,觸發器沒法弄。

最後還有一類擴充套件性需求,底層儲存介質發生變化,原來是MongoDB儲存,現在要變為MySQL儲存,這也是擴充套件性需求(雖然很少),這三類需求怎麼擴充套件?

方法是導庫,遷移資料,遷移資料有幾種做法,第一種停服務,如果大家的業務能夠接受這種方法,強烈建議使用這種方法,例如有一些遊戲公司,晚上一點到兩點伺服器維護,可能就是在幹分割槽或者合區這類導庫的事情。

圖片描述

如果業務上不允許停服務,想做到平滑遷移,雙寫法可以解決這類問題。

(1)雙寫法遷移資料的第一步是升級服務,原來的服務是寫一個庫,現在建立新的資料庫,雙寫。比如底層儲存介質的變化,我們原來是mongo資料庫,現在建立好新的mysql資料庫,然後對服務的所有寫介面進行雙庫寫升級。

(2)第二步寫一個小程式去進行資料的遷移。比如寫一個離線的程式,把兩個庫的資料重新分片,分到三個庫裡。也可能是把一個只有三個屬性的使用者表導到五個屬性的資料表裡面。這個資料遷移要限速,導完之後兩個庫的資料一致嗎?只要提前雙寫,如果沒有什麼意外,兩邊的資料應該是一致的。

什麼時候會有意外呢?在導某一條資料的過程當中正好發生了一個刪除操作,這個資料剛被服務雙寫刪除,又被遷移資料的程式插入到了新庫中,這種非常極限的情況下會造成兩邊的資料不一致。

(3)建議第三步再開發一個小指令碼,對兩邊的資料進行比對,如果發現了不一致,就將資料修復。當修復完成之後,我們認為資料是一致的,再將雙寫又變成單寫,資料完成遷移。

這個方式的優點:

第一、改動是非常小的,對服務的影響比較小,單寫變雙寫,開發兩個小工具,一個是遷移程式,從一個庫讀資料,另外一個庫插進去;還有一個資料校驗程式,兩個資料進行比對,改動是比較小的。

第二、隨時可回滾的,方案風險比較小,在任何一個步驟如果發現問題,可以隨時停止操作。比如遷移資料的過程當中發現不對,就把新的資料庫幹掉,重新再遷。因為在切換之前,所有線上的讀服務和寫服務都是舊庫提供,只有切了以後,才是新庫提供的服務。這是我們非常帥氣的一個平滑導庫的方式。

總結

本次分享首先介紹了單庫、分片、複製、分組、路由規則的概念。分片解決的是資料量大的問題,複製和分組解決的是提高讀效能,保證讀的可用性的問題。分片會引入路由,常用的三種路由的方法,按照範圍、按照hash,或者新增服務來路由。

如何保證資料的可用性?思路是冗餘,但會引發資料的不一致,58同城保證可用性的實踐是雙主當主從用,讀寫流量都在一個庫上,另一個庫standby,一個主庫掛掉流量自動遷移到另外一個主庫,只是資源利用率是50%,並且不能通過增加從庫的方式提高讀性。

讀效能的實踐,傳統的玩法是增加從庫或者增加快取。存在的問題是,主從可能不一致,同城的玩法是服務加資料庫加快取一套的方式來解決這些問題。

一致性的實踐,解決主從不一致性有兩種方法,一種是增加中介軟體,中介軟體記錄哪些key上發生了寫操作,在主從同步時間視窗之內的讀操作也路由到主庫。第二種方法是強制讀主。資料庫與快取的一致性,我們的實踐是雙淘汰,在發生寫請求的時候,淘汰快取,寫入資料庫,再做一個延時的快取淘汰操作。第二個實踐是建議為所有的item設定一個超時時間。

擴充套件性方面,今天分享了58同城一個非常帥氣的N庫擴2N庫的秒級擴容方案,還分享了一個平滑雙寫導庫的方案,解決兩庫擴三庫,資料庫欄位的增加,以及底層介質的變化的問題。

分享人:沈劍,58到家技術總監、58同城高階架構師,58同城技術委員會負責人。曾任百度高階工程師、曾參與過多個百度hi重大專案的研發,加盟58同城以後,負責過58同城即時通訊,支付系統與攤銷系統的重構。還曾參與資料庫中介軟體、58同城推薦系統、58同城商戶平臺App以及58同城二手交易平臺APP等多個系統與專案的設計與實現。

架構技術實踐系列文章(部分):


本文整理自58到家技術總監沈劍日前在“UPYUN架構與運維大會·北京站” 上的主題演講。UPYUN 架構與運維大會是國內領先的新一代雲CDN服務商UPYUN獨家主辦的大型技術會議。大會面向全國運維和架構從業者,邀請業內一線的架構師和運維專家進行純乾貨分享,旨在推動各項運維技術、產品架構等在網際網路和移動網際網路的研發和應用。


(責編/錢曙光,關注架構和演算法領域,尋求報導或者投稿請發郵件qianshg@csdn.net,交流探討可加微信qshuguang2008,備註姓名+公司+職位)
「CSDN 高階架構師群」,內有諸多知名網際網路公司的大牛架構師,歡迎架構師加微信qshuguang2008入群,備註姓名+公司+職位。

相關文章