Historia da arquitectura Dodo IS: un monolito primitivo

Ou cada empresa infeliz cun monolito é infeliz á súa maneira.

O desenvolvemento do sistema Dodo IS comezou inmediatamente, como o negocio Dodo Pizza, en 2011. Baseouse na idea da dixitalización completa e total dos procesos de negocio, e por si mesmos, que xa daquela en 2011 causou moitas preguntas e escepticismo. Pero dende hai 9 anos seguimos este camiño, co noso propio desenvolvemento, que comezou cun monolito.

Este artigo é unha "resposta" ás preguntas "Por que reescribir a arquitectura e facer cambios a tan grande escala e a longo prazo?" volta ao artigo anterior "Historia da arquitectura Dodo IS: o camiño do back office". Comezarei por como comezou o desenvolvemento de Dodo IS, como era a arquitectura orixinal, como apareceron novos módulos e por que problemas houbo que facer cambios a gran escala.

Historia da arquitectura Dodo IS: un monolito primitivo

Serie de artigos "Que é Dodo IS?" fala de:

  1. Monolito temperán en Dodo IS (2011-2015). (estás aquí)

  2. O camiño do back office: bases e autobús separados.

  3. Camiño do cliente: fachada sobre a base (2016-2017). (En progreso...)

  4. A historia dos verdadeiros microservizos. (2018-2019). (En progreso...)

  5. Acabado de serrado do monolito e estabilización da arquitectura. (En progreso...)

Arquitectura inicial

En 2011, a arquitectura Dodo IS tiña este aspecto:

Historia da arquitectura Dodo IS: un monolito primitivo

O primeiro módulo da arquitectura é a aceptación de pedidos. O proceso de negocio foi:

  • o cliente chama á pizzería;

  • o encargado colle o teléfono;

  • acepta un pedido por teléfono;

  • encheo paralelamente na interface de aceptación de pedidos: téñense en conta a información sobre o cliente, os datos sobre os detalles do pedido e o enderezo de entrega. 

A interface do sistema de información parecía algo así...

Primeira versión de outubro de 2011:

Lixeiramente mellorado en xaneiro de 2012

Sistema de información Dodo Pizza Delivery Pizza Restaurant

Os recursos para o desenvolvemento do módulo de primeira orde foron limitados. Tivemos que facer moito, rápido e cun equipo pequeno. Un pequeno equipo son 2 desenvolvedores que sentaron as bases para todo o futuro sistema.

A súa primeira decisión determinou o destino da pila tecnolóxica:

  • Backend en ASP.NET MVC, linguaxe C#. Os desenvolvedores eran dotnetchiki, esta pila era familiar e agradable para eles.

  • Frontend en Bootstrap e JQuery: interfaces de usuario sobre estilos e scripts escritos por si mesmo. 

  • Base de datos MySQL: sen custos de licenza, fácil de usar.

  • Servidores en Windows Server, porque .NET entón só podería estar baixo Windows (non falaremos de Mono).

Fisicamente, todo isto expresouse na "dedic at the hoster". 

Arquitectura de aplicacións de admisión de pedidos

Entón todo o mundo xa falaba de microservizos, e SOA utilizouse en grandes proxectos durante 5 anos, por exemplo, WCF lanzouse en 2006. Pero entón escolleron unha solución fiable e comprobada.

Aquí está.

Historia da arquitectura Dodo IS: un monolito primitivo

Asp.Net MVC é Razor, que, a petición dun formulario ou dun cliente, renderiza unha páxina HTML coa representación do servidor. No cliente, os scripts CSS e JS xa mostran información e, se é necesario, realizan solicitudes AJAX a través de JQuery.

