Načela i obrasci dizajna za vrlo istodobne primjene

1. Pregled

U ovom uputstvu razgovarat ćemo o nekim principima i uzorcima dizajna koji su uspostavljeni tijekom vremena za izgradnju istodobnih aplikacija.

Međutim, vrijedno je napomenuti da je dizajniranje istodobne aplikacije široka i složena tema, pa stoga niti jedan vodič ne može tvrditi da je iscrpan u svom tretmanu. Ovdje ćemo opisati neke od popularnih trikova koji se često koriste!

2. Osnove istodobnosti

Prije nego što nastavimo dalje, provedimo neko vrijeme razumijevajući osnove. Za početak moramo razjasniti svoje razumijevanje onoga što nazivamo istodobnim programom. Mislimo na istodobni program ako se istodobno događa više računanja.

Sad, imajte na umu da smo spomenuli računanja koja se događaju istovremeno - to jest, ona su u tijeku u isto vrijeme. Međutim, oni mogu ili ne moraju izvršavati istovremeno. Važno je razumjeti razliku kao istodobno izvršavanje proračuna naziva se paralelnim.

2.1. Kako stvoriti istodobne module?

Važno je razumjeti kako možemo stvoriti istodobne module. Postoje brojne mogućnosti, ali ovdje ćemo se usredotočiti na dva popularna izbora:

  • Postupak: Proces je instanca pokrenutog programa koji je izoliran od ostalih procesa u istom stroju. Svaki postupak na stroju ima svoje izolirano vrijeme i prostor. Stoga obično nije moguće dijeliti memoriju između procesa i oni moraju komunicirati prosljeđivanjem poruka.
  • Nit: S druge strane, nit je samo segment procesa. Unutar programa može biti više niti koje dijele isti memorijski prostor. Međutim, svaka nit ima jedinstveni stog i prioritet. Nit može biti izvorna (izvorno je planira operativni sustav) ili zelena (zakazana u runtime knjižnici).

2.2. Kako interaktivni moduli međusobno djeluju?

Sasvim je idealno ako istodobni moduli ne moraju komunicirati, ali to često nije slučaj. To dovodi do dva modela istodobnog programiranja:

  • Zajedničko sjećanje: U ovom modelu, istodobni moduli komuniciraju čitanjem i upisivanjem zajedničkih objekata u memoriju. To često dovodi do preplitanja istodobnih izračunavanja, što uzrokuje uvjete utrke. Stoga može nedeterministički dovesti do netočnih stanja.
  • Poruka prolazi: U ovom modelu, istodobni moduli komuniciraju međusobno prenoseći poruke komunikacijskim kanalom. Ovdje svaki modul uzastopno obrađuje dolazne poruke. Budući da ne postoji zajedničko stanje, relativno je lakše programirati ga, ali ovo još uvijek nije bez uvjeta utrke!

2.3. Kako se izvršavaju istodobni moduli?

Prošlo je neko vrijeme otkako je Mooreov zakon udario u zid s obzirom na taktove procesora. Umjesto toga, otkako moramo rasti, počeli smo spakirati više procesora na isti čip, često zvani višejezgreni procesori. Ali ipak, nije uobičajeno čuti za procesore koji imaju više od 32 jezgre.

Sada znamo da jedna jezgra može istodobno izvršavati samo jednu nit ili niz uputa. Međutim, broj procesa i niti može biti stotine, odnosno tisuće. Pa, kako to stvarno djeluje? Ovo je gdje operativni sustav za nas simulira istodobnost. Operativni sustav to postiže rezanje vremena - što učinkovito znači da se procesor često, nepredvidivo i nedeterministički prebacuje između niti.

3. Problemi u istodobnom programiranju

Dok raspravljamo o načelima i uzorcima za dizajniranje istodobne aplikacije, bilo bi pametno prvo razumjeti koji su tipični problemi.

