先簡單介紹一下我的經歷,最早在學校的時候,是在社團裡寫php和Java,創業時期寫js,oc和Ruby,現在是全職用Rails寫後端了。
專案簡介
我們的主要業務有兩塊,社群和電商
整體業務的峰值qps大概在3000,也算是pv過10億的站點了,後端team有4個人,除了一個八年老司機,其他人蔘加工作的年限都不是太久。
我們面對的是一個巨大的基於Rails的歷史遺留系統,最早的開發成員均已離開,導致我們常常面對遺留程式碼一臉蒙逼,到處是沒有人知道的邏輯,醜陋的實現,以及很多效能跟不上的介面。
與巨石應用的鬥爭
日常工作的重中之重,就是與這個monolith的戰鬥!
效能篇
以往每年我們搞活動,伺服器都會掛,經濟損失不少,所以優化效能,保證活動期間的訪問是第一要務。
原來的活動整體設計還是比較科學的,活動頁面本身是靜態化託管到cdn的,從來沒有出現過問題,主要瓶頸是商品詳情頁面。我們利用redis做了三層cache,解決了這個問題。第一層是資料庫的快取,直接把商品資訊快取到redis裡,避免了頻繁的資料庫訪問,第二層是單條資料的渲染快取,可以理解成一小段html,第三層是整個資料集的渲染快取。第二個瓶頸出現在一些靜態資源上,全面遷移到雲端儲存解決。做完這兩件事之後,上上次活動是我們有史以來第一次,沒有掛。
就在我們覺得,優化做的不錯的時候,上次活動卻又掛了。
要知道我們特意買了新伺服器,美滋滋覺得這下穩了,沒想到...
上次活動掛的原因有以下幾點
- redis hmget,我們通過gem提供的API,快取了一個巨大的省市區列表,但是沒有注意到快取是分離的,獲取整個列表,其實就是一條hmget獲取所有獨立的快取片段,這個操作block了redis,導致訪問極度緩慢。我們緊急把整個列表轉成json,直接貼到程式碼裡返回hotfix了這個問題
- 突然無法通過redis sential進行連線,這套sential系統是由已經離職的運維搭建的,我們繞開sential直接連線redis,解決了這個問題
- fd limit, 做完以上兩點,依然時常502, 發現運維修改的是root使用者的fd數量...坑爹....
- 在支付回撥中有一段用於統計的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構建分散式日誌分析系統
程式碼篇 在日常編碼、重構的過程中,經常使用的技術是
- 設計模式
- 超程式設計
運用設計模式,寫出符合OOP規範的程式碼。分割每個類的職責,儘量讓各個功能的邏輯內聚,只提供彼此間呼叫的介面,這是我最近才剛領悟的程式碼整潔之道。
超程式設計抽象程式碼,我很早就在使用的奇技淫巧。現在卻用的越來越少了,因為它違背了OOP,可維護性比較差,對使用者的水平有很大要求,也容易坑隊友。
簡單地說,我程式碼中的if/else越來越少了,類越來越多了,改動起來越來方便了,改動影響的部分越來少了,美滋滋。
結語
用一句古老的名言,軟體開發沒有銀彈。