As solicitudes no servidor acaban nas clases *Controller, onde se realiza o procesamento e xeración da páxina HTML final no método. Os controladores fan solicitudes a unha capa de lóxica chamada *Servizos. Cada un dos servizos correspondeu a algún aspecto da empresa:

  • Por exemplo, DepartmentStructureService deu información sobre pizzerías, departamentos. Un departamento é un grupo de pizzerías dirixido por un único franquiciado.

  • ReceivingOrdersService aceptou e calculou a composición da orde.

  • E SmsService enviou SMS chamando aos servizos da API para enviar SMS.

Servizos procesados ​​datos da base de datos, lóxica de negocio almacenada. Cada servizo tiña un ou máis *Repositorios co nome apropiado. Xa contiñan consultas a procedementos almacenados na base de datos e unha capa de mapeadores. Había lóxica empresarial nos almacenamentos, especialmente moito nos que emitían datos de informes. Non se utilizou ORM, todo o mundo confiaba en sql escrito a man. 

Tamén había unha capa do modelo de dominio e clases auxiliares comúns, por exemplo, a clase Order que almacenaba a orde. No mesmo lugar, na capa, había un axudante para converter o texto mostrado segundo a moeda seleccionada.

Todo isto pode ser representado por tal modelo:

Historia da arquitectura Dodo IS: un monolito primitivo

Camiño da Orde

Considere unha forma inicial simplificada de crear tal orde.

Historia da arquitectura Dodo IS: un monolito primitivo

Inicialmente, o sitio era estático. Tiña prezos nel, e encima: un número de teléfono e a inscrición "Se queres pizza, chama ao número e pide". Para ordenar, necesitamos implementar un fluxo sinxelo: 

  • O cliente visita un sitio estático con prezos, selecciona produtos e chama ao número que aparece no sitio.

  • O cliente nomea os produtos que quere engadir ao pedido.

  • Dá o seu enderezo e nome.

  • O operador acepta o pedido.

  • O pedido móstrase na interface de pedidos aceptados.

Todo comeza coa visualización do menú. Un usuario-operador conectado só acepta un pedido á vez. Polo tanto, o carro de borradores pódese almacenar na súa sesión (a sesión do usuario gárdase na memoria). Hai un obxecto Cart que contén produtos e información do cliente.

O cliente pon un nome ao produto, o operador fai clic + xunto ao produto e envíase unha solicitude ao servidor. A información sobre o produto sácase da base de datos e a información sobre o produto engádese ao carro.

Historia da arquitectura Dodo IS: un monolito primitivo

Nota. Si, aquí non pode extraer o produto da base de datos, senón transferilo desde o frontend. Pero para claridade, mostrei exactamente o camiño desde a base de datos. 

A continuación, introduza o enderezo e o nome do cliente. 

Historia da arquitectura Dodo IS: un monolito primitivo

Cando fai clic en "Crear pedido":

  • A solicitude envíase a OrderController.SaveOrder().

  • Recibimos o carro da sesión, hai produtos na cantidade que necesitamos.

  • Complementamos o carro con información sobre o cliente e pasámolo ao método AddOrder da clase ReceivingOrderService, onde se garda na base de datos. 

  • A base de datos ten táboas coa orde, a composición do pedido, o cliente, e todas elas están conectadas.

  • A interface de visualización de pedidos saca os últimos pedidos e móstraos.

Novos módulos

Tomar a orde era importante e necesario. Non podes facer un negocio de pizza se non tes un pedido para vender. Polo tanto, o sistema comezou a adquirir funcionalidade - aproximadamente de 2012 a 2015. Durante este tempo, apareceron moitos bloques diferentes do sistema, aos que chamarei módulos, en oposición ao concepto de servizo ou produto. 

Un módulo é un conxunto de funcións que están unidas por algún obxectivo empresarial común. Ao mesmo tempo, están fisicamente na mesma aplicación.

Os módulos pódense denominar bloques do sistema. Por exemplo, este é un módulo de informes, interfaces de administración, rastreador de alimentos na cociña, autorización. Todas estas son interfaces de usuario diferentes, algunhas incluso teñen diferentes estilos visuais. Ao mesmo tempo, todo está no marco dunha aplicación, dun proceso en execución. 