Uglavnom uključuje naše iskustvo s istodobnim programiranjem pomoću izvornih niti sa zajedničkom memorijom. Stoga ćemo se usredotočiti na neke od uobičajenih problema koji iz toga proizlaze:

  • Međusobno izuzeće (primitivi za sinkronizaciju): Preplitanje niti moraju imati ekskluzivni pristup zajedničkom stanju ili memoriji kako bi se osigurala ispravnost programa. Sinkronizacija zajedničkih resursa popularna je metoda za postizanje međusobnog isključivanja. Dostupno je nekoliko primitiva za sinkronizaciju - na primjer, brava, monitor, semafor ili mutex. Međutim, programiranje za međusobno isključivanje sklono je pogreškama i često može dovesti do uskih grla u izvedbi. Postoji nekoliko dobro raspravljenih problema povezanih s tim poput mrtve točke i livelocka.
  • Prebacivanje konteksta (teške niti): Svaki operativni sustav ima izvornu, premda raznoliku, podršku za istodobne module poput procesa i niti. Kao što je već rečeno, jedna od temeljnih usluga koju operativni sustav pruža je planiranje niti za izvršavanje na ograničenom broju procesora kroz vremensko rezanje. To zapravo znači to niti se često prebacuju između različitih stanja. U tom procesu treba sačuvati i nastaviti njihovo trenutno stanje. Ovo je dugotrajna aktivnost koja izravno utječe na ukupnu propusnost.

4. Dizajn uzoraka za visoku istodobnost

Sada, kad razumijemo osnove istodobnog programiranja i uobičajene probleme u njemu, vrijeme je da shvatimo neke od uobičajenih obrazaca za izbjegavanje tih problema. Moramo ponoviti da je istodobno programiranje težak zadatak koji zahtijeva puno iskustva. Stoga slijeđenje nekih utvrđenih obrazaca može olakšati zadatak.

4.1. Konkurentnost zasnovana na glumcu

Prvi dizajn o kojem ćemo razgovarati s obzirom na istovremeno programiranje zove se Model glumca. Ovo je matematički model istodobnog računanja koji u osnovi sve tretira kao glumca. Glumci mogu međusobno prosljeđivati ​​poruke i kao odgovor na poruku mogu donositi lokalne odluke. Ovo je prvi predložio Carl Hewitt i nadahnulo je brojne programske jezike.

Scalin primarni konstrukt za istodobno programiranje su glumci. Glumci su normalni objekti u Scali koje možemo stvoriti instanciranjem datoteke Glumac razred. Nadalje, Scala Actors knjižnica nudi mnoge korisne glumačke operacije:

