1. 程式人生 > >數據庫架構(轉)

數據庫架構(轉)

多個數據庫 100% 數據比對 線數據 記錄 實現 一個數 壓縮 什麽是數據庫

1. 業界難題-“跨庫分頁”的四種方案

1). 方法一:全局視野法

a.將order by time offset X limit Y,改寫成order by time offset 0 limit X+Y

b.服務層對得到的N*(X+Y)條數據進行內存排序,內存排序後再取偏移量X後的Y條記錄

這種方法隨著翻頁的進行,性能越來越低。

2). 方法二:業務折衷法-禁止跳頁查詢

a. 用正常的方法取得第一頁數據,並得到第一頁記錄的time_max

b. 每次翻頁,將order by time offset X limit Y,改寫成order by time where time>$time_max limit Y

以保證每次只返回一頁數據,性能為常量。

3). 方法三:業務折衷法-允許模糊數據(多個數據庫平均查出數據,損失一定的精度)

a. 將order by time offset X limit Y,改寫成order by time offset X/N limit Y/N

4). 方法四:二次查詢法

a. 將order by time offset X limit Y,改寫成order by time offset X/N limit Y

b. 找到最小值time_min

c. between二次查詢,order by time between $time_min and $time_i_max

d.設置虛擬time_min,找到time_min在各個分庫的offset,從而得到time_min在全局的offset

e. 得到了time_min在全局的offset,自然得到了全局的offset X limit Y

2. 單KEY業務,數據庫水平切分架構實踐

1).水平切分方法

a.範圍法,以用戶中心的業務主鍵uid為劃分依據,將數據水平切分到兩個數據庫實例上去:

user-db1:存儲0到1千萬的uid數據

user-db2:存儲1到2千萬的uid數據

a).範圍法的優點是:切分策略簡單,根據uid,按照範圍,user- center很快能夠定位到數據在哪個庫上 ; 擴容簡單,如果容量不夠,只要增加user-db3即可

b).範圍法的不足是:

uid必須要滿足遞增的特性

數據量不均,新增的user-db3,在初期的數據會比較少

請求量不均,一般來說,新註冊的用戶活躍度會比較高,故user-db2往往會比user-db1負載要高,導致服務器利用率不平衡

b.哈希法,也是以用戶中心的業務主鍵uid為劃分依據,將數據水平切分到兩個數據庫實例上去:

user-db1:存儲uid取模得1的uid數據

user-db2:存儲uid取模得0的uid數據

a).哈希法的優點是:切分策略簡單,根據uid,按照hash,user-center很快能夠定位到數據在哪個庫上

數據量均衡,只要uid是均勻的,數據在各個庫上的分布一定是均衡的

請求量均衡,只要uid是均勻的,負載在各個庫上的分布一定是均衡的

b).哈希法的不足是:擴容麻煩,如果容量不夠,要增加一個庫,重新hash可能會導致數據遷移,如何平滑的進行數據遷移,是一個需要解決的問題

2).用戶中心水平切分後帶來的問題:

對於uid屬性上的查詢可以直接路由到庫,假設訪問uid=124的數據,取模後能夠直接定位db-user1:對於非uid屬性上的查詢,例如login_name屬性上的查詢,就悲劇了:

假設訪問login_name=shenjian的數據,由於不知道數據落在哪個庫上,往往需要遍歷所有庫,當分庫數量多起來,性能會顯著降低。

3).根據樓主這些年的架構經驗,用戶中心非uid屬性上經常有兩類業務需求:

a.用戶側,前臺訪問,最典型的有兩類需求

用戶登錄:通過login_name/phone/email查詢用戶的實體,1%請求屬於這種類型

用戶信息查詢:登錄之後,通過uid來查詢用戶的實例,99%請求屬這種類型

用戶側的查詢基本上是單條記錄的查詢,訪問量較大,服務需要高可用,並且對一致性的要求較高。

b.運營側,後臺訪問,根據產品、運營需求,訪問模式各異,按照年齡、性別、頭像、登陸時間、註冊時間來進行查詢。

