NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

GitHub 如何从单体架构迁移到微服务架构?

  • 2021-07-21
  • 本文字数:4865 字

    阅读完需:约 16 分钟

GitHub 如何从单体架构迁移到微服务架构?

本文介绍 GitHub 如何从单体架构迁移到微服务架构,并对其中一些最佳实践做了详细说明。

旅程开启


GitHub创建于 2008 年,其宗旨是为开发人员托管和分享代码提供便利。GitHub 的创建者也是开源贡献者,他们在Ruby社区非常有影响力。正因为如此,GitHub 的架构深深地扎根于Ruby on Rails


在公司的整个发展历程中,我们雇佣了世界上最好的 Ruby 开发人员,帮助我们扩展和优化代码库。如今,我们的平台上已经有超过 5000 万名开发人员,每年有超过 8000 万个 pull 请求合并,全球各大洲有超过 1 亿个代码存储库。


如你所见,这个单体架构已经带我们走得很远。一个演进了 12 年的代码库,每天要协调多次部署。我们有一个规模很大的平台,每天处理 10 亿次 API 调用,我们还提供了一个高性能的用户界面,专注于完成这项工作。

内部快速增长


在过去 18 个月中,GitHub 内部经历了快速增长。我们已经有超过 2000 名员工,为代码库做贡献的工程师数量已经是以前的两倍多。这种增长既包括自身的逐步发展,也包括收购,如 Semmle、npm、Dependabot 和 Pull Panda。


此外,GitHub 是一个高度分散的团队,在疫情发生前,我们就有超过 70%的员工是在旧金山总部以外的地方办公。GitHub 的员工和承包商要跨六大洲展开协作,他们工作的时区各不相同。我们有 1000 多名内部开发人员,他们有各种各样的开发技能,涉及到许多不同的技术。


显然,我们需要从根本上重新考虑下 GitHub 的软件开发工作。让每个人在参与开发之前都学习 Ruby,让所有人都在同一个单体代码库上进行开发,不再是扩展 GitHub 最高效、最优化的方法。根据康威定律,任何组织设计的系统,其结构都是对组织沟通结构的复制。


反之亦然,单体架构会导致更大规模的涉众会议,更复杂的决策过程,因为交织的逻辑和共享的数据会影响所有团队。

单体 vs. 微服务


因此我们就想,是不是该从 Ruby on Rails 单体迁出,转向一种微服务架构了?如果是这样的话,我们该如何进行?单体架构和微服务架构各有所长。


在单体环境中,配置并运行应用程序更简单,不用考虑复杂的依赖关系,拉取所有必要的依赖项。新建一个 Hubber,只需几个小时就可以在本机上配置好 GitHub 并运行起来。在单体架构中,代码在有些情况下会更简洁。例如,不用添加超时处理逻辑,也不用考虑如何优雅地处理由网络延迟和中断所导致的失败。


此外,由于所有人都工作在同一个技术栈上,大家对代码库都很熟悉,所以可以方便地将开发人员和团队调去开发单体的其他特性,有利于实现特性的全局最优。考虑到 GitHub 在过去 18 个月中的增长情况,微服务环境的一部分优点吸引了我们。


例如,建立具有系统级所有权的特性团队,通过清晰定义的 API 契约确立职责边界。在遵循 API 契约的前提下,团队有充分的自由选择最适合自己的技术栈。代码库更小意味着阅读更容易、启动速度更快、问题排查更简单。开发人员不用为了提高生产力去理解一整个庞大的代码库的内部运行机制。最重要的是,服务现在可以根据各自的需求单独扩展。

务实——以赋能为出发点


在开始迁移 GitHub 之前,我们花了一些时间考虑为什么要这样做,以及这样做的目标是什么。对我们来说,这是文化上的巨大转变,需要做大量的工作。我们得想好,到底要解决什么问题和痛点。


在 GitHub,这样做可以让超过一半的开发人员(在过去的 18 个月中加入)在单体代码库之外富有成效地开展工作。我们的目标是赋能而非替代。


为此,我们得接受这样一个现实,GitHub 未来的特性将基于一个单体-微服务混合的环境。也就是说,对于我们来说,维护和改进现有的单体代码库仍然很重要。有一个很好的例子是,我们最近升级到了 Ruby2.7。感兴趣的话,可以从 GitHub 官方博客上了解我们做了什么,以及我们总体上如何改进系统。

良好的架构始于模块化


