微博數據庫3個變遷階段背后的設計思想

y139733 9年前發布 | 32K 次閱讀 數據庫 軟件架構

來自: http://h2ex.com/857

肖鵬,微博研發中心技術經理,主要負責微博數據庫(MySQL/Reids/HBase/Memcached)相關的業務保障、性能優化、架構設計,以及周邊的自動化系統建設。經歷了微博數據庫各個階段的架構改造,包括服務保障及 SLA 體系建設、微博多機房部署、微博平臺化改造等項目,10 年互聯網數據庫架構和管理經驗,專注于數據庫的高性能和高可用技術保障方向。

數據庫專家的成長感觸

“與 MySQL 結緣主要也是源于興趣。第一份工作是在一家小公司,由于人手有限,各個領域的工作都要接觸,相比之下我發現還是對數據庫最感興趣,所以就一直從事和數據庫相關的技術工作了。而隨著工作年限的增加,在數據庫方面積累的經驗也逐漸增多,越來越覺得數據庫管理員(DBA)是一個偏實踐的工種,很多理論上的東西在現實中會有各種的變化,比如“反范式”設計等。因此,如果想成為數據庫方面的專家,建議大家一定要挑選好環境,大平臺很多時候會由于量變引發質變產生很多有挑戰的問題,而解決這些問題是成為技術專家的必經之路。”  —— 肖鵬

微博數據庫經歷的變遷

首先為大家分享微博數據庫經歷的幾個重要的階段。

初創階段

初期微博作為一個內部創新產品,功能比較簡潔, 數據庫架構采用的是標準 1M/2S/1MB 結構,按照讀寫分離設計,主庫承擔寫入,而從庫承擔訪問。 如果訪問壓力過大,可以通過擴容從庫的數量獲得 scale out 的能力。

上圖紅色代表寫入、綠色代表讀取、黑色映射到內部結構,由圖可知,業務僅僅進行了垂直拆分,也就是按照業務模塊比如用戶、內容、關系等進行區分,并單獨使用了數據庫。在 初期這其實是非常好的架構,在功能模塊上提供了解耦的基礎 ,出現問題也可以很方便地進行定位、在最開始就可以做到按照不同的功能模塊進行降級。

個人認為,在初期,這種架構其實就可以滿足業務的增長了,沒有必要進行過度設計,開始就搞得過于復雜可能會導致喪失敏捷的可能。

爆發階段

隨著微博上線之后用戶活躍度的增高,數據庫的壓力也與日俱增,我們首先通過采購高性能的硬件設備來對單機性能進行 scale up,以達到支撐業務高速發展的需求。然后,通過使用高性能設備爭取來的時間對微博進行整體上的業務垂直拆分, 將用戶、關系、博文、轉發、評論等功能模塊分別獨立存儲,并在垂直拆分的基礎上,對于一些預期會產生海量數據的業務模塊再次進行了二次拆分 。

對于使用硬件這里多說幾句,由于微博最開始的時候就出現了一個很高的用戶增長峰值,在這個階段我們在技術上的積累不是很豐富,而且最主要的是沒有時間進行架構改造,所以通過購買 PCIE-Flash 設備來支持的很多核心業務,我現在還清楚記得最開始的 feed 系統是重度依賴 MySQL 的,在 2012 年的春晚當天 MySQL 寫入 QPS 曾經飆到過 35000,至今記憶猶新。

雖然看上去高性能硬件的價格會比普通硬件高很多,但是爭取來的時間是最寶貴的,很有可能在產品生命的初期由于一些性能上的問題引發產品故障,直接導致用戶流失,更加得不償失。所以, 個人認為在前期的爆發階段,暴力投入資金解決問題其實反而是最劃算的 。

繼續說數據庫拆分,以博文為例。博文是微博用戶主要產生的內容,可預見會隨著時間維度不斷增大,最終會變得非常巨大,如何在滿足業務性能需求的情況下,盡可能地使用較少的成本存儲,這是我們面臨的一個比較有挑戰性的問題。

  • 首先,我們 將索引同內容進行了拆分 ,因為索引所需存儲空間較少,而內容存儲所需空間較大,且這兩者的使用需求也不盡相同,訪問頻次也會不同,需要區別對待。
  • 然后, 分別對索引和內容采用先 hash,再按照時間維度拆分的方式進行水平拆分 ,盡量保障每張表的容量在可控范圍之內,以保證查詢的性能指標。
  • 最后, 業務先通過索引獲得實際所需內容的 id,再通過內容庫獲得實際的內容 ,并通過部署 memcached 來加速整個過程,雖然看上去步驟變多,但實際效果完全可以滿足業務需求。

