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

数据库连接从 15000 降到 100 以下:DigitalOcean 如何解决技术债

  • 2020-03-14
  • 本文字数:3226 字

    阅读完需:约 11 分钟

数据库连接从15000降到100以下:DigitalOcean如何解决技术债

本文最初发布于 DigitalOcean 官方博客,经原作者授权由 InfoQ 中文站翻译并分享。


最近,一位新员工在午餐时问我:“DigitalOcean 的技术债务是什么情况?”


听到这个问题时,我忍不住笑了。软件工程师询问一家公司的技术债务就相当于询问其信用评分。这是他们衡量一家公司过去的遗留问题以及他们所要背负的包袱的方法。DigitalOcean 对技术包袱并不陌生。


作为云提供商,我们管理着自己的服务器和硬件,我们面临着许多其他初创公司在这个云计算的新时代尚未遇到的复杂性。这些艰难的情况最终迫使我们不得不早做权衡。任何快速成长的公司都知道,你早期做出的技术决定往往会在以后影响到你。


盯着桌子对面的新员工,我深吸了一口气,然后开始说。“让我告诉你,我们有 15000 个与数据库的直接连接……”



我给这位新员工讲的故事是 DigitalOcean 迄今为止最大的技术重组。这是整个公司多年来的努力,教会了我们很多东西。我希望,讲述这个故事可以帮助未来的 DigitalOcean 开发者——或任何发现自己陷入棘手的技术债务问题的开发者。

故事缘起

DigitalOcean 从一开始就痴迷于简单性。这是我们的核心价值观之一:力求简单而优雅的解决方案。这不仅适用于我们的产品,也适用于我们的技术决策。这一点在我们最初的系统设计中最为明显。


与 GitHub、Shopify 和 Airbnb 一样,DigitalOcean 也是在 2011 年从一个 Rails 应用程序开始的。我们内部称为 Cloud 的 Rails 应用程序管理着 UI 和公共 API 中的所有用户交互。Rails 服务有两个 Perl 编写的辅助服务:调度器和 DOBE(DigitalOcean 的后端)。调度器将调度并分配 Droplet 给管理程序,而 DOBE 负责创建实际的 Droplet 虚拟机。Cloud 和调度器作为独立的服务运行,而 DOBE 则运行在 fleet 中的每台服务器上。


无论是 Cloud、调度器,还是 DOBE,彼此之间都不能直接对话。他们通过 MySQL 数据库通信。这个数据库有两个作用:存储数据和代理通信。这三个服务都使用单个数据库表作为消息队列来传递信息。


每当用户新建一个 Droplet 时,Cloud 就会向队列插入一个新的事件记录。调度器每秒一次不断地在数据库中轮询新的 Droplet 事件,并将它们的创建工作安排给一个可用的管理程序。最后,每个 DOBE 实例将等待新调度的 Droplet 创建并完成任务。为了能检测到所有新的更改,这些服务器都需要在数据库中轮询表中的新记录。



虽然在系统设计方面,无限循环和让每个服务器直接连接到数据库可能比较低级,但它很简单,而且很有效——特别是在人手短缺的技术团队面临紧迫的期限和快速增长的用户群时。


四年来,数据库消息队列一直是 DigitalOcean 技术的支柱。在此期间,我们采用了微服务体系结构,用 gRPC 代替 HTTPS 进行内部通信,并取消了 Perl,代之以 Golang 作为后端服务。然而,所有的方法都指向 MySQL 数据库。


值得注意的是,不能仅仅因为某些东西是“遗产”就认为它功能不完善,应该被取代。Bloomberg 和 IBM 都有用 Fortran 和 COBOL 编写的遗留服务,这些服务产生的收入超过整个公司。另一方面,每个系统都有一个扩展限制。我们就要达到这个限制了。


从 2012 年到 2016 年,DigitalOcean 的用户流量增长超过了 1000%。我们在目录中添加了更多的产品,在基础设施中添加了更多的服务。这增加了数据库消息队列上进入的事件。对 Droplet 需求增加意味着调度器要加班加点地把它们分配到服务器上。不幸的是,对于调度器,可用服务器的数量不是静态的。



