Dodo IS 架构的历史:后台路径

Habr 正在改变世界。 我们写博客已经一年多了。 大约六个月前,我们收到了 Khabrovites 的完全合乎逻辑的反馈:“Dodo,你到处说你有自己的系统。 这个系统是什么? 为什么披萨连锁店需要它?

我们坐下来,思考并意识到你是对的。 我们试图解释我们手指上的一切,但它是支离破碎的,没有一个完整的系统描述。 就这样开始了漫长的收集信息、寻找作者和撰写一系列关于 Dodo IS 的文章的旅程。 我们走吧!

致谢:感谢您与我们分享您的反馈。 多亏了他,我们终于描述了系统,编制了一份技术雷达,并将很快推出对我们流程的大量描述。 没有你,我们会再坐在那里 5 年。

Dodo IS 架构的历史:后台路径

系列文章“什么是 Dodo IS?” 讲述:

  1. Dodo IS 中的早期单体应用(2011-2015)。 (进行中...)
  2. 后台路径:分离基地和总线。 (你在这里)
  3. 客户端路径:基础立面(2016-2017)。 (进行中...)
  4. 真正的微服务的历史。 (2018-2019)。 (进行中...)
  5. 完成整体结构的锯切和结构的稳定。 (进行中...)

如果您有兴趣了解其他信息 - 请写在评论中。

作者对时间顺序描述的看法
我定期为新员工召开会议,主题是“系统架构”。 我们称之为“Dodo IS 架构介绍”,它是新开发人员入职流程的一部分。 以一种或另一种形式讲述我们的建筑,讲述它的特点,我已经产生了一种特定的历史描述方法。

传统上,我们将系统视为一组组件(技术或更高级别)、相互交互以实现某些目标的业务模块。 如果这样的观点适合设计,那么它不太适合描述和理解。 这里有几个原因:

  • 现实与纸上谈兵不同。 并非一切都按计划进行。 我们对它的实际结果和工作方式很感兴趣。
  • 一致的信息呈现。 事实上,您可以按时间顺序从开始到当前状态。
  • 从简单到复杂。 不是普遍的,但在我们的情况下是这样。 该架构从更简单的方法转向更复杂的方法。 通常通过复杂化,解决了实施速度和稳定性问题,以及非功能性需求列表中的许多其他属性(这里 很好地讲述了与其他要求的对比复杂性)。

2011 年,Dodo IS 的架构是这样的:

Dodo IS 架构的历史:后台路径

到了2020年,就变得有点复杂了,变成了这个样子:

Dodo IS 架构的历史:后台路径

这种演变是如何发生的? 为什么需要系统的不同部分? 做出了哪些架构决策,为什么? 让我们在本系列文章中一探究竟。

2016 年的第一个问题:服务为什么要离开单体应用

该周期的第一篇文章将介绍最先从整体中分离出来的服务。 为了让您了解情况,我将告诉您我们在 2016 年初的系统中遇到了哪些问题,我们必须处理服务分离问题。

一个单一的 MySql 数据库,当时存在于 Dodo IS 中的所有应用程序都将它们的记录写入其中。 结果是:

  • 重负载(85% 的请求是阅读)。
  • 基地长大了。 正因为如此,它的成本和支持就成了一个问题。
  • 单点故障。 如果一个写入数据库的应用程序突然开始更积极地执行此操作,那么其他应用程序也会感受到这一点。
  • 存储和查询效率低下。 通常,数据以某种结构存储,这种结构对某些场景很方便,但对其他场景不适合。 索引可以加快某些操作的速度,但会减慢其他操作的速度。
  • 有些问题是通过草率的做缓存和只读副本到基地来解决的(这将另文报道),但只是给他们争取时间,并没有从根本上解决问题。

问题是单体本身的存在. 结果是:

  • 单一和罕见的版本。
  • 多人联合开发难度大。
  • 无法引入新技术、新框架和库。

基础和单体的问题已被多次描述,例如,在 2018 年初崩溃的背景下(像蒙克一样,或者说几句技术债, Dodo IS 停止的那一天。 异步脚本 и 凤凰家族渡渡鸟的故事。 渡渡鸟的大坠落 IS),所以我不会详述。 我只想说,我们想在开发服务时提供更多的灵活性。 首先,这涉及到整个系统中负载最重和根目录的那些 - Auth 和 Tracker。

后台路径:分离基地和总线

章节导航

  1. 单体计划 2016
  2. 开始卸载单体:Auth 和 Tracker 分离
  3. 授权是做什么的?
  4. 负载从哪里来?
  5. 卸载授权
  6. 追踪器做什么?
  7. 负载从哪里来?
  8. 卸货追踪器