上圖乍一看和上一張圖一樣,但這其實只是一個博文功能模塊的數據庫架構圖,我們可以看到索引和內容各自分了很多的端口,每個端口中又分了很多的 DB,每個 DB 下的表先 hash 后按照時間維度進行了拆分,這樣就可以讓我們在后期遇到容量瓶頸或者性能瓶頸的時候,可以選擇做歸檔或者調整部署結構,無論選擇那種都非常的方便。另外,在做歸檔之后,還可以選擇使用不同的硬件來承擔不同業務,提高硬件的利用率、降低成本。

在這個階段,我們對很多的微博功能進行了拆分改造,比如用戶、關系、博文、轉發、評論、贊等,基本上將核心的功能都進行了數據拆分,以保障在遇到瓶頸的時候可以按照預案進行改造和調整。

沉淀階段

在上一個階段,微博的數據庫經歷了很多的拆分改造,這也就直接造成了規模成倍增長的狀況,而業務經歷了高速增長之后,也開始趨于穩定。在這個階段,我們 開始著重進行自動化的建設 ,將之前在快速擴張期間積攢下來的經驗用自動化工具加以實現,對外形成標準化和流程化的平臺服務。 我們相繼建設改造了備份系統、監控系統、AutoDDL 系統、MHA 系統、巡檢系統、慢查系統、maya 中間件系統 。并且為了提高業務使用效率、降低溝通成倍,相對于內部管理系統,重新開發了 iDB 系統供數據庫平臺的用戶使用。通過 iDB 系統,用戶可以很便捷地了解自己業務數據庫的運行狀態,并可以直接提交對數據庫的 DDL 修改需求,DBA 僅需點擊審核通過,即可交由 Robot 在線上執行,不但提高了工作效率,也提高了安全性和規范性。

由于涉及的自動化系統比較多,就不一一展開描述了,其實個人理解,在產品發展到一定階段之后無論如何運維都會進入到自動化階段,因為前期活兒少,人工足夠支持變更和其中操作,且有很多特殊情況需要人,尤其是人腦的介入判斷和處理。

這里要額外重點說一下規范的重要性。 以 MySQL 開發規范來說,如果提前做好約定,并進行好限制 ,雖然開發人員在使用的過程中會感覺受到的約束,但是這可以避免線上發生完全不可控的故障,并且有些問題由于規范的存在就永遠不會發生了。

舉個例子。MySQL 的慢查是導致線上性能慢的罪魁禍首,但是很多時候并不是沒有 index,只是由于代碼寫得有問題,引起了隱式轉換等問題。在這種情況下,我們一般建議所有的 where 條件都加上雙引號,這樣就可以直接消除隱式轉換的可能性了,開發人員在寫代碼的時候也不用刻意去考慮到底是字符型還是 int 型。

繼續說自動化。過了初期階段、規模擴大之后,就會出現活多人少的情況,這種壓力會促使大家自動去尋求解決方案,也就自然而然地進行了自動化改造了。當然,業務穩定后有時間開發了,其實是一個更重要的原因。

個人認為自動化分為兩個階段。 第一個階段是機器替代人工 ,也就是將大部分機械勞動交給程序來實現,解決的是批量操作,重復性勞動的問題; 第二個階段是機器替人 ,也就是機器可以替人進行一定的判斷之后進行自我選擇,解放的是人力。不過第二個階段是我們一直追求的理想狀態,至今也僅完成了很簡單的一些小功能,比如動態調整 max mem 等邏輯非常簡單的功能。

微博數據庫的優化和設計

接下來介紹一下微博數據庫平臺最近做的一些改進和優化。

數據庫平臺并不僅有 MySQL 還有 Redis、Memcached、HBase 等數據庫服務,而在緩存為王的趨勢下,微博 2015 年重點將研發精力投入在 Redis 上。

