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

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

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

原文地址:https://www.cnblogs.com/mfwtech/p/10620974.html

时间: 2024-08-23 06:56:45

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

Go语言入门篇-gRPC基于golang & java简单实现

一.什么是RPC 1.简介: RPC:Remote Procedure Call,远程过程调用.简单来说就是两个进程之间的数据交互. 正常服务端的接口服务是提供给用户端(在Web开发中就是浏览器)或者自身调用的,也就是本地过程调用. 和本地过程调用相对的就是:假如两个服务端不在一个进程内怎么进行数据交互?使用RPC. 尤其是现在微服务的大量实践,服务与服务之间的调用不可避免,RPC更显得尤为重要. 2.原理: 计算机的世界中不管使用哪种技术,核心都是对数据的操作.RPC不过是将数据的操作垮了一个

Golang并发中channel的分析

问题:面对并发问题,是用channel解决,还是用Mutex解决? 如果自己心里还没有清晰的答案,那就读下这篇文章,你会了解到: 使用channel解决并发问题的核心思路和示例 channel擅长解决什么样的并发问题,Mutex擅长解决什么样的并发问题 一个并发问题该怎么入手解解决 一个重要的plus思维 前戏 前面很多篇的文章都在围绕channel介绍,而只有前一篇sync的文章介绍到了Mutex,不是我偏心,而是channel在Golang是first class级别的,设计在语言特性中的,

spring基于接口的代理报错

报错: 1.在service层加上@Transactional注解.浏览器端报错(如下),eclipse控制台无信息 2.去掉@Transactional注解,无报错,但是数据库没有信息插入. 解决方法:添加proxy-target-class="true",并将属性值改为true proxy-target-class="true" 与proxy-target-class="false"的区别:        proxy-target-class

基于Nginx反向代理及负载均衡

基于Nginx反向代理及负载均衡 参考:http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass 只要没有被启用,默认就是开启的,因为proxy属于nginx内置标准模块,通常实现代理的时候,最核心模块是proxy_pass,用于将用户请求的rui递交至上游服务器的某个URI但这个模块大部分用于location当中,因此要实现将某一URI的访问代理某个上游服务器大致的格式为: location /name/ { pro

golang并发编程

golang并发编程 引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止goroutine将资源耗尽.以下面伪代码为例,看看goroutine如何拖垮一台DB.假设userList长度为10000,先从数据库中查询userList中的user是否在数据库中存在,存在则忽略,不存在则创建. //不使用goroutine,程序运行时间长,但数据库压力不大 for _,v:=ra

Gravitational Teleport 是一个先进的 SSH 服务器,基于 Golang SSH 构建,完全兼容 OpenSSH

Gravitational Teleport 是一个先进的 SSH 服务器,可通过 SSH 或者 HTTPS 远程访问 Linux 服务器.其目的是为了替代 sshd.Teleport 可以轻松让团队以最佳实践来使用 SSH,例如: 无需分发密钥,Teleport 使用基于证书的访问并实现自动过期 增强了两阶段身份验证 集群支持,每个 Teleport 节点是集群的一部分,而且可通过 Web UI 浏览 可以记录和重放 SSH 会话,方便分享和审计 通过会话的共享来实现协作诊断问题 可通过 HT

某互联网上市公司基于 Golang 的运维基础框架

主题:某互联网上市公司基于 Golang 的运维基础框架 目录 服务器监控系统 自动化部署系统 功能展示 高可用控制系统的演化 高可用调度系统 资源定位系统 整体架构 强一致,高可用设计 一点儿心得 主讲师:PC 豆瓣.百度.360.第四范式 知乎<面向工资编程> Overview 监控系统 自动化部署系统 标准化的上线包,流程化的上线系统 Function Points 面向服务上线 一键部署及回滚 详细的部署统计信息和历史 Window/Linux 全平台支持 控制系统 memcached

基于Golang打造的开源WAF网关

基于Golang打造的开源WAF网关 Github地址 https://github.com/Janusec/janusec 产品介绍 https://mp.weixin.qq.com/s/OOA9LwPE0ulBqkIFkXax-Q 构建可扩展的应用安全基础设施 Janusec应用网关(Janusec Application Gateway),提供WAF (Web Application Firewall, Web应用防火墙).统一Web化管理入口.证书私钥保护,Web路由以及可扩展的负载均衡

golang 并发顺序输出数字

参考 package main import ( "fmt" "sync/atomic" "time" ) func main() { var count uint32 trigger := func(i uint32, fn func()) { for { if n := atomic.LoadUint32(&count); n == i { fn() atomic.AddUint32(&count, 1) break } ti