杀死微服务

  • A+
所属分类:安全闲碎

杀死微服务


在Botify(Botify是一家与Expedia和Nike等客户合作的搜索引擎优化公司),我们工程师团队的核心价值就是“所有权”。我们赋予工程团队和产品团队充分的自主性和空间,让他们能真正“拥有”自己的项目,并把这些项目完成。然后,当团队成长到一定规模的时候,我们的技术栈也变得相当臃肿,我们面临了一些问题,比如,如何在不同团队之前共享我们的成果。

到2016年底,我们希望给予工程师和产品经理更多的“所有权”,以使其产品尽可能快且轻松地实现,并推向市场。为此,我们选择将单体的Django应用程序拆分为微服务。

这是一个关于我们如何失败以及为什么现在我们将这些服务移回到单体应用的故事。同时,我们还将花费一些时间来分享这个过程种我们的经验和教训。

在深入探讨之前,我们要强调的是,本文并不是泛泛地对微服务进行谴责。在Botify,我们坚信在合适的情况下使用最合适的工具——我们所要表达的是,微服务目前不是解决我们目前所面临的问题的正确工具。


开始微服务之旅

杀死微服务


首先,我需要普及一些我们公司和技术栈的历史。Botify成立于2012年,基于Python/Django技术栈构建。在2016年初,整个Botify平台都通过我们的Django应用的负载均衡集群提供服务。当时,我们拥有一支大约15人的产品和工程团队。

为什么选择微服务?

有两个主要目标促使我们评估在技术栈中使用微服务的可能性:

  • 提高开发上线的速度:我们希望减少前端和后端之间的依赖,并增加团队对自身项目和技术栈的“所有权”,以在一个小型团队中提高生产力。

  • 统一技术栈:我们在巴黎聘用Python/Django工程师时遇到了越来越多的困难。我们认为在前端和后端统一使用JavaScript会使招聘更加容易,因为我们将全面拥抱“全栈”的JavaScript。


仰望星空脚踏实地

我们决定把身份验证和授权服务作为我们的第一个微服务“试验”。我们认为我们应该一步一步慢慢来,把目前由Django应用程序处理的一个有限的功能范围改造程微服务。我们组建了一个小规模的JavaScript团队来实现NodeJS后端,该后端将为我们的用户处理有关帐户和权限的请求。我们自豪地称之为“客户成功”。

杀死微服务


痛点

组织

当你像我们一样在人为因素的影响下做出技术决策时,你很快就会遇到问题。新组建的团队的组织架构和流程的实施很难与现有团队保持一致。随着功能的孤立发展,知识的分区很快出现。微服务的内部模块并未在团队之间共享,缺少的跨技术栈的Code Review使我们失去了拥有感。

隔离性

大家可能注意到,在上面的服务架构中,“客户成功”的微服务并不是完全隔离的。后端服务之间存在从单体应用到微服务的通信,反之亦然。这并不算很糟糕,甚至可以说相当普遍。但我们仍然认为这会妨碍性能并会导致将服务之间的耦合。从长远来看,这还意味着需要更复杂的机制来确保正常运行和服务的可用性,例如断路器和正常的服务降级。

另一个发现是我们的关系数据库是共享的。在数据库内部,一些表被同时映射到Django和Express/Sequelize中的模型。这意味着对共享表的表结构修改需要在不同的微服务和单体应用之间进行同步。这是域严重分离的结果。

工具

我们花费了大量的时间和人力学习了如何稳健地构建和部署Django应用程序,但是我们之后必须为每个新的技术栈重新学习一遍。在微服务环境中,独立部署是核心优势,与微服务相比,我们仍然对部署单体应用更加自信。我们花了更多的时间来为我们的微服务构建一套可靠,流畅的自动化部署系统。

在日常的开发中我们遇到了越来越多的问题,我们只能通过不断地扭曲和改造我们的解决方案以使我们的体系结构能够更好地工作。在这段时间里,我们继续以Django为技术栈,我们不断地改善代码,提高测试覆盖率,代码的可测试性,优化依赖关系和性能。我们举办了一些Django和Python活动,并招募了一些出色的Django开发人员来处理我们的单体应用。

我们颠覆了驱动我们迁移到微服务的最初范例。

微服务的技术栈是从当前流行的框架中选择的,并且也是开发团队最了解的。但是,在Botify,我们坚信为正确的工作选择正确的工具:正确的工具不一定是最著名的工具,也不须要是我们已经知道的工具。

