“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

放弃 Python,Uber 用 Go 重写 Schemaless 数据库的分片层

Anders Johnsen

  • 2018-11-19
  • 本文字数:3086 字

    阅读完需:约 10 分钟

放弃 Python,Uber 用 Go 重写 Schemaless 数据库的分片层

摘要: 2014 年,Uber 构建了可扩展的容错数据库 Schemaless,但随着业务的增长,原实现方式对资源消耗更多,同时请求延迟也在增加,为了保持 Schemaless 的性能,Uber 在不影响生产服务的情况下用 Go 重写了 Schemaless 数据库的分片层,完成了将产品系统从旧实现迁移到新实现的 Frontless 项目。



2014 年,Uber 工程构建了可扩展的容错数据库Schemaless,为公司的快速发展提供了便利。我们仅在 2016 年就部署了 40 多个 Schemaless 实例和数千个存储节点。


随着业务的增长,我们的资源消耗和延迟也在增长;为了保持 Schemaless 的性能,我们需要一个能够很好的支持大规模应用的解决方案。在明确了假如将现有 Schemaless“集群”的 Python 工作节点用Go(一种支持轻量级并发特性的语言)重写的话,我们的数据库可以获得显著的性能提升后,我们在不影响正常生产的情况下,完成了将产品系统从旧实现迁移到新实现的任务。这一任务被称为 Frontless 项目,它证明了我们可以在不影响生产服务的情况下重写大型数据库的前端。


在本文中,我们会讨论如何将 Schemaless 分片层从 Python 迁移到 Go,这一改变可以使我们用更少的资源来处理更多的流量,从而改善用户对我们服务的体验。

Schemaless 的背景

作为Mezzanine项目,Schemaless 于 2014 年 10 月首次推出,当初计划将 Uber 的核心 trip 数据库从一个独立的Postgres实例迁移到一个高可用的数据库中。


包含核心 trip 数据的 Mezzanine 数据库被构建为第一个 Schemaless 实例。从那时算起,目前已经部署了 40 多个 Schemaless 实例用于众多客户端服务。(关于我们内部数据库的完整历史演进过程,请参阅我们的三篇系列文章,Schemaless 的设计架构triggers概述)。


在 2016 年中,有数千个工作节点在 Schemaless 实例中运行,每个工作节点都消耗大量的资源。工作节点最初是使用 Python 和由NGINX交付的uWSGI应用程序服务器进程中的一个Flask微框架构建的,每个 uWSGI 进程一次处理一个请求。


该模型简单易行,易于建立,但不能有效地满足我们的需求。为了处理额外的同步请求,我们必须增加更多的 uWSGI 进程,每个进程都作为一个需要额外开销的新的 Linux 进程,因而这从根本上限制了并发线程的数量。在 Go 中,goroutines 被用来构建并发程序。goroutine 采用轻量级设计,是由 Go 的运行时系统管理的线程。


为了研究重写 Schemaless 分片层的优化增益,我们创建了一个实验性的工作节点,该节点实现了一个使用频率较高、资源消耗也比较高的端点。重写的结果显示,延迟减少了 85%,资源消耗减少的甚至更多。



图 1:该图描述了 Frontless 形式实现的端点中值请求延迟情况


在进行了这个实验之后,我们明确了重写将使 Schemaless 通过释放 CPU 和内存来支持其所有实例中的依赖服务和工作节点。有了这些知识基础,我们启动了这个 Frontless 项目,用 Go 重写整个 Schemaless 分片层。

Frontless 架构设计

为了成功地重写 Uber技术堆栈的这个重要部分,我们需要确保我们的重新实现 100%与现有的工作节点兼容。我们做了一个关键的决定,以验证新实现与原始代码的关系,这意味着每个对新 Go 工作节点的请求都要得到跟之前对 Python 工作节点请求相同的结果。


我们估计一个完整的重写会花费我们六个月的时间。在此期间,在 Uber 的生产系统中实现的新功能和 bug 修复将在 Schemaless 的情况下进行,所以我们的迁移有了一个动态的目标。我们选择了迭代开发形式,这样我们就可以一次性在一个端点上不断的从遗留的 Python 代码库中迁移出功能,并同时在新的 Go 代码库中验证。