为了满足 Droplet 需求的不断增长,我们不断地增加服务器来处理流量。每个新的管理程序都意味着一个新的数据库持久连接。截至 2016 年初,该数据库拥有超过 1.5 万个直接连接,每一个连接每 1 到 5 秒就查询一次新事件。如果这还不够糟糕的话,每个管理程序用来获取新 Droplet 事件的 SQL 查询也变得越来越复杂。它已经变成了一个巨人,有 150 多行,18 张表关联在一起。令人印象深刻的是,它不稳定,而且很难维持。


不出所料,正是在这个时期,问题开始显现。单点故障和成千上万的依赖关系攫取了共享资源,不可避免地导致了一段时间的混乱。表锁和查询积压会导致停机和性能下降。


由于系统的紧耦合,我们没能找到一个清晰简单的解决方案来解决这个问题。Cloud、调度器和 DOBE 都是瓶颈。如果只修补其中一两个组件,就会将负载转移到其余的瓶颈组件上。因此,经过深思熟虑,工程师们想出了一个三管齐下的方案来解决这个问题:


  1. 减少数据库上直接连接的数量;

  2. 重构调度器的排名算法,改进可用性;

  3. 移除数据库的消息队列职责。

开始重构

为了解决数据库依赖,DigitalOcean 的工程师创建了事件路由器。事件路由器充当区域代理,代表每个数据中心中的每个 DOBE 实例轮询数据库。这样,就只有少数代理在做查询,而不是数以千计的服务器。每个事件路由器代理将获取特定区域中的所有活动事件,并将每个事件委派给适当的管理程序。事件路由器还将庞大的轮询查询拆分成更小、更容易维护的轮询查询。



当事件路由器上线后,数据库连接的数量从 15000 个减少到不足 100 个。


接下来,工程师们把目光投向了下一个目标:调度器。如前所述,调度器是一个 Perl 脚本,它决定哪个管理程序将托管一个创建好的 Droplet。它使用一系列查询来对服务器进行排序。每当用户创建一个 Droplet 时,调度器就用最好的机器更新表行。


虽然听起来很简单,但该调度器有一些缺陷。它的逻辑很复杂,很难处理。它是单线程的,在流量高峰期间性能会受影响。最后,该调度器只有一个实例,却必须为整个 fleet 服务。这是一个不可避免的瓶颈。为了解决这些问题,工程团队创建了调度器 V2。


更新后的调度器彻底修改了排名系统。它不从数据库中查询服务器指标,而是从管理程序中聚合它们,并将它们存储在自己的数据库中。此外,调度器团队使用并发和复制保证新服务的负载性能。


事件路由器和调度器 V2 取得了许多了不起的成果,消除了当时的许多架构缺陷。尽管如此,还是有一个明显的障碍。到 2017 年初,集中式 MySQL 消息队列仍然在使用——甚至还很频繁。它每天处理 40 万条新记录,每秒更新 20 次。


遗憾的是,移除数据库消息队列并不容易。第一步是避免服务直接访问它。数据库需要一个抽象层。它还需要一个 API 来聚合请求并代它执行查询。任何服务想要创建一个新事件,都需要通过 API 来实现。于是,Harpoon 诞生了。


不过,为事件队列构建接口这部分比较容易。事实证明,从其他团队那里获得支持更加困难。与 Harpoon 集成意味着团队将不得不放弃他们的数据库访问,重写他们的部分代码库,并最终改变他们一直以来做事的方式。这可不是件容易的事。


Harpoon 的工程师们逐个团队、逐个服务地将整个代码库迁移到他们的新平台上。这花了差不多一年的时间,但到 2017 年底,Harpoon 成为数据库消息队列的唯一发布者。


现在真正的工作开始了。对事件系统的完全控制意味着 Harpoon 可以自由地重新设计 Droplet 工作流了。


