三年前,本來我只是個不學無術的數據小碼農,空有一腔熱情;而當時公司也處在艱難的轉型期,舊產品不見轉機,新產品前途未卜。想見着也不可能用這麼小的數據玩出花來,而新產品的數據也不是一時半會能成規模。仍是本着最大限度學習的心思,鼓足勇氣和老闆提換崗,要去扛後臺開發的大旗,最大程度參與到產品的一線去。一個小決定,換來的是整整半年的不眠之夜,眼見着第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都不會要了咱們的命。
細枝末節且不提,總結當時半年內服務器高速發展期留下來的經驗,我認爲最重要的就是這五點,業務場景不一樣,服務器的架構和側重點也確定會略有差別;不過這五點基本等同於錦囊,等同於基石,等同於保命符,作好了,這飯店生意必定蒸蒸日上。恭喜你,老闆!
更多精彩內容,歡迎關注微信公衆號「碼農咖啡館」