馬蜂窩搜索基于Golang并發代理的一次架構升級
搜索業務是馬蜂窩流量分發的重要入口。很多用戶在使用馬蜂窩時,都會有目的性地主動搜索與自己旅行需求相關的各種信息,衣食住行,事無巨細,從而做出***需求的旅行決策。
因此在馬蜂窩,搜索業務交互的下游模塊非常多,主要有目的地、POI、熱門景點、美食、商場、酒店、問答、攻略、機票火車票等等,通過實時、精準地返回搜索結果,幫助用戶做出個性化旅行決策。
面對越來越高的流量,馬蜂窩技術團隊積極嘗試對搜索架構進行優化和升級,來保證搜索業務的穩定和性能。
方案背景
由于歷史原因,優化前的搜索服務與下游模塊交的互方式主要為調用各下游模塊提供的函數,并且采用串行調用。
圖 1: 馬蜂窩搜索業務架構和技術體系
搜索技術體系
- 存儲——MySQL、Memcache
- 模塊交互——Function Call
- 檢索——Elasticsearch
搜索業務架構
我們將搜索業務抽象為三個功能模塊:
1. 決策系統
負責根據用戶意圖、運營策略、點擊日志等數據,結合決策系統相關算法和模型,決策應該展示哪些模塊(游記、商品等)及各模塊展示順序。
2. Agent
負責根據決策系統確定要展示的模塊,從 Elasticsearch 和業務方獲取模塊(如游記、商品等)數據。
3. Format
負責根據不同模塊的 UI 交互定義格式化數據,補充 UI 交互缺失數據。
串行的函數級調用方式,使之前的搜索服務架構存在一系列問題:
- 業務間耦合度高。隨著交互模塊越來越多,導致搜索服務耗時變得很長,平均達到 400-500 ms;
- 由于與各業務間交互的方式是 Function Call,使上游很難控制下游模塊阻塞時間;
- 下游調用增加響應時間相應呈線性增長,使其很難再疊加新的功能,可擴展性差;
- 如果下游模塊出現故障,會由于接口阻塞引起超時,導致搜索服務整體都受到影響,表現出白頁,用戶體驗嚴重下降。
圖 2:問題分析
因此,我們需要找到一種方式來降低搜索服務對于下游模塊的依賴,以及模塊間的耦合,從而提升架構的整體可用性和性能。
基于 Golang 的并發代理實現
經過調研,我們開發了基于 Golang 協程實現的并發請求代理工具,將之前函數級調用的方式變為基于 TCP/IP 的 HTTP 接口調用來與下游模塊解耦,同時將串行調用變為并發,實現超時控制和異常容錯處理。
主要技術選型——協程(Goroutine)
Goroutine 是 Golang 輕量級線程實現,由 Go runtime 管理。它是 Go 并行設計的核心,也是 Golang 最重要的特性之一,相比于進程、線程任務的搶占式調度,需要頻繁進行上下文信息的內核和用戶空間切換,Goroutine 可以由程序控制,使得它更易用、更高效、更輕便。
Goroutine 維護了一組數據結構和多個線程,任務放在一個待執行隊列中,由 Goroutine 維護的線程來拉取執行。當任務執行了操作系統的 IO 操作等需要等待時,Goroutine 利用 Linux IO 多路復用技術 (Epoll、Select) 進行執行隊列的任務切換來實現并發。
相比于其他語言的線程,其默認占用內存為 2KB, 遠小于其他語言的 M 級別。在性能開銷方面,由于任務調度基本有程序控制,開銷也遠小于線程。
選型的過程中,我們對比了 PHP 的 Swoole、Java 多線程并行處理方案,它們的 CPU 和內存消耗比 Golang 的 Goroutine 要高出很多,并且并行請求數量會受到資源的限制,在高并發的情況下如果控制不當會導致服務崩潰。而使用 Goroutine 實現的并發代理,可以輕松支持***別的并發請求。
圖 3:并行與并發
Golang 并發代理實現
代理服務按請求的處理流程,可以劃分為 HTTP Server ——> 參數處理——> 并行請求 (協程調度)——> HTTP 模塊 ——> API 層。目前我們的方案支持 HTTP/HTTPS 協議的請求。
圖 4:并發代理架構圖
各模塊功能概要:
- HTTP Sever:使用 Go 語言 httpserver package 實現,用于接收和處理有代理需求的上游模塊的 HTTP 請求;
- 參數處理:根據定義好的交互協議,將上游模塊的請求解析為并行請求商品、游記等下游模塊的請求任務;
- 協程調度:使用 Go 語言的 Goroutine 實現,負責執行對下游模塊的并發請求任務;
- HTTP 模塊:使用 Go 語言的 ioutil/http package 實現,負責與下游 API 模塊以 HTTP 協議形式交互;
- API 模塊:將下游模塊的函數調用封裝為 TCP/IP接口,將函數形式交互變為 HTTP 接口形式交互。
搜索業務應用代理后,整體架構變化為:
圖 5:并發代理在搜索業務中的應用
小結與后續規劃
基于 Golang 的并發代理在馬蜂窩搜索業務中已經使用了一段時間,很好地解決了之前存在的一些問題。目前,搜索服務平均耗時已經降低到240ms 左右,架構的可用性和可擴展性也得到很大提升,并且有效提高了系統資源的利用率。
現在并發代理只支持 HTTP,后續會增加 RPC,來更好地支持整體的服務化改造。在推進和實施搜索架構升級的過程中,我們也會把更多的經驗分享出來,希望大家持續關注。
本文作者:王江濤,馬蜂窩搜素推薦研發工程師。
【本文是51CTO專欄作者馬蜂窩技術的原創文章,作者微信公眾號馬蜂窩技術(ID:mfwtech)】