写点什么

放弃 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:002267

评论 1 条评论

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

用 Python 实现一个简易版的 Pong 游戏 (二)

Matrix Chan

Python Python Turtle Python 游戏编程

【原创】经验分享:一个Content-Length引发的血案(almost....)

一枝花算不算浪漫

oeasy 教您玩转 linux 010207 黑客帝国 matrix

o

java安全编码指南之:表达式规则

程序那些事

java安全编码 java安全 安全编码规则

数据质量管理工具的意义和定位

苏槐

数据治理 数据质量管理 数据质量平台

解Bug之路-串包Bug

无毁的湖光

redis socket Java 分布式

深度解析!--阿里开源分布式事务框架Seata

攀岩飞鱼

分布式 分布式事务 微服务 分布式锁 Seate

USDT承兑商币支付系统搭建,USDT跑分承兑商app

第三周作业

Vincent

极客大学

第三周学习总结

Vincent

极客大学

19.解决 Flink 升级1.11 报错 No ExecutorFactory found to execute the application

小知识点

scala 大数据 flink

面试不会微服务没关系,跟着我4天学会微服务!

小Q

Java spring 架构 分布式 微服务

干货!如何平稳用户无感知的完成系统重构升级

X先生

架构 运维 后台

week13 作业

Geek_2e7dd7

就靠这几段代码,带你玩转rpc通信协议,不信你学不明白

小Q

Java 架构 面试 RPC 网络

架构师训练营-week13-作业

晓-Michelle

极客大学架构师训练营

甲方日常 9

句子

Java 运维 工作 随笔杂谈 日常

嘿,我想要寄一封挂号信,收件时间是 6 年后,标题是: 让 6 年后的我,加倍奉还。

叶小鍵

学习 成功学 心理学 李笑来

为什么我的缓存设置在chrome中不生效

书虫

chrome 缓存 浏览器 HTTP

新基建夯实粤港澳大湾区高质量发展基础

CECBC

区块链 人工智能 大数据

week 13 学习总结

Geek_2e7dd7

JavaScript七大语言类型你知多少?

Walker

Java 大前端 编程语言

Spring 5 中文解析测试篇-集成测试之概要和注解

青年IT男

单元测试 Spring5

第4周作业

Vincent

极客时间 极客大学

Docker 容器编排利器 Docker Compose

哈喽沃德先生

Docker 容器 微服务 Docker-compose

敏捷教练的软技能

研发管理Jojo

软技能 敏捷教练 引导者

通证与通证经济你真的理解吗

CECBC

区块链 通证经济

万字长文 | 23 个问题 TCP 疑难杂症全解析

yes

TCP 计算机网络

面试官:TCP/IP 协议到底在讲什么?想彻底搞懂TCP协议:还得从 TCP 三次握手四次挥手说起

编程 程序员 互联网 面试 计算机网络

第4周总结

Vincent

极客时间 极客大学

媒体电视台跟进,船长梁晓玲平台拉人头卖课引起多方报道!

成周

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