搜索業務是馬蜂窩流量分發的重要入口。不少用戶在使用馬蜂窩時,都會有目的性地主動搜索與本身旅行需求相關的各類信息,衣食住行,事無鉅細,從而作出最符合需求的旅行決策。算法
所以在馬蜂窩,搜索業務交互的下游模塊很是多,主要有目的地、POI、熱門景點、美食、商場、酒店、問答、攻略、機票火車票等等,經過實時、精準地返回搜索結果,幫助用戶作出個性化旅行決策。數據結構
面對愈來愈高的流量,馬蜂窩技術團隊積極嘗試對搜索架構進行優化和升級,來保證搜索業務的穩定和性能。多線程
因爲歷史緣由,優化前的搜索服務與下游模塊交的互方式主要爲調用各下游模塊提供的函數,而且採用串行調用。架構
圖 1: 馬蜂窩搜索業務架構和技術體系併發
咱們將搜索業務抽象爲三個功能模塊:函數
1. 決策系統高併發
負責根據用戶意圖、運營策略、點擊日誌等數據,結合決策系統相關算法和模型,決策應該展現哪些模塊(遊記、商品等)及各模塊展現順序。工具
2. Agent性能
負責根據決策系統肯定要展現的模塊,從 Elasticsearch 和業務方獲取模塊(如遊記、商品等)數據。優化
3. Format
負責根據不一樣模塊的 UI 交互定義格式化數據,補充 UI 交互缺失數據。
串行的函數級調用方式,使以前的搜索服務架構存在一系列問題:
圖 2:問題分析
所以,咱們須要找到一種方式來下降搜索服務對於下游模塊的依賴,以及模塊間的耦合,從而提高架構的總體可用性和性能。
通過調研,咱們開發了基於 Golang 協程實現的併發請求代理工具,將以前函數級調用的方式變爲基於 TCP/IP 的 HTTP 接口調用來與下游模塊解耦,同時將串行調用變爲併發,實現超時控制和異常容錯處理。
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:並行與併發
代理服務按請求的處理流程,能夠劃分爲 HTTP Server ——> 參數處理——> 並行請求 (協程調度)——> HTTP 模塊 ——> API 層。目前咱們的方案支持 HTTP/HTTPS 協議的請求。
圖 4:併發代理架構圖
各模塊功能概要:
搜索業務應用代理後,總體架構變化爲:
圖 5:併發代理在搜索業務中的應用
基於 Golang 的併發代理在馬蜂窩搜索業務中已經使用了一段時間,很好地解決了以前存在的一些問題。目前,搜索服務平均耗時已經下降到240ms 左右,架構的可用性和可擴展性也獲得很大提高,而且有效提升了系統資源的利用率。
如今併發代理只支持 HTTP,後續會增長 RPC,來更好地支持總體的服務化改造。在推動和實施搜索架構升級的過程當中,咱們也會把更多的經驗分享出來,但願你們持續關注。
本文做者:王江濤,馬蜂窩搜索推薦研發工程師。
關注馬蜂窩技術,找到更多你想要的內容