最初,Frontless 工作节点只是在现有的 uWSGI Schemaless 工作节点前面的一个代理,所有请求都通过该节点。迭代将从重新实现一个端点开始,然后在生产中进行验证;当不再有错误出现后,新的实现才会正式上线。


从部署的角度来看,Frontless 和 uWSGI Schemaless 的工作是一起构建和部署的,这使得在所有实例中都可以实现统一的 Frontless,并同时支持所有生产场景的验证。



图 2:在我们的迁移过程中,一个名为 worker 节点的服务,其中 Frontless 和 Schemaless 在同一个容器中运行。Frontless 随后收到请求,并决定是否应该将其转发给 Schemaless,或者由 Frontless 处理。最后,Schemaless 或 Frontless 从存储节点获取结果,并将其返回给服务。

读取端点:对比验证

我们首先聚焦在用 Go 重新实现读取端点上。在我们最初的实现中,Schemaless 实例上读取端点处理平均占用 90%的流量,并且它也是最消耗资源的。


当一个端点用 Frontless 实现后,将会启动验证进程,检测与 Python 实现的差异性。Frontless 和 Schemaless 执行请求操作时便会触发验证并对比响应结果。



图 3:当一个服务发送请求到 Frontless 时,它会将请求转发给 Schemaless,该请求将通过查询存储节点生成响应。然后,由 Schemaless 做出的响应将返回到 Frontless,并将其转发给服务。Frontless 还将通过查询存储节点来创建响应。这两种响应是由 Frontless 和 Schemaless 构建的,如果出现任何差异,结果将作为 bug 报告发送给 Schemaless 开发团队。


使用此方法验证,将使发送到存储工作节点的请求数量增加一倍;为了使请求数量增加后工作正常,我们添加了配置标志来激活每个端点的验证,并调整请求验证的百分比阈值。这样便可以在几秒内启动或禁用对指定端点任意部分的验证功能。

写入端点:自动集成测试

Schemaless 的写入请求只能一次性成功,所以为了验证这些我们不能再使用以前的策略了。然而,由于与读取端点相比,在 Schemaless 中写入端点要简单得多,因此我们决定通过自动化集成测试来测试它们。


我们建立起了集成测试环境,这样 Schemaless Python 和 Frontless Go 就可以运行相同的测试场景了。测试是自动化的,可以在本地执行,也可以在几分钟内通过持续的集成来执行,这可以加快开发周期。


为了规模化测试我们的实现,我们设置了一个 Schemaless 测试实例,其中流量测试模拟了生产流量。在这个测试实例中,我们将 Schemaless 的 Python 流量写入实现迁移到了 Frontless 上,并运行验证来确保写入的正确性。


最后,一旦所有实现都满足生产环境时,我们就可以通过运行时配置将 Schemaless 的 Python 实现的流量写入功能缓慢地迁移到 Frontless 上,这样便可以在几秒钟内将部分流量写入工作移动到新的实现中。

Frontless 的成果

到 2016 年 12 月为止,所有的 Mezzanine 数据库都是由 Frontless 处理的。如图 4 所示,所有请求的中值延迟降低了 85%,p99 请求延迟降低了 70%:



图 4:上图展示了由 Python(Schemaless 的工作语言,用红色表示)和 Go(Frontless 的工作语言,用蓝色表示)实现时数据库请求处理的时间。


随着我们 Go 的实现,Schemaless 的 CPU 使用率下降了 85%以上。这种效率的增加让我们减少了在所有 Schemaless 实例中使用工作节点的数量,这些节点也是基于与以前相同的 QPS,这从而提高了节点利用率。



图 5:上面的图展示了在我们的数据库中由 Python(Schemaless 工作语言,红色的)和 Go(Frontless 的工作语言,蓝色的)处理的一个稳定的请求流中的 CPU 使用情况。

Frontless 的未来