Harpoon 的第一个任务是将消息队列职责从数据库提取到自身。为此,Harpoon 创建了自己的内部消息队列,由 RabbitMQ 和异步 worker 组成。当 Harpoon 在一侧把新事件推入队列时,worker 在另一侧拉取它们。由于 RabbitMQ 取代了数据库队列,worker 可以方便地直接与调度器和事件路由器交互。因此,调度器 V2 和事件路由器不是通过轮询从数据库获取新变化,而是由 Harpoon 直接将更新推送给它们。截止到 2019 年本文撰写时,这就是 Droplet 事件架构所处的位置。


一路向前

在过去的 7 年里,DigitalOcean 已经从一家小型创业公司成长为今天这样的成熟的云提供商。与其他转型中的科技公司一样,DigitalOcean 会定期处理遗留代码和技术债务。无论是拆分单体应用、创建多区域服务,还是消除单点故障,我们 DigitalOcean 的工程师们始终致力于打造优雅而简单的解决方案。


这就是我们的基础设施如何随着用户群的发展而扩展的故事,希望你觉得这是个有趣而又有启发性的故事。我很乐意在下面的评论中看到你的想法!


原文链接:


From 15,000 database connections to under 100: DigitalOcean’s tale of tech debt


2020-03-14 10:001924

评论

发布
暂无评论
发现更多内容

史上最全的Java容器集合之HashMap(源码解读)

自然

集合 Java core 9月月更

架构实战营模块一作业

π

架构实战营

极客时间-架构师训练营作业-模块一

沐の爹

挑战Python的语法练习

攻城狮Wayne

项目实战 9月月更 模块创建

架构实战训练营模块 1 作业

atcgnu

详解数据计算能力的四种类型

穿过生命散发芬芳

9月月更 数据计算

【最右】面向TS生态的新型Flutter框架

刘剑

typescript 小程序 移动端 动态化 flutter for web

Python语法之模块和包(2)

攻城狮Wayne

9月月更 模块创建 包的应用

Python语法之异常处理

攻城狮Wayne

异常处理 9月月更 Python异常处理方法

人工智能知识介绍

阿柠xn

人工智能 AI 科普 9月月更

跟着卷卷龙一起学Camera--3D LUT

卷卷龙

ISP 9月月更

1

神经蛙

模块一作业

愚人夜行者

微信业务架构图&“学生管理系统”毕设架构设计

Louis

跟着卷卷龙一起学Camera--TNR

卷卷龙

ISP 9月月更

天天都在谈的防火墙到底是个啥,有哪些分类?如何选择防火墙?

wljslmz

网络安全 防火墙 9月月更

架构实战训练营模块 1 作业

Geek_b35d92

架构训练

跟着卷卷龙一起学Camera--PDAF 01

卷卷龙

ISP 9月月更

大数据ELK(七):安装Elasticsearch-head插件

Lansonli

elasticsearch 9月月更

跟着卷卷龙一起学Camera--PDAF 02

卷卷龙

ISP 9月月更

【编程实践】详解 MySQL 在 Python 中的使用(2)-pymysql的使用

迷彩

MySQL 数据库 增删改查 pymysql 9月月更 数据库操作

2022-09-28:以下go语言代码输出什么?A:1 1;B:1 2;C:2 2;D:不确定。 package main import ( “fmt“ ) func main() { var

福大大架构师每日一题

golang 福大大 选择题

后疫情时代,RTE“沉浸式”体验还能这么玩?丨RTE 2022 编程挑战赛赛后专访

声网

人工智能

作业一

Geek_408c99

Linux下通过tar包方式安装MySQL,详细教程

阿柠xn

运维 MySQL 运维 Linux tar 9月月更

网络中一些很常见的协议,以及他们对应的报文格式介绍

阿柠xn

TCP 网络协议 9月月更 ARP

【web 开发基础】php 开发基础快速入门 (2)-PHP的程序开发

迷彩

php开源 9月月更 PHP语法 PHP面向对象

架构实战营9期第一模块课后作业

旋风

「架构实战营」

作业一

小虎

架构实战营

什么是地址转换协议ARP?工作流程是什么样的?

wljslmz

9月月更 ARP

数据开发也能双轮驱动?

乌龟哥哥

9月月更

数据库连接从15000降到100以下:DigitalOcean如何解决技术债_数据库_Sunny Beatteay_InfoQ精选文章