良好的架构始于模块化。拆分单体的第一步是考虑基于特性功能分割代码和数据。这个过程可以在真正在微服务环境中拆分之前在单体中完成。使代码库易于管理,通常都是一种良好的架构实践。确保每个服务都有自己的数据,并且能够控制对这些数据的访问,而且只能通过明确定义的 API 契约访问。


我看到,在很多情况下,人们会首先抽出代码逻辑,但仍然使用单体的共享数据库。这往往会导致分布式单体,这是最糟糕的单体,同时也是最糟糕的分布式。没有获得任何好处(比如,单独快速地向生产环境中部署一组特性),却还要应对微服务的复杂性。

数据拆分


正确地拆分数据是从单体架构转向微服务的基础。这里将稍微详细地介绍下 GitHub 的做法。


首先,我们在现有的数据库模式中识别功能边界,并按照这些边界将实际的数据库表分组。例如,我们将所有存储库相关的表分到一起,所有用户相关的分到一起,所有项目相关的分到一起。我们将生成的功能分组称为模式域,并记录在 YAML 定义文件中。现在,这个文件就成了事实来源。在数据库模式中添加或删除表,都要更新这个文件。我们通过一种静态分析测试方法来提醒开发人员,在修改数据库模式时,要更新这个文件。


接下来,对于每个模式域,我们找了一个分区键。这是一个共享字段,将一个功能组中的所有信息联系在一起。例如,存储库模式域(其中包含所有与存储库相关的数据,如问题、pull 请求、评审意见)使用存储库 ID 作为分区键。最终,创建数据库模式功能组帮助我们将数据拆分到微服务架构所需的不同服务器和集群上。


对于当前的跨域查询,我们做了修复,以防数据拆分对产品造成破坏。在 GitHub,我们在单体中实现了一个查询监视器来帮助我们检测,并在发现跨域查询时发出告警信息。我们会根据域边界,把这些查询拆分并重写成多个,并在应用程序层实现必要的连接。在划分完功能组后,我们开始通过一个类似的过程,进一步将数据分片到相应的租户组。


GitHub 有超过 5000 万用户和 1 亿个存储库,在这样的规模下,功能组可能会变得非常大。这时,分区键就派上用场了。例如,一种简单的方法是根据数值范围将不同的用户分配到不同的数据存储。更常见的可能是根据每个数据集的特性(如区域和大小)所做的逻辑分组。Tenantizing 是一个很好的方法,可以将数据存储故障的爆炸半径限制在客户的一个子集里,而不是一下子影响到所有人。

从核心服务和共享资源入手


我们已经花了很多时间讨论数据拆分的重要性。现在,我们换个话题,介绍下从单体中抽取服务的基础工作。一定要记住,依赖方向只能从单体内到单体外,不能反过来,否则,我们最终会得到一个分布式单体。也就是说,当从单体中抽取服务时,要从核心服务入手,然后逐步到特性层面。


接下来,找出开发人员在单体环境中开发时所使用的助力工具。随着时间的推移构建一些共享工具以方便单体开发,这是很常见的。例如,我们的特性标识,可以让单体开发者安心地将新特性从测试环境转到生产环境,因为在这个过程中,他们可以通过这个标识控制谁能看到这些特性。将助力工具转移出来,让开发人员在单体之外也可以使用这些工具。


最后,在新服务上线运行后,务必要删除旧的代码路径。通过工具来识别谁在调用这个服务,并规划好如何将流量全部导向新服务,这样你就不用老是为两套代码提供支持了。在 GitHub,我们使用一个名为 Scientist 的工具帮我们处理这种上线,我们可以用它并排运行和比较新旧代码路径。

AuthN/AuthZ 抽取


在 GitHub,我们决定首先抽取的核心服务是身份验证和授权。身份验证相当复杂,因为所有东西都依赖于它。网站和 Git 操作之间有一大堆的共享逻辑。也就是说,如果 github.com 宕掉了,那么 Git 系统就无法访问了,即使是使用命令行窗口,也无法执行像 pull、push 这样的 Git 操作。这就是为什么把这些基础部分抽取出来如此重要,那可以让主要功能脱离单体而运行。


对于我们来说,身份验证已经很简单,因为我们已经在单体外部将它重写为一个镜像服务。当前的 Rails 应用程序(即我们的单体)使用 Twirp(这是一个 gRPC 风格的服务到服务通信框架)和它通信,依赖方向是由内到外。

运营变化