Tecnicamente, os módulos foron deseñados como Área (tal idea aínda se mantivo núcleo asp.net). Había ficheiros separados para o frontend, os modelos, así como as súas propias clases de controlador. Como resultado, o sistema transformouse deste ...

Historia da arquitectura Dodo IS: un monolito primitivo

...nisto:

Historia da arquitectura Dodo IS: un monolito primitivo

Algúns módulos están implementados por sitios separados (proxecto executable), debido a unha funcionalidade completamente separada e en parte debido a un desenvolvemento lixeiramente separado e máis centrado. Isto:

  • Web - primeira versión sitio dodopizza.ru.

  • Exportar: cargando informes de Dodo IS para 1C. 

  • Persoal - Conta persoal do traballador. Desenvolveuse por separado e ten o seu propio punto de entrada e deseño separado.

  • fs — un proxecto para aloxamento de estática. Máis tarde afastámonos del, trasladando toda a estática ao CDN de Akamai. 

O resto dos bloques estaban na aplicación BackOffice. 

Historia da arquitectura Dodo IS: un monolito primitivo

Explicación do nome:

  • Caixeiro - Caixeiro do restaurante.

  • ShiftManager - interfaces para o papel de "Xestor de quendas": estatísticas operativas sobre as vendas da pizzería, a capacidade de poñer produtos na lista de paradas, cambiar a orde.

  • OfficeManager: interfaces para os roles de "Xestor de pizzería" e "Franquiciado". Aquí están recollidas funcións para configurar unha pizzería, as súas promocións de bonificación, recibir e traballar cos empregados, informes.

  • PublicScreens: interfaces para televisores e tabletas colgadas en pizzerías. Os televisores mostran menús, información publicitaria, estado do pedido na entrega. 

Usaron unha capa de servizo común, un bloque de clases de dominio Dodo.Core común e unha base común. Ás veces aínda poderían liderar as transicións entre si. Incluíndo sitios individuais, como dodopizza.ru ou personal.dodopizza.ru, acudiron aos servizos xerais.

Cando apareceron novos módulos, tentamos reutilizar ao máximo o código de servizos, procedementos almacenados e táboas xa creados na base de datos. 

Para unha mellor comprensión da escala dos módulos realizados no sistema, aquí tedes un diagrama de 2012 con plans de desenvolvemento:

Historia da arquitectura Dodo IS: un monolito primitivo

En 2015, todo estaba no mapa e aínda máis estaba en produción.

  • A aceptación de pedidos converteuse nun bloque separado do Contact Center, onde o operador acepta o pedido.

  • Había pantallas públicas con menús e información colgadas nas pizzerías.

  • A cociña ten un módulo que reproduce automaticamente a mensaxe de voz "New Pizza" cando chega un novo pedido, e tamén imprime unha factura para o mensaxeiro. Isto simplifica moito os procesos na cociña, permite que os empregados non se distrairán por un gran número de operacións sinxelas.

  • A unidade de entrega converteuse nunha caixa de entrega separada, onde o pedido se enviou ao correo que fixera previamente a quenda. O seu tempo de traballo tívose en conta para o cálculo da nómina. 

Paralelamente, de 2012 a 2015, apareceron máis de 10 desenvolvedores, abriron 35 pizzerías, despregaron o sistema en Romanía e preparáronse para a apertura de puntos de venda nos Estados Unidos. Os desenvolvedores xa non se ocupaban de todas as tarefas, senón que estaban divididos en equipos. cada un especializado na súa parte do sistema. 

Problemas

Incluso pola arquitectura (pero non só).

Caos na base

Unha base é conveniente. Nel pódese conseguir coherencia, e a costa das ferramentas integradas nas bases de datos relacionais. Traballar con el é familiar e cómodo, especialmente se hai poucas táboas e poucos datos.