我们认为是时候去认真地(重新)考虑我们的微服务架构和我们的“客户成功”的后端了。


去而复返:回到单体应用的未来

杀死微服务


每日在JavaScript身份验证服务和Django单体应用之间反复来回切换、做着枯燥乏味的工作,我们终于和工程师们围坐在桌子旁,开始权衡我们架构的利弊,以及迁移的潜在成本。我们大胆地选择将身份验证后端服务嵌入到Django应用中,这意味着我们将把Node服务迁移到Django,就如同我们在2016年把API从Django迁移到Node一样。以下是为什么我们会像这样退回到单体应用模式的一些普遍原因。

原因

正确问题的正确解决方案:请牢记我们的案例以及Botify是为谁而服务。我们的平台是一项企业级B2B服务,可帮助网络上大型网站改善其SEO。我们面临的扩展挑战主要在于对客户数据的分析,而不是我们的应用流量:我们每个月都在处理PB级的数据,比如网络爬虫或是数据处理这类长时间运行的任务。但是,我们不需要也没有大量的面向个人用户的流量,因为在撰写本文时,我们的用户主要是那些大型网站的SEO经理。处理用户相关数据的微服务架构旨在为大流量B2C平台提供服务,而Botify的挑战在于动态聚合GB数量级的SEO数据并使其在数秒内可用。处理大约几万个客户的元数据,以毫秒为单位的响应并不是一项需要高度可扩展的微服务架构的任务。相反,我们的后端之间的通信正在减缓这些轻量的检索过程,并使这些请求花费更多时间。

共享资源意味着必须同步部署多个服务。为此说明这个问题,我们可以拿出这样一个案例:最近一个项目要删除一个不再维护的依赖项(它将JSON类型的数据库列作为文本处理),以利用PostgreSQL中的内置jsonb列类型。但是,这些文本类型的列在两个代码库之间共享,并且进行平滑迁移是很麻烦的。同步部署多个后端很容易出错,尤其是在扩容多副本机制和负载均衡的要求下,并且它也违背了微服务的最初优势。在Botify,我们一直衷爱这个带有警告色彩的故事,来表明我们对同步部署的厌恶,如果你有时间的话可以细品一下,这非常有趣。

割裂的代码库会淡化知识的共享,所有权并具有很强的隐含性:微服务最初是由很少的人实现的,后来为了使新来的同事不再需要去维护这些旧的代码,这些人解决了所有的演进问题。这种现象与我们的代码审查心态背道而驰:任何人都可以审查并了解正在发生的事情。我们喜欢在Botify开放工作,因此团队中的任何人都可以查看所有代码并提出意见或建议。有了单独的技术栈,团队和语言,我们失去了团队合作和严格审查带给开发团队的许多好处。

这些是促使我们杀死身份验证微服务并将其API迁移回Django单体服务的主要原因。

杀死一个微服务

没有100种解决方法。弃用和终止微服务非常简单。

我们想通过尽可能少的改动来“杀死”客户成功后端,这意味着需要模仿微服务的行为,在单体应用中逐一实现API接口并逐步切换路由。这样,更改只会影响API,而不会影响前端,因此避免了可怕的同步部署或API版本控制。

这些是我们杀死微服务的步骤:

  • 确认路由规则,列出微服务提供的所有API路由,并确定其用途和目标。其中部分的路由是非常容易处理的:这些路由可能未使用或不必要。

  • 在目标后端创建路由,最麻烦的任务是在目标代码库中编写相同的逻辑。相同的输入,相同的输出,旨在最大程度地降低切换风险。

  • 对请求进行灰度,比较不同代码给出的响应,设置一个简单的灰度系统,该系统将同时使用两个后端并比较响应以捕获不匹配项。

  • 对你的结果进行质量检查,如果有问题,就做一些修复,对逻辑进行广泛的质量检查,以捕获任何剩余的错误。

  • 逐步扩大灰度的百分比,越来越多地灰度的流量以使用新迁移的逻辑。

  • 一旦流量100%落在单体应用上,删除微服务并清理,删除正在运行的实例和主机。


我们很自豪地说,我们在2月4日10:34成功杀死了我们的“客户成功”NodeJS身份验证后端,没有使用任何同步部署或API版本控制。

杀死微服务


痛点

如前所述,杀死后端非常简单,使用我们的方法,我们并未遇到重大的阻碍。

