天天看點

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

提醒,本文較長,可提前收藏/轉發。

本文将以“使用者中心”為例,介紹“單KEY”類業務,随着資料量的逐漸增大,資料庫性能顯著降低,資料庫水準切分相關的架構實踐:

如何來實施水準切分

水準切分後常見的問題

典型問題的優化思路及實踐

一、使用者中心

使用者中心是一個非常常見的業務,主要提供使用者注冊、登入、資訊查詢與修改的服務,其核心中繼資料為:

User(uid, login_name, passwd, sex, age, nickname, …)           

其中:

uid為使用者ID,主鍵

login_name, passwd, sex, age, nickname, …等使用者屬性

資料庫設計上,一般來說在業務初期,單庫單表就能夠搞定這個需求,典型的架構設計為:

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

user-center:使用者中心服務,對調用者提供友好的RPC接口

user-db:對使用者進行資料存儲

二、使用者中心水準切分方法

當資料量越來越大時,需要對資料庫進行水準切分,常見的水準切分算法有“範圍法”和“哈希法”。

範圍法,以使用者中心的業務主鍵uid為劃分依據,将資料水準切分到兩個資料庫執行個體上去:

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

user-db1:存儲0到1千萬的uid資料

user-db2:存儲1到2千萬的uid資料

範圍法的優點是:

切分政策簡單,根據uid,按照範圍,user- center很快能夠定位到資料在哪個庫上

擴容簡單,如果容量不夠,隻要增加user-db3即可

範圍法的不足是:

uid必須要滿足遞增的特性

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

請求量不均,一般來說,新注冊的使用者活躍度會比較高,故user-db2往往會比user-db1負載要高,導緻伺服器使用率不平衡

哈希法,也是以使用者中心的業務主鍵uid為劃分依據,将資料水準切分到兩個資料庫執行個體上去:

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

user-db1:存儲uid取模得1的uid資料

user-db2:存儲uid取模得0的uid資料

哈希法的優點是:

切分政策簡單,根據uid,按照hash,user-center很快能夠定位到資料在哪個庫上

資料量均衡,隻要uid是均勻的,資料在各個庫上的分布一定是均衡的

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

哈希法的不足是:

擴容麻煩,如果容量不夠,要增加一個庫,重新hash可能會導緻資料遷移,如何平滑的進行資料遷移,是一個需要解決的問題

三、使用者中心水準切分後帶來的問題

使用uid來進行水準切分之後,整個使用者中心的業務通路會遇到什麼問題呢?

對于uid屬性上的查詢可以直接路由到庫,假設通路uid=124的資料,取模後能夠直接定位db-user1:

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

對于非uid屬性上的查詢,例如login_name屬性上的查詢,就悲劇了:

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

假設通路login_name=shenjian的資料,由于不知道資料落在哪個庫上,往往需要周遊所有庫,當分庫數量多起來,性能會顯著降低。

如何解決分庫後,非uid屬性上的查詢問題,是後文要重點讨論的内容。

四、使用者中心非uid屬性查詢需求分析

任何脫離業務的架構設計都是耍流氓,在進行架構讨論之前,先來對業務進行簡要分析,看非uid屬性上有哪些查詢需求。

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

(1)使用者側,前台通路,最典型的有兩類需求

使用者登入:通過login_name/phone/email查詢使用者的實體,1%請求屬于這種類型

使用者資訊查詢:登入之後,通過uid來查詢使用者的執行個體,99%請求屬這種類型

使用者側的查詢基本上是單條記錄的查詢,通路量較大,服務需要高可用,并且對一緻性的要求較高。

(2)營運側,背景通路,根據産品、營運需求,通路模式各異,按照年齡、性别、頭像、登陸時間、注冊時間來進行查詢。

營運側的查詢基本上是批量分頁的查詢,由于是内部系統,通路量很低,對可用性的要求不高,對一緻性的要求也沒這麼嚴格。

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

五、使用者中心水準切分架構思路

使用者中心在資料量較大的情況下,使用uid進行水準切分,對于非uid屬性上的查詢需求,架構設計的核心思路為:

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

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

六、使用者中心-使用者側最佳實踐

【索引表法】

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

解決方案:

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

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

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

如果資料量過大,可以通過login_name來分庫

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

【緩存映射法】

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

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

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

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

如果資料量過大,可以通過login_name進行cache水準切分

潛在不足:多一次cache查詢

【login_name生成uid】

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

在使用者注冊時,設計函數login_name生成uid,uid=f(login_name),按uid分庫插入資料

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

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

【login_name基因融入uid】

思路:不能用login_name生成uid,可以從login_name抽取“基因”,融入uid中

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

假設分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直接定位到庫

七、使用者中心-營運側最佳實踐

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

背景營運側,業務需求各異,基本是批量分頁的通路,這類通路計算量較大,傳回資料量較大,比較消耗資料庫性能。

如果此時前台業務和背景業務公用一批服務和一個資料庫,有可能導緻,由于背景的“少數幾個請求”的“批量查詢”的“低效”通路,導緻資料庫的cpu偶爾瞬時100%,影響前台正常使用者的通路(例如,登入逾時)。

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

而且,為了滿足背景業務各類“奇形怪狀”的需求,往往會在資料庫上建立各種索引,這些索引占用大量記憶體,會使得使用者側前台業務uid/login_name上的查詢性能與寫入性能大幅度降低,處理時間增長。

對于這一類業務,應該采用“前台與背景分離”的架構方案:

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

使用者側前台業務需求架構依然不變,産品營運側背景業務需求則抽取獨立的web / service / db 來支援,解除系統之間的耦合,對于“業務複雜”“并發量低”“無需高可用”“能接受一定延時”的背景業務:

可以去掉service層,在營運背景web層通過dao直接通路db

不需要反向代理,不需要叢集備援

不需要通路實時庫,可以通過MQ或者線下異步同步資料

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

單KEY業務,資料庫水準切分架構實踐 | 架構師之路

八、總結

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

水準切分方式:

範圍法

哈希法

水準切分後碰到的問題:

通過uid屬性查詢能直接定位到庫,通過非uid屬性查詢不能定位到庫

非uid屬性查詢的典型業務:

使用者側,前台通路,單條記錄的查詢,通路量較大,服務需要高可用,并且對一緻性的要求較高

營運側,背景通路,根據産品、營運需求,通路模式各異,基本上是批量分頁的查詢,由于是内部系統,通路量很低,對可用性的要求不高,對一緻性的要求也沒這麼嚴格

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

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

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

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

login_name生成uid

login_name基因融入uid

營運背景側,“前台與背景分離”最佳實踐:

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

可以采用資料備援的設計方式

可以采用“外置索引”(例如ES搜尋系統)或者“大資料處理”(例如HIVE)來滿足背景變态的查詢需求

其他類型業務的水準切分架構方案,未來和大家聊。