微博使用 Redis 的時間較早,并且一開始量就很大,于是在實際使用過程中遇到了很多實際的問題,我們的內部分支版本都是針對這些實際問題進行優化的,比較有特點的有如下幾個。

  • 增加基于 pos 位同步功能 。在 2.4 版本中,Redis 的同步一旦出現中斷就會重新將主庫的數據”全部”傳輸到從庫上,這會造成瞬時的網絡帶寬峰值,并且對于數據量較大的業務來說,從庫恢復的時間較慢,為此我們聯合架構組的同學借鑒 MySQL 的主從同步復制機制,將 Redis 的 aof 改造為記錄 pos 位,并讓從庫記錄已經同步的 pos 位,這樣在網絡出現波動的時候即使重傳,也僅是一部分數據,并不會影響業務。
  • 在線熱升級 。在使用初期,由于很多新功能的加入,Redis 版本不斷升級,為了不影響業務, 每次升級都需要進行主庫切換,給運維帶來了很大的挑戰,于是開發了熱升級機制,通過動態加載 libredis.so 來實現版本的改變,不再需要進行主庫切換,極大地提升了運維效率,也降低了變更帶來的風險。
  • 定制化改造 。在使用 Redis 的后期,由于微博產品上技術類的需求非常多,為此專門開發了兼容 Redis 的 redisscounter,用以專門存儲技術類數據,通過使用 array 替換 hash table 極大地降低內存占用。而在此之后,開發了基于 bloom filter 的 phantom 解決判斷類場景需求。

Redis 中間件

在 2015 年我們自研的 Redis 中間件 tribe 系統完成了開發和上線, tribe 采用有中心節點的 proxy 架構設計,通過 configer server 管理集群節點,并借鑒官方 Redis cluster 的 slot 分片的設計思路來完成數據存儲,最終實現了路由、分片、自動遷移、fail over 等功能 ,并且預留了操作和監控的 API 接口,以便同其他的自動化運維系統對接。

我們開發 tribe 最主要的目的是解決自動遷移的問題,由于 Redis 內存使用會呈現波動性變化,很多時候前一天還是 10%,第二天有可能就變成了 80%,這種時候人工去遷移肯定無法響應業務的變化,而且如果這時候恰巧還碰到了物理內存上的瓶頸,那就更麻煩了,涉及業務進行重構數據 hash 都有可能導致故障的發生。

基于 slot 的動態遷移,首先對業務無感知,其次不再需要整臺服務器,只需找有可用內存的服務器就可以將部分 slot 遷移過去,直接解決擴容遷移問題,可以極大地提高服務器的利用率、降低成本。

提供的路由功能,可以降低開發門檻,再也不用將資源邏輯配置寫到代碼或者前端配置文件中了,每次更改變更的時候也不用再進行上線了,這極大地提高了開發效率,也降低了線上變更引發的故障風險,畢竟 90% 的故障是主動變更引發的。

補充一點,關于重復造輪子,個人認為每個公司都有自己的場景,開源軟件可以給我們提供一個很好的解決思路,但并不能百分百適應應用場景,所以重構并不是堅決不可接受的,有些事情你總要妥協。  

Databus

由于我們先有 MySQL 后有 Redis 和 HBase 等數據庫,故存在一種場景就是目前數據已經寫入 MySQL 中,但是需要將這些數據同步到其他的數據庫中,我們為此開發了 Databus,可以基于 MySQL 的 binlog 將數據同步到其他異構的數據庫中,并且支持自定義業務邏輯。目前已經實現了 MySQL 到 Redis 和 MySQL 到 HBase 的數據流向,下一步計劃開發 Redis 到 MySQL 的數據流向。

我們開發 databus 最初的初衷是解決寫 Redis 的問題,由于有些數據即需要寫入 MySQL 中,也需要寫入 Redis 中。如果在前端開啟雙寫也是可以解決的,但是這會造成代碼復雜現象;如果在后端實現一個數據鏈路,會讓代碼更加清晰,同時也可以保障數據的最終一致性。后來在實際應用中,databus 慢慢也開始承擔導數據的功能。

下面說一下目前 微博積累下來的數據庫的設計習慣。通常來說我們都會采用一些“反范式”的設計思路 ,而“反范式“設計帶來便利的同時確實也帶來了一些問題,尤其是在數據規模變大之后。有如下幾種解決方案。

  • 預拆分 。在接到需求的時候提前針對于容量進行評估,并按照先垂直后水平進行拆分,如果可以按照時間維度設計,那就納入歸檔機制。通過數據庫的庫表拆分,解決容量存儲問題。
  • 引入消息隊列 。利用隊列的一寫多讀特性或多隊列來滿足冗余數據的多份寫入需求,但僅能保障最終一致性,中間可能會出現數據延遲。
  • 引入接口層 。通過不同業務模塊的接口將數據進行匯總之后再返回給應用層,降低應用層開發的編碼復雜度。

另外一點就是,如果數據庫預估量比較大的話,我們會 參考博文的設計思路,在最開始的時候進行索引和內容的分離 ,并設計好 hash 和時間維度的分表,最大可能地減少后續拆分時可能遇到的問題和困難。