運營側的查詢基本上是批量分頁的查詢,由於是內部系統,訪問量很低,對可用性的要求不高,對一致性的要求也沒這麽嚴格。

這兩類不同的業務需求,應該使用什麽樣的架構方案來解決呢?

4).用戶中心水平切分架構思路

用戶中心在數據量較大的情況下,使用uid進行水平切分,對於非uid屬性上的查詢需求,架構設計的核心思路為:

針對用戶側,應該采用“建立非uid屬性到uid的映射關系”的架構方案

針對運營側,應該采用“前臺與後臺分離”的架構方案

5).用戶中心-用戶側最佳實踐

a.索引表法

思路:uid能直接定位到庫,login_name不能直接定位到庫,如果通過login_name能查詢到uid,問題解決

解決方案:

建立一個索引表記錄login_name->uid的映射關系

用login_name來訪問時,先通過索引表查詢到uid,再定位相應的庫

索引表屬性較少,可以容納非常多數據,一般不需要分庫

如果數據量過大,可以通過login_name來分庫

潛在不足:多一次數據庫查詢,性能下降一倍

b.緩存映射法:

思路:訪問索引表性能較低,把映射關系放在緩存裏性能更佳

解決方案:

login_name查詢先到cache中查詢uid,再根據uid定位數據庫

假設cache miss,采用掃全庫法獲取login_name對應的uid,放入cache

login_name到uid的映射關系不會變化,映射關系一旦放入緩存,不會更改,無需淘汰,緩存命中率超高

如果數據量過大,可以通過login_name進行cache水平切分

潛在不足:多一次cache查詢

c.login_name生成uid:

思路:不進行遠程查詢,由login_name直接得到uid

解決方案:

在用戶註冊時,設計函數login_name生成uid,uid=f(login_name),按uid分庫插入數據

用login_name來訪問時,先通過函數計算出uid,即uid=f(login_name)再來一遍,由uid路由到對應庫

潛在不足:該函數設計需要非常講究技巧,有uid生成沖突風險

d.login_name基因融入uid

思路:不能用login_name生成uid,可以從login_name抽取“基因”,融入uid中。假設分8庫,采用uid%8路由,潛臺詞是,uid的最後3個bit決定這條數據落在哪個庫上,這3個bit就是所謂的“基因”。

解決方案:在用戶註冊時,設計函數login_name生成3bit基因,login_name_gene=f(login_name),如上圖粉色部分

同時,生成61bit的全局唯一id,作為用戶的標識,如上圖綠色部分

接著把3bit的login_name_gene也作為uid的一部分,如上圖屎黃色部分

生成64bit的uid,由id和login_name_gene拼裝而成,並按照uid分庫插入數據

用login_name來訪問時,先通過函數由login_name再次復原3bit基因,login_name_gene=f(login_name),通過login_name_gene%8直接定位到庫

e.用戶中心-運營側最佳實踐

前臺用戶側,業務需求基本都是單行記錄的訪問,只要建立非uid屬性 login_name / phone / email 到uid的映射關系,就能解決問題。

後臺運營側,業務需求各異,基本是批量分頁的訪問,這類訪問計算量較大,返回數據量較大,比較消耗數據庫性能。

如果此時前臺業務和後臺業務公用一批服務和一個數據庫,有可能導致,由於後臺的“少數幾個請求”的“批量查詢”的“低效”訪問,導致數據庫的cpu偶爾瞬時100%,影響前臺正常用戶的訪問(例如,登錄超時)。

而且,為了滿足後臺業務各類“奇形怪狀”的需求,往往會在數據庫上建立各種索引,這些索引占用大量內存,會使得用戶側前臺業務uid/login_name上的查詢性能與寫入性能大幅度降低,處理時間增長。

對於這一類業務,應該采用“前臺與後臺分離”的架構方案:

戶側前臺業務需求架構依然不變,產品運營側後臺業務需求則抽取獨立的web / service / db 來支持,解除系統之間的耦合,對於“業務復雜”“並發量低”“無需高可用”“能接受一定延時”的後臺業務:

