非典型的千萬用戶後臺之路

来源:http://www.cnblogs.com/codingcofee/archive/2015/12/27/5079838.html
-Advertisement-
Play Games

從零到千萬,伺服器的架構怎麼變?泣血乾貨集。長文,慎入。


三年前,原本我只是個不學無術的數據小碼農,空有一腔熱情;而當時公司也處在艱難的轉型期,舊產品不見起色,新產品前途未卜。想見著也不可能用這麼小的數據玩出花來,而新產品的數據也不是一時半會能成規模。還是本著最大限度學習的心思,鼓足勇氣和老闆提換崗,要去扛後臺開發的大旗,最大程度參與到產品的一線去。一個小決定,換來的是整整半年的不眠之夜,眼見著第1個用戶到第500萬個用戶,眼見著1台到4台再到10台伺服器,眼見著後臺業務由單一的播放到能播放能上傳再到有完整的社交交互。從剛開始三天兩頭崩潰出事故,到最終一點不怕市場的同事搞拉新的活動,什麼狀況都能做到心中有數、遇事不慌。回頭一想嚇一大跳:自己並不是後臺工程師科班出身,從來對語言和框架的爭論無感無力,網路編程的基礎知識更是差強人意,但是憑著小米步槍,憑著奇技淫巧,憑著持續思考和不斷嘗試,居然也能搭建起一個支撐千萬級別用戶的後臺框架。總結那半年,留下了5條事關生死的建議,在這裡泣血奉上。

數據的讀寫是伺服器性能的核心

一個完整的後臺服務,組件其實就只分3種:接入、邏輯和數據。這好比一家飯店,後臺工程師就是開店的老闆,客人數量小於1萬,服務流程是第一位的,老闆們吭哧吭哧忙著寫邏輯;1萬到10萬之間,接入組件的設計會是重中之重:一個店的服務能力有限,老闆們忙著多開幾個分店,讓客人分流,而決定客人到哪一個分店的,就是接入組件;但是用戶一旦大於10萬,數據的讀寫能力就決定了這家超級飯店的服務容量,不管開多少個分店,都要保證數據是一致的,讀起來又快又準,而寫數據不會影響到讀的性能。表結構怎麼設計,資料庫怎麼分佈(主從、讀寫分離、分庫、分表),緩存怎麼選怎麼分佈,就是老闆們最重要的工作(讓老闆高興的是,名片也可以改印個高大上的抬頭:架構師)。

一旦用戶量過了十萬,要再想光靠資料庫一部卡車打天下就不太現實了,而緩存(物理存儲地在記憶體,天生比資料庫讀寫性能強)這匹野馬的出現就滿足了我們對於速度的極致需求。緩存對伺服器的架構帶來了兩個深遠的影響:一是熱數據和冷數據的分離:熱數據訪問的人多,緩存擋在前面,為資料庫分擔巨大的讀壓力;而熱數據從產品的角度也更應獲得快速的響應。二是數據一致性的門檻提高,更新資料庫的同時必須更新緩存,一旦緩存更新失敗,資料庫也一定要回滾而保證數據的一致性,不能鬧給客人上冷盤的笑話。當然緩存存什麼、怎麼存,也是大有一番學問,容我下一小節再講。但緩存的重要性總結一句話:沒有緩存是萬萬不能的。無論你是選老馬Memcached還是火熱的頭馬Redis,一定要在資料庫感受到壓力之前上馬,並且做好緩存備份和恢復的預案。當然,平安無事你是沒辦法感受到緩存的好處的,它就像一個平時提醒你吃飯睡覺多喝熱水的備胎,只有當她棄你而去之時,你看著伺服器嘩嘩成百倍上漲的響應時間,恨不得找塊豆腐一頭撞死。

列表、實體和冗餘

Web時代,由於翻頁前後用戶出現了界面的切換,用戶對於列表本身的變化並不敏感(假如翻頁的同時列表新加入了內容,只要保證用戶瀏覽的這個片段沒有重覆就可以),但是移動端這種滾動列表的設計簡直就是所有後臺工程師的夢魘(加入用戶上拉列表獲取更多的同時新加入了內容,那用戶會看到相鄰兩個重覆的內容,然後就氣炸了,什麼破APP!),應對「列表重覆」這個難題的方法出一本書都夠了。因為這個需求,我們只能放棄了原有的自增ID,採用時間戳作為獲取列表片段的方式:簡單來講,就是客戶端每次都上報一個當前頁最後一個內容的時間戳,伺服器再去取比這個時間更舊的若幹個內容。這裡必須要感謝Redis的作者提供瞭如此豐富的緩存使用的API,我覺得Redis最出色的一點就是把列表的所有使用場景都設想得很通透。