Frontless 项目表明,我们有可能在零停机的情况下,用一种全新的语言重写一个关键系统。通过重新实现服务而不改变 Schemaless 的现有客户端,我们能够在几天内而不是数周或几个月内实现、验证和启用端点。重点是,验证过程(新的端点实现与现有生产中的实现进行比较)给了我们信心,因为 Frontless 和 Schemaless 可以得到相同的结果。


然而,最重要的是,我们在生产中重写关键系统的能力证明了 Uber 迭代开发过程的可伸缩性。


原文链接:https://eng.uber.com/schemaless-rewrite/


2018-11-19 09:002063

评论 1 条评论

发布
用户头像
难道我大python没有未来了吗?
2018-11-21 17:33
回复
没有更多了
发现更多内容

【Promise 源码学习】第十二篇 - Promise.race 的实现

Brave

源码 Promise 12月日更

极客时间架构实战营 - 模块九及大作业

jjn0703

架构实战营

『腾讯网』腾讯分分彩计划最准软件[手机乐乎]

天马行空

腾讯分分彩计划最准软件

『腾讯网』1分彩计划软件下载[手机乐乎]

天马行空

1分彩计划软件下载

『腾讯网』网贷强制上岸后果[手机乐乎]

天马行空

网贷强制上岸后果

Android 框架解析:深入理解 Retrofit 实现

拭心

android Framework 内容合集 签约计划第二季 shixinzhang

极客时间架构实战营总结

jjn0703

架构实战营

架构实战营毕业总结

白开水又一杯

#架构实战营

Android 框架解析:OkHttp 请求原理基本认识

拭心

android Framework 内容合集 签约计划第二季 shixinzhang

Android 框架解析:Picasso 源码基本架构

拭心

android Framework 内容合集 签约计划第二季 shixinzhang

『腾讯网』赛车飞艇平台[手机乐乎]

天马行空

赛车飞艇平台

『腾讯网』腾讯分分彩 官网平台[手机乐乎]

天马行空

腾讯分分彩 官网平台

为什么不上台?(5/28)

赵新龙

28天写作

音视频学习从理论到实战

liuzhen007

内容合集 签约计划第二季 技术专题合集

终于有了一个人人可以访问的网站了

老表

WordPress 个人网站 跟老表学云服务器

『腾讯网』极速飞艇游戏[手机乐乎]

天马行空

极速飞艇游戏

Android 进阶之路:深入理解常用框架实现原理

拭心

android Framework 内容合集 技术专题合集 shixinzhang

极客时间【架构实战营】第二期 毕业设计

Geek_91606e

架构实战营

Git进阶(八):git stash 与 git add

No Silver Bullet

git 12月日更

Android 框架解析:热修复框架 Tinker 从使用到 patch 加载、生成、合成原理分析

拭心

android Framework 内容合集 签约计划第二季 shixinzhang

JavaScript基础语法之对象的哪些事

你好bk

JavaScript 大前端 js 基础语法 12月日更

Android 框架解析:Picasso 核心功能实现原理

拭心

android Framework 内容合集 签约计划第二季 shixinzhang

随手记录一下消息队列的一些模型

存储引擎漫话

ElvinYang

B-tree LSM-Tree Hash Index MySQL InnoDB

『腾讯网』飞艇技巧图片图解【重点推荐】[手机乐乎]

天马行空

飞艇技巧图片图解

Rust 元宇宙 13 —— 客户端连接

Miracle

rust 元宇宙

Android 框架解析:EventBus 3.0 的特点与如何使用

拭心

android Framework 内容合集 签约计划第二季

Android 框架解析:EventBus 3.0 如何实现事件总线

拭心

android Framework 内容合集 签约计划第二季 shixinzhang

『腾讯网』幸运彩票是真是假[手机乐乎]

天马行空

幸运彩票是真是假

Android 框架解析:从 EventBus 中学到的精华

拭心

android Framework 内容合集 签约计划第二季 shixinzhang

JetPack-Paging3-入门

Changing Lin

12月日更

放弃 Python,Uber 用 Go 重写 Schemaless 数据库的分片层_软件工程_InfoQ精选文章