可以去掉service層,在運營後臺web層通過dao直接訪問db

不需要反向代理,不需要集群冗余

不需要訪問實時庫,可以通過MQ或者線下異步同步數據

在數據庫非常大的情況下,可以使用更契合大量數據允許接受更高延時的“索引外置”或者“HIVE”的設計方案

f.總結

將以“用戶中心”為典型的“單KEY”類業務,水平切分的架構點,本文做了這樣一些介紹。

水平切分方式:範圍法;哈希法

水平切分後碰到的問題:通過uid屬性查詢能直接定位到庫,通過非uid屬性查詢不能定位到庫

非uid屬性查詢的典型業務:用戶側,前臺訪問,單條記錄的查詢,訪問量較大,服務需要高可用,並且對一致性的要求較高;運營側,後臺訪問,根據產品、運營需求,訪問模式各異,基本上是批量分頁的查詢,由於是內部系統,訪問量很低,對可用性的要求不高,對一致性的要求也沒這麽嚴格

這兩類業務的架構設計思路:

針對用戶側,應該采用“建立非uid屬性到uid的映射關系”的架構方案

針對運營側,應該采用“前臺與後臺分離”的架構方案

用戶前臺側,“建立非uid屬性到uid的映射關系”最佳實踐:

索引表法:數據庫中記錄login_name->uid的映射關系

緩存映射法:緩存中記錄login_name->uid的映射關系

login_name生成uid

login_name基因融入uid

運營後臺側,“前臺與後臺分離”最佳實踐:

前臺、後臺系統web/service/db分離解耦,避免後臺低效查詢引發前臺查詢抖動

可以采用數據冗余的設計方式

可以采用“外置索引”(例如ES搜索系統)或者“大數據處理”(例如HIVE)來滿足後臺變態的查詢需求

3. 100億數據1萬屬性數據架構設計

1). 什麽是數據庫擴展的version + ext方案?

使用ext來承載不同業務需求的個性化屬性,使用version來標識ext裏各個字段的含義。

優點:a.可以隨時動態擴展屬性,擴展性好 ;b.新舊兩種數據可以同時存在,兼容性好

不足:a.ext裏的字段無法建立索引 ; b.ext裏的key值有大量冗余,建議key短一些

2). 如何將不同品類,異構的數據統一存儲起來,采用的就是類似version+ext的方式:

tiezi(tid,uid, time, title, cate, subcate, xxid, ext)

a.一些通用的字段抽取出來單獨存儲

b.通過cate, subcate, xxid等來定義ext是何種含義(和version有點像?)

c.通過ext來存儲不同業務線的個性化需求

3). 解決了海量異構數據的存儲問題,遇到的新問題是:

a.每條記錄ext內key都需要重復存儲,占據了大量的空間,能否壓縮存儲

b.cateid已經不足以描述ext內的內容,品類有層級,深度不確定,ext能否具備自描述性

c.隨時可以增加屬性,保證擴展性

4).統一類目屬性服務

抽象出一個統一的類目、屬性服務,單獨來管理這些信息,而帖子庫ext字段裏json的key,統一由數字來表示,減少存儲空間。

數字是什麽含義,屬於哪個子分類,值的校驗約束,統一都存儲在類目、屬性服務裏。

除此之外,如果ext裏某個key的value不是正則校驗的值,而是枚舉值時,需要有一個對值進行限定的枚舉表來進行校驗

5). 統一檢索服務

數據量很大的時候,不同屬性上的查詢需求,不可能通過組合索引來滿足所有查詢需求,怎麽辦呢?

58同城的先賢們,從一早就確定了“外置索引,統一檢索服務”的技術路線:

a.數據庫提供“帖子id”的正排查詢需求

b.所有非“帖子id”的個性化檢索需求,統一走外置索引

6).元數據與索引數據的操作遵循:

a.對帖子進行tid正排查詢,直接訪問帖子服務

b.對帖子進行修改,帖子服務通知檢索服務,同時對索引進行修改

