使用者中心,幾乎是所有網際網路公司,必備的子系統。隨著資料量不斷增加,吞吐量不斷增大,使用者中心的架構,該如何演進呢。使用者中心是一個通用業務,主要提供使用者註冊、登入、資訊查詢與修改的服務。User(uid, login_name, passwd, sex, age, nickname, …)(2)login_name, passwd, sex 等是使用者屬性;在業務初期,單庫單表,配合使用者中心微服務,就能滿足絕大部分業務需求,其典型的架構為:(1)user-center:使用者中心服務,對呼叫者提供友好的RPC介面;當資料量越來越大,例如達到1億註冊量時,會出現什麼問題呢?
隨著資料量越來越大,單庫無法承載所有的資料,此時需要對資料庫進行水平切分。範圍法,以使用者中心的業務主鍵uid為劃分依據,採用區間的方式,將資料水平切分到兩個資料庫例項上去:(1)user-db1:儲存0到1千萬的uid資料;(2)user-db2:儲存1千萬到2千萬的uid資料;(1)切分策略簡單,根據uid,按照範圍,user-center很快能夠定位到資料在哪個庫上;(2)擴容簡單,如果容量不夠,只要增加user-db3,擴充2千萬到3千萬的uid即可;(2)資料量不均,新增的user-db3,在初期的資料會比較少;(3)請求量不均,一般來說,新註冊的使用者活躍度會比較高,故user-db2往往會比user-db1負載要高,導致伺服器利用率不平衡;畫外音:資料庫層面的負載均衡,既要考慮資料量的均衡,又要考慮負載的均衡。雜湊法,也是以使用者中心的業務主鍵uid為劃分依據,採用雜湊的方式,將資料水平切分到兩個資料庫例項上去:(1)切分策略簡單,根據uid,按照hash,user-center很快能夠定位到資料在哪個庫上;(2)資料量均衡,只要uid是隨機的,資料在各個庫上的分佈一定是均衡的;(3)請求量均衡,只要uid是隨機的,負載在各個庫上的分佈一定是均衡的;畫外音:如果採用分散式id生成器,id的生成,一般都是隨機的。(1)擴容麻煩,如果容量不夠,要增加一個庫,重新hash可能會導致資料遷移;使用者中心架構,實施了水平切分之後,會帶來什麼新的問題呢?使用uid來進行水平切分之後,對於uid屬性上的查詢,可以直接路由到庫,假設訪問uid=124的資料,取模後能夠直接定位db-user1:但對於非uid屬性上的查詢,就悲劇了,例如login_name屬性上的查詢:假設訪問login_name=shenjian的資料,由於不知道資料落在哪個庫上,往往需要遍歷所有庫,當分庫數量多起來,效能會顯著降低。在進行架構討論之前,先來對業務進行簡要分析,使用者中心非uid屬性上,有兩類典型的業務需求。第一大類,使用者側,前臺訪問,最典型的有兩類需求:(1)使用者登入:透過登入名login_name查詢使用者的實體,1%請求屬於這種型別;(2)使用者資訊查詢:登入之後,透過uid來查詢使用者的例項,99%請求屬這種型別;使用者側的查詢,基本上是單條記錄的查詢,訪問量較大,服務需要高可用,並且對一致性的要求較高。第二大類,運營側,後臺訪問,根據產品、運營需求,訪問模式各異,按照年齡、性別、頭像、登陸時間、註冊時間來進行查詢。運營側的查詢,基本上是批次分頁的查詢,由於是內部系統,訪問量很低,對可用性的要求不高,對一致性的要求也沒這麼嚴格。對於這兩類不同的業務需求,應該使用什麼樣的架構方案來解決呢?總的來說,針對這兩類業務需求,架構設計的核心思路為:(1)使用者側,採用“建立非uid屬性到uid的對映關係”的架構方案;使用者側,如何實施“建立非uid屬性到uid的對映關係”呢?索引表法的思路是:uid能直接定位到庫,login_name不能直接定位到庫,如果透過login_name能查詢到uid,問題便能得到解決。(1)建立一個索引表記錄login_name與uid的對映關係;(2)用login_name來訪問時,先透過索引表查詢到uid,再透過uid定位相應的庫;(3)索引表屬性較少,可以容納非常多資料,一般不需要分庫;(4)如果資料量過大,可以透過login_name來分庫;快取對映法的思路是:訪問索引表效能較低,把對映關係放在快取裡,能夠提升效能。(1)login_name查詢先到cache中查詢uid,再根據uid定位資料庫;(2)假設cache miss,掃描所有分庫,獲取login_name對應的uid,放入cache;(3)login_name到uid的對映關係不會變化,對映關係一旦放入快取,不會更改,無需淘汰,快取命中率超高;(4)如果資料量過大,可以透過login_name進行cache水平切分;生成uid法的思路是:不進行遠端查詢,由login_name直接得到uid。(1)在使用者註冊時,設計函式login_name生成uid,uid=f(login_name),按uid分庫插入資料;(2)用login_name來訪問時,先透過函式計算出uid,即uid=f(login_name)再來一遍,由uid路由到對應庫;該函式設計需要非常講究技巧,且有uid生成衝突風險。畫外音:uid衝突,是業務無法接受的,故生產環境中,一般不使用這個方法。基因法的思路是:不能用login_name生成uid,但可以從login_name抽取“基因”,融入uid中。假設分8庫,採用uid%8路由,潛臺詞是,uid的最後3個bit決定這條資料落在哪個庫上,這3個bit就是所謂的“基因”。(1)在使用者註冊時,設計函式login_name生成3bit基因,login_name_gene = f(login_name),如上圖粉色部分;(2)同時,生成61bit的全域性唯一id,作為使用者的標識,如上圖綠色部分;(3)接著把3bit的login_name_gene也作為uid的一部分,如上圖屎黃色部分;(4)生成64bit的uid,由id和login_name_gene拼裝而成,並按照uid分庫插入資料;(5)用login_name來訪問時,先透過函式由login_name再次復原3bit基因,login_name_gene = f(login_name),透過login_name_gene%8直接定位到庫;使用者側,如何實施“前臺與後臺分離”的架構方案呢?前臺使用者側,業務需求基本都是單行記錄的訪問,只要建立非uid屬性login_name到uid的對映關係,就能解決問題。後臺運營側,業務需求各異,基本是批次分頁的訪問,這類訪問計算量較大,返回資料量較大,比較消耗資料庫效能。此時,前臺業務和後臺業務共用一批服務和一個資料庫,有可能導致,由於後臺的“少數幾個請求”的“批次查詢”的“低效”訪問,導致資料庫的cpu偶爾瞬時100%,影響前臺正常使用者的訪問(例如,登入超時)。而且,為了滿足後臺業務各類“奇形怪狀”的需求,往往會在資料庫上建立各種索引,這些索引佔用大量記憶體,會使得使用者側前臺業務uid/login_name上的查詢效能與寫入效能大幅度降低,處理時間增長。對於這一類業務,應該採用“前臺與後臺分離”的架構方案。使用者側前臺業務需求架構依然不變,產品運營側後臺業務需求則抽取獨立的 web / service / db 來支援,解除系統之間的耦合,對於“業務複雜”“併發量低”“無需高可用”“能接受一定延時”的後臺業務:(1)可以去掉service層,在運營後臺web層透過dao直接訪問db;(3)不需要訪問實時庫,可以透過MQ或者線下非同步同步資料;(4)在資料庫非常大的情況下,可以使用更契合大量資料允許接受更高延時的“索引外接”或者“HIVE”的設計方案;使用者中心,是典型的“單KEY”類業務,這一類業務,都可以使用上述架構方案。(1)透過uid屬性查詢能直接定位到庫,透過非uid屬性查詢不能定位到庫;(1)使用者側,前臺訪問,單條記錄的查詢,訪問量較大,服務需要高可用,並且對一致性的要求較高;(2)運營側,後臺訪問,根據產品、運營需求,訪問模式各異,基本上是批次分頁的查詢,由於是內部系統,訪問量很低,對可用性的要求不高,對一致性的要求也沒這麼嚴格;(1)使用者側,採用“建立非uid屬性到uid的對映關係”的架構方案;前臺使用者側,“建立非uid屬性到uid的對映關係”,有四種常見的實踐:(1)索引表法:資料庫中記錄login_name與uid的對映關係;(2)快取對映法:快取中記錄login_name與uid的對映關係;(3)生成uid法:login_name生成uid;(4)基因法:login_name基因融入uid;(1)前臺、後臺系統 web/service/db 分離解耦,避免後臺低效查詢引發前臺查詢抖動;(3)可以採用“外接索引”(例如ES搜尋系統)或者“大資料處理”(例如HIVE)來滿足後臺變態的查詢需求;
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69959420/viewspace-2704837/,如需轉載,請註明出處,否則將追究法律責任。