Pero ao longo de 4 anos de desenvolvemento, a base de datos resultou ter unhas 600 táboas, 1500 procedementos almacenados, moitos dos cales tamén tiñan lóxica. Por desgraza, os procedementos almacenados non traen moita vantaxe cando se traballa con MySQL. Non son almacenados na caché pola base e almacenar a lóxica neles complica o desenvolvemento e a depuración. A reutilización do código tamén é difícil.

Moitas táboas non tiñan índices axeitados, nalgún lugar, pola contra, había moitos índices, o que dificultaba a súa inserción. Foi necesario modificar unhas 20 táboas: a transacción para crear un pedido podía levar uns 3-5 segundos. 

Os datos das táboas non sempre foron da forma máis adecuada. Nalgún lugar había que facer desnormalización. Parte dos datos recibidos regularmente estaban nunha columna en forma de estrutura XML, isto aumentou o tempo de execución, alongou as consultas e complicou o desenvolvemento.

Ás mesmas mesas producíronse moi solicitudes heteroxéneas. As mesas populares sufriron especialmente, como a táboa mencionada anteriormente. ordes ou táboas pizzería. Utilizáronse para mostrar interfaces operativas na cociña, analíticas. Outro sitio contactou con eles (dodopizza.ru), onde en cada momento poderían chegar de súpeto moitas solicitudes. 

Os datos non foron agregados e moitos cálculos facíanse sobre a marcha usando a base. Isto creou cálculos innecesarios e carga adicional. 

Moitas veces o código pasaba á base de datos cando non podía facelo. Nalgún lugar non había suficientes operacións masivas, nalgún lugar sería necesario difundir unha solicitude en varias a través do código para acelerar e aumentar a fiabilidade. 

Cohesión e ofuscación no código

Os módulos que debían ser responsables da súa parte do negocio non o fixeron honestamente. Algúns deles tiñan duplicación de funcións para roles. Por exemplo, un comerciante local que é responsable da actividade de mercadotecnia da rede na súa cidade tivo que utilizar tanto a interface "Administrador" (para crear promocións) como a interface "Xestor de oficina" (para ver o impacto das promocións na empresa). Iso si, dentro de ambos módulos utilizaba o mesmo servizo que funcionaba coas promocións de bonificación.

Os servizos (clases dentro dun gran proxecto monolítico) poderían chamarse entre si para enriquecer os seus datos.

Coas propias clases do modelo que almacenan datos, o traballo no código realizouse de forma diferente. Nalgún lugar había construtores a través dos cales era posible especificar campos obrigatorios. Nalgún lugar, isto fíxose a través de propiedades públicas. Por suposto, a obtención e transformación de datos da base de datos foi variada. 

A lóxica estaba ben nos controladores ou nas clases de servizo. 

Estes parecen ser problemas menores, pero retardaron moito o desenvolvemento e reduciron a calidade, o que provocou inestabilidade e erros. 

A complexidade dun gran desenvolvemento

As dificultades xurdiron no propio desenvolvemento. Foi necesario facer diferentes bloques do sistema, e en paralelo. Axustar as necesidades de cada compoñente nun único código facíase cada vez máis difícil. Non foi doado poñerse de acordo e agradar a todos os compoñentes ao mesmo tempo. A isto sumáronse limitacións na tecnoloxía, especialmente no que se refire á base e frontend. Foi necesario abandonar JQuery cara a marcos de alto nivel, especialmente no que se refire aos servizos ao cliente (sitio web).

Nalgunhas partes do sistema poderían utilizarse bases de datos máis adecuadas para iso.. Por exemplo, máis tarde tivemos o caso de uso de pasar de Redis a CosmosDB para almacenar unha cesta de pedidos. 