c.對帖子進行復雜查詢,通過檢索服務滿足需求

4.數據庫秒級平滑擴容架構方案

1).部署方案:

a.並發量大,流量大的互聯網架構,一般來說,數據庫上層都有一個服務層,服務層記錄了“業務庫名”與“數據庫實例”的映射關系,通過數據庫連接池向數據庫路由sql語句以執行

b.隨著數據量的增大,數據要進行水平切分,分庫後將數據分布到不同的數據庫實例(甚至物理機器)上,以達到降低數據量,增強性能的擴容目的

c.互聯網架構需要保證數據庫高可用,常見的一種方式,使用雙主同步+keepalived+虛ip的方式保證數據庫的可用性

d.綜合上文的(2)和(3),線上實際的架構,既有水平切分,又有高可用保證

提問:如果數據量持續增大,分2個庫性能扛不住了,該怎麽辦呢?

回答:繼續水平拆分,拆成更多的庫,降低單庫數據量,增加庫主庫實例(機器)數量,提高性能。

2).停服務方案:暫停所有服務,遷移數據。

回滾方案:如果數據遷移失敗,或者遷移後測試失敗,則將配置改回x庫,恢復服務,改天再掛公告。

方案優點:簡單

方案缺點:a.停服務,不高可用;

b.技術同學壓力大,所有工作要在規定時間內做完,根據經驗,壓力越大約容易出錯(這一點很致命)

c.如果有問題第一時間沒檢查出來,啟動了服務,運行一段時間後再發現有問題,難以回滾,需要回檔,可能會丟失一部分數據

3).秒級、平滑、帥氣方案

a.修改配置

主要修改兩處:

a).數據庫實例所在的機器做雙虛ip,原來%2=0的庫是虛ip0,現在增加一個虛ip00,%2=1的另一個庫同理

b).修改服務的配置(不管是在配置文件裏,還是在配置中心),將2個庫的數據庫配置,改為4個庫的數據庫配置,修改的時候要註意舊庫與辛苦的映射關系:

%2=0的庫,會變為%4=0與%4=2;

%2=1的部分,會變為%4=1與%4=3;

這樣修改是為了保證,拆分後依然能夠路由到正確的數據。

b.reload配置,實例擴容

服務層reload配置,reload可能是這麽幾種方式:

a).比較原始的,重啟服務,讀新的配置文件

b).高級一點的,配置中心給服務發信號,重讀配置文件,重新初始化數據庫連接池

不管哪種方式,reload之後,數據庫的實例擴容就完成了,原來是2個數據庫實例提供服務,現在變為4個數據庫實例提供服務,這個過程一般可以在秒級完成。

整個過程可以逐步重啟,對服務的正確性和可用性完全沒有影響:

a).即使%2尋庫和%4尋庫同時存在,也不影響數據的正確性,因為此時仍然是雙主數據同步的

b).服務reload之前是不對外提供服務的,冗余的服務能夠保證高可用

完成了實例的擴展,會發現每個數據庫的數據量依然沒有下降,所以第三個步驟還要做一些收尾工作

c.收尾工作,數據收縮:

有這些一些收尾工作:

a).把雙虛ip修改回單虛ip

b).解除舊的雙主同步,讓成對庫的數據不再同步增加

c).增加新的雙主同步,保證高可用

d).刪除掉冗余數據,例如:ip0裏%4=2的數據全部幹掉,只為%4=0的數據提供服務啦

這樣下來,每個庫的數據量就降為原來的一半,數據收縮完成。

5. 100億數據平滑數據遷移,不影響服務

針對互聯網很多“數據量較大,並發量較大,業務復雜度較高”的業務場景,在

a.底層表結構變更

b.分庫個數變換

c.底層存儲介質變換

的眾多需求下,需要進行數據遷移,完成“平滑遷移數據,遷移過程不停機,保證系統持續服務”有兩種常見的解決方案。

1).追日誌法,五個步驟:

a.服務進行升級,記錄“對舊庫上的數據修改”的日誌

b.研發一個數據遷移小工具,進行數據遷移

