微服情缘经验分享Allegro(服务微服团队部署数据库)「微服网络」

微服情缘经验分享Allegro(服务微服团队部署数据库)

这篇博文讨论了作者在中欧主要电子商务平台 Allegro 使用微服务的 10 年经验。
以下是关键点:问题原因2013 年,Allegro 因 PHP 应用程序单一且数据库单一而面临开发瓶颈。
该公司决定彻底改革其架构。
微服务Rubicon 项目启动,旨在过渡到微服务、Java、云部署和敏捷方法这是一次重大的赌博,但对于公司的发展而言却是必要的。
转型涉及大量基础设施工作、工具开发和学习。
所需辅助工作量比最初预计的要大得多。
主要变化包括:从 SQL 切换到 Cassandra 和 MongoDB 等 NoSQL 数据库转向云部署,首先使用 OpenStack (IaaS),然后使用 Mesos 和 Marathon 的 PaaS为开发人员开发自助服务工具创建用于服务部署和维护的自定义应用程序控制台微服务架构允许尝试不同的编程语言。
虽然最初选择了 Java,但该公司现在使用 Kotlin、Scala、Go、Python、Elixir 等语言来提供各种服务。
向微服务的过渡实现了更快的开发速度、更好的可扩展性和更灵活的技术选择。
作者强调,虽然遵循最佳实践通常是可取的,但有时打破规则(例如将服务拆分为读写组件)可以有效地解决特定问题。
经验分享:1、NoSQL在将庞大的单体重构为较小的微服务时,我们还需要为每个微服务选择要使用的数据库。
由于我们关注的是水平可扩展性,因此我们尽可能选择NoSQL 数据库。
这是一个很大的变化,因为单体解决方案依赖于单个庞大的 SQL 数据库。
最重要的是,它没有很好地模块化,在许多地方,领域层和持久层之间几乎没有或根本没有分离。
如果单体结构良好,将其拆分为单独的服务会容易得多。
不幸的是,事实并非如此,因此我们必须在进行其他重构和清理的同时,过渡到 NoSQL。
通常,我们必须深入重构数据和处理数据的操作,尤其是事务操作,以便它们可以在新环境中执行。
即使我们可以将代码划分为事务或相关操作集最终位于同一服务中,这通常也是一项艰巨的工作。
如果操作跨越新架构中的多个服务(和数据库),事情会变得更加复杂。
这就是为什么将大型应用程序划分为较小块比最初看起来困难得多的原因之一。
Cassandra最初是我们用于大多数任务的首选 NoSQL 数据库。
过了一段时间,我们才知道每个数据库在某些用例中都有用,而在其他用例中则用得不好,我们需要多语言持久性来实现高性能并在所有情况下获得所需的灵活性。
我所在的团队是公司最早采用 Cassandra 的团队之一,就像第一次在生产中运行某些东西时经常出现的情况一样,我们在 Cassandra 部署中发现了许多问题,这些部署“准备就绪”但尚未在生产中测试。
负责数据库的团队和我们一样,也在学习全新的东西。
有人有时会反对将应用程序的持久层与域逻辑分离,理由是“你永远不会用另一个数据库替换它”。
大多数情况下确实如此,但在一项服务中,我们确实不得不从 Cassandra 切换到 MongoDB,因为 我们发现我们的访问模式与 Cassandra 的数据模型不太一致。
我们设法在为期两周的冲刺内完成了这项工作,除了服务变得更快之外,它的客户不会注意到任何差异,因为外部 API 保持不变。
虽然切换数据库的前景(通常是理论上的)并不是分离域层和持久层的唯一原因,但在这种情况下确实有很大帮助,大约在这个时候,我开始明白为什么我们要创建这么多类,即使你可以将所有代码塞进一个类中。
在我学习大数据处理并创建了一项原本要处理数据库中某些数据的作业时,我也曾设法终止了我们的 Cassandra 实例。
该作业的并行性非常高,以至于它生成的大量请求甚至让 Cassandra 不堪重负。
幸运的是,这种情况也显示了为每项服务设置单独数据库的优势,因为只有该单个服务会遇到中断。
2、进入 云端在加入 Allegro 之前,我只部署过物理服务器,因此迁移到云是一个很大的变化。
起初,我们将服务部署到OpenStack中配置的虚拟机上。
只需单击几下即可设置完整的虚拟服务器,而不必等待数天才能获得物理机器,这真是太方便了。
我们使用Puppet为每个服务完全配置虚拟机,因此虽然您必须编写一次配置,但之后几乎可以立即启动为您的服务配置的新服务器。
这种IaaS(基础设施即服务)方法非常方便,而且有很大变化,但在很多方面它仍然与我以前所知道的相似:你有一台机器,即使是虚拟的,你也可以ssh在那里运行任何你想要的命令,即使这很少需要,因为 Puppet 已经为你设置好了一切。
真正的革命发生在我们转向PaaS(平台即服务)模型时,当时该模型基于 Mesos和Marathon。
突然之间,不再有虚拟机,你也无法ssh 访问运行软件的服务器。
对我来说,这真是一次文化冲击,尽管到目前为止,我对我们推出的所有酷炫技术都非常热衷,但一想到不再ssh有虚拟机,我就感到害怕。
如果我甚至无法访问系统,我怎么知道系统里发生了什么?尽管我有所保留,但我逐渐发现,尽管无法通过访问机器,但你确实可以部署和监控软件ssh。
回想起来,这听起来很奇怪,但这是我职业生涯中最困难的技术转型之一。
一段时间后,我们在 Mesos 上构建了一些抽象层,包括一个自定义应用控制台,它允许您部署服务并执行所有维护任务。
它将您与底层系统的大多数细节隔离开来,而且非常有效,以至于当我们后来从 Mesos 迁移到Kubernetes 时,对于如此大的变化,对大多数团队的影响比您想象的要小得多。
我们的应用控制台是一个内部项目,但如果您熟悉Backstage,它应该可以让您了解我们在这里谈论的是什么样的工具。
3、监控最初所有的监控都是集中的,由一个团队处理。
如果你想在 Zabbix中使用任何非标准图表或任何自定义警报(显然,你想这样做),你必须在 JIRA 中创建一个工单,准确描述你想要什么,过一段时间,监控团队就会为你设置。
整个过程大约需要一周时间,而且很多时候,在看到新图表后,你就知道你想改进它,所以你会提交另一张工单并再等一周。
不用说,这非常令人沮丧,我认为这是我早期取得的巨大成功之一,当时我一直敦促监控团队,直到他们最终让步,允许开发团队自己配置所有可观察性设置。
4、走向多语言Rubicon 最初打算用 Java 重写我们的软件,但我们很快就开始尝试其他 JVM 语言。
我所在的团队考虑过 Scala 一段时间,但经过一些实验后,决定不将其作为主要语言。
然而,其他一些团队确实选择了它,尽管他们在 Allegro 中只占少数,但直到今天,我们仍然有一些用 Scala 编写的微服务。
另一方面,在编写Spark作业时,Scala 是 Allegro 的主要语言。
大约在 2015 年的某个时候,一位队友发现了一种相对较新但前景光明的语言,叫做Kotlin。
当时我们刚开始开发一种新的微服务,它还非常简单,也不是很关键。
他决定用它作为试验台,我记得在两天之内就用 Kotlin 重写了整个程序。
由于这些服务是独立的,而且这个服务还不是很重要,我们可以安全地在生产中进行实验,并评估重写服务的稳定性。
通过编写实际可用于生产的代码来学习该语言,而不仅仅是玩弄一次性代码,这使我们能够在实际使用场景下检查该语言的优缺点。
Kotlin 流行起来,我们逐渐开始将它用于越来越多的新服务,并将其用于现有 Java 服务的新功能,因为将两者混合起来很容易。
许多服务已经使用 Groovy和Spock进行测试。
目前,Kotlin 在 Allegro 比 Java 更受欢迎,我们在博客上发表了一些关于 Kotlin 的文章,其中一篇不幸引起了很多争议,并在公司内部和外部引起了(在我看来是应得的)轩然大波。
除了 JVM 语言之外,我们现在还有用C#、 Go、Python、 Elixir以及我忘记提到的其他几种语言编写的微服务。
这只是后端,但我们的 前端架构还允许使用各种语言编写的组件。
除了面向客户的业务代码外,还有内部工具和实用程序,有时使用其他通用语言和DSL编写。
最后,还有整个 AI 世界,包括提示生成 AI,您也可以将其视为一种编程语言。
我在这里要强调的重点是,使用微服务使我们能够安全地试验各种编程语言,有意识地限制这些试验的爆炸半径,以防出现任何问题,并逐步进行所有转换。
当然,这一切都有一个目的:找到最适合这项工作的工具,并在它们对我们最有帮助的地方使用所有不同语言的优势。
这不是为了它而引入新工具,这只会造成混乱并带来与未来维护相关的风险。
我认为团队在做出技术决策时获得的自主权,以及对结果的责任,使我们能够学习和找到新的方式,同时限制与实验相关的风险。
与许多其他情况一样,当组织的工作方式(团队自主权)与技术解决方案(微服务)保持一致时,事情就会顺利进行。
5、明智地 使用反模式良好的做法是启发式的:大多数时候,遵循它们是个好主意。
例如,两个微服务不应共享数据库表,因为这会导致紧密耦合:您不能对架构进行更改,而只部署一个服务而不部署另一个服务。
您的两个服务不是独立的,而是形成一个分布式整体。
避免这种情况只是常识。
不过,您应始终牢记良好做法存在的原因、它能保护您免受哪些损害以及它会带来哪些成本。
有一次,我们团队内部讨论了如何最好地处理一个特殊的性能问题。
我们的服务连接到 Elasticsearch 实例并执行两种操作:读取和写入。
读取次数更多,但写入会带来沉重的负载(对服务本身而言 — Elastic 可以处理)。
写入是突发的,因此大多数时候运行良好,但是当写入突然到来时,整个服务的性能就会受到影响,读取时间也会受到影响。
我们尝试了各种机制来隔离这两种操作,但无法有效地做到这一点。
一位同事建议我们将服务一分为二,一个负责处理读取,另一个负责写入。
我们进行了长时间的讨论,我提出了让一个服务作为数据所有者、负责读取和写入的论点,并强调了拆分可能出现的问题。
虽然保持服务完整似乎是一件优雅的事情,但我没有一个好的解决性能问题的方法。
另一方面,我同事提出的拆分服务的想法虽然有些混乱,但确实提供了一个解决问题的机会。
因此,我们决定尝试一下,看看这种方法是否能解决性能问题,以及副作用有多严重。
我们就是这么做的,而且基于反模式的解决方案效果很好:性能问题消失了,尽管共享通用的 Elasticsearch 集群,但两个服务仍然可以维护。
我们无法立即全面评估这方面,但时间也证明了我的同事是对的:在我们后来使用该代码库的 3 年多时间里,我们只遇到过一次与共享 Elasticsearch 相关的问题,并且我们设法快速解决了这个问题。
不过,这确实有帮助,因为这两个服务一直由同一个团队开发,而且在我们引入拆分时,模式已经非常稳定,并且不会经常更改。
尽管如此,如果我坚持保持清洁,我们可能要花更多的时间来对抗性能问题,而不是在服务之间共享 Elasticsearch 导致的单个问题上浪费的时间。
知道何时使用模式,知道何时使用反模式,并明智地使用两者。
6、一种尺寸并不适合所有情况我认为我们在确定微服务规模方面一直非常务实。
很难定义一套具体的规则来找到合适的规模,但过于偏向某一方向会造成相当大的麻烦。
如果服务规模过大,单个团队将难以维护和开发,或者会出现与单体应用类似的扩展问题。
如果服务规模过小,您可能会因将逻辑分散到太多地方、调试问题以及系统分布过度导致的性能损失而感到不堪重负。
我在 Allegro 工作过的大多数服务都不算小,而且包含了一些不小的逻辑。
有时会有激烈的讨论,讨论在哪里实现某个功能,特别是应该在现有服务中还是在新服务中实现。
事后看来,我认为大多数决定都是合理的,但肯定也有一些我们认为会发展起来的功能最终被放到了新服务中,但新服务却从未发展起来,规模仍然太小;还有一些情况,有些功能被附加到现有服务上,因为这样更容易实现,但后来却带来了一些麻烦。
我认为我只见过一次团队陷入纳米服务陷阱,即服务设计得太小,拆分带来的麻烦比它本身的价值还要多。
另一方面,肯定有一些服务无论如何都不能再称为微型服务了。
这并不一定是坏事。
只要服务履行了明确定义的角色,一个团队就足以处理它,而且你必须一起部署和扩展整个服务,那么一切都会好起来。
在某些情况下,服务变得太大了(迹象是它们包含的逻辑片段彼此之间只有非常松散的联系,并且在某个时候多个团队经常有兴趣做出贡献),我们确实会回到他们身边并将它们拆分。
这不是很容易,但可以做到,第二难的部分通常是找到正确的划分路线。
唯一更难的是找到时间来执行此类操作,但经过一些谈判和坚持,一段时间后我们通常会成功。
有人一直在讨论我们是否有太多的微服务。
这不是一个紧迫的问题,例如基础设施中的某些技术限制和过度配置的成本(每个服务都会分配内存或 CPU 等资源,并有一定的余量,这些余量加起来会数量很大)。
尽管如此,我们的服务数量远远超过一千个,但它们的数量却只是一个小麻烦,这说明我们的工具和组织能力很好。
事实上,由于一些自定义工具,创建新服务非常容易(也许太容易了?),管理现有的服务也非常愉快。
这要归功于我们早期(并将继续)的巨额投资:我们从一开始就知道,虽然每个微服务可能相对简单,但将整个系统结合在一起的粘合剂却非常复杂。
没有它,事情就不会那么顺利。
另一个因素显然是我们的系统有微服务的实际用例:我们有数百个团队,一个容量和复杂性不断增长的系统,以及一个真正分布式系统的规模。
我认为,如今互联网上反对微服务的大部分情绪都源于将微服务视为可以解决任何问题的灵丹妙药,无论它们在特定情况下是否真正有意义,或者没有意识到它们可以带来巨大的回报,但也需要大量投资。
7、服务网格和通用库与我们微服务生态系统相关的最近真正重大的变化可能是向服务网格的迁移。
从开发人员的角度来看,这似乎并不是那么激进,但它需要基础设施团队的大量工作。
最重要的收获是可以在一个地方控制服务行为的某些方面。
例如,最初如果您想在服务之间建立安全连接,您必须使用通用库在代码中支持TLS。
使用服务网格,您可以全局启用它,而开发人员甚至不必知道。
这使得维护由一千多个服务组成的庞大生态系统变得更加容易。
每个微服务都需要某些行为才能在我们的环境中正常工作。
例如,它需要一个健康检查端点,允许 Kubernetes 判断服务实例是否正常工作。
我们有一个书面的微服务合同来定义这些要求。
还有一些功能不是绝对必要的,但许多服务会发现它们很有用,例如各种指标。
我们最初的方法是拥有一组通用库,提供必需的功能和许多不错的功能。
当然,如果您不能或不想使用这些库,您可以自由地这样做,只要您的服务以其他方式实现微服务合同即可。
随着时间的推移,这些库的作用发生了变化,总体方向是缩小其范围。
原因有3个:第一个原因是越来越多的功能可以移至基础设施层,而 Service Mesh 是其中的重要组成部分。
例如,最初与另一项服务通信需要服务发现客户端,该客户端在共享库中实现。
现在,所有这些逻辑都已委托给 Service Mesh,不需要共享库或服务代码中的特殊支持。
另一个原因是开源库已经流行起来,一些我们过去需要自己实现的功能(例如某些指标)现在在 Spring Boot 或其他框架中都可以开箱即用。
重新发明轮子并维护更多代码是没有意义的。
最后,库的问题在于,在 1000 多个服务中更新库是一个缓慢且成本高昂的过程。
而服务网格提供的功能可以几乎立即为所有服务启用或重新配置。
尽管我们不再青睐公用库,但有些功能很难仅靠基础架构实现。
即使是日志记录等简单功能,有时我们也需要只有在服务内运行的代码才能访问的数据。
当我们想要填写某些标准字段以便于搜索日志时,某些字段(例如host或dc)可以轻松由基础架构填写,但有些字段(例如 )thread_name只有服务内部知道,无法在外部处理。
因此,库的作用虽然减弱了,但并没有完全消失。
为了使使用共享库不那么麻烦,我们正在研究尽可能自动化升级的方法,这样我们就可以保持所有版本都是最新的,而不会花费太多开发人员的时间。
原文:十年情缘:Allegro微服务经验分享 - 极道

联系我们

在线咨询:点击这里给我发消息