短網址開發運維經驗總結分享

發表於2013-09-09

每個蘿蔔下都隱藏一個坑。

前段時間955短網址日重定向次數最高達400萬,主要開銷是重定向請求的使用者資料儲存與分析。分別經歷了記憶體瓶頸、IO 瓶頸後,高峰期達到 CPU 上限,幾乎榨乾了機器,下文是經驗總結分享。

前置條件

由於短網址很難盈利,硬體特別寒磣,帶著鐐銬跳舞反而別有風味,當然,人力投入,技術方面也不能和其他大網站比,所以如果要拍磚請輕下手——哎喲。

我們採用的硬體: 盛大雲微型,1G記憶體,單核共享型 CPU。 後期追加了一個同等配置的內網機器做 MongoDB replSet。

Startup 硬體成本:

既然專案本身基本沒法帶來收益,要生存就只能充分壓榨硬體,大膽使用新技術。根據國內雲的計費方式,一般收費的維度是

  • 記憶體:使用非同步模式代替同步多程式。
  • 頻寬:2M雙線,301並不需要太多的頻寬開銷
  • 硬碟:雲硬碟,按容量收費
  • CPU:單核

由此我們做了對應的技術選型:

  • Nginx:無需多說了吧?
  • Tornado:Facebook 開源的 python 非同步微框架
  • MongoDB:效能好,熱資料少記憶體開銷也少
  • Redis:事實上 MongoDB 寫入的 IO 開銷太大
  • nodejs(with coffeescript):後期新增,node.js 是天生的非同步
  • supervisord:監控程式

來照張相——咔嚓 45f981ce7a95d8d18e4efac24e4f0332_article

開發與運維

既然目前專案投入的開發和運維都只有我一個人,那就可以美其名曰:DevOps 啦。聽上去是不是很高階大氣國際化。

使用者特點

二八法則基本適用:20% 的 URL 佔用了 80% 的資源(尤其是我們預設為所有短網址開啟了統計之後)。

監控先行

很多小團隊犯的第一個毛病就是不做監控,等到使用者來告訴你網站無法開啟的時候就太晚了。為了省事我們用了監控寶和阿里雲監控(主要阿里雲監控有免費簡訊)。

每次出現無法開啟網站的狀態時,都應該定位此次問題的原因。如果頻次增加,就要考慮應對策略了。loadavg 很好地反應了系統的負載,可以判斷是否硬體出現瓶頸。

如果是在事發時間,我們可以藉助這些工具檢視系統狀態:htop(定位哪個程式的問題)、iftop(是否有異常的流量和ip)、iotop(定位 io 瓶頸)。此外就是看日誌。

如果事發時在睡覺,那麼就看監控歷史記錄。

慘痛教訓一:硬碟容量——為將來留下後路

MongoDB在硬碟容量不夠的時候會拒絕啟動。而如果之前沒有使用 lvm 這類工具,將無法快速擴充套件容量,而國內的雲不像 Linode 那麼智慧地在後臺提供容量的一鍵 resize(雖然這個功能曾把檔案系統搞出錯了)。後果很可能是停機幾個小時。

慘痛教訓二:最大開啟檔案描述符

非同步模式下不可避免遇到新問題——最大開啟檔案描述符。我們先後遇上了 tornado 和 nginx 的最大開啟檔案描述符問題。 tornado 的表現為:CPU 100%,日誌裡出現500;Nginx 則在日誌裡報錯,開啟緩慢。

要避免此類問題,要做相應 ulimit 的設定。

ulimit -n顯示的只是當前會話的(!important)。正確做法是檢視程式的 limits: cat /proc/{$pid}/limits

Nginx 的配置檔案裡還需要設定兩個引數:

下圖是 nginx 達到上限的監控圖,很明顯被卡在1000左右了 —— Linux 預設限制為 1024。

1673eb3d73f666f38ee47c32d4892c90_article

慘痛教訓三:Python 不是天生非同步的語言

說實話,用 Python 來設計的過程可不是一個愉快的過程。為了避免潛在編碼問題,我們使用了 python3。下面的問題是:

缺乏非同步的支援:

  • Redis 非同步驅動只支援 Python2(當然,等了大約半年後 tornado-redis 的作者終於更新了對 python3 的支援)。
  • 不少元件仍然無法支援 python3, pip install 後直接報錯的感覺就是:傻眼了。
  • Bitly 的 asyncmongo 簡直是沒有文件,最後只能選了 Motor。
  • Tornado 本身的文件也不夠詳盡

後來一部分元件使用 nodejs 開發後,簡直是相見恨晚,CoffeeScript 語法糖的表現也很出色。

慘痛教訓四:謹慎選用資料庫

資料庫幾乎是web應用裡最關鍵的一部分,越是有大局觀的技術人員越會謹慎選型。 事實上我們把所有壓力都放 MongoDB 的做法還是過於激進了。

MongoDB 的正規化化與反正規化化。

幾乎所有對 MongoDB一知半解的人都會告訴你不要用 SQL 的思維來思考 MongoDB,要使用內嵌文件來實現需求。但是他們忘記告訴你,不斷增長的內嵌文件將導致 IO 瓶頸(參考《深入學習 MongoDB》73頁)。

事實上正規化化和反正規化化(內嵌文件)還有很多要考慮的因素。

複雜查詢時 MongoDB 的無力

在面對需要計算的查詢時,MongoDB 的 map-reduce 很慢;複雜情況下對內嵌文件處理有難度;Documents 比 MySQL 更少。年輕人,不要在 mysql 遇到問題時第一時間想到替換資料庫。

就這個專案而言,統計部分要快速出多樣報表時明顯有難度。

不要等到著火了才想起 MongoDB replSet

  1. 如果 MongoDB 寫入壓力大,並且沒有做分片,那麼單純加機器不會緩解寫入壓力。如果是讀取壓力倒有所幫助。
  2. 從單機到 replSet 起碼需要鎖住資料庫。程式程式碼也需要修改。打算切換到 replSet 的話,需要提前做準備。
  3. 最後我們的做法是將頻繁更新的資料放 redis,定時刷入資料庫,效果很明顯。

正確使用 Redis

控制記憶體,控制起步成本

如果你打算省錢的,就不要把所有東西都放 Redis 裡,哪怕看上去資料量不大——時間久了也佔了不少記憶體。而在 MongoDB 裡只有熱資料佔記憶體。 二八法則也適用這種情況:熱資料只佔20%。

當然如果你是土豪請你走開!

不要用 pub/sub 做佇列

如果不想丟失資料就不要用 pub/sub 做佇列。程式重啟時將丟失訂閱管道的資訊。你可以用 lpush 和 brpop 來實現佇列。

受夠盛大雲了

  • 內網主機完全不能訪問外網。你想 apt-get update 下?臨時買頻寬吧。
  • 被 DDOS 攻擊?直接斷網,沒有任何通知,你還百思不得其解。
  • io效能太差,讀寫大約 5-6M/s 的時候就要掛了。當然阿里雲的好像更差。

最後的忠告

「年輕人啊,要多讀書多看報,多思考多學習」——萬峰

9e7372b16bb617017e663f72359b42cc_article

看到這裡肯定有不少人想噴我了,來吧,我的微博是: @dai-jie ,有錯我改,我改……

相關文章