Blocs de construcció d'aplicacions distribuïdes. Segona aproximació

Anunci

Col·legues, a mitjans d'estiu tinc previst publicar una altra sèrie d'articles sobre el disseny de sistemes de cua: "The VTrade Experiment" - un intent d'escriure un marc per a sistemes comercials. La sèrie examinarà la teoria i la pràctica de la construcció d'un intercanvi, subhasta i botiga. Al final de l'article us convido a votar els temes que més us interessen.

Blocs de construcció d'aplicacions distribuïdes. Segona aproximació

Aquest és l'article final de la sèrie sobre aplicacions reactives distribuïdes a Erlang/Elixir. EN primer article podeu trobar els fonaments teòrics de l'arquitectura reactiva. Segon article il·lustra els patrons i mecanismes bàsics per construir aquests sistemes.

Avui plantejarem temes de desenvolupament de la base de codi i projectes en general.

Organització dels serveis

A la vida real, quan es desenvolupa un servei, sovint heu de combinar diversos patrons d'interacció en un controlador. Per exemple, el servei d'usuaris, que resol el problema de la gestió dels perfils d'usuari del projecte, ha de respondre a les sol·licituds de req-resp i informar de les actualitzacions de perfils a través de pub-sub. Aquest cas és bastant senzill: darrere de la missatgeria hi ha un controlador que implementa la lògica del servei i publica actualitzacions.

La situació es complica quan hem d'implementar un servei distribuït tolerant a errors. Imaginem que els requisits per als usuaris han canviat:

  1. ara el servei hauria de processar sol·licituds en 5 nodes de clúster,
  2. poder realitzar tasques de processament en segon pla,
  3. i també poder gestionar de manera dinàmica llistes de subscripcions per a actualitzacions de perfils.

Nota: No considerem el problema de l'emmagatzematge coherent i la replicació de dades. Suposem que aquests problemes s'han resolt abans i que el sistema ja té una capa d'emmagatzematge fiable i escalable, i els gestors tenen mecanismes per interactuar-hi.

La descripció formal del servei als usuaris s'ha complicat. Des del punt de vista d'un programador, els canvis són mínims a causa de l'ús de missatgeria. Per satisfer el primer requisit, hem de configurar l'equilibri al punt d'intercanvi req-resp.

El requisit de processar tasques en segon pla es produeix amb freqüència. En els usuaris, això podria ser comprovar documents d'usuari, processar multimèdia descarregat o sincronitzar dades amb les xarxes socials. xarxes. Aquestes tasques s'han de distribuir d'alguna manera dins del clúster i supervisar el progrés de l'execució. Per tant, tenim dues opcions de solució: o bé utilitzar la plantilla de distribució de tasques de l'article anterior o, si no convé, escriure un programador de tasques personalitzat que gestionarà el conjunt de processadors de la manera que necessitem.

El punt 3 requereix l'extensió de plantilla pub-sub. I per a la implementació, després de crear un punt d'intercanvi pub-sub, hem de llançar addicionalment el controlador d'aquest punt dins del nostre servei. Així, és com si estiguéssim traslladant la lògica de processament de subscripcions i baixes de la capa de missatgeria a la implementació dels usuaris.

Com a resultat, la descomposició del problema va mostrar que, per complir els requisits, hem de llançar 5 instàncies del servei en diferents nodes i crear una entitat addicional: un controlador pub-sub, responsable de la subscripció.
Per executar 5 controladors, no cal que canvieu el codi de servei. L'única acció addicional és establir regles d'equilibri al punt d'intercanvi, de les quals parlarem una mica més endavant.
També hi ha una complexitat addicional: el controlador pub-sub i el programador de tasques personalitzats han de funcionar en una única còpia. De nou, el servei de missatgeria, com a fonamental, ha de proporcionar un mecanisme per seleccionar un líder.

L'elecció del líder

En els sistemes distribuïts, l'elecció del líder és el procediment per designar un únic procés responsable de programar el processament distribuït d'alguna càrrega.

En sistemes que no són propensos a la centralització, s'utilitzen algorismes universals i basats en consens, com ara paxos o raft.
Com que la missatgeria és un intermediari i un element central, coneix tots els controladors de serveis: líders candidats. La missatgeria pot designar un líder sense votar.

Després d'iniciar i connectar-se al punt d'intercanvi, tots els serveis reben un missatge del sistema #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}. Si LeaderPid coincideix amb pid procés actual, es designa com a líder, i la llista Servers inclou tots els nodes i els seus paràmetres.
En el moment que n'apareix un de nou i es desconnecta un node de clúster en funcionament, tots els controladors de servei reben #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} и #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts} respectivament.

D'aquesta manera, tots els components són conscients de tots els canvis i es garanteix que el clúster tindrà un líder en cada moment.

Intermediaris