c.研發一個讀取日誌小工具,追平數據差異

d.研發一個數據比對小工具,校驗數據一致性

e.流量切到新庫,完成平滑遷移

2).雙寫法,四個步驟:

a.服務進行升級,記錄“對舊庫上的數據修改”進行新庫的雙寫

b.研發一個數據遷移小工具,進行數據遷移

c.研發一個數據比對小工具,校驗數據一致性

d.流量切到新庫,完成平滑遷移

6. MySQL冗余數據的三種方案

1).為什麽要冗余數據

例如:訂單業務,對用戶和商家都有訂單查詢需求:

Order(oid, info_detail);

T(buyer_id, seller_id, oid);

如果用buyer_id來分庫,seller_id的查詢就需要掃描多庫。

如果用seller_id來分庫,buyer_id的查詢就需要掃描多庫。

此時可以使用數據冗余來分別滿足buyer_id和seller_id上的查詢需求:

T1(buyer_id, seller_id, oid)

T2(seller_id, buyer_id, oid)

同一個數據,冗余兩份,一份以buyer_id來分庫,滿足買家的查詢需求;一份以seller_id來分庫,滿足賣家的查詢需求。

2).服務同步雙寫

顧名思義,由服務層同步寫冗余數據,如上圖1-4流程:

業務方調用服務,新增數據

服務先插入T1數據

服務再插入T2數據

服務返回業務方新增數據成功

優點:

不復雜,服務層由單次寫,變兩次寫

數據一致性相對較高(因為雙寫成功才返回)

缺點:

請求的處理時間增加(要插入兩次,時間加倍)

數據仍可能不一致,例如第二步寫入T1完成後服務重啟,則數據不會寫入T2

3).服務異步雙寫

數據的雙寫並不再由服務來完成,服務層異步發出一個消息,通過消息總線發送給一個專門的數據復制服務來寫入冗余數據,如上圖1-6流程:

業務方調用服務,新增數據

服務先插入T1數據

服務向消息總線發送一個異步消息(發出即可,不用等返回,通常很快就能完成)

服務返回業務方新增數據成功

消息總線將消息投遞給數據同步中心

數據同步中心插入T2數據

優點:請求處理時間短(只插入1次)

缺點:系統的復雜性增加了,多引入了一個組件(消息總線)和一個服務(專用的數據復制服務)

因為返回業務線數據插入成功時,數據還不一定插入到T2中,因此數據有一個不一致時間窗口(這個窗口很短,最終是一致的)

在消息總線丟失消息時,冗余表數據會不一致

不管是服務同步雙寫,還是服務異步雙寫,服務都需要關註“冗余數據”帶來的復雜性。如果想解除“數據冗余”對系統的耦合,引出常用的第三種方案。

如果系統對處理時間比較敏感,引出常用的第二種方案。

4).線下異步雙寫:

為了屏蔽“冗余數據”對服務帶來的復雜性,數據的雙寫不再由服務層來完成,而是由線下的一個服務或者任務來完成,如上圖1-6流程:

業務方調用服務,新增數據

服務先插入T1數據

服務返回業務方新增數據成功

數據會被寫入到數據庫的log中

線下服務或者任務讀取數據庫的log

線下服務或者任務插入T2數據

優點:數據雙寫與業務完全解耦; 請求處理時間短(只插入1次)

缺點:返回業務線數據插入成功時,數據還不一定插入到T2中,因此數據有一個不一致時間窗口(這個窗口很短,最終是一致的)

數據的一致性依賴於線下服務或者任務的可靠性

5).總結:

互聯網數據量大的業務場景,常常:

使用水平切分來降低單庫數據量

使用數據冗余的反範式設計來滿足不同維度的查詢需求

使用服務同步雙寫法能夠很容易的實現數據冗余

為了降低時延,可以優化為服務異步雙寫法

為了屏蔽“冗余數據”對服務帶來的復雜性,可以優化為線下異步雙寫法

內容轉自微信公眾號:架構師之路

數據庫架構(轉)