AppsFlyer将API网关服务从Clojure迁移到Golang

本文要点

  • AppsFlyer每天处理超过700亿个HTTP请求,并且是使用微服务架构风格构建。系统的入口点是一个被称为API网关的关键任务(非微型)服务,它封装了所有前端服务。

  • 原先的API网关使用了AppsFlyer的默认语言Clojure,其技术债务开始增加。

  • 为获得有关新设计的API网关服务的建议,他们选择使用Golang作为对Clojure进行基准测试的语言。

  • 作为一个选项,基准测试是在NGINX (经过Lua增强)上进行的,同时使用了Golang和Clojure。Go比Clojure提供了更好的吞吐量,因此被选为首选的实现语言。

  • API网关现在是用类型化语言构建的,借助Golang的库支持和社区的力量,我们可以更轻松地添加各种功能及引入新技术。

  • 新部署的解决方案能够支持的流量比现在多得多——随着流量和请求以10倍的规模增长,从前瞻性的角度来看,这是非常重要的。

AppsFlyer是一个领先的移动归因和市场分析平台,每天处理近700亿次HTTP请求(大约每分钟处理5000万次请求),使用微服务架构风格构建。系统的入口点是一个被称为API网关的关键任务(非微型)服务,它封装了所有前端服务。本质上,就是将客户流量通过单点路由到我们的后端服务,这极大地简化了客户端的身份验证和授权,但同时也可能导致单点故障。

\"\"

本文探讨了工程团队为什么以及如何从基于Clojure的API网关实现迁移到基于Go的实现。

API网关的技术债务日益增加

我们之前已经讨论过技术债务是如何产生的,并且这种情况经常发生,就像我们的API网关服务那样。

最初,AppsFlyer的服务是一个Python单体,它需要一个身份验证和授权解决方案作为这个单体的一部分。随着时间的推移,流量和复杂性不断增加,我们迁移到了微服务架构。因此,我们需要创建一个统一的API网关解决方案,它将作为我们的身份验证和授权提供者。

我们稍微做了下准备,就开始用Clojure编写,跳过了设计阶段,以概念验证模式构建服务。我们公司是EMEA最大的Clojure工场之一,因此,在很多情况下,Clojure都是默认选择的语言,无需考虑手头的具体项目。虽然这有利于提高速度,符合“把事情做完”的心态,但对于项目的长期维护来说就不那么理想了。随着流量的增长,我们很快意识到——新推出的API网关的代码太复杂了,需要不断地重构以支持所需的吞吐量。

我们最终到了需要做出抉择的时候,服务太不稳定了,我们意识到,我们需要完全重写这个项目——要么用Clojure(但是要有更好的设计),要么探索其他语言选项。通过这次迭代,我们决定抛弃我们的认知偏见,不再回到我们的Clojure舒适区,而是做恰当的设计工作来构建我们需要的服务,而不仅仅是重新构建我们已经拥有的服务。

我们最终选择了Golang作为这个Clojure API网关服务的比较基准,这也额外带来了语言多样性的好处,而且,掌握额外的语法有益于我们的代码技艺心态。

我们理解了向堆栈中添加另一种编程语言的另一面。我们是CI/CD思想的忠实信徒,我们引入了一种新的语言,它不是基于JVM(与Clojure完全不同)的,这有其运维成本,但是我们能够在短时间内解决这个问题。

当然,掌握一门新语言也有学习曲线,而且需要确保代码长期来看足够优秀健壮,在使用特定的语言实际编写第一个项目,并看一下它在生产中的表现之前,这很难知道。

我将简要说明下为什么我们选择Go来提供这个特定的服务(仅供参考)。作为一种过程式异步语言,Go为我们提供了函数式编程(我们已经在内部使用)、面向对象功能和更好的扩展。它是一种类型化语言,使得维护变得更加容易,而且不需要重新发明轮子。尤其是对于我们打算重写的服务,它有一个得天独厚的优势,就是内置了一个经过实践证明的反向代理。作为同步语言,Clojure非常适合多线程和并发,而事实证明,这个特定的服务有很大的I/O开销。

评估我们的选项

我们知道,为了能够恰当地评估不同语言的适应性,我们需要进行几个方面的检查——每种语言的性能以及对于手头的具体任务所带来的特定好处。要测量性能,我们知道,我们需要在尽可能接近生产的环境中对Clojure和Go进行适当的基准测试。

为此,我们开始做压力测试,我们把NGINX(经过Lua增强)作为一个选项,同时使用了Golang和Clojure。Go比Clojure提供了更高的吞吐量。