Per implementar processos complexos de processament distribuït, així com en problemes d'optimització d'una arquitectura existent, és convenient utilitzar intermediaris.
Per no canviar el codi del servei i resoldre, per exemple, problemes de processament addicional, enrutament o registre de missatges, podeu habilitar un gestor de servidor intermediari abans del servei, que realitzarà tot el treball addicional.

Un exemple clàssic d'optimització pub-sub és una aplicació distribuïda amb un nucli empresarial que genera esdeveniments d'actualització, com ara canvis de preu al mercat, i una capa d'accés: N servidors que proporcionen una API de websocket per als clients web.
Si decideixes de front, el servei d'atenció al client és el següent:

  • el client estableix connexions amb la plataforma. Al costat del servidor que finalitza el trànsit, s'inicia un procés per donar servei a aquesta connexió.
  • En el context del procés de servei, es produeix l'autorització i la subscripció a les actualitzacions. El procés crida al mètode de subscripció per a temes.
  • Un cop es genera un esdeveniment al nucli, es lliura als processos que donen servei a les connexions.

Imaginem que tenim 50000 subscriptors al tema "notícies". Els subscriptors es distribueixen de manera uniforme en 5 servidors. Com a resultat, cada actualització, arribant al punt d'intercanvi, es replicarà 50000 vegades: 10000 vegades a cada servidor, segons el nombre de subscriptors que hi hagi. No és un esquema molt efectiu, oi?
Per millorar la situació, introduïm un proxy que tingui el mateix nom que el punt d'intercanvi. El registrador global de noms ha de poder retornar el procés més proper pel nom, això és important.

Llencem aquest servidor intermediari als servidors de la capa d'accés, i tots els nostres processos que serveixen l'API websocket s'hi subscriuran, i no al punt d'intercanvi pub-sub original del nucli. El proxy es subscriu al nucli només en el cas d'una subscripció única i replica el missatge entrant a tots els seus subscriptors.
Com a resultat, s'enviaran 5 missatges entre el nucli i els servidors d'accés, en lloc de 50000.

Encaminament i equilibri

Req-Resp

A la implementació actual de missatgeria, hi ha 7 estratègies de distribució de sol·licituds:

  • default. La sol·licitud s'envia a tots els controladors.
  • round-robin. Les sol·licituds s'enumeren i es distribueixen cíclicament entre els controladors.
  • consensus. Els controladors que serveixen el servei es divideixen en líders i esclaus. Les sol·licituds s'envien només al líder.
  • consensus & round-robin. El grup té un líder, però les peticions es reparteixen entre tots els membres.
  • sticky. La funció hash es calcula i s'assigna a un controlador específic. Les sol·licituds posteriors amb aquesta signatura van al mateix gestor.
  • sticky-fun. En inicialitzar el punt d'intercanvi, la funció de càlcul hash per sticky equilibri.
  • fun. De manera similar a sticky-fun, només tu pots redirigir-lo, rebutjar-lo o processar-lo prèviament.

L'estratègia de distribució s'estableix quan s'inicia el punt d'intercanvi.

A més d'equilibrar, la missatgeria us permet etiquetar entitats. Vegem els tipus d'etiquetes del sistema:

  • Etiqueta de connexió. Permet entendre a través de quina connexió es van produir els esdeveniments. S'utilitza quan un procés controlador es connecta al mateix punt d'intercanvi, però amb claus d'encaminament diferents.
  • Etiqueta de servei. Us permet combinar gestors en grups per a un sol servei i ampliar les capacitats d'encaminament i equilibri. Per al patró req-resp, l'encaminament és lineal. Enviem una sol·licitud al punt de canvi, després la transmet al servei. Però si necessitem dividir els controladors en grups lògics, la divisió es fa mitjançant etiquetes. Quan s'especifica una etiqueta, la sol·licitud s'enviarà a un grup específic de controladors.
  • Sol·licita l'etiqueta. Permet distingir entre respostes. Com que el nostre sistema és asíncron, per processar les respostes del servei hem de poder especificar una RequestTag quan enviem una sol·licitud. A partir d'ell podrem entendre la resposta a quina petició ens va arribar.

Pub-sub

Per al pub-sub tot és una mica més senzill. Tenim un punt d'intercanvi on es publiquen missatges. El punt d'intercanvi distribueix missatges entre els subscriptors que s'han subscrit a les claus d'encaminament que necessiten (podem dir que això és anàleg als temes).

Escalabilitat i tolerància a errors

L'escalabilitat del sistema en conjunt depèn del grau d'escalabilitat de les capes i components del sistema:

  • Els serveis s'escalen afegint nodes addicionals al clúster amb controladors per a aquest servei. Durant l'operació de prova, podeu triar la política d'equilibri òptima.
  • El servei de missatgeria en si dins d'un clúster independent s'escala generalment movent punts d'intercanvi especialment carregats a nodes de clúster separats o afegint processos intermediaris a àrees especialment carregades del clúster.
  • L'escalabilitat de tot el sistema com a característica depèn de la flexibilitat de l'arquitectura i de la capacitat de combinar clústers individuals en una entitat lògica comuna.