實體就是熱數據,熱數據的緩存有兩問:一是存什麼?有人會說簡單,把整個結構體轉化為一個JSON存進去不就得了?但這其實是有問題的,當你的伺服器要面對數十萬同時到來的用戶,可能短短一瞬就要做數以千萬計的JSON到結構體之間的來回切換,而這個過程的效率實際上是很不理想的,那麼也許你要想一些更快的方案(此處買個關子)。二是怎麼存?雪崩效應並不罕見,一旦源數據改變,一時間許多個線程同時去訪問更新緩存的API,伺服器瞬間堵死,想到後臺工程師會因此而失業,我默默加了一個鎖。

小張是端菜的服務員,這次上菜,他要先去冷盤區取個土豆絲、再去葷菜區取個東坡肉、順到素菜區取個手撕包菜、最後到飲料區再拎兩瓶果汁,聽起來很低效,對不?這和數據獲取的過程是類似的,資料庫的表設計首要考慮的是歸類,比如用戶的信息存一張表,用戶和小組的關係再存一張表,那麼如果有一個場景需要讀用戶以及他最後訪問過的小組,就得做兩次的數據表讀取,一旦這個場景頻繁出現,適當的數據冗餘(把用戶最後訪問的小組ID加入到用戶表的欄位中)就能夠降低資料庫的讀取壓力。所以表設計一定一定一定(重要的事情說三遍)要考慮業務場景

非同步,是不是真非同步?

有的小盆友跑來問我,我這個伺服器框架選的牛啊,非同步多線程的,單進程併發一萬多輕而易舉,怎麼還是慢啊?我說,「非同步」這個詞可不要說得太輕鬆,底層非同步了,流程里的每個步驟是不是非同步的呢?資料庫讀寫、緩存讀寫、外部介面的訪問,這些都不能非同步吧?既然不是非同步,卡在哪裡你還不知道呢,還不趕緊打日誌。還是說說最令我崩潰的一個案例:某次伺服器炸了,打多少次日誌都沒辦法定位到卡住的原因。最後猜是怎麼著?竟然是日誌組件(Log4j)就不是非同步的,打日誌這個步驟就卡住了,欲哭無淚。

日誌、監控和有損服務

一個高級飯店要有廚師,要有大堂經理,要有端盤子的,要有收銀的,但千萬別忘了還要有保全。他雖然不是飯店成功與否的核心因素,但是如果缺了他,危機時刻就會應付不來。下麵這三位哥們就是伺服器的保全:日誌、監控和有損服務。

先說日誌,日誌是很微妙的,打多了不行,影響性能、占據空間,打少了,關鍵問題排查不出原因。那麼哪些是必打的呢?我認為有三點:一是行為的基本屬性,無非是何時何地何人,時間、用戶ID、IP、版本(存下來除了排錯,還可以用來做數據分析);二是往返的參數,尤其是客戶端上報的參數,伺服器返回的數據也許會很大,不建議所有都列印,可以列印統計數據,比如返回了多少個小組之類;三是報錯信息,底層一定要catch所有的出錯信息,並把它打到單獨的日誌里。

再說監控,日誌是一旦發現了問題幫助我們找出問題的原因的工具,那麼什麼能幫我們發現問題呢?答案是監控和告警。監控與日誌不同,要抓核心的數據,不能多,我建議取三個數據:用戶的併發訪問數、讀取的人均響應時間、寫入的人均響應時間,告警的話再加上伺服器的崩潰、重啟的次數,以及主機性能相關的指標(CPU、記憶體、硬碟等)。