单体计划 2016

以下是 Dodo IS 2016 单体的主要模块,下方是其主要任务的记录。
Dodo IS 架构的历史:后台路径
收银员送货。 为快递员做账,给快递员下单。
联络中心. 通过运营商接受订单。
Site. 我们的网站(dodopizza.ru、dodopizza.co.uk、dodopizza.by 等)。
AUTH. 后台的授权和认证服务。
跟踪器. 厨房中的订单跟踪器。 在准备订单时标记准备状态的服务。
餐厅收银台. 在餐厅接单,收银界面。
出口. 在 1C 中上传报告以进行会计处理。
通知和发票. 厨房中的语音命令(例如,“新披萨到了”)+ 为快递员打印发票。
值班经理. 轮班经理的工作界面:订单列表、绩效图表、员工转移到班次。
办公室主管. 加盟商和经理的工作接口:员工接待、比萨店工作报告。
餐厅记分牌. 比萨店电视上的菜单显示。
行政. 特定比萨店的设置:菜单、价格、会计、促销代码、促销活动、网站横幅等。
员工个人账户. 员工的工作时间表,员工信息。
厨房激励板. 一个独立的屏幕挂在厨房里,显示披萨制造商的速度。
沟通. 发送短信和电子邮件。
文件存储. 用于接收和发布静态文件的自有服务。

解决问题的最初尝试帮助了我们,但它们只是暂时的喘息。 它们并没有成为系统解决方案,因此很明显必须对碱基做些什么。 例如,将通用数据库划分为几个更专业的数据库。

开始卸载单体:Auth 和 Tracker 分离

然后从数据库中记录和读取的主要服务比其他服务更多:

  1. 授权。 后台的授权和认证服务。
  2. 跟踪器。 厨房中的订单跟踪器。 在准备订单时标记准备状态的服务。

授权是做什么的?

Auth是用户登录后台的服务(客户端有单独的独立入口)。 请求中还要求它确保存在所需的访问权限,并且这些权限自上次登录以来没有更改。 通过它,设备进入比萨店。

例如,我们想在大厅悬挂的电视上打开一个显示订单完成状态的屏幕。 然后我们打开auth.dodopizza.ru,选择“以设备身份登录”,出现一个代码,可以在班长电脑上的一个特殊页面输入,表示设备(设备)的类型。 电视本身将切换到其比萨店所需的界面,并开始显示那里已准备好订单的顾客姓名。

Dodo IS 架构的历史:后台路径

负载从哪里来?

后台的每个登录用户都会访问数据库,针对每个请求访问用户表,通过 sql 查询提取用户,并检查他是否具有对该页面的必要访问权限和权限。

每个设备只对设备表执行相同的操作,检查其角色和访问权限。 对master数据库的大量请求导致其加载,这些操作造成了公共数据库资源的浪费。

卸载授权

Auth 有一个隔离的域,即有关用户、登录名或设备的数据进入服务(暂时)并保留在那里。 如果有人需要他们,那么他会去这个服务获取数据。

曾是。 原来的工作计划如下:

Dodo IS 架构的历史:后台路径

我想解释一下它是如何工作的:

  1. 来自外部的请求来到后端(有Asp.Net MVC),带有一个会话cookie,用于从Redis(1)获取会话数据。 它要么包含有关访问的信息,然后打开对控制器的访问 (3,4),要么不打开。
  2. 如果没有访问权限,则需要完成授权程序。 在这里,为了简单起见,它显示为同一属性中路径的一部分,尽管它是到登录页面的过渡。 在积极的情况下,我们将获得一个正确完成的会话并转到后台控制器。
  3. 如果有数据,则需要检查它在用户数据库中的相关性。 他的角色改变了吗,现在不应该允许他出现在页面上吗? 在这种情况下,在收到会话(1)后,您需要直接进入数据库并使用身份验证逻辑层(2)检查用户的访问权限。 接下来,要么转到登录页面,要么转到控制器。 如此简单的系统,但并不十分标准。
  4. 如果所有过程都通过了,那么我们将进一步跳过控制器和方法中的逻辑。

用户数据与所有其他数据分离,存储在单独的成员表中,来自 AuthService 逻辑层的功能很可能成为 api 方法。 域边界定义得非常清楚:用户、他们的角色、访问数据、授予和撤销访问权限。 一切看起来都可以在单独的服务中取出。

变得。 所以他们这样做了:

Dodo IS 架构的历史:后台路径

这种方法有很多问题。 例如,在进程内部调用方法与通过 http 调用外部服务是不一样的。 操作的延迟、可靠性、可维护性、透明性完全不同。 Andrey Morevskiy 在他的报告中更详细地谈到了这些问题。 “微服务的 50 种阴影”.