监控、CI/CD、容器化都不是什么新概念,但为了支持从单体到微服务的转型,节省时间,加速向微服务的过渡,运营要做必要的改变。在修改这些工作流时,要时刻记着微服务的特性。与为一个大型单体运行单个高度定制化的管道相比,为众多小型的、独立运行的、基于不同技术栈的服务提供运营支持存在很大的差别。将监控从功能调用指标升级为网络指标和契约接口。推动实现自动化程度更高、更可靠的 CI/CD 管道,并使其可以在服务之间共享。使用容器化技术支持各种语言和技术栈。创建工作流模板以实现重用。


例如,在 GitHub,我们创建了一个自助服务运行时平台,可以用于微服务的打包交付。其目的是大幅减轻每个团队创建微服务时的运营负担。它提供了现成的 Kubernetes 模板,可自由使用的 Ingress 负载均衡设置。它可以将日志自动提取到 Splunk,并集成了我们内部的部署流程。这样,任何团队想要试验或上线一个新的微服务都会更容易。

小处着手,考虑产品/业务价值


到目前为止,我们主要讨论的还是结构性变化,以及从单体成功过渡到微服务架构所需要的基础工作。此后,任何新特性都应该创建成单体外的一个微服务。


下一步,找一些简单的小特性从单体中迁移出来,例如,那些没有复杂依赖和共享逻辑的特性。在 GitHub,我们是从 webhook 推送和语法高亮开始的。我们希望在迁移更多更大的单体功能之前,找出常见的模式和两种架构之间的差别。我们是根据产品和业务价值来确定微服务的大小。


我们通过查找经常一起更改和部署的代码和数据,来确定耦合度较高的特性或功能,并以此为基础,自然地划分成可以独立于其他部分单独迭代和部署的分组。此外,专注于产品和业务价值,还有助于组织内跨工程团队、产品和设计开展紧密合作。请注意,拆分得太小往往会增加不必要的复杂度和开销。例如,需要维护单独的部署密钥,更多的服务台职责,以及由于缺少知识共享而导致的单点故障。

实现异步性和弹性代码


从单体转向微服务是重大的模式转变。在这个过程中,不管是软件开发流程,还是实际的代码库,都会发生很大的变化。在最后一部分内容中,我们将快速了解下服务之间的通信以及失败机制(designing for failure),这两个都是微服务开发中非常重要的概念。


服务之间的通信方式有两种:同步和异步。使用同步通信,客户端在发送请求后会等待服务器的响应。使用异步通信, 客户端在发送请求后不会等待响应,每条消息都可以由多个接收者处理。在 GitHub,我们使用 Twirp 实现单体与单体外部核心服务(如授权)之间的同步通信。


然而,随着越来越多的服务移到单体之外,同步通信开始变得非常低效。而且,那还导致了服务之间的紧耦合,背离了迁移到微服务架构的初衷。更好的做法是创建一个共享的事件管道,协调多个生产者和消费者之间的消息。在 SendGrid,我们使用的就是这种架构。


由于服务不再是运行在一台服务器上,所以考虑网络通信中的延迟和故障非常重要。对于大部分暂时的网络问题,使用一种简单的重试机制,定义好重试频率和最大重试次数,就足够了。可以考虑使用指数退避让重试逻辑变得更加智能。例如,随着重试次数的增加延长等待时间,而不是间隔同样的时间,从而缓解那些因为过载而无法响应的服务器的压力。作为一种自我保护和自愈机制,还可以在服务之间增加断路器。例如,在多次尝试失败之后,断路器会打开,在服务恢复之前,不再允许额外的请求进入。为服务设置超时时间,这样服务就不会一直等待外部服务的响应。设法实现优雅的失败,可以向用户展示友好的提示信息,或者恢复到缓存中上一个已知的良好状态。关注用户体验,做对企业有益的事。

小结


本文前 4 部分主要介绍了在开启从单体到微服务的旅程之前应该了解的基础内容。关注迁移原因。考虑模块化和数据拆分。从核心服务和共享资源入手,做必要的运营调整。做好这些准备,整个组织的微服务转型之旅就会更加令人愉快。接下来,我们讨论了从哪里入手,以及如何将微服务与产品和业务价值联系起来。最后,我们介绍了微服务的两个关键概念:服务之间的通信和构建弹性系统。


关于作者


