Pas de missatges
- Model de programació síncron i asíncron
- Comunicació asíncrona
- Gestió síncrona de peticions
- Gestió asíncrona de peticions
- Exemples
- Programació i sistemes reactius
El pas de missatges pot implementar-se:
- Dins d'un procés, mitjançant fils. Utilitzant buffers o cues, per exemple.
- Entre processos. Habitualment, es fa utilitzant el paradigma client/servidor i mitjançant xarxes. Un possible mecanisme és l'ús de sòcols, com es podrà veure a la UF Sòcols i serveis. En aquesta comunicació no hi ha compartició de dades mutables, però pot passar que múltiples clients accedeixin simultàniament a un mateix servidor.
En el diagrama pot veure's una implementació entre processos.
Model de programació síncron i asíncron
La comunicació entre les dues parts es pot realitzar de forma síncrona o de forma asíncrona, segons hi hagi un bloqueig E/S (entrada/sortida).
Comes pot veure al diagrama, en la forma síncrona el client espera la resposta del servidor (bloqueig E/S), i mentrestant no fa res. A la forma asíncrona envia la petició, continua treballant i en un moment donat rep la resposta (sense bloqueig E/S).
Quina forma és més convenient? Depèn de les circumstàncies. La forma síncrona és més fàcil d'implementar, però l'asíncrona permet millorar el rendiment del sistema introduint la concurrència.
Comunicació asíncrona
Les peticions asíncrones han de permetre al client conèixer el resultat a posteriori. Alguns solucions possibles:
- Cap: el client només pot saber com va anar fent una o diverses consultes posteriors (polling).
- Una crida de codi: quan acaba la petició, el servidor fa una crida al codi. Podria implementar-se mitjançant callbacks.
- Un missatge: quan acaba la petició, el servidor envia un missatge que pot rebre el client. Aquest missatge pot viatjar en diferents protocols, i se sol implementar mitjançant algun tipus de middleware. Habitualment, els missatges van a parar a cues, que després gestionen els servidors.
Gestió síncrona de peticions
Quan utilitzem el model síncron (amb bloqueig), un sol fil no pot gestionar diverses peticions simultànies. Això vol dir que necessitem crear un fil per gestionar cada petició i retornar la resposta. En diem arquitectura basada en fils.
Habitualment, es limita el nombre de fils que es permeten gestionar simultàniament per evitar el consum excessiu de recursos.
Gestió asíncrona de peticions
Es reprodueix el patró productor-consumidor: els productors són l'origen dels esdeveniments, i només saben que un ha ocorregut; mentre els consumidors necessiten saber que hi ha un nou esdeveniment, i l'han d'atendre (handle). En diem arquitectura basada en esdeveniments.
Algunes tècniques per implementar el servei:
- El patró reactor: les peticions es reben i es processen de forma síncrona, en un mateix fil. Funciona si les peticions es processen ràpidament.
- El patró proactor: les peticions es reben i es divideix el processament asíncronament, introduint concurrència.
A Java tenim Vert.x, una implementació multireactor (amb N bucles d'esdeveniments).
Una altra tècnica per a gestionar peticions asíncrones és el model d'actors. Aquest model permet crear programes concurrents utilitzant actors no concurrents.
- Un actor és una unitat de computació lleugera i desacoblada.
- Els actors tenen estat, però no poden accedir a l'estat d'altres actors.
- Es pot comunicar amb altres actors mitjançant missatges asíncrons immutables.
- L'actor processa els missatges seqüencialment, evitant contenció sobre l'estat.
- Els missatges poden estar distribuïts per la xarxa.
- No es pressuposa cap ordre concret en els missatges.
A Java, tenim un exemple de llibreria: Akka.
Exemples
Una forma d'implementar-lo és passar missatges entre fils mitjançant l'ús d'una cua sincronitzada. Pot haver-hi un o més productors i un o més consumidors. La cua ha de ser thread-safe. A Java, les implementacions de BlockingQueue, ArrayBlockingQueue i LinkedBlockingQueue, en són exemples. Els objectes a aquestes cues han de ser d'un tipus immutable.
Buffer asíncron (cua)
En aquest exemple, un fil productor envia treballs (1, 2, 3, 4) a un fil consumidor mitjançant una cua thread-safe. La mida màxima de la cua es 2.
Les accions són:
- put (prod): afegir un treball, esperant si no hi ha prou espai.
- take (cons): llegir un treball per processar-lo, i esperar si no hi ha cap.
Flux de crides de la impressora asíncrona
De vegades, les peticions fan referència a un recurs compartit que no permet el seu ús per més d'un client alhora. En aquests casos, es pot implementar una cua que gestioni les peticions de forma asíncrona:
- El client realitza la petició asíncrona, i més endavant pot rebre la resposta o confirmació de la petició.
- El servidor registra la petició en una cua, que va atenent per ordre a un fil independent.
La impressora és un únic fil (servidor) que va llegint els treballs afegits a la cua per diferents usuaris (fils), i atenent-los.
També podríem tenir més d'una cua, si hi ha la possibilitat de tenir més d'un punt per atendre les peticions (diverses impressores).
Programació i sistemes reactius
La programació passiva és la tradicional als dissenys OO: un mòdul delega en un altre per a produir un canvi al model.
L'alternativa plantejada es diu programació reactiva, on utilitzem callbacks per a invertir la responsabilitat.
El terme "reactiu" s'utilitza en dos contextos:
- La programació reactiva està basada en esdeveniments (event-driven). Un esdeveniment permet el registre de diversos observadors. Habitualment funciona de forma local.
- Els sistemes reactius generalment es basen en missatges (message-driven) amb un únic destí. Es corresponen més sovint a processos distribuïts que es comuniquen a través d'una xarxa, potser com a microserveis que cooperen.
En l'exemple de la cistella de la compra, podem veure com implementar-ho amb programació passiva i reactiva:
- Amb passiva, la cistella actualitza la factura. Per tant, la cistella és la responsable del canvi i depèn de la factura.
- Amb reactiva, la factura rep un esdeveniment de producte afegit i s'actualiza a si mateixa. La factura depèn de la cistella, ja que li ha de dir que vol sentir els seus esdeveniments.
Pros i contres:
- La programació reactiva permet entendre millor com funciona un mòdul: només cal mirar al seu codi, ja que és responsable d'ell mateix. Amb la passiva és més difícil, ja que cal mirar-se els altres mòduls que el modifiquen.
- Per altra banda, amb programació passiva és més fàcil entendre a quins mòduls afecta un: mirant quines referències es fan. Amb programació reactiva cal mirar-se quins mòduls generen un cert esdeveniment.
La programació reactiva és asíncrona i sense bloqueig. Els fils que busquen recursos compartits no bloquegen l’espera que el recurs estigui disponible. En el seu lloc, continuen la seva execució i són notificats després quan el servei s'ha completat.
Les extensions reactives permeten que llenguatges imperatius, com Java, puguin implementar programació reactiva. Ho fan utilitzant programació asíncrona i streams observables, que emeten tres tipus d'esdeveniments als seus subscriptors: següent, error i completat.
Des de Java 9 s'han definit els streams reactius utilitzant el patró Publish-Subscribe (molt semblant al patró observador) mitjançant les interfícies Flow. Les implementacions més utilitzades són Project Reactor (p. ex. Spring WebFlux) i RxJava (p. ex. Android).
Per altra banda, un sistema reactiu és un estil d'arquitectura que permet que diverses aplicacions puguin comportar-se com una sola, reaccionant al seu entorn, mantenint-se al corrent els uns dels altres, i permetent la seva elasticitat, resiliència i responsivitat basats (habitualment) en cues de missatges dirigits a receptors concrets (vegeu el Reactive Manifesto). Una aplicació dels sistemes reactius són els microserveis.
Tant els patrons reactor/proactor com el model d'actors permeten implementar sistemes reactius.