去吧! PHP团队如何着手编写微服务

大家好! 我叫Alexey Skorobogaty,我是Lamoda的系统架构师。 在2019年2月,我仍然在核心团队的团队领导职位上在Go Meetup上发表演讲。 今天,我想提出一份报告的笔录,您也可以看到。


我们的团队之所以被称为核心,是因为:责任范围包括与电子商务平台中的订单相关的所有内容。 该团队是由PHP开发人员和专家组成的我们的订单处理程序,当时这是一个整体。 我们一直参与并继续处理将其分解为微服务的问题。


图片


我们系统中的订单由相关组件组成:有一个交货单位和一个篮子,折扣和付款单位,最后还有一个按钮,用于发送要在仓库中收集的订单。 此时,订单处理系统的工作开始了,所有的订单数据都将得到验证并汇总信息。


图片


所有这一切都是复杂的多准则逻辑。 块彼此交互并相互影响。 业务连续不断的变化增加了标准的复杂性。 此外,我们拥有不同的平台,客户可以通过这些平台创建订单:网站,应用程序,呼叫中心,B2B平台。 以及严格的SLA / MTTI / MTTR标准(注册指标和事件解决方案)。 所有这些都要求服务具有高度的灵活性和稳定性。


建筑遗产


就像我已经说过的那样,在我们团队成立之时,订单处理系统是一个整体-几乎有10万行直接描述业务逻辑的代码。 主要部分是在2011年使用经典的多层MVC架构编写的。 它基于PHP(ZF1框架),逐渐被用于与各种服务交互的适配器和symfony组件所淹没。 在其存在期间,该系统有50多个参与者,尽管我们设法保持了统一的代码编写风格,但这也施加了其局限性。 另外,出现了许多混合上下文-由于各种原因,系统中实现了一些与订单处理没有直接关系的机制。 所有这些导致了一个事实,目前我们拥有一个大于1 TB的MySQL数据库。


示意性地,初始架构可以表示如下:


图片


当然,顺序在每个层上-但是除了顺序之外,还有其他上下文。 我们首先定义订单的受限上下文并将其称为“客户订单”,因为除了订单本身之外,我一开始还提到了很多块:交付,付款等。 在整体内部,所有这些都难以管理:任何更改都会导致依赖性增加,代码在很长时间内交付给产品,并且错误和系统故障的可能性一直在增加。 但是,我们谈论的是创建订单,这是在线商店的主要指标-如果未创建订单,那么其余的就不那么重要了。 系统故障会导致销售立即下降。


因此,我们决定将客户订单上下文从订单处理系统转移到一个单独的微服务中,该服务称为订单管理。


图片


要求和工具


在确定了我们决定首先从整体中删除的上下文之后,我们形成了我们未来服务的要求:


  • 性能表现
  • 资料一致性
  • 可持续发展
  • 可预测性
  • 透明性
  • 变化增量

我们希望代码尽可能清晰,易于编辑,以便下一代开发人员可以快速进行业务所需的更改。


结果,我们得出了在所有新的微服务中使用的特定结构:


有限的语境 。 从订单管理开始,每一项新的微服务都是根据业务需求创建的。 必须对系统的哪个部分以及为什么需要将其放在单独的微服务中进行具体说明。


现有的基础架构和工具。 我们不是Lamoda中第一个开始实施Go的团队;在我们之前有先驱者-Go团队本身,他们准备了基础架构和工具:


  1. Gogi(swagger)是一个swagger规范生成器。
  2. Gonkey(测试)-用于功能测试。
  3. 我们使用Json-rpc并通过招摇来生成客户端/服务器绑定。 我们还将所有这些部署到Kubernetes,在Prometheus中收集指标,使用ELK / Jaeger进行跟踪-所有这些都包含在Gogi为规范为每个新微服务创建的捆绑包中。

这就是我们新的订单管理微服务的外观:


图片


在输入中,我们拥有数据,我们对其进行汇总,验证,与第三方服务进行交互,做出决策并将结果进一步传输到“订单处理”(订单处理),这是一个庞大,不稳定且需要大量资源的整体。 构建微服务时也需要考虑这一点。


范式转移


选择Go,我们立即获得了以下优势:


  • 静态强类型立即消除了一定范围的可能错误。
  • 并发模型非常适合我们的任务,因为我们需要走动并同时轮询多个服务。
  • 组成和接口也有助于我们进行测试。
  • 研究的“简单性” -在这里不仅发现了明显的优点,而且发现了问题。

Go语言限制了开发人员的想象力。 这成为我们团队的绊脚石,当我们转而使用Go进行开发时,已经习惯了PHP。 我们面临着真正的范式转变。 我们必须经历几个阶段并了解一些事情:


  1. Go很难建立抽象。
  2. 可以说Go是基于对象的,但不是面向对象的语言,因为没有直接继承和其他一些东西。
  3. Go有助于显式编写,而不是将对象隐藏在抽象后面。
  4. 去有流水线。 这启发了我们建立数据处理器链。

结果,我们开始理解Go是一种过程编程语言。
图片


数据优先


我在想如何可视化我们面临的问题并遇到这张照片:


图片


这是世界的“面向对象”视图,我们在其中构建抽象并在其后关闭对象。 例如,这里不仅是一扇门,而且是室内会话初始化程序。 不是瞳孔,而是访客监视器界面-依此类推。


我们放弃了这种方法,将实体放在第一位,而不会被抽象所掩盖。


以这种方式进行推理,我们首先将数据放在首位,然后在服务中获得以下管道:


图片


最初,我们定义一个进入处理程序管道的数据模型。 数据是可变的,并且更改可以顺序发生,也可以并发发生。 这样,我们就可以赢得胜利。


回到未来


突然,开发微服务,我们来到了70年代的编程模型。 70年代后,出现了大型企业整体,出现了面向对象的编程和功能编程-大型抽象使将代码保留在这些整体中成为可能。 在微服务中,我们不需要所有这些,我们可以使用出色的CSP模型( 通信顺序过程 ), Charles Choir于20世纪70年代提出了这一想法


我们还使用顺序/选择/交互-一种结构编程范例,根据该范例所有程序代码都可以由相应的控制结构组成。


嗯,过程编程是70年代的主流:)


项目结构


图片


就像我说的那样,我们首先放置数据。 此外,我们用面向业务的项目代替了“从基础设施”项目的建设。 这样,开发人员在输入项目代码后即可立即看到服务在做什么-这是我们已经确定的非常透明的状态,这是微服务结构的基本要求之一。


结果,我们有了一个扁平的体系结构:一个小的API层加上数据模型。 并且所有逻辑(在我们的上下文中受微服务的业务需求所限制)都存储在处理器(处理程序)中。


没有业务部门的明确要求,我们尽量不要创建新的单独微服务-这就是我们控制整个系统粒度的方式。 如果存在与现有微服务密切相关但本质上是指不同上下文的逻辑,那么我们首先在所谓的服务中对其进行总结。 而且只有在出现持续的业务需求时,我们才将其带入一个单独的微服务中,然后转向使用rpc调用。


为了控制粒度并且不深思熟虑地生成微服务,我们在服务层中总结了一个与该上下文不直接相关但与该微服务紧密相关的逻辑。 然后,如果有业务需要,我们将其带到单独的微服务-然后将其与rpc调用一起使用以访问它。


图片


因此,对于服务处理器中的内部API,交互不会以任何方式改变。


可持续发展


我们决定不预先使用任何第三方库,因为我们处理的数据非常敏感。 因此,我们循环了一下:)例如,我们自己实现了一些经典的机制-幂等性,队列工作者,容错和补偿事务。 我们的下一步是尝试重用它。 包装在图书馆中,也许在Kubernetes Pods中包装在车上。 但是现在我们可以应用这些模式。


我们在我们的系统中实现了一种称为平稳降级的模式:无论聚合信息的外部调用如何,服务都必须继续工作。 以创建订单为例:如果请求进入服务,无论如何我们都会创建一个订单。 即使相邻服务崩溃了,它也必须负责我们必须汇总或验证的部分信息。 而且,即使我们不能在短期内拒绝必须处理的订单处理,我们也不会丢失订单。 这也是我们决定是否将逻辑放在单独服务中的标准之一。 如果以下服务在网络上不可用时,服务无法提供其工作,那么您要么需要重新设计它,要么考虑是否应该将其从整体中取出。


去吧!


当您从经典的面向服务的体系结构(尤其是PHP)编写面向业务的产品微服务时,您会遇到范式转换。 它必须通过,否则您可以无休止地踩耙。 该项目的面向业务的结构使我们不必再次使代码复杂化,而可以控制服务的粒度。


我们的主要任务之一是提高服务的稳定性。 当然,Go并不能立即提供增强的稳定性。 但是,以我的观点,在Go生态系统中,事实证明,即使不使用第三方库,也可以轻松创建所有必要的可靠性工具包。


另一个重要的任务是增加系统的灵活性。 在这里,我可以肯定地说,业务所需的更改引入率已大大提高。 由于新的微服务的体系结构,开发人员无需再承担业务功能;他无需考虑构建客户端,发送监视,发送跟踪以及设置日志记录。 我们恰好为开发人员留出了编写业务逻辑的层,从而使他不必考虑整个基础架构捆绑包。


我们是否要完全重写Go上的所有内容并放弃PHP?


不可以,因为我们正在摆脱业务需求,并且在某些情况下PHP非常适合-它不需要这么快的速度和整个Go-go工具包。 用于交付订单和照相馆管理的所有自动化操作都是在PHP中完成的。 但是,例如,在客户方的电子商务平台中,我们几乎重写了Go上的所有内容,因为这样做是合理的。

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


All Articles