Sha Ma 是 GitHub 软件工程部门的副总裁,负责核心平台和生态系统。在加入 GitHub 之前,她是 SendGrid 工程部门的副总裁,是 2017 年将公司上市的领导团队的一员。


原文链接:


https://www.infoq.com/presentations/github-rails-monolith-microservices/

2021-07-21 16:205299
用户头像

发布了 690 篇内容, 共 399.7 次阅读, 收获喜欢 1498 次。

关注

评论 1 条评论

发布
用户头像
提个建议,专业术语如 pipeline,就不要翻译了
2021-07-26 00:03
回复
没有更多了
发现更多内容

ShareSDK 微信平台注册指南

MobTech袤博科技

超越YOLOv8,飞桨推出精度最高的实时检测器RT-DETR!

飞桨PaddlePaddle

人工智能 计算机视觉 目标检测 百度飞桨

卧薪尝胆30天!啃透京东大牛的高并发设计进阶手册,终获P7意向书

做梦都在改BUG

Java 系统设计 高并发

PostgreSQL插件那么多,怎样管理最高效?

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

强强联合,ByteHouse携手亚马逊云科技,新一代云数仓服务重磅升级

字节跳动数据平台

数据仓库 云原生 Clickhouse 企业号 4 月 PK 榜

多位P8大牛联袂推出:国内最牛的Java面试八股,不接受反驳

Java你猿哥

Java 面试 ssm 面经 八股文

百度APP iOS端包体积50M优化实践(二) 图片优化

百度Geek说

ios 开发语言 企业号 4 月 PK 榜

Ts中string、number和any等类型 不能当做索引用,怎么处理?

肥晨

三周年连更

open3d将pcd存数据库

linux大本营

sqlite 数据库 存储 :MySQL 数据库

让GitHub低头认错的这份阿里内部绝密Java面试八股文手册有多强?

Java你猿哥

Java 面试 ssm 面经 八股文

融云 CTO 岑裕:出海技术前沿探索和排「坑」实践

融云 RongCloud

运维 网络 融云 泛娱乐 出海

泰库辣!京东首席架构师:亿级流量网站架构核心技术,肝完薪资飙升

Java你猿哥

架构 高可用 ssm 高并发 架构设计

震撼!阿里架构师全新产出Java面试突击宝典。我觉得泰库辣!

Java你猿哥

Java redis spring Spring Boot JVM

校园共享电单车是否值得投放

共享电单车厂家

共享电动车厂家 共享电单车投放 校园共享电动车 本铯共享电动车

使用 IDEA 远程 Debug 调试(一篇懂所有)

Java你猿哥

Java Spring Boot ssm IDEA

目前江西省等级测评公司有几家?都在南昌吗?

行云管家

江西 等保 等级保护 等保2.0

写一个回调函数

linux大本营

回调函数 C++

用纯python写web app:Streamlit

AIWeker

Python python小知识 三周年连更

SaaS 软件的 SLA 和 Escalation

Jerry Wang

SaaS Cloud 三周年连更

深入探索数据库MySQL,性能优化与复杂查询相关操作

做梦都在改BUG

Java MySQL 数据库 性能优化

canvas-绘制一个柱状图

格斗家不爱在外太空沉思

CSS canvas 三周年连更

带你了解关于FastAPI快速开发Web API项目中的模板和Jinja

华为云开发者联盟

微服务 前端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

华为ISDP亮相长沙电力行业信息化年会,分享数字化转型实践与技术创新

平平无奇爱好科技

用C语言实现,终端输入1.2.3.4/32,解析输出unsignedint类型的1.2.3.4和32

linux大本营

C语言

华为亮相KubeCon EU 2023 新云原生开源项目Kuasar推动“云上演进”

华为云开发者联盟

开源 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

【Python实战】Python采集皮肤图片数据

BROKEN

三周年连更

数仓实践丨主动预防-DWS关键工具安装确认

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

Java 应用程序在 Kubernetes 上棘手的内存管理

做梦都在改BUG

Java Kubernetes JVM 内存管理

免费云堡垒机用哪个牌子软件好?包含哪些功能?

行云管家

云计算 网络安全 IT运维 云堡垒机

如果不知道这4种缓存模式,敢说懂缓存吗?

Java你猿哥

缓存 架构 ssm 架构设计 cache

华为ISDP数字化现场作业亮相第十七届工程建设行业信息化发展大会

平平无奇爱好科技

GitHub 如何从单体架构迁移到微服务架构?_架构_Sha Ma_InfoQ精选文章