「發生這種事,大家都不想的。餓不餓,我給你煮碗面?」,伺服器運氣不好崩潰了,我便常常用這句TVB的經典臺詞與小伙伴們調侃。其實無論事前機關算盡,成長期的APP總會遇到伺服器出狀況的。但是,以我有限的經驗,伺服器的問題往往不出在自身,而是它所依賴組件導致的問題,比如Memcached機器dump、轉碼服務隊列阻塞、或者圖片存儲空間爆滿等等。那麼在問題被解決之前,總不能幹瞪眼,看著用戶投訴一波波來吧?我們會想,對於現在的業務來說,最不能崩潰的場景是什麼?比如播放是我們的最基礎服務,那我們死也要保證任何外部組件的崩潰都不能影響熱門內容的播放,因此我們要把這部分少而重要的熱數據載入到記憶體,以防止外部存儲出了什麼問題,伺服器自己還有碗面吃。真正是,自己的事情自己乾,靠天靠地靠祖宗,不算是好漢。

服務分離與複製

伺服器體系越長越大,我們首要做的事情是分封,兒子長大了,總要給他一塊地盤,當個小王,從此自己打拼去。於是數據讀寫被抽象成服務了,同時對APP和前端負責,做最大的一個王;編碼解碼抽象成服務了,反正編碼解碼是給UGC用戶提供的,想當明星的人總要等得起;日誌存儲和解析也抽象成服務了,反正有少許的丟失我們也不介意。錶面看來伺服器被拆得支離破碎,增加了網路時延,是一筆不划算的生意,但實際上對伺服器的穩定性大有助益。為什麼?一是大王國被拆成小王國了,定位問題更容易,遷移和複製也更簡單,數據讀寫有壓力?沒問題!再給兩塊地盤。二是在整個鏈條上,任何一個環節都是多點,俗話說,不把雞蛋都放在一個籃子,任何一臺伺服器dump都不會要了我們的命。

細枝末節且不提,總結當時半年內伺服器高速發展期留下來的經驗,我認為最重要的就是這五點,業務場景不同,伺服器的架構和側重點也肯定會略有差異;不過這五點基本等同於錦囊,等同於基石,等同於保命符,做好了,這飯店生意一定蒸蒸日上。恭喜你,老闆!

更多精彩內容,歡迎關註微信公眾號「碼農咖啡館」


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 回到目錄Lind.DDD框架里提出了對數據集的控制,某些許可權的用戶為某些表添加某些數據集的許可權,具體實現是在一張表中存儲用戶ID,表名,檢索欄位,檢索值和檢索操作符,然後用戶登陸後,通過自己許可權來構建對應表的查詢語句,即動態構建表達式樹,這種操作一些被寫在業務層上,我們可以在業務層需要進行數據集許可權...
  • 1.橋接模式 個人覺得有些類似於抽象工廠模式2.命令模式 提供了一個中介,中介集成了各類功能,客戶端可以添加刪除某些功能,可以執行某些功能
  • 一些大型網站常用到的技術概覽,經驗談。
  • 回到目錄工作單元UoW我們幾乎在任務一個像樣的框架里都可以找到它的足跡,是的,對於大型系統來說,他是很重要的,保持數據一致性,維持事務狀態這都是它要為系統實現的功能,而在不同的框架里,實現UoW的機制也是不同的,在Lind.DDD中,採用了一種共同註冊,統一提交的方式來實現UoW!UoW結構圖我們來...
  • 回到目錄之前已經發生了大叔之前介紹過關於redis的文章,有緩存,隊列,分散式pub/sub,數據集緩存以及倉儲redis的實現等等,而今天在Lind.DDD的持久化組件里,redis當然也有一席之地,作為當今最紅的key/value存儲機制,它在nosql的陣營中發揮著無可代替的作用!下麵是red...
  • 回到目錄之前已經發生了大叔之前講過被倉儲化了的Mongodb,而在大叔開發了Lind.DDD之後,決定把這個東西再搬到本框架的倉儲層來,這也是大勢所趨的,畢竟mongodb是最像關係資料庫的NoSql,它的使用場景是其它nosql所不能及的,這點是毋庸置疑的!下麵是大叔總結的Mongodb文章目錄,...
  • 本文是負載均衡詳解的第四篇,主要介紹了LVS的三種請求轉發模式和八種負載均衡演算法,以及Haproxy的特點和負載均衡演算法。
  • 硬體負載均衡性能優越,功能全面,但是價格昂貴,一般適合初期或者土豪級公司長期使用。因此軟體負載均衡在互聯網領域大量使用。常用的軟體負載均衡軟體有Nginx,Lvs,HaProxy等。本文主要介紹Ngnix的架構,功能特點和常用應用架構。
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...