我们遇到的最令人沮丧的问题实际上是我们自己引入的一个问题:构建“客户成功”后端时,我们选择了无论REST调用结果如何始终以200响应的策略,并带有一个布尔值和statusCode响应键,用于展示更丰富的错误信息:

{
  "errors": [
    {
      "message""Organization was not found",
      "code": 404,
      "name""NotFoundError"
    },
  ],
  "success"false,
  "statusCode": 404
}

尽管这是一种常见的REST做法,但在Django REST框架中并不是一个很好的选择。在不更改接口的情况下将这些端点从Express迁移到Django包括大量改写了Django REST框架行为以匹配我们在Express中实现的行为。我们没有修改HTTP状态代码的错误,而是对其进行了修改,以便可以使用相同的响应键返回200个状态代码。虽然令人沮丧,但并非世界末日。这种差异会带来一些问题,如果我们认为有必要,我们会将其作为我们连续的“Kitchen”(读作技术债务)的一部分进行修复。

从好的方面来说,我们的微服务定义了一个(更)聪明的跨源资源共享策略,该策略必须在我们的单体应用上重新实现。即使一开始就很麻烦,但我们认为它能够更好地保护应用程序免受恶意攻击。


总结

杀死微服务


我们在这里不是想谴责微服务,也并不想对Django或者Express作为REST后端的优劣进行评论。他们两者都有适用的场景,但是我们坚信在正确的时候选择正确的解决方案来设计开发我们的产品。今天,我们显然已经从这些微服务的迁移实践中受益,我们不再需要维护两套不同的方案。

附带说明一下,当我们构建微服务后端时,我们实际上在其旁边构建了另一个后端,今天它仍然好好地活着(可能以后也会继续存在?)。它的功能与用户以及我们的主要服务之间的联系不多,因此它可以很好地工作,并且依赖和痛点也更少。如果我们不得不再次实现类似的功能,我们可能会采用我们的单体架构方案,但是我们不需要修复目前尚未解决的问题。

对于某些案例和场景,我们仍然相信微服务。所以,我们并不排除将来转型微服务架构的可能性。但是,我们已经从错误中吸取了教训。

我们的团队组织也不断在发展,现在分为团队,独立实体,由前端和后端工程师,产品经理和测试工程师来处理产品功能和技术挑战。从过去的经历中学到经验和教训,每个团队都以近乎独立的方式设计和实现其功能,但是代码库是共享的,跨团队的,Botify的所有工程师都能够进行代码审查。这使我们能够分享技术栈的所有权,共享各个团队的知识储备、实践经验,同时对其进行的不断的更新维护也能使所有人受益。作为一个团队,我们希望借此共同达到卓越的技术水平。

原文链接:https://medium.com/botify-labs/to-kill-a-microservice-d6c9e7ad444c


往期精彩

Linux 操作系统知识地图2.0,我看行

《我的Python精进之路:60天和70个案例》

《MySQL 面试,必须掌握的 8 个知识点》

《Docker是什么?》

Kubernetes是什么?

《Kubernetes和Docker到底有啥关系?》

教你如何快捷的查询选择网络仓库镜像tag

《Docker镜像进阶:了解其背后的技术原理》

《教你如何修改运行中的容器端口映射》

k8s学习笔记:介绍&上手

k8s学习笔记:缩扩容&更新

Docker 基础用法和命令帮助

在K8S上搭建Redis集群

灰度部署、滚动部署、蓝绿部署

PM2实践指南

Docker垃圾清理

Kubernetes(k8s)底层网络原理刨析

容器环境下Node.js的内存管理

MySQL 快速创建千万级测试数据

Linux 与 Unix 到底有什么不同?

浅谈几种常见 RAID 的异同

Git 笔记-程序员都要掌握的 Git

老司机必须懂的MySQL规范

Docker中Image、Container与Volume的迁移

漫画|如何用Kubernetes搞定CICD

写给前端的Docker实战教程

Linux 操作系统知识地图2.0,我看行

16个概念带你入门 Kubernetes

程序员因接外包坐牢456天,长文叙述心酸真实经历

IT 行业老鸟,有话对你说

HTTPS 为什么是安全的?说一下他的底层实现原理?



免责声明:本文内容来源于网络,所载内容仅供参考。转载仅为学习和交流之目的,如无意中侵犯您的合法权益,请及时联系Docker中文社区!



杀死微服务
杀死微服务
杀死微服务

www.dockerchina.cn

杀死微服务

看完文章,点个「在看」奥,谢谢!杀死微服务

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: