Paralelnost s LMAX Disruptor - Uvod

1. Pregled

Ovaj članak predstavlja LMAX Disruptor i govori o tome kako pomaže postići istovremenu podudarnost softvera s malim kašnjenjem. Također ćemo vidjeti osnovnu upotrebu knjižnice Disruptor.

2. Što je disruptor?

Disruptor je Java biblioteka otvorenog koda koju je napisao LMAX. To je istodobni programski okvir za obradu velikog broja transakcija, s malim kašnjenjem (i bez složenosti istodobnog koda). Optimizacija izvedbe postiže se softverskim dizajnom koji iskorištava učinkovitost osnovnog hardvera.

2.1. Mehanička simpatija

Počnimo s temeljnim konceptom mehaničke simpatije - to je sve o razumijevanju kako temeljni hardver djeluje i programiranju na način koji najbolje funkcionira s tim hardverom.

Na primjer, pogledajmo kako organizacija CPU-a i memorije mogu utjecati na performanse softvera. CPU ima nekoliko slojeva predmemorije između sebe i glavne memorije. Kad CPU izvodi operaciju, prvo traži podatke u L1, zatim L2, zatim L3 i na kraju glavnu memoriju. Što dalje mora ići, to će duže trajati operacija.

Ako se ista operacija izvodi na komadu podataka više puta (na primjer, brojač petlje), ima smisla te podatke učitati na mjesto vrlo blizu CPU-a.

Neke indikativne brojke troškova propuštenih predmemorija:

Latencija od CPU-a doCPU ciklusiVrijeme
Glavna memorijaVišestruko~ 60-80 ns
L3 predmemorija~ 40-45 ciklusa~ 15 ns
L2 predmemorija~ 10 ciklusa~ 3 ns
L1 predmemorija~ 3-4 ciklusa~ 1 ns
Registar1 ciklusVrlo vrlo brzo

2.2. Zašto ne Redovi

Implementacije u redu čekanja imaju tendenciju za pisanje na varijablama glave, repa i veličine. Redovi su obično uvijek blizu punih ili gotovo prazni zbog razlika u tempu između potrošača i proizvođača. Vrlo rijetko djeluju u uravnoteženoj sredini gdje je stopa proizvodnje i potrošnje ravnomjerno usklađena.

Da bi se riješio spor s pisanjem, red često koristi brave, što može uzrokovati prebacivanje konteksta na jezgru. Kada se to dogodi, uključeni procesor vjerojatno će izgubiti podatke u svojim predmemorijama.

Da bi se postiglo najbolje ponašanje u predmemoriranju, dizajn bi trebao imati samo jednu jezgru koja zapisuje na bilo koje memorijsko mjesto (više čitača je u redu, jer procesori često koriste posebne brze veze između svojih predmemorija). Redovi propuštaju princip jednog pisca.

Ako dvije odvojene niti zapisuju u dvije različite vrijednosti, svaka jezgra onesposobljava predmemoriju druge linije (podaci se prenose između glavne memorije i predmemorije u blokovima fiksne veličine, koji se nazivaju crte predmemorije). To je prepirka između dviju niti iako se upisuju u dvije različite varijable. To se naziva lažno dijeljenje, jer svaki put kad se pristupi glavi, pristupi se i repu, i obrnuto.

2.3. Kako djeluje disruptor

Disruptor ima kružnu strukturu podataka koja se temelji na nizu (me uspremnik prstena). To je niz koji ima pokazivač na sljedeći dostupni utor. Ispunjen je unaprijed dodijeljenim objektima prijenosa. Proizvođači i potrošači izvode upisivanje i čitanje podataka u prsten bez zaključavanja ili prepirke.

U Disruptoru se svi događaji objavljuju svim potrošačima (multicast), radi paralelne potrošnje kroz zasebne nizvodne redove. Zbog paralelne obrade od strane potrošača, potrebno je koordinirati ovisnosti između potrošača (grafikon ovisnosti).

Proizvođači i potrošači imaju brojač sekvenci koji pokazuje na kojem utoru u međuspremniku trenutno radi. Svaki proizvođač / potrošač može napisati svoj brojač sekvenci, ali može čitati tuđi brojač sekvenci. Proizvođači i potrošači čitaju brojače kako bi osigurali da je utor u koji želi upisati dostupan bez ikakvih brava.