以下是有关测试的基本统计数据:

  • 我们使用WRK作为我们的基准测试工具

  • 3分钟的突发流量

  • 64线程

  • 1000个连接池

  • 2分钟请求超时

  • 每个请求返回一个500kb的静态文件

  • 为了降低使用c4 xlarge实例的网络噪声,所有的流量都从同一个AZ发起

代理方案每秒请求数每秒事务量总请求数总事务量错误请求平均延迟
直连19072 MB3450012.8 GB~ 400 (drop:200)4.41 Sec
NGINX18573 MB3348612.7 GB~ 300 (drop:37)7.95 Sec
Clojure(基本Http-Kit 实现)19072 MB3441212.8 GB~ 100 (drop:600)8.48 Sec
Golang(原生反向代理\u0026amp;http层)18573 MB3344312.7 GB~ 200 (drop: 0)5.42 Sec

除了上述这些结果之外,从语法的角度来看,Go也提供了额外的好处,作为一种类型化语言,它容易更新和迭代,而且更容易通过现有的包和库进行扩展(许多东西不用从头开始写),从功能的角度来看,其内核内置的反向代理组件是一个重要的好处。

我们放弃使用Clojure重写服务,避免走捷径去复制过时的代码,践行类型化思维方式。

在设计阶段,首先列出服务需要提供的功能,在确定好基本概念之后,我们研究了在将我们的生产用户迁移到新服务时的向后兼容性问题和潜在陷阱。一旦我们能够确定我们已经覆盖了所有的主要部分,我们就开始为项目分配架构师和开发人员,并开始相关工作。

从概念到交付

我们很惊讶,这么快就完成了项目的一部分编码,大约只需要两个月的工作。因为这是我们第一次将Go引入内部,我们在进行项目这部分的编码时很小心。对于每个功能,我们做了两次迭代,以确保我们做对了,而且,我们还做了大量的代码评审。这是因为我们知道这段代码必须精雕细琢,干净利落,因为这将作为后续其他Go项目的一个基础。

尽管这是我们引入的第一个Go项目,但我们有机会好好掌握这门语言及其核心功能,因为我们必须弥补Clojure在与栈中其他部分如Redis(用户登录计数器的持久状态,预防DDoS和bot病毒)和Kafka(我们管理着一个域事件CQRS,其中一个是成功或不成功的登录)进行通信时所使用的库的不足,这需要我们在Go中创建类似的库。Clojure这种语言需要我们的应用程序编译为Java字节码,并且,作为一个先决条件,需要在我们作为部署目标的机器上运行JVM,而Go是一种静态编译语言,不需要在一台机器上运行一个VM。

为了实现开箱即用的功能,如CPU和内存使用情况指标、业务逻辑计数器等等——我们基本上需要从头开始编写整个栈,这使我们可以更快地深入到Golang的错综复杂之处。

在大约两个月之后,我们做好了基本的迁移准备,覆盖了基本的功能和测试。我们开始在父节点组(域组)中以可控的方式将服务迭代地迁移到新的API网关,基本上是一次金丝雀发布。

在这个过程的最初几个周里,我们决定以可控的方式推出第一批的几个服务,这样,我们就可以发现生产中的错误和缺陷,并且有时间在推出我们所有的服务之前修复它们。在从最初的API解决方案迁移时,由于速度过快而导致了错误,并最终导致了低质量交付,我们希望吸取教训。

一旦我们觉得自己已经准备好并修复了所有的缺陷,我们就开始了所有服务的迁移计划。这包括每个服务的迁移指南PDF,其中包括迁移到新服务所需的具体步骤,这一举措的好处,以及根据具体的栈和依赖的不同确定执行迁移的最优方法。

为了以循序渐进的方式推出一个新的反向代理,我们使用了一个应用程序负载均衡器(ALB)基于一组预定义的URL来进行流量路由,这些URL是我们想要通过新旧API网关暴露的服务。

\"\"

这为我们如何以最小的努力和风险路由流量提供了一种非常可控的方法。我们把我们的时间用在了测试每个要迁移的服务以及与其他负责用户服务的团队的协作上。我们花了六个月的时间,但我们设法使大约40个微服务在零停机的情况下迁移到了新的API网关。

结果

最终的结果是,我们从25个运行Clojure代码的实例(c4 xlarge)——能够处理60个并发请求——减少到两个运行Go代码的实例(c3.2xlarge),大约能够支持一分钟5000个并发请求,这是一个巨大的改进。对于我们下一阶段的增长,新的架构设计也是一个足够健壮的解决方案,它给我们提供了一个功能强大的服务,得益于其过程式方法,可以承受业务复杂性的大规模增长,同时,它也为我们处理大规模业务的工具箱添加了一门新语言。