微博數據庫平臺未來的計劃

最后,我想分享一下微博數據庫平臺發展的一點思考,希望可以給大家提供一些思路,當然,更希望大家也給我提出一些建議和意見,讓我少走彎路、少掉坑。

隨著業務的發展,會遇到越來越多的場景, 我們希望可以引進最適合的數據庫來解決場景問題 ,比如 PostgreSQL、SSDB 等。同時,利用 MySQL 新版本的特性,比如 MySQL 5.7 的并行復制、GTID、動態調整 BP,不斷優化現有服務的性能和穩定性。

另外, 推進現有 NoSQL 服務的服務化,通過使用 proxy 將存儲節點進行組織之后對外提供服務 ,對外降低開發人員的開發復雜度和獲取資源的細度,對內提高單機利用率并解決資源層橫向擴展的瓶頸問題。

同時, 嘗試利用各大云計算資源,實現 cache 層的動態擴縮容 ,充分利用云計算的彈性資源,解決業務訪問波動的問題。

Q & A

1. 數據和索引分離是業務層做還是中間件做?感覺中間件做了很多工作,這部分能不能稍微展開一下?

由于在當初拆分改造的時候并沒有考慮中間件的方案,故目前是業務邏輯上實現的索引和內容的分離。以我個人的經驗來看,即使使用中間件的解決方案,依然應該在業務邏輯層將索引和內容分割。

中間件最核心的功能就是讓程序和后端資源隔離,后端不管有多少資源,對于程序來說都是一個統一的入口,所以中間件解決的是水平拆分的問題,而索引和內容分離屬于垂直拆分的范圍,個人認為不應該由中間件解決。

2. 印象最深的一次數據庫服務故障能否回憶并說幾點注意事項?

要說印象最深的一次就是數據庫服務的故障,當時有個同事不小心執行了 drop table 命令,了解數據庫的人都知道這個命令有多大的威力,我們利用架構上面爭取來的時候緊急進行了單表恢復,雖然降級了一段時間,但是整體上并沒有影響用戶。

對此我要說的注意事項就是規范。自那之后,我們修改了所有刪表需求的流程,并保證嚴格執行,無論多么緊急的刪除需求都必須進行24小時的冷卻,具體如下。

  • 執行 rename table 操作,將 table rename 成 table—will-drop。
  • 等待 24 小時之后再執行 drop 操作。

3. 在微博暴漲階段,對表進行拆分的部分,“對索引和內容進行hash,之后再按著時間緯度進行拆分”,這個做hash的部分是否能展開說一下?

這個其實沒有那么復雜。首先我們會預估一下一年大概的數量級別,然后計算需要拆分的表數目,并且盡量將每個表控制在 3 千萬行記錄以內(當然這只是希望,現實證明計劃趕不上變化)。比如我們使用模 1024 的辦法,根據博文 id 將所有產生的博文分到 1024 張表中(這個博文 id 又涉及我們的 uuid 全局發號器就不展開了)。

由于微博大部分用戶產生的內容都會和時間掛鉤,所以時間維度對我們來說是強屬性,基本都會有,假設每個月都建表,這樣就等于每個月會生產 1024 張表。如果數據庫的容量出現瓶頸了,我們就可以根據時間維度來解決,比如將 2010 年的所有表都遷移到其他的數據庫中。

4. Linkedin 多年前出過一個類似 databus 的項目,后續也有一些開源項目支持 MySQL 到 HBase、ES 等的數據同步,微博的做法能不能展開一下?

這個異構數據的同步確實很多公司都有,據我所知阿里的 DRC 也是做同樣的事情。我們的實現思路主要就是依賴于 MySQL 的 binlog,大家都知道在 MySQL 的 binlog 設置成 row 格式的情況下,它會將所有受到影響的數據都記錄到日志中,這樣就提供了所有數據的變化。

我們通過解析 row 格式的 MySQL binglog 將數據的變化讀取到 databus 中,然后將實際需要的業務邏輯通過。so 文件 load 到 databus 中,databus 會根據業務邏輯重新再處理一下數據的變化,然后輸出給下游資源。

databus 這個項目我們已經開源,大家可以直接在 GitHub 上搜“MyBus”即可。

5. 問題 1 中說的索引是指什么,如何找到對應內容的算法?

