三俗話題:LNMP架構卡頓如何升級換代?

  • 2019 年 11 月 13 日
  • 筆記

我要先廢話一波兒

千算萬算,思維縝密,語言上前後思考,上一篇文章終究還是沒有逃過一劫。上一篇文章是啥?—《持續搞【附近】—聽說MongoDB是專業的(三)》

【劫】是啥?反正就是壞在這個文章標題上了:大家說我忽視了PostGIS和Redis,紛紛表示讓我了解一下,【愛PostGIS人士】表示十分難過並表示遺憾。我真的應該換個萬金油文章標題,比如沿用之前的《可能》系列,比如說:

《持續搞【附近】—很有可能MongoDB是專業的(三)》

很顯然,這個實現LBS地理位置查詢的方案調研明顯整體走向要變成《論地球上LBS方案實現方案橫向demo演示》。沒關係,寫就寫吧,下篇文章我索性把ES和PostGIS全部終結掉。只不過相對於ES來說,我以前從來沒有用過PostGIS。

然而,知其然不知其所以然。按照我的理解,諸位了解他們的【引擎】演算法和存儲數據結構,實際上要更重要。說白了就是你用R-Tree,我用B-Tree,他搞geo-hash,剩下的搞S2。其實這些都搞明白了,那無論是ES還是PostGIS抑或MySQL 5.7,翻來覆去這點兒東西,你心裡都清楚地跟明鏡似的。

然而,我快頂不住了:

  • 一來是我自己實在是想換個其他的口味BB一下
  • 二來是有寶貝兒跟我說「 你能不能給整點兒實際的,別擱那兒一天天整那些唬人的玩意 」,「 我們就想知道我們老闆的網站卡了怎麼辦 」,「 老闆說了APP太卡讓加班,也不給錢升級伺服器,我哪兒知道咋辦 」

所以,今天的話題就是如何整治LNMP的卡頓擁堵問題,而且還要捎帶改進一下單點架構,避規掉絕大多數的單體故障。多年老軍醫老李手把手帶你解決實際問題,工作中能幫老闆省錢,面試中能糊一下面試官。

實際上這件事源自於去年的一件真實案例(依稀記得是2018年的7月份左右),我之前發在了社區里,不過閱讀量並不高。所以,今天索性偷懶(實際上是太忙了,沒法騙工資沒空寫文章了)就簡單修改了一下搬到公眾號里,算是搞二次貢獻。這篇文章主要就是教你利用雲服務商提供好的現成服務,簡單地將LNMP架構改造升級為【可能是高可用的LNMP架構】,請注意我用了【可能】。

本文建議閱讀對象:

  • 小型公司,業務量已經有一定的量,需要保證線上穩定
  • 由於老闆問題導致技術團隊里並沒有老司機帶隊,開發人員能應付傳統CRUD業務,但是對整體架構上比較陌生

開始裝逼

文中以及標題中所有人名、公司名以及產品名均以化名方式出現

上周日接到微信群里一個好友(後文中稱XF)的求助,大概意思就是不知道為什麼產品新註冊用戶開始猛增,導致伺服器崩潰,在收到通知後通過緊急升級伺服器配置到16核CPU和16G記憶體勉強支撐住了。這個產品的名字叫做YQB,所以標題中我就用洋槍棒來稱呼產品了。

首先介紹一下他們產品原有的技術狀況,整體是LNMP架構,PHP框架使用的是YII 2.0,http伺服器自然就是nginx了,mysql、redis、memcache、php擠在一台伺服器上,產品前端以app為主,網頁只有一個官網。本來平時沒這麼多用戶,突然由於路子打開了,導致每天以比原來百倍的用戶新增量持續增長,所以,原有的架構開始腐化,開始出現卡頓、持續高負載。

XF這次求助主要是因為他們公司服務端只有兩個人,而且工作經驗都比較短,都不知道清楚該怎麼改造才能適應以後,所以讓我來出個解決方案。

