技术债务的永恒问题


这是最酷的项目浮雕之一。 在图中-CPU处理所有用户请求所花费的总时间的图表。 最后,您可以看到向PHP 7.0的过渡。 从5.6版开始。 这是2016年,从11月24日下午开始切换。

从计算的角度来看,Tutu.ru主要是一个从A点到B点购买机票的机会。为此,我们制定了大量时间表,缓存许多航空公司系统的响应,并定期对数据库进行难以置信的长时间联接查询。 通常,我们是用PHP编写的,直到最近我们才完全使用它(如果语言准备正确,您甚至可以在其上构建实时系统)。 最近,在Go上重构了性能至关重要的区域。

我们不断欠技术债 。 这发生得比我们想要的快。 好消息:您不必全部报道。 不好:随着支持功能的增长,技术负担也成比例增加。

通常,技术债务是在决策中犯错误的代价。 您没有像架构师那样预测,即您在信息不足的情况下犯了预测错误或做出了决定。 在某个时候,您了解到您需要更改代码中的某些内容(通常是在体系结构级别)。 然后,您可以立即进行更改,但是您可以等待。 如果您等待-利息就冲向了技术债务。 因此,优良作法是不时进行重组。 好吧,或者宣布自己破产,然后重新编写整个程序块。

一切如何开始:整体和一般功能


Tutu.ru项目于2003年作为当时的常规Runet网站开始。 也就是说,它是一堆文件而不是数据库,而HTML + JS前面的PHP页面。 我的同事尤里(Yuri)有一些出色的技巧,但是有一天他最好告诉他。 我于2006年加入该项目,首先是一名外部顾问,可以提供建议和法规方面的帮助,然后在2009年,我作为技术总监调任该州。 首先,有必要按机票方向整理东西:这是建筑中最重,最复杂的部分。

我提醒您,在2006年,有火车时刻表,并且有机会购买火车票。 我们决定将机票部分作为一个单独的项目进行,也就是说,所有这些仅在前端结合在一起。 最终,这三个项目(火车时间表,铁路和航空)都以自己的方式编写。 那时,该代码对我们来说似乎是正常的,但还没有完成。 非完美主义者。 然后他变老了,用拐杖遮住了自己,到2010年在铁路上变成了南瓜。

在铁路方面,我们没有时间偿还技术债务。 重构是不现实的:问题出在架构上。 我们决定再次拆除并重做所有内容,但是在进行中的项目上也很困难。 结果,仅将旧的URL留在最前面,然后逐块进行重写。 作为基础,我们采用了一年前在航空业发展中使用的方法。

用PHP重写。 显然,这不是唯一的方法,但是对我们来说没有合理的选择。 他们之所以选择它,是因为他们已经有了经验和成就,很显然这是高级开发人员掌握的一种好语言。 在C和C ++的替代方案中,生产力令人发疯,但是任何重新构建或对其进行更改都像一场噩梦。 好的,没有提醒。 真是一场噩梦。

从高负载项目的角度来看,MS和所有.NET甚至都没有考虑。 然后,除了基于Linux之外,别无选择。 Java是一个很好的选择,但是它需要内存中的资源,它永远不会宽容初级错误,因此它无法迅速发布发行版-很好,或者我们不知道。 即使到现在,我们也不会将Python视为后端,仅用于数据处理任务。 JS-完全在前面。 那时(和现在)没有Ruby on Rails开发人员。 去不见了。 仍然有Perl,但是专家认为它对Web开发没有帮助,因此他们也放弃了它。 剩下的是PHP。

下一个完整的故事是PostgreSQL与MySQL。 一个更好的地方,另一个地方。 通常,选择最佳结果是一种很好的做法,因此我们选择了MySQL及其分支。

开发方法是整体的,然后根本没有其他方法,而是库的正交结构。 这是现代以API为中心的方法的开始,当每个库都在外部具有外观时,可以从项目的其他部分直接在代码内部提取外观。 当每个级别在输入处具有特定格式并将某种格式传递给代码时,库就以“层”形式编写,单元测试在它们之间旋转。 那就是像测试驱动开发之类的东西,但是像素化且令人恐惧。

所有这些都托管在多台服务器上,这使得在负载下进行扩展成为可能。 但是,与此同时,不同项目的代码库在系统级别上的交集很强。 实际上,这意味着铁路项目的变化可能会影响我们自己的飞机。 并经常感动。 例如,在铁路中,有必要扩展支付工作-这是对共享库的修订。 飞机也可以使用它,因此需要进行联合测试。 我们通过测试筛选了依存关系,这或多或少是正常的。 即使在2009年,该方法也相当先进。 但是,负载仍然可以从一种资源中添加另一种资源。 数据库中存在一个交叉点,导致整个站点中的制动器形式产生令人不快的效果,并且在一个产品中存在局部问题。 由于对数据库的大量查询,铁路部门多次在磁盘上杀死了飞机。