Os equipos e desenvolvedores implicados no seu campo querían claramente máis autonomía para os seus servizos, tanto no que se refire ao desenvolvemento como ao lanzamento. Combina conflitos, libera problemas. Se para 5 desenvolvedores este problema é insignificante, entón con 10, e máis aínda co crecemento planificado, todo sería máis grave. E por diante ía estar o desenvolvemento dunha aplicación móbil (comezou en 2017, e en 2018 foi gran caída). 

As diferentes partes do sistema requirían diferentes niveis de estabilidade, pero debido á forte conectividade do sistema, non puidemos proporcionar isto. Un erro no desenvolvemento dunha nova función no panel de administración ben podería ter lugar na aceptación dun pedido no sitio, porque o código é común e reutilizable, a base de datos e os datos tamén son os mesmos.

Probablemente sería posible evitar estes erros e problemas no marco dunha arquitectura tan monolítica-modular: facer unha división de responsabilidade, refactorizar tanto o código como a base de datos, separar claramente as capas entre si, controlar a calidade todos os días. Pero as solucións arquitectónicas elixidas e o foco na rápida expansión da funcionalidade do sistema provocaron problemas de estabilidade.

Como o blog Power of the Mind puxo as caixas rexistradoras dos restaurantes

Se o crecemento da rede de pizzerías (e da carga) continuase ao mesmo ritmo, despois dun tempo as caídas serían tales que o sistema non subiría. Ben ilustra os problemas aos que comezamos a enfrontarnos en 2015, aquí tes unha historia así. 

No blog "Poder mental” era un widget que mostraba datos sobre os ingresos do ano de toda a rede. O widget accedeu á API pública de Dodo, que proporciona estes datos. Esta estatística está dispoñible actualmente en http://dodopizzastory.com/. O widget mostrouse en todas as páxinas e fixo solicitudes nun temporizador cada 20 segundos. A solicitude foi a api.dodopizza.ru e solicitou:

  • o número de pizzerías na rede;

  • ingresos totais da rede desde o inicio do ano;

  • ingresos para hoxe.

A solicitude de estatísticas sobre ingresos pasou directamente á base de datos e comezou a solicitar datos sobre pedidos, agregar datos sobre a marcha e entregar o importe. 

As caixas dos restaurantes acudiron á mesma táboa de pedidos, descargaron unha lista de pedidos recibidos para hoxe e engadíronse novos pedidos. As caixas rexistradoras fixeron as súas solicitudes cada 5 segundos ou na actualización da páxina.

O diagrama tiña este aspecto:

Historia da arquitectura Dodo IS: un monolito primitivo

Un outono, Fyodor Ovchinnikov escribiu un artigo longo e popular no seu blog. Moita xente chegou ao blog e comezou a ler todo con atención. Mentres cada unha das persoas que viñeron lendo o artigo, o widget de ingresos funcionaba correctamente e solicitaba a API cada 20 segundos.

A API chamou a un procedemento almacenado para calcular a suma de todos os pedidos desde principios de ano para todas as pizzerías da rede. A agregación baseouse na táboa de pedidos, que é moi popular. Todas as caixas de todos os restaurantes abertos nese momento van a ela. As caixas deixaron de responder, non se aceptaron pedidos. Tampouco foron aceptados desde o sitio, non apareceron no rastreador, o xefe de quendas non podía velos na súa interface. 

Esta non é a única historia. No outono de 2015, todos os venres a carga no sistema era crítica. Varias veces desactivamos a API pública, e unha vez, incluso tivemos que desactivar o sitio, porque nada axudou. Incluso había unha lista de servizos cunha orde de parada baixo cargas pesadas.

A partir de agora comeza a nosa loita coas cargas e pola estabilización do sistema (de outono de 2015 a outono de 2018). Foi entón cando pasou"gran caída". Ademais, ás veces tamén se producían fallos, algúns eran moi sensibles, pero o período xeral de inestabilidade agora pódese considerar superado.

Rápido crecemento empresarial