其實講道理,是沒太大難度的。不過問題說回來,只說架構上的問題,我盡量不去摻乎具體的程式碼。所以,這次改造的目的就是如下幾條:

  • 解體原有的巨無霸一體系統
  • 將架構分層,每層都避免單點故障
  • 更容易地進行橫向擴展
  • 更容易和安全地進行程式碼增量測試
  • 當上線新業務的時候,不用停服

其實,一旦說機器卡,大家肯定張口就是「加機器」。然而,加機器也是有講究的:

首先是機器加到刀刃上,一加必中!

其次是加的又快又方便

像洋槍棒這種原有的架構,加機器的時候就很難滿足上面的兩點要求。因為MYSQL、PHP-FPM、NGINX是擁擠在一台機器上的,所以這三個中任何一個出現性能上的瓶頸都會導致服務卡頓。如果說是MYSQL卡,實際上只需要升級MYSQL需要的部分即可,但是mysql、php、nginx擁擠在一起,機器升級了,但並不是升級在刀刃了,這個會很難受。其次是,這樣加機器並不是很方便。雖然你可以製作一個鏡像,然後通過鏡像快速配置好第二台機器的軟體環境,但是,第二台機器的mysql、redis以及memcache是完全沒用的,還得用第一台機器的mysql、redis等等。

其次是,由於都是單點,所以一旦出現故障,就意味著服務將處於完全不可用的狀態。

最後是,如果你有新的程式碼要上線,但由於測試並不充分,所以能不能可以只讓一小部分用戶會觸發新的業務程式碼,一旦發現錯誤,就可以穩穩地回滾程式碼,程式碼上的問題不會影響大部分用戶。

所以,為了滿足一些這些要求,這次的改動就以下面為準:

上述架構將原來的巨無霸單體系統進行了拆解分層,大體來說從上到下分為nginx,php-fpm,redis,mysql四層。升級完成後,相對於原來優點如下:

  • 避免了原來單點故障帶來的停服風險。比如nginx掛了一台,那麼最起碼還有一台nginx繼續提供服務
  • 通過性能分析可以鎖定是具體哪一層出現性能卡頓,只需要橫向擴展該層的機器即可。比如通過觀察,發現fpm層的沒每台伺服器cpu都很高了,那麼只需要為fpm這一層新加一台機器即可
  • 上線新的程式碼可以通過負載均衡流量權重入口來實驗程式碼是否存在巨大故障而不影響絕大部分的用戶。新的業務程式碼可以只先上到一台fpm機器上,然後負載均衡上為該台機器只分配一個很小的權重,這樣通過小流量長時間觀察就可以收集到是否有嚴重錯誤

利用雲服務商提供的服務有:

  • 負載均衡層的高可用可由雲服務商提供
  • rds的高可用由雲服務商提供
  • redis由雲服務商提供的主從雙機版本提供高可用

通過上述改造達成的新架構可以保證相當長一段時間內架構上健康程度。實際上,截止到寫這篇公眾號文章的今天,這種簡單粗暴的LNMP架構一直支撐XF公司的業務支撐到現在,差不多快一年了(PS:業務量每日新增用戶約在幾千左右,整體並發大約在幾千左右)。

上午,我和XF大概講了這個整體結構。

下午,在與XF的商議過程中發現其在程式碼上的幾處不合理的地方:

  • 用戶的token存儲在了mysql資料庫中,每次訪問都要去mysql資料庫中查詢token對應的用戶資訊才能完成完整的session,給mysql形成了巨大的訪問壓力
  • MySQL或者Redis可以考慮使用pconnect方式
  • 如果願意,可以嘗試將nginx+fpm更改為【swoole http server】或者【golang server】。升級後的新架構可以允許他們將重構好的swoole或golang業務程式接入到入口負載均衡中去,小流量觀察

如果將上述地方再修改一下,不僅機器數量可以縮減節省成本,而且整體的響應速度一定會繼續得到一次提升。

畢竟收人家錢了…