Laborator 5

Tematica laboratorului

1. Mecanisme de serializare

O platforma utilizata in transmiterea si procesarea de flux de date (stream processing) dispune in mod obisnuit de un mecanism intern de serializare a mesajelor (evenimentelor) pentru a trimite continutul acestora intre unitatile de procesare distribuite (intre operatori). De exemplu, Storm foloseste un astfel de mecanism pentru serializarea mesajelor trimise intre operatori care implicit poate serializa valori incluse in tuple care sunt fie de tip primitiv, fie array de octeti, fie tipurile ArrayList, HashMap sau HashSet pentru Java.

Pentru alte tipuri mai complexe de date serializarea interna in Storm se face utilizand Kryo. Sa consideram de exemplu clasa CustomMessage ce reprezinta un mesaj simplu care contine un nume si o lista de numere de telefon asociate. Daca dorim sa trimitem un astfel de mesaj ca valoare intr-un tuplu, mesajul trebuie serializat folosind Kryo. Cea mai simpla modalitate, transparenta pentru programator este utilizarea unei clase de serializare FieldSerializare care se ocupa in mod implicit de serializarea campurilor din clasa noastra mesaj. Pentru aceasta e suficient ca la configurarea topologiei sa se indice necesitatea serializarii pentru clasa mesaj:

config.registerSerialization(CustomMessage.class);

Daca se doreste o operatie specifica de executat in cadrul serializarii si deserializarii e necesara definirea unei clase Serializer asociata clasei pe care vrem sa o serializam. Un exemplu din documentatia Kryo pentru serializarea explicita asociata unei clase Color este urmatorul:

public class ColorSerializer extends Serializer {

	public void write (Kryo kryo, Output output, Color object) {
            output.writeInt(object.getRGB());
        }

        public Color read (Kryo kryo, Input input, Class type) {
            return new Color(input.readInt());
        }
}

Pentru utilizarea unei clase explicite de serializare e suficient apelul metodei registerSerialization care sa preia ca al doilea parametru respectiva clasa explicita. Exemple de surse: Color.java ; ColorSerializer.java

Celelalte surse actualizate topologie numarare cuvinte:

2. Google Protocol Buffers

Google Protocol Buffers (GPB) ofera o modalitate de a serializa mesajele la nivel extern, de exemplu intre un client si o masina care ruleaza un operator spout din topologia Storm. GPB ofera o modalitate de serializare independenta de platforma sau limbaj avand suport pentru variante multiple. Solutia este deci cu precadere utila intr-un sistem distribuit unde aplicatiile client pot rula pe platforme diferite.

Specificarea unui mesaj in GPB se face mai intai intr-un fisier text .proto. GPB ofera un asa zis compilator care pe baza acestui fisier va genera un cod sursa asociat in limbajul dorit. Sa consideram urmatorul exemplu:

syntax = "proto3";
package tutorial;

option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";

option csharp_namespace = "Google.Protobuf.Examples.AddressBook";

message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

Primele doua linii definesc sintaxa fisierului si un pachet pentru evitarea de conflicte. Sintaxa proto3 este diferita de proto2 simplificand anumite aspecte. Ne vom referi la aceasta. Urmatoarele optiuni definesc caracteristici specifice pentru generarea de cod Java sau C Sharp.

Un camp message include continutul unui bloc mesaj serializat fiind reprezentat de exemplu in Java de o clasa in codul generat. Un mesaj, poate contine la randul sau alte blocuri de tip mesaj definite ca interne unui mesaj, sau enumerari de constante.

Mesajul este in sine o simpla compunere formata dintr-o enumerare de campuri definite folosind un tip, un nume al campului si un marker. Tipurile campurilor pot fi scalari conform acestei liste, alte mesaje, sau enumerari. Markerii asociati fiecarui camp ("= numar") reprezinta un tag folosit in codarea binara. Se recomanda folosirea tagurilor 1-15 pentru campurile care vor fi prezente mai des in mesajul efectiv (de exemplu ca parte a unei liste), deoarece ocupa mai putin spatiu la serializare.

In versiunea 2 a GPB campurile erau adnotate explicit cu directive "required" sau "optional" specificand obligativitatea prezentei sau nu a campului in mesajul generat efectiv. In versiunea 3 s-a eliminat aceasta adnotare un camp singular fiind conditionat sa fie prezent cel mult o data in mesaj. O adnotare care s-a pastrat este "repeated" care specifica faptul ca respectivul camp va fi prezent ca o colectie in mesajul generat care poate contine 0 sau mai multe elemente.

O adaugire importanta in versiunea 3 a GPB o reprezinta suportul pentru maps care se pot defini ca in urmatorul exemplu:

message Foo {
  map<string, string> values = 1;
}

Compilarea unui astfel de fisier .proto se face folosind un compilator pus la dispozitie de GPB - protoc - disponibil in pachetele puse la dispozitie. Ultima varianta pentru versiunea proto3 se gaseste aici. Generarea surselor se face printr-un simplu apel precum urmatorul (exemplu pentru Java):

protoc --java_out=director_destinatie fisier.proto

Pentru fiecare mesaj va fi generata o clasa asociata si o clasa Builder ce este utilizata pentru a instantia clasa mesaj. Ambele clase dispun de metode getter pentru a obtine valoarea campului getNumeCamp() cu retur avand tipul campului. Versiunea proto2 includea si o metoda aditionala pentru a verifica daca un anume camp este setat: hasNumeCamp() cu retur boolean.
Clasele builder dispun de metode aditionale pentru modificarea valorii campurilor: setNumeCamp(TipCamp valoare) pentru setarea unui camp si clearNumeCamp() pentru stergerea campului respectiv.

In plus, pentru clasele ce contin campuri de tip colectie, sunt adaugate metode specifice de genul getteri cu index sau addNumeCamp(TipCamp valoare) care sa adauge o valoare la o colectie.

Pentru a construi o instanta a unui mesaj, clasa asociata builder este instantiata, se completeaza campurile dorite, dupa care se apeleaza metoda build(), care va returna o instanta a clasei mesaj respectiva. Odata creata instanta mesajului este immutable (nemodificabila), din aceasta cauza instantierea fiind realizata cu ajutorul clasei builder.

Pentru serializarea efectiva se pot utiliza urmatoarele metode:

  • byte[] toByteArray() - serializeaza mesajul intr-un array de octeti
  • static TipMesaj parseFrom(byte[] data) - deserializeaza mesajul dintr-un array de octeti
  • void writeTo(OutputStream output) - serializeaza mesajul intr-un OutputStream
  • static TipMesaj parseFrom(InputStream input) - deserializeaza mesajul dintr-un InputStream

La acest link se regaseste exemplul pentru serializarea fisierului proto de mai sus, propusa de asemeni ca exemplu pe site-ul GPB, iar respectiv aici exemplul pentu deserializare. Pentru dependente necesare Maven, ultima versiune stabila de GPB are intrarea de mai jos:

GroupId: com.google.protobuf
ArtifactId: protobuf-java
Version: 3.19.4

© 2022 Emanuel Onica. Parts of design by W3Layouts