索引其實指的并不是內容的算法,舉個例子,如果你需要存儲博文,那么必然會存儲一個唯一的 id 來進行區分,然后會存儲一些這個博文發表時候的狀態,比如誰發的、什么時候發的、我們認為這些狀態和 id 都是索引;而博文內容就是實際的內容,由于內容比較大,和索引存儲在一起會導致 MySQL 的性能下降,而且很多查詢只需要最終拿到博文 id 其實就等于拿到了實際的博文。

我們可以對索引進行各種過濾之后得到一個最終要輸出給用戶的索引 list,再根據這個 list 從內容庫中查找實際內容,這樣就符合 MySQL 的返回結果集越小性能越高的規律,從而起到優化的效果。

6. NoSQL發揮著哪些作用?

在微博NoSQL發揮了越來越重要的作用,比如說 rediscounter,我們自研的計數服務,當初計數存貯在 MySQL 中,由于所有計數都是 update set count = count +1 這種高并發對單行數據的寫操作,會引發MySQL多種鎖,而且并發越高鎖得越厲害。

由下圖可見,如果對單行的并發操作達到500 以上,tps就會從上萬變成幾百,所以MySQL無論怎么優化都無法很好地支持這個業務場景,而Redis就可以。

我個人認為,NoSQL 就像瑞士軍刀,在最適合的地方它就是最優的方案,個人認為這也是 NoSQL 未來發展的方向,每一個都有一個最佳場景。

7. 我們公司將來會有很多智能設備,今年或許就兩萬個以上的設備,一年后可能再翻很多倍,要收集數據,都是 JSON 報文格式,JSON 里字段標識不同類型的報文,看似不太適合以字符串存進 varchar 或 longtext 字段,DB 存儲是否可充分利用 MYSQL 5.7 的原生JSON,存一列就搞定,而不必用無法擴展的“寬列”進行存儲,或者POSTGRES 還是其他的 DB 能提供更方便的存儲嗎?前期還用的時候,MySQL 5.7 還沒 FINAL, 利用 mariadb 多主分擔N多設備接進來,DB 寫入層的負載,對這種大量設備一直要傳數據的寫入場景有什么指導?

我們其實也在看 MySQL 5.7 的新特性,其中 JSON 對于數據庫設計會帶來比較大的沖擊,按照你的說法,確實不應該使用 varchar 或者 text 之類的字段,不過我個人建議智能設備這種場景,如果有可能,最好直接上 HBase,因為遲早數據量會變得 MySQL 難以支撐,這屬于天生沒有優勢。

8. “數據和索引分離”是什么意思呢,是數據文件和索引文件分開存放到不同機器上嗎?

應該是內容和索引,這樣更好理解,大家把這個理解為業務層上的垂直拆分就好。由于進行了垂直拆分,必然存在于不同的數據庫實例中,所以可以放在一臺物理機上,也可以放到不同的物理機上,我們按照習慣都是放在不同的物理機上的,以避免互相干擾。

9. 哪些信息存MySQL?哪些存NoSQL?用的是哪種NoSQL?

這就涉及一個分層存儲的問題了,目前我們主流是 MySQL + Redis+mc,mc 和 Redis 都用來抗熱點和峰值,而 MySQL 則為數據落地,保障最終有原始數據可查。大部分請求會到 mc 或者 Redis 層就返回了,只有不到 1% 的數據會到 MySQL上。

當然也有特殊的,比如之前說的計數服務,我們就把 rediscounter 當初落地存儲使用,后面并沒有在 MySQL 中再存儲一份數據了。

最后,個人認為 NoSQL 最大的優勢是開發的便捷性,開發人員使用 NoSQL 會比使用 MySQL 更簡單,因為就是 KV 結構,所有查詢都是主鍵查詢,不用考慮 index 優化的問題,也不用考慮如何建表,這對于現在的互聯網速度來說是有致命誘惑的。

10. 請問全局唯一發號器和自動生成id對業務來說優劣分別是什么?

全局唯一發號器有很多實現的方案,我們是用 mc 協議改的 Redis,主要追求性能。我也見過使用 MySQL 的自增 id 作為發號器,但是由于 MySQL 的鎖太重,業務起量之后經常會出現問題,所以就放棄了。

再說另外一個好處,全局唯一發號器的開發難度并不高,可以將一些你想要的屬性封裝到 uuid 中,比如 id 的格式可以是這樣“時間戳 + 業務flog + 自增序列數”,這樣你拿到這個 id 的時候直接就可以知道時間和對應的業務了,與 MySQL 自身發出來的簡單的一個沒意義的序列號相比,可以做更多的事情。

 本文由用戶 y139733 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!