Por que non se puido facer de inmediato? Basta mirar os seguintes gráficos.

Historia da arquitectura Dodo IS: un monolito primitivo

Tamén en 2014-2015 houbo unha apertura en Romanía e estaba a prepararse unha en EE.UU.

A rede creceu moi rapidamente, abríronse novos países, apareceron novos formatos de pizzerías, por exemplo, abriuse unha pizzería na praza de abastos. Todo isto requiriu unha importante atención específicamente á expansión das funcións de Dodo IS. Sen todas estas funcións, sen rastrexar na cociña, contabilizar produtos e perdas no sistema, mostrar a emisión dunha orde no salón de comedor, dificilmente estaríamos falando da arquitectura "correcta" e do enfoque "correcto" desenvolvemento agora.

Outro obstáculo para a revisión oportuna da arquitectura e, en xeral, a atención aos problemas técnicos foi a crise de 2014. Cousas como esta afectan moito ás oportunidades de crecemento dos equipos, especialmente para unha empresa nova como Dodo Pizza.

Solucións rápidas que axudaron

Problemas que necesitan solucións. Convencionalmente, as solucións pódense dividir en dous grupos:

  • Uns rápidos que apagan o lume e dan unha pequena marxe de seguridade e nos dan tempo para cambiar.

  • Sistémico e, polo tanto, longo. Reenxeñaría dunha serie de módulos, división dunha arquitectura monolítica en servizos separados (a maioría deles non son en absoluto servizos micro, senón macro, e hai algo sobre iso). Informe de Andrey Morevskiy). 

A lista seca de cambios rápidos é a seguinte:

Aumentar a escala do mestre base

Por suposto, o primeiro que se fai para facer fronte ás cargas é aumentar a capacidade do servidor. Isto fíxose para a base de datos mestra e para os servidores web. Por desgraza, isto só é posible ata un certo límite, entón tórnase demasiado caro.

Desde 2014, mudámonos a Azure, tamén escribimos sobre este tema nese momento no artigo "Como Dodo Pizza ofrece pizza usando Microsoft Azure Cloud". Pero despois dunha serie de aumentos no servidor para a base, enfrontáronse ao custo. 

Réplicas base para lectura

Fixéronse dúas réplicas para a base:

Ler réplica para solicitudes de referencia. Utilízase para ler directorios, tipo, cidade, rúa, pizzería, produtos (dominio cambiado lentamente) e naquelas interfaces onde se admite un pequeno atraso. Foron 2 destas réplicas, garantimos a súa dispoñibilidade do mesmo xeito que os mestres.

ReadReplica para solicitudes de informes. Esta base de datos tiña unha dispoñibilidade inferior, pero todos os informes dirixíronse a ela. Permítelles ter grandes solicitudes de recálculos de datos enormes, pero non afectan á base de datos principal nin ás interfaces operativas. 

Cachés no código

Non había cachés en ningún lugar do código (en absoluto). Isto levou a solicitudes adicionais, non sempre necesarias, á base de datos cargada. Os cachés foron primeiro tanto en memoria como nun servizo de caché externo, que era Redis. Todo foi invalidado polo tempo, a configuración especificouse no código.

Múltiples servidores backend

O backend da aplicación tamén debeu ser escalado para xestionar o aumento das cargas de traballo. Era necesario facer un clúster desde un servidor iis. Reprogramamos sesión de aplicación da memoria a RedisCache, o que permitiu facer varios servidores detrás dun sinxelo equilibrador de carga con round robin. Ao principio, utilizouse o mesmo Redis que para os cachés, despois dividiuse en varios. 

Como resultado, a arquitectura volveuse máis complicada...

Historia da arquitectura Dodo IS: un monolito primitivo

... pero parte da tensión foi eliminada.

E despois foi necesario refacer os compoñentes cargados, o que asumimos. Disto falaremos na seguinte parte.

Fonte: www.habr.com

Engadir un comentario