3. Korištenje knjižnice disruptor

3.1. Ovisnost Mavena

Počnimo s dodavanjem ovisnosti knjižnice Disruptor u pom.xml:

 com.lmax disruptor 3.3.6 

Najnoviju verziju ovisnosti možete provjeriti ovdje.

3.2. Definiranje događaja

Definirajmo događaj koji nosi podatke:

javna statička klasa ValueEvent {private int vrijednost; javni konačni statički EventFactory EVENT_FACTORY = () -> novi ValueEvent (); // standardni geteri i postavljači} 

The EventFactory omogućuje Disruptoru unaprijed dodijeliti događaje.

3.3. Potrošač

Potrošači čitaju podatke iz međuspremnika prstena. Definirajmo potrošača koji će se baviti događajima:

javna klasa SingleEventPrintConsumer {... public EventHandler [] getEventHandler () {EventHandler eventHandler = (događaj, slijed, endOfBatch) -> ispis (event.getValue (), slijed); vrati novi EventHandler [] {eventHandler}; } private void print (int id, long sequenceId) {logger.info ("Id je" + id + "id sekvence koji je korišten je" + sequenceId); }}

U našem primjeru potrošač samo ispisuje na zapisnik.

3.4. Konstruiranje disruptora

Konstruirajte disruptor:

ThreadFactory threadFactory = DaemonThreadFactory.INSTANCE; WaitStrategy waitStrategy = novi BusySpinWaitStrategy (); Disruptor disruptor = novi Disruptor (ValueEvent.EVENT_FACTORY, 16, threadFactory, ProducerType.SINGLE, waitStrategy); 

U konstruktoru Disruptor definiraju se sljedeće:

  • Tvornica događaja - odgovorna za generiranje objekata koji će biti pohranjeni u međuspremnik prstena tijekom inicijalizacije
  • Veličina međuspremnika prstena - Definirali smo 16 kao veličinu odbojnika prstena. To mora biti snaga 2, inače bi izbacilo iznimku tijekom inicijalizacije. To je važno jer je lako izvesti većinu operacija pomoću logičkih binarnih operatora, na pr. mod rad
  • Tvornica niti - tvornica za stvaranje niti za procesore događaja
  • Vrsta proizvođača - određuje hoćemo li imati jednog ili više proizvođača
  • Strategija čekanja - definira kako bismo željeli postupati s sporim pretplatnikom koji ne ide u korak s tempom proizvođača

Povežite rukovatelja potrošača:

disruptor.handleEventsWith (getEventHandler ()); 

Moguće je opskrbiti više potrošača Disruptor-om za obradu podataka proizvedenih od proizvođača. U gornjem primjeru imamo samo jednog rukovatelja događaja koji se naziva i potrošač.

3.5. Pokretanje disruptor

Da biste pokrenuli Disruptor:

RingBuffer ringBuffer = disruptor.start ();

3.6. Produkcija i objavljivanje događaja

Proizvođači u nizu smještaju podatke u međuspremnik prstena. Proizvođači moraju biti svjesni sljedećeg dostupnog terminala kako ne bi prepisali podatke koji još nisu potrošeni.

Koristiti RingBuffer od Disruptor-a za objavljivanje:

for (int eventCount = 0; eventCount <32; eventCount ++) {long sequenceId = ringBuffer.next (); ValueEvent valueEvent = ringBuffer.get (sequenceId); valueEvent.setValue (eventCount); ringBuffer.publish (sequenceId); } 

Ovdje producent proizvodi i objavljuje stavke u nizu. Ovdje je važno napomenuti da Disruptor djeluje slično protokolu dvofaznog urezivanja. Čita novo slijedId i objavljuje. Sljedeći put kad bi trebalo slijedId + 1 kao sljedeći slijedId.

4. Zaključak

U ovom uputstvu vidjeli smo što je Disruptor i kako postiže istodobnost uz malu latenciju. Vidjeli smo pojam mehaničke simpatije i kako se on može iskoristiti za postizanje niske latencije. Tada smo vidjeli primjer korištenja knjižnice Disruptor.

Primjer koda može se naći u projektu GitHub - ovo je projekt zasnovan na Mavenu, pa bi ga trebalo lako uvesti i pokrenuti takav kakav jest.


$config[zx-auto] not found$config[zx-overlay] not found