马蜂窝搜索基于 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:并发代理架构图

各模块功能概要

  1. HTTP Sever:使用 Go 语言 httpserver package 实现,用于接收和处理有代理需求的上游模块的 HTTP 请求;

  2. 参数处理:根据定义好的交互协议,将上游模块的请求解析为并行请求商品、游记等下游模块的请求任务;

  3. 协程调度:使用 Go 语言的 Goroutine 实现,负责执行对下游模块的并发请求任务;

  4. HTTP 模块:使用 Go 语言的 ioutil/http package 实现,负责与下游 API 模块以 HTTP 协议形式交互;

  5. API 模块:将下游模块的函数调用封装为 TCP/IP接口,将函数形式交互变为 HTTP 接口形式交互。

搜索业务应用代理后,总体架构变化为:

图 5:并发代理在搜索业务中的应用

小结与后续规划

基于 Golang 的并发代理在马蜂窝搜索业务中已经使用了一段时间,很好地解决了以前存在的一些问题。目前,搜索服务平均耗时已经下降到240ms 左右,架构的可用性和可扩展性也获得很大提高,而且有效提升了系统资源的利用率。

如今并发代理只支持 HTTP,后续会增长 RPC,来更好地支持总体的服务化改造。在推动和实施搜索架构升级的过程当中,咱们也会把更多的经验分享出来,但愿你们持续关注。

本文做者:王江涛,马蜂窝搜索推荐研发工程师。

关注马蜂窝技术,找到更多你想要的内容

相关文章
相关标签/搜索