L'èxit d'un projecte sovint depèn de la simplicitat i la velocitat d'escalat. La missatgeria en la seva versió actual creix juntament amb l'aplicació. Encara que ens falti un clúster de 50-60 màquines, podem recórrer a la federació. Malauradament, el tema de la federació està fora de l'abast d'aquest article.

Reserva

Quan analitzem l'equilibri de càrrega, ja vam parlar de la redundància dels controladors de servei. Tanmateix, també s'ha de reservar la missatgeria. En cas d'error de node o màquina, la missatgeria s'hauria de recuperar automàticament i en el menor temps possible.

En els meus projectes faig servir nodes addicionals que recullen la càrrega en cas de caiguda. Erlang té una implementació estàndard en mode distribuït per a aplicacions OTP. El mode distribuït realitza la recuperació en cas d'error llançant l'aplicació fallida en un altre node llançat anteriorment. El procés és transparent; després d'un error, l'aplicació es mou automàticament al node de failover. Podeu llegir més informació sobre aquesta funcionalitat aquí.

Productivitat

Intentem, com a mínim, comparar aproximadament el rendiment de rabbitmq i la nostra missatgeria personalitzada.
Trobo resultats oficials proves rabbitmq de l'equip d'openstack.

En el paràgraf 6.14.1.2.1.2.2. El document original mostra el resultat del RPC CAST:
Blocs de construcció d'aplicacions distribuïdes. Segona aproximació

No farem cap configuració addicional al nucli del sistema operatiu o a la màquina virtual d'erlang per endavant. Condicions per a la prova:

  • erl opta: +A1 +sbtu.
  • La prova dins d'un únic node erlang s'executa en un ordinador portàtil amb un antic i7 en versió mòbil.
  • Les proves de clúster es realitzen en servidors amb una xarxa 10G.
  • El codi s'executa en contenidors Docker. Xarxa en mode NAT.

Codi de prova:

req_resp_bench(_) ->
  W = perftest:comprehensive(10000,
    fun() ->
      messaging:request(?EXCHANGE, default, ping, self()),
      receive
        #'$msg'{message = pong} -> ok
      after 5000 ->
        throw(timeout)
      end
    end
  ),
  true = lists:any(fun(E) -> E >= 30000 end, W),
  ok.

Escenari 1: La prova s'executa en un ordinador portàtil amb una versió mòbil antiga i7. La prova, la missatgeria i el servei s'executen en un node en un contenidor Docker:

Sequential 10000 cycles in ~0 seconds (26987 cycles/s)
Sequential 20000 cycles in ~1 seconds (26915 cycles/s)
Sequential 100000 cycles in ~4 seconds (26957 cycles/s)
Parallel 2 100000 cycles in ~2 seconds (44240 cycles/s)
Parallel 4 100000 cycles in ~2 seconds (53459 cycles/s)
Parallel 10 100000 cycles in ~2 seconds (52283 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (49317 cycles/s)

Escenari 2: 3 nodes que s'executen en diferents màquines sota docker (NAT).

Sequential 10000 cycles in ~1 seconds (8684 cycles/s)
Sequential 20000 cycles in ~2 seconds (8424 cycles/s)
Sequential 100000 cycles in ~12 seconds (8655 cycles/s)
Parallel 2 100000 cycles in ~7 seconds (15160 cycles/s)
Parallel 4 100000 cycles in ~5 seconds (19133 cycles/s)
Parallel 10 100000 cycles in ~4 seconds (24399 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (34517 cycles/s)

En tots els casos, la utilització de la CPU no va superar el 250%

Resultats de

Espero que aquest cicle no sembli un abocament mental i la meva experiència sigui de veritable benefici tant per als investigadors de sistemes distribuïts com per als professionals que estan al començament de la construcció d'arquitectures distribuïdes per als seus sistemes empresarials i estan mirant Erlang/Elixir amb interès. , però tens dubtes val la pena...

Sessió De Fotos @chuttersnap

Només els usuaris registrats poden participar en l'enquesta. Inicia sessiósi us plau.

Quins temes he de tractar amb més detall com a part de la sèrie VTrade Experiment?

  • Teoria: Mercats, comandes i el seu moment: DAY, GTD, GTC, IOC, FOK, MOO, MOC, LOO, LOC

  • Llibre de comandes. Teoria i pràctica de la implementació d'un llibre amb agrupacions

  • Visualització del comerç: ticks, barres, resolucions. Com emmagatzemar i com enganxar

  • Backoffice. Planificació i desenvolupament. Seguiment dels empleats i investigació d'incidències

  • API. Anem a esbrinar quines interfícies es necessiten i com implementar-les

  • Emmagatzematge d'informació: PostgreSQL, Timecale, Tarantool en sistemes de comerç

  • Reactivitat en els sistemes comercials

  • Altres. Escriuré als comentaris

Han votat 6 usuaris. 4 usuaris es van abstenir.

Font: www.habr.com

Afegeix comentari