让我们通过例子看一下Clojure和Go中的反向代理解决方案。

Clojure中:

;; Creating a connection manager(let [cm (clj-http.conn-mgr/make-reusable-conn-manager {:timeout 1 :threads 20 :default-per-route 10})]);; Creating a proxy server using cm (connection manager) (client/request {:method\t:get                           :url\t(service/service-uri service-spec uri-match)                           :headers\t(dissoc (into {} (:headers req)) “content-length”)                           :body\t(when-let [len (get-in req [:headers “content-length”])]                                                     (bs/to-byte-array (:body req)))                           :follow-redirects   false                           :throw-exceptions   false                           :connection-manager cm                           :as\t:stream}))

Golang中:

func NewProxy(spec *serviceSpec.ServiceSpec, director func(*http.Request), respDirector func(*http.Response) error, dialTimeout, dialKAlive, transTLSHTimeout, transRHTimeout time.Duration) *MultiReverseProxy {\treturn \u0026amp;MultiReverseProxy{\t\tproxy: \u0026amp;httputil.ReverseProxy{\t\t\tDirector:       director, //Request director function\t\t\tModifyResponse: respDirector,\t\t\tTransport: \u0026amp;http.Transport{\t\t\t\tDial: (\u0026amp;net.Dialer{\t\t\t\t\tTimeout:   dialTimeout, //limits the time spent establishing a TCP connection (if a new one is needed).\t\t\t\t\tKeepAlive: dialKAlive,  //limits idle keep a live connection.\t\t\t\t}).Dial,\t\t\t\tTLSHandshakeTimeout:   transTLSHTimeout, //limits the time spent performing the TLS handshake.\t\t\t\tResponseHeaderTimeout: transRHTimeout,   //limits the time spent reading the headers of the response.\t\t\t},\t\t},

请注意,Golang的许多特性都是为了更好地管理连接池以及将反向代理功能集成到其核心类中。

总结

事实是,使用类型化语言构建使我们能够借助Golang的库支持和社区的力量更轻松地插入各种功能并引入新技术。对我们来说,从性能和吞吐量的角度来看,最重要的部分是改善我们的客户体验。新部署的解决方案能够支持比现在多得多的流量——随着我们的流量和请求以10倍的规模增长,从前瞻性的角度来看,这是非常重要的。

关于作者

Asaf Yonay是AppsFlyer研发组经理,非常乐于接受管理和技术挑战,在其中加入人的元素,把它们变成成功的故事。Asaf坚信,可以通过定义流程帮助研发团队成长和发展,而又没有速度损失,他始终在迎接全栈的挑战——相信这可以让经理进化为领导者。他一直在初创企业中承担不同的角色,包括技术支持、QA和各种研发角色,使用Clojure、Golang、Node.js和Python构建可伸缩的、可靠的系统来支撑React和Angular服务,并使用Kafka、Aerospike和Neo4J处理大规模或复杂的业务逻辑状态。

查看英文原文:Rewriting an API Gateway Service from Clojure to Golang: AppsFlyer Experience Report

microservices-with-clojure:使用Clojure的微服务的学习笔记 Clojure微服务 研究笔记。 立即下载

相关推荐

clojure-rust:Clojure转换为Rust编译器

clojure-rust:Clojure转换为Rust编译器

IOS防作弊产品技术原理分析

参考:https://www.cnblogs.com/2014asm/p/10945441.html

骑着蜗牛找马儿 1121

MiniZinc的Clojure接口库clojure2minizinc.zip

clojure2minizinc 是一个通过 MiniZinc 解决约束满足问题的 Clojure 库。clojure2minizinc 提供一个 state-of-the-art constraint solvers (via MiniZinc) 和高级编程语言 Clojure 之间的接口。代码示例:(ns clojure2minizinc.examples   (:require [clojure2minizinc.core :as mz]))   (mz/minizinc   (mz/clj2mnz   (let [a (mz/variable (mz/-- -1 1))          b (mz/variable (mz/-- -1 1))]     (mz/constraint (mz/!= a b))     (mz/solve :satisfy)     (mz/output-map {:a a :b b}))) :num-solutions 3)

appsFlyer调用聚合api接口

throw new IOException("请求失败,错误码:" + response.code());System.out.println("文件下载成功!保存路径:" + filePath);调用完成,使用网络请求发送获取csv文件进行下载,下载完成后通过读取csv文件获取数据。如果csv文件表头带有符号类型,在实体类中使用注解进行映射。// 创建文件并写入响应体数据。//匹配数据进行入库操作。// 获取响应体字节流。CSV文件Reader读取。

weixin_44754681的博客 672

clooj, clooj,一个用于clojure的轻量级 IDE.zip

clooj, clooj,一个用于clojure的轻量级 IDE clooj,一个面向clojure的轻量级 IDE应用程序clooj是一个小型的。简单的IDE ( 集成开发环境),用于编程语言,可以免费下载 。 clooj完全用clojure编写,使用基于swing的GUI 。 它是跨平台( 假

Clojure入门教程- Clojure – Functional Programming for the JVM中文版

Clojure入门教程- Clojure – Functional Programming for the JVM中文版

两类常见场景下的云原生网关迁移实践

本文将基于 MSE 云原生网关,介绍两类场景下的云原生网关迁移实践,第一类,是自建 Nginx Ingress 迁移到 MSE,我们提供了不更改原有 SLB,便可实现不停机迁移的能力,控制迁移风险,将对此将展开详细介绍;第二类是传统的微服务网关迁移到 MSE,我们通过提供详细的操作文档来说明。

阿里巴巴云原生的博客 1122

PMAC上位机通信原理(C#)

基础是引用 两个 ODT dll 原理是先注册到communication.AsyncDataAvailable事件中去 if (bSuccess1) { communication.AsyncDataAvailable += communication_AsyncDataAvailable; } bool bSuccess2 = communication1.GPAsciiConnect(strIP, iPort, strName, strPassword); i

weixin_45507349的博客 1087

AI多轮对话转Word:结构化转换原理与工程实践

AI多轮对话本质是携带角色、内容类型、嵌套层级等语义信息的结构化文本流,而非普通纯文本。其转换难点在于保留用户/AI身份标识、代码块、数学公式、列表等格式语义,而非简单粘贴。关键技术路径包括基于Markdown的Pandoc声明式转换、基于JSON Lines的python-docx对象级编程控制,以及二者协同的预处理-转换-后处理三层工作流。该方案支撑技术文档归档、跨团队协作、AI交付物标准化等核心场景,是AIGC内容落地办公系统的关键中间件能力。

weixin_34198881的博客 268

DeepSeek的训练与优化流程

DeepSeek的训练体系不仅追求极致的计算效率,更注重将物理规律、人类知识、安全伦理深度融入训练过程,其"效率-安全-可解释性"三角平衡的设计理念,为行业提供了重要参考范式。

m0_56896669的博客 1437

从JX-2R-01热敏打印机芯到稳定打印:硬件设计与时序控制的实践指南

本文详细解析了JX-2R-01热敏打印机芯的硬件设计与驱动开发全流程。核心内容包括:实现严格的双电源上电时序控制、采用PWM恒流驱动步进电机确保走纸平稳,以及精确控制SPI数据加载与加热脉冲时序以获得清晰打印效果。文章提供了从电路设计、芯片选型到代码调试的实战指南,旨在帮助开发者构建稳定可靠的热敏打印模块。

weixin_29169899的博客 223

家用NAS怎么选?双盘位、2.5G网口与品牌系统全面解析

网络附加存储(NAS)是家庭数字资产集中管理的基础设施,其核心价值在于将分散在手机、电脑中的照片、视频与文档统一存储、备份和共享。理解NAS的盘位、阵列与网络链路原理,是避免资源浪费的关键:双盘位配合RAID1镜像可在数据安全与容量成本间取得平衡,而2.5G网口逐渐成为家庭网络的性能分水岭,万兆则需整条链路匹配才能真正发挥作用。从数据量估算、场景定位到品牌系统(群晖的成熟稳定、极空间的易用、绿联的硬件性价比)与网络升级的优先级,用户可依据自身需求做出理性选择。本文聚焦双盘位NAS在家庭环境中的黄金定位,结合

weixin_33859844的博客 301

Error: dependent ‘..\..\..\..\Qt\5.15.2\msvc2019\include\QtWidgets\QMainWindow‘ does not exist.

核心问题:修改 Build 目录为源码子目录后,MSVC 编译报错,本质是变量设置异常,导致 qmake 生成多余回溯路径;临时解决:在 .pro 文件添加;最优解决:将上述配置写入 Qt 的 msvc 对应 qmake.conf 全局文件,一劳永逸;禁止 qmake 计算路径时向上回溯,避免生成异常路径。

mumufan05的专栏 878
上一篇: Python2+Selenium入门02-第一个Selenium Python脚本
下一篇: 蚂蚁金服数据质量治理架构与实践
weixin_34026484
博客等级 码龄11年 4642粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值