在旋轉拍賣 Carousell 一年看到的後端架構、挑戰與生活
在面試文後過了一年多,大概寫了上萬行程式碼,提交了沒千也有百的 commits。除了寫程式碼外,也操刀了不少架構規劃以及效能調教,這次打算挑戰公司文化極限,盡可能的分享在 Carousell 的 Backend Engineering 工作生活、如何開發一個又一個的系統,又是如何一起跟隨我們的 Infra Team 做架構討論及效能調教,作為一個里程碑的記錄點。
先簡單介紹一下筆者在「旋轉拍賣 Carousell」(後續會以 Carousell 表示)的負責項目,前大半時段的日子都是在台灣的 Convenience Team 一起將金物流(CarouPay)帶進台灣以及新加坡市場,也主要負責 Payment/Wallet/Ledger 與錢有關的架構及開發。目前則是在 Core Technology 下一個隸屬於新加坡的 Team,將舊有 Monolithic 專案從 Django 用 Golang 改寫成 Microservice 架構。
這篇文章會介紹 Carousell 的工程架構概要,以及目前作為一個後端工程師遇到的開發挑戰,及平常我們是如何維運系統。希望藉由此文能讓更多人知道一個以世界級工程團隊為目標的團隊,做出了怎麼樣的伺服器架構與開發流程,以及常面對怎麼樣的挑戰,與大家一同交流。
Carousell Infrastructure Overview
這是 Carousell 概括的伺服器架構圖(大約是 2018 年初的版本,文後會揭露完整版),搭配 Google Cloud Platform 的 Solution。從圖中可看到除一般外部的 Load Balancer 外,內部(Internal)的 HAs 目前 HTTP 協定是使用 HAProxy,GRPC 協定走的 HTTP/2 則為 Envoy。
讓我們簡單用一個 API 範例來說明從 Client 呼叫 Request 開始到相關的服務,如何將資料吐回去。假設目前用戶正在觀看一個商品:
1. App 發送讀取商品細節的 API Request(e.g. /listings/{listing_id}/)
2. Request 進到我們的 Routing Proxy(會一同記錄 request log, …等)
3. 再由 Routing Proxy 根據設定檔(Nginx)決定這個 Request要給那個服務的 HA(Nginx + Port Forwarding -> Internal Load Balancer)
接著就是進到每個 Microservice 各自的商業處理邏輯,但,在 Microservice 的架構設計中,第一個與 User Request 溝通的大多是 api-gateway,由 api-gateway 依據這個 Request 的邏輯,呼叫相對應的服務(比如 product, user, offer, etc.),再組裝這些資料變成 Response 傳回給 Client/App。
更多架構設計內容可以參考此部影片:Scaling Infrastructure at Carousell — Carousell TechTalk — YouTube
Consul/Config Service
目前 Carousell 有超過數百台機器,以及 10 種以上不同的微服務由不同 Delivery Team 各自維護,加上最少都有 Staging 與 Production 兩種環境以上,如何保持採用雲端服務商的彈性,同時又保持服務的穩定性以及開發快速?Carousell Infra Team 採用了 Consul 這套服務。
在每一個機器的 Dockerfile 中都會先安裝 Infra Team 自己開發的一個 Debian Package,裡面大致上處理了 Consul Client 相關的設定(當然還做了許多其他事情,但一直沒時間去看 Source Code,所以先稱他為「黑魔法」),得以讓機器在同樣的 VPN 環境下彼此的開放給其他機器連線。
每個服務在會有一個根據 Consul-Template 語法的設定檔,可直接透過服務名稱去取得其他服務的連線管道。舉例來說,一般產品頁面最少需要與 User, Product 和 Recommendation 這三個服務溝通取得資料,那以 api-gateway 的角色來說,只要在 *.tmpl 檔內根據語法給予 user-svc-ha, product-svc-ha, 以及 recom-svc-ha 等外部服務的名稱,即可在 Consul 的運作機制下,隨時取得最新的連線資訊。甚至,我們也能採取不同的雲端服務商,像是混搭 GCP、AWS、Linode 等做適度的成本控制,只要機器都能在同樣的 VPN 以及 Consul 環境下。
Development Framework
過去 Carousell 都是以 Django 作為主要開發框架,目前則都以 Golang 開發 Microservice 為主,同時也在一步步將 Django 拆成獨立的 Microservice。用過 Golang 的讀者會知道這是一個相對接近底層的程式語言,目前也還沒有像 Django 這樣一個專注在 Web 開發的框架,Carousell 目前 Open Source 一套我們自己做的框架 Orion。
Orion is a small lightweight framework written around grpc/protobuf with the aim to shorten time to build microservices at Carousell.
Orion 主要目的是讓每個 Team 能夠更簡單、快速建立更多的 Microservice,預設則已搭載了 Zipkin, Hystrix, Live reload of configuration, Prometheus 等常用於 Logging, Latency and Performance monitoring 等用途的套件。當要建立一個新的服務,只要下一個指令,就可以建立出一個預設的服務檔案架構,接著專注在 Protocol Buffers 的定義(比如有哪些 endpoint)以及 service func 的商業邏輯撰寫即可上線!
Continuous Integration/Delivery
目前 Carousell 是使用了 Jenkins 作為上線部屬的工具,主要可分為 Staging, Canary, 與 Production 三個環境。Staging 作為準備上線前的功能測試環境,搭配 App 的 feature/beta build 即可在 Staging 上測試功能。
正式環境的上線流程目前為 Two-Phase Deployment,會將建構好的 Docker Image 部屬到 Canary 機器上,而這台 Canary 機器固定都會服務 1~10% 左右的流量,作為功能正式上線前的實際流量/流程測試,正常來說我們都會觀察大約 10 分鐘左右的時間,確定 New Relic 以及 Rollbar 等 Log 追蹤服務沒有顯示特別的錯誤或是暴增的 Error Rate,則可將新版本正式 Release 到其他的機器上面。
開發上常遇到的挑戰
由於篇幅有限,將會以目前自身常遇到的 High QPS 與 Microservice 溝通間同時作為 Client 與 Server 角色的愛恨情仇做一些經驗分享。
Carousell@SimilarWeb
High QPS
QPS 全名為 Query Per Second,即表示在某一區間內平均每秒收到多少個 Request,與一般 GA(Google Analytics)看到的同時在線人數不一樣。以 Carousell 為例,上百的 QPS 是常見的事情,更別說上千的 Endpoint。
自身就遇到過一個慘痛的經驗,當時在擴充一個 Gateway 處理的 Response,為了往後開發與維護的彈性,索性將 pb-mashaller(將 Response 編為 2 進制內容)改為 json-marshaller(將 response 編為 json 格式),以便未來擴展時可以直接透過 key 做 value assignment。當時也做了 Benchmark 比較,雖然 pb 比 json 快了超過十倍,但因為都小於 1ms 便覺得影響不大,也用了三寸不爛之舌取得了其他人的 approvement,但在幾天後機器數量整整多了兩倍…還要多。
在這簡單做個數學運算,舉例來說一個 endpoint latency 本來 1ms,所以一秒約可處理 1000 QPS,那現在變成可能 10ms,QPS 只剩下 100,那不夠的 900 QPS 該怎麼辦?想當然就是多需要約 9 台機器才得以去服務這 1000 QPS。
在這之後也發現常在討論時,開始會更多的與同事討論到 Time Complexity,回想到做 Leet Code 時通常會以 10⁶ 作為機器基本每秒可處理的運算數量,來算看看當前寫的演算法在要處理的資料量下是否可以做到有效的處理。(許多大公司考 Time Complexity 還是有其道理的呀~)
Microservice 間的溝通(Idempotency Key)
從 Monolithic 到 Microservice 開發目前感觸最深就是常會忘記正在開發的功能不只單單扮演 Server 端角色,同時作為 Client 呼叫其他服務。而常為了應付呼叫的服務不穩,做了 Retry 的機制,這在做 CarouPay 的訂單與付款的架構下學到了許多的教訓。
簡單來說,因為 Microservice 的架構以及得利於整體 Infrastructure,使得服務間的呼叫變的更加透明、便利,像是我們的 Payment System 不只會被訂單服務也會被物流的服務所呼叫,導致會在訂單與物流各自的 State Machine 邏輯下,同時觸發相同的動作(比如:付款或是退款),或是因為不小心用了相同的 Library 沒注意到預設內建 Retry。這使得相同的訂單同時被呼叫了相同的退款動作,導致重複退款,這時候如果合作的第三方支付也沒辦法處理快速且同時的請求,就會是 disaster(很常在討論時聽到新加坡同事用這詞 XDD)
那要怎麼應付這樣的問題,要麻你呼叫的外部服務支援 Idempotency Key (IK)機制,不然就是自身要支援這樣的架構,避免在 write/update 的動作下被重複的呼叫。而 IK 機制是指每個 Request 一定會有一個「唯一」可被識別的 Key,只要這個 Key 一樣,不管呼叫幾次這個 Endpoint,回傳的 Response 都會是一樣(包含 ID)。那 IK 機制的作法有很多種,這邊就不贅述,以我們的案例來說,訂單 ID 就是一個非常明確的 Idempotency Key。
服務的監控
講完了開發框架、流程與架構後,最後再帶一下我們用了哪些記錄追蹤的服務,目前主要用自行開發的 Log Service 做 request log 的記錄(Elasticsearch + Kibana)、Prometheus 搭配 Grafana 的 Dashboard,以及常見的 Rollbar 與 New Relic。效能追蹤上目前主要為 Hystrix 與 Zipkin,作為外部呼叫以及內部效能追蹤。
效能追蹤
Hystrix 是一套由 Netflix 開發的套件,用作追蹤外部服務 Latency 及容錯性的功用,並由 hystrix-dashboard 即可知道目前服務與外部服務(不只內部其他服務或外部服務,連 Redis, Postgresql 等也算是外部系統)的溝通狀況。
Powered by hystrix-dashboard
Zipkin 作為追蹤某個 Endpoint 從頭到尾每個被執行的函式所花費時間,更重要的是 Zipkin 是一個給分散式系統用的追蹤套件,能一路追到其他 Microservice 內。如同下圖的 Zipkin 官方截圖所示,可以明確知道這個 Endpoint 被哪幾個服務呼叫,並且各自花了多少時間。
Powered by Zipkin
危機處理
在狀況通報與處理上,Carousell 是將 Prometheus 與 New Relic 的數據整合到 VictorOps 上,讓其針對我們設定的條件(比如:Queue 的 Message 過多、Database master-slave 同步 delayed、服務 CPU Loading 過高等等),會發送 Notification 給該服務/系統負責的 Team 目前 On-call 的人。這個通知包含了簡訊、Email、以及語音電話…沒錯,是個會打電話叫你三更半夜起床尿(ㄒ一ㄡ)尿(ㄌ一ˇ)的機制。
目前流程大致是,當收到危機警報通知時,會先在 VictorOps 上告訴系統你知道了(Ackownledge),看狀況並於 Slack 上告訴大家目前這問題為何,若狀況一發不可收拾,導致多數用戶受到影響,甚至完全無法使用服務的狀況,會進入 ICS(Incident Command System) 流程。
每一個 ICS 基本上都會有一個 Slack Channel,並統一於 Channel 內討論解決方案。還記得剛進 Carousell 還很菜的時候,曾經凌晨 2,3 點在 Slack 上參與線上會議,聽聽大家是怎麼發現問題到解除危機。最後也都會有 RCA(Root Cause Analysis)的會議,從問題發生到每一個觀察到的可能性,以 Timeline 的方式表達幾點幾分發生什麼狀況/做了什麼事,最後伴隨著許多代辦事項(Action Items)以及 ETA(Estimated Time of Arrival)。完成所有的 Action Items 後,才算這個狀況處理完整的結束,這些也都被一一記錄成文件。
後端架構小結
Powered by Carousell Infra Team
簡單回顧一下我們的架構,目前 Carousell 將伺服器架設在 GCP(Google Cloud Platform) 搭配 K8S,透過外部現有的 Load Balancer 服務以及自架設的 Internal HAs 讓各個服務可以在 Consul + VPN 內方便的互相連結。
用自建的 Orion 框架快速架設許多 Microservice,並整合外部服務 Hystrix, Zipkin, Prometheus, Rollbar, New Relic 等做到盡可能全面的監控。在 Deployment 的部分基於 Jenkins 上做 Two-Phase Deployment。
將 Carousell 的後端架構由廣至內的走完,因為過去是做純 Web 的背景,跳到 App-based 的產品然後相對大許多的用戶規模,討論的已經不是熟知的 GA 在線人數,而是每秒接受到的請求數量(QPS)。對於從 Monolithic 架構到 Microservice +多執行序平行處理的迫切性感受更是強烈,希望後續有機會能在與大家分享我們是如何一步步轉為 Microservice。
在 Carousell 的工程文化與生活
最後,來與大家分享幾個在 Carousell 的生活與觀察。文化的部分,台灣的 Engineering Manager — Achi 在深入旋轉拍賣台灣研發中心 — 有專業有熱情,更能讓你實現成長的工作環境一文分享的很多,有興趣瞭解更多的人建議可以看一下。
人與人的溫度
在 Carousell 感受許多的是人與人的溫度,在充實的工作生活之餘,常會有員工每工作滿一年、生日等慶祝的卡片與團拍。像近期就幾乎每週五,都會在台灣辦公室的 slack channel 看到台灣團隊又在幫誰慶祝的合照 XDD。同時也有許多有趣的自主社團,像是 ig-movie, ig-blockchain, ig-mala, ig-beefnoodle, ig-climbing 等等。
每週五是公司全球辦公室統一的 Family Friday 活動,中餐會由公司贊助像是 Buffet 或是便當等,並有約半小時到一小時由各地辦公室報告近期活動更新,以及高層們對於每個 OKR 目標當前進度與未來展望等內容。
擁抱改變
進來 Carousell 到現在也經歷五個以上完整的季度,印象中工程組織架構改變了最少三次,都為了更靠近越來越清楚的一至兩年後的目標。接受更多想法,擁抱更多改變是來自公司文化之一「Stay Humble」,也正如 Carousell CTO 曾分享的「We are always less than 1% done.」,在達成目標的路上不斷努力前進。
自由開放
基本上每兩週都會有與主管的 1on1,這過程不怕沒有產出,只怕你不願意講心中在意與想要的規劃,負責做工程師 People Management 的 EMs,總願意接納各式各樣的意見與想法,給予職涯發展的建議。
而每一季公司也都會有 Hackathon,為期兩天由公司贊助生活所需,根據 Monetization、Growth、User Engagement 等等的主題自由發想創意,並做出一個 Prototype。甚至在台灣還出產過一個作品是在 App 模擬螢幕上有蒼蠅飛來飛去,搭配蒼蠅的叫聲….促使用戶去按下「送出訂單」的按鈕 XDD
結語
近期與一些在台灣知名外商工作的朋友們聊,才感受到這些文化呈現的工作生活體現並非如此的稀鬆平常,在相對多的自由、資訊透明與強健的公司文化下,是蠻精彩的一年。旅程也還在持續的進行中,請大家不用擔心。
最後來放個徵才訊息,目前台灣辦公室持續徵才中!若你喜歡這樣的文化,也想參與一個正在走向世界級水準工程團隊的過程,歡迎加入我們!
文章出自:熱英雄手札