進到了最後一天,剛好『雙11』結束沒多久,在『雙11』當天,台灣兩大購物平台紛紛掛點。那剛好敝公司也有類似的經驗,那今天就來談談,我如何重構敝公司的系統優化,把原本的 php 重構成 golang,改善瞬間流量的問題。

首先,為什麼一個系統會掛點?我個人最終覺得就是一個問題,每個 request 的處理速度不夠快,只要 request 處理不夠快,最前面的服務就會開始卡連線,造成一連串的雪崩效應。當然任何東西還是有所謂的物理極限所在,所以當 request 數量多一多,每一台機器單個 request 處理速度一定會等比上升。

舉個例子好了,敝公司當時有一個服務單個 request 平均時間約是 800ms,當時用壓力測試工具對它做壓測,設定 1000 req/s,這時測起來,數據想當然慘不忍睹,平均時間拉到 2s 以上了,這個回應時間如果拉的越長,想當然會佔用服務的 port 越久,所以持續下去就會造成服務整個 crash。

所以就開始重構的計畫,重構有歷史的 code ,需要非常大量的閱讀,但是大致上整理出當時的幾個消耗點,如果直接針對那幾點做優化,我相信也能拉低平均的 reqeust 時間。

  1. 過多的防禦邊界,每個套件間都在做重複的驗證,造成一個 request 可能重複驗證了3-4次以上。
  2. 不合理的 sql query,重複在迴圈裡 query 相同東西。
  3. 不合理的 sql 語法。

那接下來,我們來介紹一下重構的思維,因為一些敏感的議題,所以我們這邊先類比成一個購物最後結帳的 api,我們大致上的流程有下面流程

  1. 確認使用者身份
  2. 確認優惠卷是否有效
  3. 如有一人限買一個的限制特別做做檢查
  4. 結帳扣款完成交易

那正常的設計流程圖如下

buy-system-workflow

就是很直線的方式,沒有什麼不對,但其實可以仔細思考,2、3 兩點其實是可以抽出來平行處理做檢查的,如果 2、3 每個各需要花費 50ms,用直線的方式處理,就至少吃了 100ms,所以總時間花費需要 200ms。那為什麼會挑 2、3 出來說呢? 因為 1 是身份驗證,沒有 1 後面什麼都不能做。4 則是扣款也是一個很獨立的流程。反之 2、3 他跟主流成沒有絕對的前後依賴關係,所以讓他們平行處理我覺得再適合不過了,利用 golang 的特性,我相信很容易做到下圖

buy-system-final

雖然使用 goroutine 很方便,但是畢竟它是併發運算(concurrency),而不是平行運算(parallel),一定有會有一些 CPU context switch 的相關消耗,所以這邊假設兩個流程併發處理是 60ms,而不是變成 50ms。 這邊還是要注意,不要太過於依賴 goroutine ,任何系統開發都還是有所謂的資源限制,在前面 Day 26『Golang Concurrency Pattern 』有簡單介紹,如何控管 goroutine 數量,有興趣的可以往前閱讀。

用上面的方式就有別於 php ,運用 golang 的特性,去做效能上的提升,當然裡面還有不少細節在裡面,礙於一些敏感議題,不太適合公開介紹,只能做一些概念性的講解。

感謝

很高興,終於進入第十屆 ithome鐵人賽最後一天,當初也是參加了 modernweb2018,才興起的參加念頭。一開始給自己的目標就是,自己在公司內也投入三年多的資源在 golang 上面,大小專案也都陸續上線,這中間自己也踩了不少雷,雖然有替同仁做幾堂簡單的簡報教學,但是如果能更有系統的做一系列的教學文件,或許能更幫助新進同仁、甚至回饋給更多想加入 golang 這個社群的人,畢竟在 opensource 圈裡,社群越大,也代表相關資源工具會越多,大家開發起來也就越方便,是一種正向的提升。

在這過程中,還好一開始有人提點,先把30天的大標先列起來放,相對的再進行這30天的過程中,會比較輕鬆。這點建議對我來說受益良多,所以我也蠻建議後續想要參賽的參賽者,如果你的背景像我一樣,是一個初次在網路上連續分享的人,真的強烈建議,參賽前先把大綱列好,中間可以動態調整沒關係。不然像我上班也有上班該要的進度、回家還有小孩要顧、甚至六日都會有一些家庭活動,如果沒先有個底,參賽起來壓力非常之大。不過如果是平常常在網路上做分享、信手捻來就是文章的人,其實就不需要像我這樣,做預先準備。

最後還是要感謝公司的同仁們、主管們,畢竟參賽期間心力並沒有100%放在工作上,也感謝之前有參賽過的前輩們,第一次參賽,有參考過歷屆前輩們的文章,參考要怎麼鋪陳,拆章節。最後也感謝 ithome 辦這個活動,讓大家有個動力可以分享更多東西出來。