身份验证服务及其设备服务用于后台,即用于生产中使用的服务和接口。 客户端服务(如网站或移动应用程序)的身份验证单独进行,无需使用 Auth。 分离花了大约一年的时间,现在我们再次处理这个话题,将系统转移到新的身份验证服务(使用标准协议)。

为什么分手这么久?
一路上有很多问题让我们放慢了速度:

  1. 我们希望将用户、设备和身份验证数据从国家/地区特定的数据库中整合到一个数据库中。 为此,我们必须将所有表和用法从 int 标识符转换为全局 UUId 标识符(最近修改了此代码 Roman Bukin 《Uuid——小结构的大故事》 和开源项目 基元). 存储用户数据(因为它是个人信息)有其局限性,对于某些国家/地区,有必要单独存储它们。 但是用户的全局id必须是。
  2. 数据库中的许多表都有关于执行操作的用户的审计信息。 这需要一种额外的一致性机制。
  3. 在创建 api 服务之后,有一段漫长而渐进的过渡到另一个系统的时期。 切换必须对用户来说是无缝的,并且需要手动操作。

披萨店的设备注册方案:

Dodo IS 架构的历史:后台路径

提取 Auth 和 Devices 服务后的总体架构:

Dodo IS 架构的历史:后台路径

注意. 2020 年,我们正在开发基于 OAuth 2.0 授权标准的新版 Auth。 该标准相当复杂,但对于开发直通身份验证服务很有用。 在文章“授权的微妙之处:OAuth 2.0 技术概述» 我们 Alexey Chernyaev 试图尽可能简单明了地讲述该标准,以便您节省学习时间。

追踪器做什么?

现在介绍第二个加载的服务。 跟踪器执行双重角色:

  • 一方面,它的任务是向厨房的员工展示当前正在执行的订单,现在需要烹调哪些产品。
  • 另一方面,将厨房中的所有流程数字化。

Dodo IS 架构的历史:后台路径

当订单中出现新产品(例如比萨饼)时,它会转到 Rolling out 跟踪站。 在这个站,有一个比萨制造商拿了一个所需大小的面包并将其擀开,之后他在跟踪器平板电脑上记录他已经完成了他的任务并将擀好的面坯转移到下一站 - “启动” .

在那里,下一个比萨饼师傅将比萨饼填满,然后在平板电脑上注明他已完成任务并将比萨饼放入烤箱(这也是一个必须在平板电脑上注明的独立站)。 这样的系统从Dodo一开始就有,从Dodo IS存在的一开始就有。 它允许您完全跟踪和数字化所有交易。 此外,跟踪器建议如何烹饪特定产品,根据其制造方案指导每种类型的产品,存储产品的最佳烹饪时间,并跟踪产品上的所有操作。

Dodo IS 架构的历史:后台路径这是平板电脑屏幕在跟踪器“Raskatka”站的样子

负载从哪里来?

每家比萨店都有大约五台带追踪器的平板电脑。 2016 年,我们拥有 100 多家比萨店(现在超过 600 家)。 每个平板电脑每10秒向后端发出一次请求,并从订单表(与客户端和地址的连接)、订单构成(与产品的连接和数量指示)、动机核算表(在其中跟踪按下的时间)。 当披萨制造商点击跟踪器上的产品时,所有这些表中的条目都会更新。 订单表是通用的,它还包含接受订单时的插入、系统其他部分的更新和大量读数,例如,挂在比萨店的电视上向客户显示完成的订单。

在与负载斗争期间,当一切都被缓存并转移到 base 的异步副本时,这些与 tracker 的操作继续转到 master base。 不应该有任何滞后,数据应该是最新的,不同步是不可接受的。

此外,由于它们缺少自己的表和索引,因此无法编写为它们的使用量身定制的更具体的查询。 例如,跟踪器在订单表上有一个比萨店的索引可能是有效的。 我们总是从跟踪器数据库中删除比萨店订单。 同时,对于接到一个订单,它落在哪家披萨店并不重要,更重要的是哪个客户下了这个订单。 并且意味着客户端上的索引是必要的。 跟踪器也没有必要在订单表中存储与订单关联的打印收据或奖金促销的 ID。 我们的跟踪器服务不关心此信息。 在一个常见的单体数据库中,表只能是所有用户之间的折衷。 这是最初的问题之一。

曾是。 原来的架构是:

Dodo IS 架构的历史:后台路径

即使在被分成不同的进程之后,大部分代码库对于不同的服务仍然是通用的。 控制器下的所有东西都是单一的,并且存在于同一个存储库中。 我们使用了通用的服务方法、存储库、一个通用的基础,其中放置了通用的表。

卸货追踪器

跟踪器的主要问题是数据必须在不同数据库之间同步。 这也是它与分离的Auth服务的主要区别,顺序和它的状态是可以改变的,应该在不同的服务中显示。

我们在餐厅结账处接受订单(这是一项服务),它以“已接受”状态存储在数据库中。 之后,他应该到达追踪器,在那里他会多次改变自己的状态:从“厨房”到“打包”。 同时,订单可能会受到 Cashier 或 Shift Manager 界面的一些外部影响。 我将在表格中给出订单状态及其描述:

Dodo IS 架构的历史:后台路径
更改订单状态的方案如下所示:

Dodo IS 架构的历史:后台路径

不同系统之间的状态会发生变化。 这里的跟踪器不是数据关闭的最终系统。 在这种情况下,我们已经看到了几种可能的分区方法:

  1. 我们将所有订单操作集中在一项服务中。 在我们的例子中,此选项需要太多服务才能处理订单。 如果我们停下来,我们将得到第二个整体。 我们不会解决问题。
  2. 一个系统调用另一个系统。 第二个选项已经更有趣了。 但是有了它,调用链是可能的(连锁故障),组件的连通性越高,管理难度就越大。
  3. 我们组织事件,每个服务通过这些事件与另一个服务进行通信。 结果,选择了第三个选项,根据该选项,所有服务开始相互交换事件。

我们选择第三个选项的事实意味着跟踪器将拥有自己的数据库,并且对于订单中的每次更改,它都会发送一个关于此的事件,其他服务订阅该事件并且该事件也属于主数据库。 为此,我们需要一些服务来确保服务之间的消息传递。

到那时,我们已经在堆栈中有了 RabbitMQ,因此最终决定将其用作消息代理。 该图显示了订单从餐厅​​收银台到跟踪器的转换,它在跟踪器中更改了它的状态并显示在经理的订单界面上。 WAS:

Dodo IS 架构的历史:后台路径

订单路径逐步
订单路径从订单源服务之一开始。 这是餐厅的收银员:

  1. 在结账时,订单已完全准备好,是时候将其发送到跟踪器了。 跟踪器订阅的事件被抛出。
  2. Tracker 为自己接受订单,将其保存到自己的数据库中,生成事件“Order Accepted by Tracker”并将其发送到 RMQ。
  3. 每个订单已经有多个处理程序订阅了事件总线。 对我们来说,与整体基础同步的那个很重要。
  4. 处理程序接收到一个事件,从中选择对它重要的数据:在我们的例子中,这是订单的状态“已被跟踪器接受”,并在主数据库中更新其订单实体。

如果有人需要单片表 orders 中的订单,那么您也可以从那里读取它。 例如,班次管理器中的订单界面需要这样:

Dodo IS 架构的历史:后台路径

所有其他服务也可以从跟踪器订阅订单事件以供自己使用。

如果一段时间后订单开始工作,那么它的状态首先在其数据库(Tracker 数据库)中发生变化,然后立即生成“OrderIn Progress”事件。 它还进入 RMQ,从那里它在一个单一的数据库中同步并交付给其他服务。 一路上可能会遇到各种各样的问题,更多细节可以在Zhenya Peshkov的报告中找到 关于Tracker中Eventual Consistency的实现细节.

Auth 和 Tracker 更改后的最终架构

Dodo IS 架构的历史:后台路径

总结中间结果: 最初,我的想法是将 Dodo IS 系统九年的历史打包成一篇文章。 我想快速简单地谈谈进化的阶段。 然而,坐下来看材料,我意识到一切都比看起来要复杂和有趣得多。

反思这些材料的好处(或缺乏),我得出结论,如果没有完整的事件记录、详细的回顾和对我过去的决定的分析,持续发展是不可能的。

我希望了解我们的道路对您有用且有趣。 现在我面临着选择在下一篇文章中描述 Dodo IS 系统的哪一部分:写评论或投票。

只有注册用户才能参与调查。 登录拜托

您希望在下一篇文章中了解 Dodo IS 的哪一部分?

  • 24,1%Dodo IS 中的早期单体(2011-2015)14

  • 24,1%首批问题及其解决方案(2015-2016)14

  • 20,7%客户端路径:外观高于基础(2016-2017)12

  • 36,2%真正的微服务的历史(2018-2019)21

  • 44,8%完整锯切整体结构并稳定架构26

  • 29,3%关于系统开发的进一步计划17

  • 19,0%我不想了解 Dodo IS11 的任何信息

58 位用户投票。 6 名用户弃权。

来源: habr.com

添加评论