马蜂窝搜索基于 Golang 并发代理的一次架构升级

2023-10-13 07:20

本文主要是介绍马蜂窝搜索基于 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,来更好地支持整体的服务化改造。在推进和实施搜索架构升级的过程中,我们也会把更多的经验分享出来,希望大家持续关注。

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

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

这篇关于马蜂窝搜索基于 Golang 并发代理的一次架构升级的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/201639

相关文章

Python UV安装、升级、卸载详细步骤记录

《PythonUV安装、升级、卸载详细步骤记录》:本文主要介绍PythonUV安装、升级、卸载的详细步骤,uv是Astral推出的下一代Python包与项目管理器,主打单一可执行文件、极致性能... 目录安装检查升级设置自动补全卸载UV 命令总结 官方文档详见:https://docs.astral.sh/

苹果macOS 26 Tahoe主题功能大升级:可定制图标/高亮文本/文件夹颜色

《苹果macOS26Tahoe主题功能大升级:可定制图标/高亮文本/文件夹颜色》在整体系统设计方面,macOS26采用了全新的玻璃质感视觉风格,应用于Dock栏、应用图标以及桌面小部件等多个界面... 科技媒体 MACRumors 昨日(6 月 13 日)发布博文,报道称在 macOS 26 Tahoe 中

HTML5 搜索框Search Box详解

《HTML5搜索框SearchBox详解》HTML5的搜索框是一个强大的工具,能够有效提升用户体验,通过结合自动补全功能和适当的样式,可以创建出既美观又实用的搜索界面,这篇文章给大家介绍HTML5... html5 搜索框(Search Box)详解搜索框是一个用于输入查询内容的控件,通常用于网站或应用程

华为鸿蒙HarmonyOS 5.1官宣7月开启升级! 首批支持名单公布

《华为鸿蒙HarmonyOS5.1官宣7月开启升级!首批支持名单公布》在刚刚结束的华为Pura80系列及全场景新品发布会上,除了众多新品的发布,还有一个消息也点燃了所有鸿蒙用户的期待,那就是Ha... 在今日的华为 Pura 80 系列及全场景新品发布会上,华为宣布鸿蒙 HarmonyOS 5.1 将于 7

Golang 日志处理和正则处理的操作方法

《Golang日志处理和正则处理的操作方法》:本文主要介绍Golang日志处理和正则处理的操作方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录1、logx日志处理1.1、logx简介1.2、日志初始化与配置1.3、常用方法1.4、配合defer

python多线程并发测试过程

《python多线程并发测试过程》:本文主要介绍python多线程并发测试过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、并发与并行?二、同步与异步的概念?三、线程与进程的区别?需求1:多线程执行不同任务需求2:多线程执行相同任务总结一、并发与并行?1、

golang float和科学计数法转字符串的实现方式

《golangfloat和科学计数法转字符串的实现方式》:本文主要介绍golangfloat和科学计数法转字符串的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望... 目录golang float和科学计数法转字符串需要对float转字符串做处理总结golang float

golang实现延迟队列(delay queue)的两种实现

《golang实现延迟队列(delayqueue)的两种实现》本文主要介绍了golang实现延迟队列(delayqueue)的两种实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目录1 延迟队列:邮件提醒、订单自动取消2 实现2.1 simplChina编程e简单版:go自带的time

Golang实现Redis分布式锁(Lua脚本+可重入+自动续期)

《Golang实现Redis分布式锁(Lua脚本+可重入+自动续期)》本文主要介绍了Golang分布式锁实现,采用Redis+Lua脚本确保原子性,持可重入和自动续期,用于防止超卖及重复下单,具有一定... 目录1 概念应用场景分布式锁必备特性2 思路分析宕机与过期防止误删keyLua保证原子性可重入锁自动

golang 对象池sync.Pool的实现

《golang对象池sync.Pool的实现》:本文主要介绍golang对象池sync.Pool的实现,用于缓存和复用临时对象,以减少内存分配和垃圾回收的压力,下面就来介绍一下,感兴趣的可以了解... 目录sync.Pool的用法原理sync.Pool 的使用示例sync.Pool 的使用场景注意sync.