我们通过添加实例并在它们之间进行平衡来进行扩展。 巨石原样。

轮胎年龄


然后,我们走了一条相当微不足道的道路。 一方面,我们开始分配服务(今天这种方法称为微服务,但我们不知道“微”这个词),但是为了进行交互,我们开始使用总线进行数据传输,而不是像现在那样使用REST或gRPC。 我们选择AMQP作为协议,选择RabbitMQ作为消息代理。 到那时,我们已经非常有名地掌握了PHP守护程序的启动(是的,有一个完全可用的fork()实现以及用于处理进程的所有其他功能),因为很长一段时间以来,我们一直使用诸如Gearman之类的东西将请求并行化到保留系统。 。

他们在兔子身上搭了一个经纪人,结果证明这一切并没有真正承受重担。 某种网络丢失,转发,延迟。 例如,由几位经纪人组成的集群“开箱即用”,其行为与开发人员所说的有所不同(它从来没有发生过,而且从未发生过)。 总的来说,他们学到了很多东西。 但是最后,我们获得了服务所需的SLA。 例如,负载最大的RPS服务的速率为400 rps,这是从客户端到客户端的第99个百分位数往返,包括总线和35 ms左右的服务处理。 现在,在总线上总共可以观察到约18 krps。

然后是公共汽车的方向。 我们立即在服务体系结构上编写了完整的文章。 由于所有内容都是从头开始编写的,因此结果非常好,快速且方便,尽管有必要不断完善工具以采用新方法。 是的,所有这一切都在虚拟机上进行,PHP守护程序在其中通过总线进行通信。 恶魔是从Docker容器开始的,但没有像Openshift或Kubernetes这样的编排解决方案。 在2014年,我们才刚刚开始谈论它,但我们并未考虑采用这种销售方式。

如果比较飞机票和火车票售出了多少张公交车票,您将大跌眼镜。 在火车和飞机上,很难转换到新的架构,因为它具有正常工作的功能,真正的负担,而且总要在做新的事情或花钱偿还技术债务之间做出选择。

迁移到服务是一件好事,但很长一段时间,但是您现在需要处理负载和可靠性。 因此,与此同时,他们开始采取有针对性的措施来改善整体结构的寿命。 后端被分为产品类型,也就是说,它们开始根据类型来更灵活地控制请求的路由:与铁路分开的航空等。可以独立地预测负载和规模。 例如,当他们知道铁路上的新年销售高峰时,便添加了几个虚拟机实例。 然后,恰好在一年的最后一个工作日之前的45天开始,并且在11月14日至15日,我们遇到了双重负担。 现在,FPK和其他运营商已开始销售60天,90天甚至120天的许多机票,并且这种高峰已经扩散。 但是在4月的最后一个工作日,5月之前总是有电动火车的负荷,而且仍然有高峰。 但是关于车票的季节性和复员的方式,我的同事们从铁路上可以更好地了解,而我将继续讲一下建筑。

在2014年的某个地方,他们开始建立包含许多小型数据库的大型数据库。 这很重要,因为它正在危险地增长,并且跌落至关重要。 我们开始为特定功能分配单独的小型数据库(用于5-10个表),这样其他服务将减少影响,并且所有这些都可以轻松扩展。 值得注意的是,为了进行负载平衡和扩展,我们使用了副本进行读取。 复制失败后,要恢复大型基地的复制副本可能要花费几个小时,而所有这些时间我都不得不“假装飞过,一只翅膀飞翔”。 这种时期的记忆仍然在耳朵之间的某些地方引起令人不快的寒冷。 现在,我们有大约200个不同基础的实例,并且用手管理如此多的安装是一项费力且不可靠的工作。 因此,我们使用Github Orchestrator,它可以自动执行副本和proxySql的工作,以分配负载并防止特定数据库的故障。

像现在


通常,我们逐渐开始分配异步任务,并在事件处理程序中分离它们的启动,以使一个任务不会干扰另一个任务。

当PHP 7发布时,我们在测试中看到了性能和减少资源消耗方面的巨大进步。 转移到它的过程中,痔疮很少,整个项目从测试开始到整个产品的完全翻译都花了六个多月的时间,但此后资源消耗减少了近一半。 CPU负载时间表-在文章顶部。