klasa myActor produžuje Actor {def act () {while (true) {receive {// Izvedi neku radnju}}}}

U gornjem primjeru, poziv na primiti metoda unutar beskonačne petlje suspendira glumca dok ne stigne poruka. Po dolasku, poruka se uklanja iz glumčevog poštanskog sandučića i poduzimaju potrebne radnje.

Glumački model uklanja jedan od temeljnih problema s istodobnim programiranjem - dijeljenu memoriju. Glumci komuniciraju putem poruka, a svaki glumac obrađuje poruke iz svojih ekskluzivnih poštanskih sandučića uzastopno. Međutim, glumce izvršavamo preko spremišta niti. Vidjeli smo da izvorne niti mogu biti teške i, prema tome, ograničene u broju.

Postoje, naravno, i drugi obrasci koji nam ovdje mogu pomoći - kasnije ćemo ih pokriti!

4.2. Istodobnost zasnovana na događajima

Dizajni temeljeni na događajima izričito rješavaju problem zbog kojeg se izvorne niti skupo stvaraju i rade. Jedan od dizajna temeljenih na događajima je petlja događaja. Petlja događaja radi s pružateljem događaja i skupom obrađivača događaja. U ovoj postavci, petlja događaja blokira davatelja događaja i šalje ga obrađivaču događaja po dolasku.

U osnovi, petlja događaja nije ništa drugo do dispečer događaja! Sama petlja događaja može se izvoditi na samo jednoj izvornoj niti. Pa, što se zapravo događa u petlji događaja? Pogledajmo primjer pseudo-koda stvarno jednostavne petlje događaja:

while (true) {događaji = getEvents (); za (e u događajima) processEvent (e); }

U osnovi, sve što naša petlja događaja radi kontinuirano tražimo događaje i, kada se događaji pronađu, obrađujemo ih. Pristup je doista jednostavan, ali ubire korist dizajna vođenog događajima.

Izgradnja istodobnih aplikacija pomoću ovog dizajna daje veću kontrolu nad aplikacijom. Također, uklanja neke tipične probleme višenitnih aplikacija - na primjer, mrtvu točku.

JavaScript implementira petlju događaja kako bi ponudio asinkrono programiranje. Održava skup poziva kako bi se evidentirale sve funkcije koje treba izvršiti. Također održava red događaja za slanje novih funkcija na obradu. Petlja događaja neprestano provjerava niz poziva i dodaje nove funkcije iz reda događaja. Svi async pozivi šalju se na web API-je, koje obično pruža preglednik.

Sama petlja događaja može se izvoditi iz jedne niti, ali web API-ji pružaju zasebne niti.

4.3. Neblokirajući algoritmi

U algoritmima koji ne blokiraju, suspenzija jedne niti ne dovodi do suspenzije drugih niti. Vidjeli smo da u našoj aplikaciji možemo imati samo ograničeni broj izvornih niti. Sada, algoritam koji blokira nit očito značajno smanjuje propusnost i sprječava nas u stvaranju istodobnih aplikacija.

Neblokirajući algoritmi uvijek iskoristite atomski primitiv usporedi i zamijeni koji pruža temeljni hardver. To znači da će hardver usporediti sadržaj memorijskog mjesta s danom vrijednošću i samo ako su isti, ažurirat će vrijednost na novu zadanu vrijednost. Ovo može izgledati jednostavno, ali nam učinkovito pruža atomsku operaciju koja bi inače zahtijevala sinkronizaciju.

To znači da moramo pisati nove podatkovne strukture i knjižnice koje koriste ovu atomsku operaciju. To nam je dalo ogroman set implementacija bez čekanja i zaključavanja na nekoliko jezika. Java ima nekoliko neblokirajućih struktura podataka poput AtomicBoolean, AtomicInteger, AtomicLong, i AtomicReference.

Razmotrite aplikaciju u kojoj više niti pokušava pristupiti istom kodu:

logička vrijednost open = false; if (! open) {// Učinite nešto otvoreno = false; }

Jasno je da gornji kod nije siguran u niti, a njegovo ponašanje u okruženju s više niti može biti nepredvidljivo. Naše su mogućnosti ovdje sinkronizirati ovaj dio koda s bravom ili upotrijebiti atomsku operaciju:

AtomicBoolean otvoren = novi AtomicBoolean (netačno); if (open.compareAndSet (false, true) {// Učini nešto}

Kao što vidimo, upotreba neblokirajuće strukture podataka poput AtomicBoolean pomaže nam da napišemo kôd siguran za nit bez upuštanja u nedostatke brava!

5. Podrška u programskim jezicima

Vidjeli smo da postoji više načina na koje možemo konstruirati istodobni modul. Iako programski jezik čini razliku, uglavnom je kako osnovni operativni sustav podržava koncept. Međutim, kao istodobnost zasnovana na nitima koju podržavaju izvorne niti udaraju u nove zidove s obzirom na skalabilnost, uvijek su nam potrebne nove mogućnosti.

Provedba nekih dizajnerskih praksi o kojima smo govorili u prošlom odjeljku dokazuju se učinkovitima. Međutim, moramo imati na umu da to komplicira programiranje kao takvo. Ono što nam uistinu treba jest nešto što pruža snagu istodobnosti temeljene na nitima bez neželjenih učinaka koje ona donosi.

Jedno rješenje koje nam je dostupno su zelene niti. Zelene niti su niti koje planira runtime knjižnica umjesto da ga osnovni operativni sustav izvorno zakaže. Iako se ovim ne rješavaju svi problemi u istodobnosti temeljenoj na nitima, zasigurno nam u nekim slučajevima može pružiti bolje performanse.

Sada nije trivijalno koristiti zelene niti, osim ako to podržava programski jezik koji odaberemo. Nema svaki programski jezik ovu ugrađenu podršku. Također, ono što mi slobodno nazivamo zelenim nitima različiti programski jezici mogu implementirati na vrlo jedinstvene načine. Pogledajmo neke od ovih opcija koje su nam dostupne.

5.1. Goroutine u Gou

Goroutine u programskom jeziku Go lagane su niti. Oni nude funkcije ili metode koje se mogu istodobno izvoditi s drugim funkcijama ili metodama. Goroutine jesu izuzetno jeftini, jer za početak zauzimaju samo nekoliko kilobajta.

Što je najvažnije, goroutine se multipleksiraju s manjim brojem izvornih niti. Štoviše, goroutine međusobno komuniciraju pomoću kanala, izbjegavajući tako pristup zajedničkoj memoriji. Dobivamo gotovo sve što nam treba, i pogodite što - ne radeći ništa!

5.2. Procesi u Erlangu

U Erlangu se svaka nit izvršenja naziva procesom. Ali, nije baš poput procesa o kojem smo do sada razgovarali! Erlang procesi su male težine s malim otiskom memorije i brze su za stvaranje i odlaganje s niskim rasporedom iznad glave.

Ispod haube, Erlangovi procesi nisu ništa drugo nego funkcije za koje izvršavanje rukuje rasporedom. Štoviše, Erlangovi procesi ne dijele nikakve podatke i međusobno komuniciraju prosljeđivanjem poruka. To je razlog zašto te "procese" uopće nazivamo!

5.3. Vlakna u Javi (prijedlog)

Priča o paralelnosti s Javom kontinuirano je evoluirala. Java je za početak imala podršku za zelene niti, barem za operativne sustave Solaris. Međutim, to je prekinuto zbog prepreka izvan opsega ovog vodiča.

Od tada je istodobnost u Javi sve o izvornim nitima i načinu pametnog rada s njima! Ali iz očitih razloga, uskoro možemo imati novu apstrakciju istodobnosti u Javi, nazvanu fiber. Projekt Loom predlaže uvesti nastavke zajedno s vlaknima, što može promijeniti način na koji pišemo istodobne aplikacije na Javi!

Ovo je samo kratki pogled na ono što je dostupno u različitim programskim jezicima. Postoje daleko zanimljiviji načini na koje su se drugi programski jezici pokušali nositi s istodobnošću.

Štoviše, vrijedno je napomenuti da kombinacija dizajnerskih obrazaca o kojima je bilo riječi u posljednjem odjeljku, zajedno s podrškom programskog jezika za apstrakciju nalik zelenoj niti, može biti izuzetno moćna pri dizajniranju izuzetno istodobnih aplikacija.

6. Primjene s visokom istodobnošću

Aplikacija iz stvarnog svijeta često ima više komponenata koje međusobno komuniciraju preko žice. Obično mu pristupamo putem interneta, a sastoji se od više usluga poput proxy usluge, pristupnika, web usluge, baze podataka, usluge direktorija i datotečnih sustava.

Kako u takvim situacijama osigurati visoku podudarnost? Istražimo neke od ovih slojeva i mogućnosti koje imamo za izgradnju izuzetno istodobne aplikacije.

Kao što smo vidjeli u prethodnom odjeljku, ključ za izgradnju aplikacija s visokom istodobnošću je uporaba nekih od tamo raspravljenih koncepata dizajna. Moramo odabrati pravi softver za posao - onaj koji već uključuje neke od ovih praksi.

6.1. Web sloj

Web je tipično prvi sloj na koji stižu korisnički zahtjevi i ovdje je neizbježno osiguravanje visoke konkurentnosti. Pogledajmo koje su neke od mogućnosti:

  • Čvor (također se naziva NodeJS ili Node.js) je JavaScript izvođenja s otvorenim kodom, više platformi izgrađen na Chromeovom V8 JavaScript mehanizmu. Čvor prilično dobro funkcionira u rukovanju asinkronim I / O operacijama. Razlog zbog kojeg Node to čini tako dobro je što implementira petlju događaja preko jedne niti. Petlja događaja uz pomoć povratnih poziva asinkrono obrađuje sve operacije blokiranja poput I / O-a.
  • nginx je web poslužitelj otvorenog koda koji obično koristimo kao obrnuti proxy među ostalim svojim običajima. Razlog zbog kojeg nginx pruža visoku istodobnost jest taj što koristi asinkroni pristup temeljen na događajima. nginx radi s glavnim postupkom u jednoj niti. Glavni postupak održava radničke procese koji vrše stvarnu obradu. Stoga radnički procesi obrađuju svaki zahtjev istodobno.

6.2. Sloj aplikacije

Tijekom dizajniranja aplikacije, postoji nekoliko alata koji će nam pomoći u stvaranju visoke konkurentnosti. Ispitajmo nekoliko ovih knjižnica i okvira koji su nam dostupni:

  • Akka je komplet alata napisan u Scali za izgradnju istodobnih i distribuiranih aplikacija na JVM-u. Akkin pristup rješavanju istodobnosti temelji se na glumačkom modelu o kojem smo ranije razgovarali. Akka stvara sloj između glumaca i temeljnih sustava. Okvir rješava složenost stvaranja i raspoređivanja niti, primanja i slanja poruka.
  • Projektni reaktor je reaktivna knjižnica za izgradnju neblokirajućih aplikacija na JVM-u. Temelji se na specifikaciji Reactive Streams i usredotočuje se na učinkovito prosljeđivanje poruka i upravljanje zahtjevima (povratni pritisak). Operateri reaktora i planeri mogu održavati visoku brzinu protoka poruka. Nekoliko popularnih okvira pruža implementacije reaktora, uključujući Spring WebFlux i RSocket.
  • Netty je asinkroni mrežni aplikacijski okvir koji se pokreće događajima. Netty možemo koristiti za razvoj istodobnih poslužitelja protokola i klijenata. Netty koristi NIO, koji je skup Java API-ja koji nudi asinkroni prijenos podataka kroz međuspremnike i kanale. Nudi nam nekoliko prednosti poput bolje propusnosti, nižeg kašnjenja, manje potrošnje resursa i smanjenja nepotrebne memorijske kopije.

6.3. Podatkovni sloj

Napokon, niti jedna aplikacija nije potpuna bez svojih podataka, a podaci dolaze iz trajne pohrane. Kada razgovaramo o visokoj istodobnosti u odnosu na baze podataka, najveći dio fokusa ostaje na NoSQL obitelji. To je prije svega zbog linearne skalabilnosti koju mogu ponuditi baze podataka NoSQL, ali je teško postići u relacijskim varijantama. Pogledajmo dva popularna alata za podatkovni sloj:

  • Cassandra je besplatna distribuirana baza podataka NoSQL otvorenog koda koji pruža visoku dostupnost, visoku skalabilnost i otpornost na kvarove na robnom hardveru. Međutim, Cassandra ne pruža ACID transakcije koje obuhvaćaju više tablica. Dakle, ako naša aplikacija ne zahtijeva jaku dosljednost i transakcije, možemo iskoristiti Cassandrine operacije s malim kašnjenjem.
  • Kafka je distribuirana streaming platforma. Kafka pohranjuje tok zapisa u kategorijama koje se nazivaju teme. Može pružiti linearnu vodoravnu skalabilnost i za proizvođače i za potrošače zapisa, istodobno pružajući visoku pouzdanost i trajnost. Particije, replike i brokeri neki su od temeljnih koncepata na kojima pruža masovno distribuiranu istodobnost.

6.4. Sloj predmemorije

Pa, niti jedna web aplikacija u modernom svijetu koja cilja na visoku istodobnost ne može si priuštiti da svaki put pogodi bazu podataka. To nam ostavlja da odaberemo predmemoriju - po mogućnosti predmemoriju u memoriji koja može podržati naše vrlo istodobne aplikacije:

  • Lijeska je distribuirano spremište objekata u memoriji, prilagođeno oblaku i računarski mehanizam koji podržava širok spektar struktura podataka kao što je Karta, Postavi, Popis, MultiMap, RingBuffer, i HyperLogLog. Ima ugrađenu replikaciju i nudi visoku dostupnost i automatsku particiju.
  • Redis je spremište strukture podataka u memoriji koje primarno koristimo kao predmemoriju. Pruža bazu podataka ključ / vrijednost u memoriji s opcijskom trajnošću.Podržane podatkovne strukture uključuju nizove, hasheve, popise i skupove. Redis ima ugrađenu replikaciju i nudi visoku dostupnost i automatsku particiju. U slučaju da nam ne treba upornost, Redis nam može ponuditi značajku, umreženu predmemoriju u memoriji izvanrednih performansi.

Naravno, jedva smo ogrebali površinu onoga što nam je dostupno u našoj potrazi za izgradnjom izuzetno istodobne aplikacije. Važno je napomenuti da bi nas više od dostupnog softvera trebao voditi prema stvaranju odgovarajućeg dizajna. Neke od ovih opcija mogu biti prikladne, dok druge možda nisu prikladne.

I, ne zaboravimo da postoji mnogo više dostupnih opcija koje bi mogle biti bolje prilagođene našim zahtjevima.

7. Zaključak

U ovom smo članku razgovarali o osnovama istodobnog programiranja. Razumjeli smo neke od temeljnih aspekata istodobnosti i probleme do kojih ona može dovesti. Dalje, prošli smo kroz neke uzorke dizajna koji nam mogu pomoći da izbjegnemo tipične probleme u istodobnom programiranju.

Konačno, prošli smo neke okvire, knjižnice i softver koji nam je dostupan za izgradnju izuzetno istodobne aplikacije od kraja do kraja.


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