後端的一些經驗與心得

Mooooon發表於2017-12-14

先簡單介紹一下我的經歷,最早在學校的時候,是在社團裡寫php和Java,創業時期寫js,oc和Ruby,現在是全職用Rails寫後端了。

專案簡介

我們的主要業務有兩塊,社群和電商

整體業務的峰值qps大概在3000,也算是pv過10億的站點了,後端team有4個人,除了一個八年老司機,其他人蔘加工作的年限都不是太久。

我們面對的是一個巨大的基於Rails的歷史遺留系統,最早的開發成員均已離開,導致我們常常面對遺留程式碼一臉蒙逼,到處是沒有人知道的邏輯,醜陋的實現,以及很多效能跟不上的介面。

與巨石應用的鬥爭

日常工作的重中之重,就是與這個monolith的戰鬥!

效能篇

以往每年我們搞活動,伺服器都會掛,經濟損失不少,所以優化效能,保證活動期間的訪問是第一要務。

原來的活動整體設計還是比較科學的,活動頁面本身是靜態化託管到cdn的,從來沒有出現過問題,主要瓶頸是商品詳情頁面。我們利用redis做了三層cache,解決了這個問題。第一層是資料庫的快取,直接把商品資訊快取到redis裡,避免了頻繁的資料庫訪問,第二層是單條資料的渲染快取,可以理解成一小段html,第三層是整個資料集的渲染快取。第二個瓶頸出現在一些靜態資源上,全面遷移到雲端儲存解決。做完這兩件事之後,上上次活動是我們有史以來第一次,沒有掛。

就在我們覺得,優化做的不錯的時候,上次活動卻又掛了。

要知道我們特意買了新伺服器,美滋滋覺得這下穩了,沒想到...

上次活動掛的原因有以下幾點

  1. redis hmget,我們通過gem提供的API,快取了一個巨大的省市區列表,但是沒有注意到快取是分離的,獲取整個列表,其實就是一條hmget獲取所有獨立的快取片段,這個操作block了redis,導致訪問極度緩慢。我們緊急把整個列表轉成json,直接貼到程式碼裡返回hotfix了這個問題
  2. 突然無法通過redis sential進行連線,這套sential系統是由已經離職的運維搭建的,我們繞開sential直接連線redis,解決了這個問題
  3. fd limit, 做完以上兩點,依然時常502, 發現運維修改的是root使用者的fd數量...坑爹....
  4. 在支付回撥中有一段用於統計的sql,訂單量大了以後slow query,block了資料庫,我們直接註釋了這段可有可無的老程式碼,解決。

總結一下,對於web應用的場景來說,大都是讀多寫少,快取讀請求,非同步寫請求,是我們經常採用的兩種效果不錯的方式。在資料庫層面,對於遺留程式碼中效率低下的查詢進行重寫,重點改寫了所有N+1查詢,對一些逐條插入的語句用batch insert合併寫入操作,也有不錯的提升。

替換篇

做的比較有意思的事,是寫了我們內部用的個推GEM。原來使用的是github上開源的一個GEM,但是已經很久沒更新了,無法適應我們的使用需求。我基於個推最新的HTTPS的API,寫了一個Ruby的包裝。

這裡要吐槽的是個推的技術水平。推送服務是做的不錯,但API怎麼做的這麼low。他們定義了一個叫authorize的http header用來傳遞身份資訊...違背了RFC關於HTTP頭必須大寫開頭的規範。一些語言的標準庫(Go、Ruby...)會自動幫你把authorize轉化成Authorize,導致個推那邊一直返回auth error...而個推的介面又是HTTPS的,抓包除錯很困難,浪費了我很長時間除錯這個問題。

重構篇 重構的主要方針就是拆分,儘可能把功能從巨石應用中拆出去。如果一時半會難以拆分的,程式碼上也儘可能讓邏輯高度內聚,方便以後遷移。

訊息系統的重構 訊息系統是一個,出點問題沒什麼,但做得好會非常出彩的功能。我一直覺得,像知乎這種社群的成功,除了內容,很大一部分要歸功於訊息的體驗。目前,我們幾乎所有頁面,都會展示新訊息的數量,導致每次請求都會去主資料庫的訊息表做count,計算各種訊息的數量返回給前端。我正在著手把整個系統遷移到另一個獨立的資料庫,以後可以作為單獨的服務供內部呼叫,降級限流什麼的都很方便。

搜尋的重構

原來的搜尋是基於Solr的java工程,是一個我們內部沒人維護好多年的爛攤子,雖然各方面表現都不錯。我們還是決定未來要用Elasticsearch換掉它。

新系統 我新寫了內部的財務系統,過程中遇到很多問題,寫的也很痛苦,但最終效果還是不錯。因為原來的各種報表都是直接基於生產資料庫的,對業務會有衝擊,新系統寫了一個同步模組,可以增量同步訂單資料到財務系統的專用資料庫,這樣就不會對業務帶來影響。

遇到的比較大的坑就是記憶體爆炸。有一些耗時計算我放到了訊息對列裡,整個worker程式的記憶體佔用瘋狂上升。最終發現是Ruby記憶體模型的問題。

我通過時間換空間的方式,把之前載入全部資料做計算,改成了載入部分資料做計算,然後彙總結果這樣的方式,極大降低了記憶體佔用,並通過每天重啟worker程式,解決了最主要的記憶體問題。

這個專案讓我真實感覺到,有些場景真的不是Ruby擅長的領域。Ruby的記憶體模型,就是儘量分配物件,從不真正回收,只會重用。Ruby VM啟動就有大量空物件等著被分配,假如我載入了很多資料,空物件不夠用了,VM就向作業系統申請一批記憶體,用完後也不釋放,等著下次重用。而報表計算的最佳場景就是能載入大量資料,算一下結果,算完釋放掉記憶體。

監控 可以看我之前的文章使用ELK構建分散式日誌分析系統

程式碼篇 在日常編碼、重構的過程中,經常使用的技術是

  1. 設計模式
  2. 超程式設計

運用設計模式,寫出符合OOP規範的程式碼。分割每個類的職責,儘量讓各個功能的邏輯內聚,只提供彼此間呼叫的介面,這是我最近才剛領悟的程式碼整潔之道。

超程式設計抽象程式碼,我很早就在使用的奇技淫巧。現在卻用的越來越少了,因為它違背了OOP,可維護性比較差,對使用者的水平有很大要求,也容易坑隊友。

簡單地說,我程式碼中的if/else越來越少了,類越來越多了,改動起來越來方便了,改動影響的部分越來少了,美滋滋。

結語

用一句古老的名言,軟體開發沒有銀彈。

相關文章