整体程序一直存活到今天,据我估计,大约占代码库的40%。 值得一提的是,没有明确设置用服务替换整个整体的任务。 我们务实地迈进:所有新的事情都在微服务上完成,如果您需要在整体中完善旧功能,那么,如果只有很小的改进,我们会尝试将其转移到服务体系结构中。 同时,测试中涵盖了整体,因此我们可以每周两次部署足够质量的产品。 功能以不同的方式涵盖,单元测试相当完整,UI测试和验收测试几乎涵盖了整个门户功能(我们有大约15,000个测试用例),API测试或多或少是完整的。 我们几乎不做负载测试。 更准确地说,我们的登台在结构上与产品类似,但在功率上却不相同,并且具有相同的监视功能。 我们生成了一个负载,如果我们发现旧版本上的先前运行在时间上有所不同,我们会发现它有多关键。 如果新版本与旧版本大致相同,则我们将它们发布到产品中。 无论如何,所有功能都将在开关下熄灭,因此,如果出现问题,您可以随时将其关闭。

重型功能总是经过1%用户的测试。 然后我们转到2%,5%,10%,因此我们覆盖了所有用户。 也就是说,在浪涌导致服务器死亡并提前断开连接之前,我们总是可以看到非典型负载。

必要时,当团队专注于特定任务时,我们将(并将花费4-5个月)进行重新设计项目。 当本地重构不再有用时,这是削减Gordian结的好方法。 因此,我们几年前就开始进行空中开发:我们重新设计了架构,做到了–立即加快了开发速度,并能够启动许多新功能。 重新设计两个月后,由于功能,它们为客户增长了一个数量级。 他们开始更准确地管理价格,联系合作伙伴,一切变得更快。 喜乐 我必须说,现在是时候再次做同样的事情了,但这是命运:建立应用程序的方式正在改变,新的解决方案,方法和工具正在出现。 要保持业务发展,您需要成长。

重新设计的主要任务是进一步加快发展。 如果不需要新的东西,则不需要重新设计。 无需发明新的产品:对现代化进行投资是没有意义的。 因此,在保持现代堆栈和体系结构的同时,人们可以更快地投入工作,新的连接速度更快,系统的行为更加可预测,开发人员对项目的工作也更加感兴趣。 现在,有一项任务是完成整体设计而不将其完全丢弃,以便每个产品都可以上传更新,而不必依赖于其他产品。 也就是说,以整体形式获取特定的CI / CD。

今天,我们不仅使用Rabbit,还使用REST和gRPC在服务之间交换信息。 一些微服务是用Golang编写的:那里的计算速度和内存处理非常出色。 有人呼吁实现对nodeJS的支持,但最后我们只将该节点留给服务器渲染,而业务逻辑留在PHP和Go中。 原则上,选择的方法允许我们以几乎任何语言开发服务,但是我们决定限制动物园,以免增加系统的复杂性。

现在,我们将介绍在OpenShift编排下可在Docker容器中工作的微服务。 一年半之内的任务-所有90%的任务都在平台内部进行。 怎么了 因此,部署速度更快,版本检查速度更快,开发环境的销售差异也较小。 开发人员可以更多地考虑其实现的功能,而不是如何部署环境,如何配置环境,从何处启动环境,这就是更多好处。 再次-操作问题:微服务很多,需要由管理人员自动化。 手动-非常高的成本,手动控制出错的风险,并且平台可以正常缩放。

每年,我们的工作量都在增加-30%至40%:越来越多的人正在掌握Internet的窍门,不再去实体收银台,我们在现有产品和功能上添加新产品和功能。 现在,每天约有100万用户访问该门户。 当然,并非所有用户都产生相同的负载。 有些东西根本不需要计算资源,例如,搜索是相当耗费资源的组件。 在那里,航空中的一个滴答“加减三天”将负担增加了49倍(来回看时,矩阵是7乘7)。 与在铁路系统和空中寻找机票相比,其他所有事情都非常简单。 资源中最简单的事情是冒险旅行搜索 (就架构而言,这并不是最简单的缓存,但是旅行的数量远远少于票证组合),然后是火车时刻表 (可以通过标准方式轻松地进行缓存),然后其他所有内容。

当然,技术债务仍然堆积如山。 从四面八方。 最主要的是及时了解您可以在哪里进行重构,一切都会好起来,您无需动手(有时会发生:如果没有计划的更改,我们将与Legacy一起生活),但是我们需要在某个地方着急并进行自我改造,因为没有未来不会。 当然,我们会犯错误,但总体而言,Tutu.ru已经存在16年了,我喜欢这个项目的发展动力。

Source: https://habr.com/ru/post/zh-CN456430/


All Articles