今年騰訊開心鼠項目的用戶量每天都在肉眼可見的急劇增長,某些周的複合增長率甚至達到了10%,隨着用戶量的增長和業務複雜性的增加,數據庫的高峯性能壓力和存儲壓力不斷變大,下面整體介紹下我們進行的一系列存儲架構的調整以及未來的規劃。
年初項目的整體架構如下,大部分模塊都在以(ip+port)的方式使用同個DB實例。
在量較小的情況下,核心DB的CPU和存儲負載都沒有太大壓力,但量變大後整體風險逐步暴露,主要有:
耦合度高:任何一個業務svr的SQL性能都會影響所有業務的性能,一旦某個svr沒控制好,可能整體全掛
擴展性差:整個存儲的磁盤空間,都寫性能都會受到單機限制。
階段1:一主多從
結合高峯期數據庫審計日誌和對業務模塊的梳理,發現數據庫訪問有以下特點:
讀寫QPS比例大致在10 : 1;
寫主要集中在用戶的物品數據,課程數據,學習數據等;
讀主要分佈在賬號體系,集訓營,管理端,支付物流,學習記錄等;
慢查詢主要集中在集訓營和管理端的業務上。
針對讀寫比例的特點,優先優化讀請求。
在一主一從的基礎上擴充兩個RO組,從數據實時性和業務重要性對讀操作進行拆分。
數據實時性
針對實時性要求高的用戶賬號,物品數據讀取主庫;
針對用戶課程數據,學習數據讀取從庫;
業務重要性
APP主功能,支付等讀取主庫和從庫1;集訓營和管理端讀取從庫2;數據統計、分析讀取從庫3;
進行讀庫的拆分後,線上運行穩定,主庫CPU負載從40%下載到了20%。
階段2:橫向拆分
在整個讀拆分過程發現,從業務劃分來看,數據庫數據主要分爲APP用戶數據,集訓營數據,支付物流數據,運營活動數據;
其中除了用戶賬號數據是多個業務模塊所需的,其他的基本上是獨立的,於是我們考慮將數據庫進行拆分,拆分爲4個數據庫:
公共數據庫:用戶APP賬號數據,微信序賬號數據,課程靜態數據;
集訓營數據庫:集訓營分配數據,上課數據,老師數據;
支付/物流數據庫:支付數據,物流數據,運營活動數據;
UGC數據庫:用戶課程數據,學習數據,活動數據。
每個數據庫對應一個modle模塊,將各表的增刪改查等操作封裝,各自以RPC方式去調用其他庫的數據庫操作。
橫向拆分主要涉及代碼的改造和數據的遷移,兩個核心問題是:
儘可能降低遷移的改造成本
如何保障業務不中斷平滑遷移
以UGC數據庫爲例,該庫數據主要分爲兩大類:
用於數據分析的少量關係化查詢的 用戶活動數據 ;
存在大量關係化查詢的 用戶課程數據 。
在用戶規模不大時,原有的UGC數據都存儲在mysql中,但隨着用戶數增長,mysql的存儲空間和寫性能都無法滿足訴求。
用戶課程數據
該部分數據需要支持關係化查詢,我們採用了騰訊雲的mysql集羣解決方案TDSQL,TDSQL主要優勢在於:
整體容量隨着分片數的增加而增加,並且是動態擴容的,不影響業務;
讀寫分離,擁有更好的讀寫性能;
提供Proxy代理,業務像使用單機Mysql一樣;
SQL語句完全兼容,業務遷移成本低;
完備的監控指標和告警支持,同時支持性能分析。
數據表的平滑遷移
選擇合適的sharedkey在TDSQL庫中創建新的表;
使用mysqldump的數據導出服務,導出已有的全量數據,再導入到TDSQL;
通過騰訊雲的DTS(數據傳輸服務),接入在線教育的統一binlog notify服務,將數據增量變更寫入到TDSQL;
將項目中數據庫配置進行讀庫和寫庫的改造;
進行項目中的該表讀操作改造,將其改造到TDSQL對應表;
線上業務監控及觀察一段時間,如有問題,及時回滾讀庫配置;
讀請求遷移平穩後,對項目中的該表寫操作改造,將其改造到TDSQL對應表;
線上業務及監控觀察一段時間,如有問題,及時回滾寫庫配置;
停止DTS服務和binlog notify服務,遷移完成。
當然我們的UGC業務有一定的延時誤差是被允許接受的;如果對延時的接受程度較低,可以採用Mysql,TDSQL的雙寫方案來進行改造;先遷移寫再遷移讀來進行遷移改造,這裏不再贅述,歡迎隨時討論。
用戶活動數據
以用戶學習數據爲例,主要爲用戶完成學習活動過程中,在各個環節的表現,該部分數據主要用於數據分析,基本全爲寫操作,我們最終選擇mongdb來存儲這部分數據,主要優勢在於:
mongdb面向集合存儲,模式自由,可以方便地擴展學習數據字段;
mongdb支持大數據量的存儲,可以滿足這種隨時間膨脹的特厲害的存儲訴求;
mongdb支持一定程度的關係化查詢,滿足按用戶ID,活動ID來查詢數據;
強大的聚合工具,完美配合MapReduce等數據分析工具;
支持數據複製和恢復能力,便於分析數據的傳輸。
數據表的平滑遷移
在mongdb中建立對應的數據庫表
改造項目代碼,將原有數據庫表的寫請求存儲到Kafka隊列中
使用遷移服務,對原有數據表的全量數據進行遷移,寫入到mongdb中
步驟3完成後,啓動Kafka對應topic的消費服務,開始將數據平滑寫入到mongdb
針對各庫的每張表都可以採用類似的方案進行遷移,至此我們可以開始對單庫進行優化。
階段3:單庫優化
以公共數據庫爲例,通過分析業務高峯期的的數據庫審計日誌,發現:
50%的請求基本集中在用戶賬號表上,10%的請求集中在課程靜態數據上;
下面將討論如何優化這兩部分請求。
用戶賬號表
該表主要特點有:
寫請求佔比大致爲11:1;
寫請求主要集中在密碼、login時間等少數字段上;
大部分用戶數據:像手機號,ID等基本不變,同時這些字段可接受一定的時延。
於是將用戶賬號表拆解成兩部分:
對於時延不敏感數據拆成一張mysql表,同時將表數據緩存到redis中;
寫請求較多的拆解成爲redis中的緩存。
對比了業界主要的緩存更新方案,考慮對業務無入侵、實時性優、監控完善等特點;
選用便於接入的在線教育統一的DTS更新服務。
課程相關數據表
該表數據特點:
基本爲靜態數據表,不常變更;
佔用空間大致在10M左右,且可預見的數據都不會很大。
可採用內存緩存的來進行讀加速,在服務啓動時初始化後定時更新。
同時還對各庫進行了一系列的慢查詢優化,索引優化,儘量保證單庫的可用性及性能。
階段4:整體優化
在針對性對單庫進行性能優化後,在實際開發和維護過程中還存在些痛點:
業務方調用成本高,得理解不同數據庫的差異;
多數據庫的安全防護,統計功能、監控等功能都很分散;
UGC數據大表業務高峯寫入QPS很高;
UGC數據大表無法在線DDL操作。
問題1/2:DataProxy統一代理
除了針對各個svr合理調整連接數外,引入DB代理也是個較好的解決方案;於是採用Data Proxy的方案,由其代理各個數據庫的訪問,提供統一接口給業務方調用;同時在proxy可進行各種安全防護措施,比如過載保護,緩存崩塌保護,穿透保護等;另外還能對外提供數據統計、延時監控功能。
問題3:Kafka平滑寫入
由於業務特點,高峯期用戶數在平時的10倍以上,單純的擴容數據庫機器在大部分時間內都是極其浪費的;同時由於數據特點,少量數據的讀延遲是可以接受的;於是引入KAFKA隊列,先由PROXY統一寫入到隊列中,再由消費服務平滑寫入TDSQL與MongoDB;這樣就能較低成本扛住高峯期流量和突增流量,並且有很好的擴展性。
問題4:在線DDL暫停消費服務
對於大表的DDL一直是數據庫優化過程中的一個老大難問題;數據庫表的變更,不管是索引變更還是字段變更基本都會縮表從而引發業務中斷;在引入隊列平滑寫入情況下,可在線隨時停止消費服務後進行的DDL變更,但不會影響正常業務的運轉。
最終存儲架構