Scalabilitatea în rețele de comunicații de date

Ion Stoica, Mihai Budiu -- istoica+@cs.cmu.edu, mihaib+@cs.cmu.edu
http://www.cs.cmu.edu/~istoica, http://www.cs.cmu.edu/~mihaib

18 aprilie 1999

Subiect:
Un algoritm scalabil pentru alocarea echitabilă a traficului în Internet
Cunoștințe necesare:
Cunoștințe elementare despre funcționarea rețelelor de calculatoare, răbdare
Cuvinte cheie:
ruter, flux, rată de transmisie, disciplinele cozilor, FIFO, fair queuing, core-stateless fair queuing, scalabilitate


Contents




În acest articol ilustrăm una dintre direcțiile cercetării contemporane în domeniul Internetului: căutarea de algoritmi practici pentru a rezolva felurite probleme; problema pe care o ilustrăm este cea a alocării echitabile a traficului pe legăturile dintre calculatoare, în așa fel încît toți participanții la trafic să primească aceeași cantitate. Deși infrastructura curentă a Internetului nu permite acest lucru, implementarea acestui gen de algoritmi va fi necesară pentru a transforma Internetul într-o rețea care poate garanta calitatea serviciilor pe care le oferă.

Rutere și fluxuri

Rutere

Internetul este o rețea compusă din agregarea unui mare număr de rețele diferite într-o construcție unică. Dacă două organizații au rețelele lor proprii, folosind tehnologii eventual diferite, aceste două rețele pot fi conectate între ele cu ajutorul unui calculator special, care este cuplat la ambele rețele. Rolul acestui calculator este de a prelua dintr-o rețea datele care sunt destinate cuiva din cealaltă rețea și de a le retransmite dincolo. Această funcțiune se numește rutare, iar calculatorul însuși se numește ruter.

Un ruter are deci două funcțiuni:

(a)
De a discuta cu celelalte rutere pentru a descoperi topologia rețelei.

(b)
De a folosi informațiile despre topologie pentru a trimite fiecare pachet primit spre destinația lui.

Într-un articol anterior din PC Report, Mihai a prezentat (a), rutarea în Internet1. Subiectul acelui articol era modul în care ruterele învață încotro trebuie trimis fiecare pachet primit, și modul în care ruterele schimbă între ele informații despre structura rețelei.

În textul de față vom complementa informațiile oferite cu acea ocazie, discutînd despre (b), modul în care ruterele transmit pachete.

Transmiterea pachetelor

Un ruter este legat la fiecare rețea pe care o deservește printr-o interfață. Un ruter va avea deci cel puțin două interfețe. Cînd un pachet apare prin una din interfețe, ruterul pune acel pachet într-o memorie internă. După aceea ruterul prelucrează pachetul și decide în ce direcție trebuie să-l trimită. Această informație este obținută din adresa destinație, care se află în interiorul pachetului, și din tabela de rutare, care conține informații despre forma rețelei, și pe care ruterul a construit-o conversînd cu vecinii săi. După ce decide încotro trebuie trimis pachetul, ruterul îl trimite pe interfața care este cea mai potrivită pentru destinație.

Figura 1 ilustrează arhitectura internă a unui ruter.

Figure 1: Operația de ``înaintare'' (forwarding) a unui ruter: un pachet este primit pe o interfață, este clasificat în funcție de destinație, și este înaintat pe interfața de ieșire. Pachetele adesea trebuie să aștepte în cozi prelucrarea pachetelor anterioare.
\begin{figure}\centerline{\epsfxsize=10cm\epsffile{rutare.eps}}\end{figure}

Aparent nimic nu este mai simplu: iei pachetul, cauți destinația în tabelă, și îl trimiți mai departe. O astfel de operație se numește stochează și transmite (store and forward), din motive evidente. Ce mai e de discutat aici?

Dacă veți încerca însă să construiți un ruter, veți vedea că există o sumedenie de întrebări la care răspunsul nu este de loc evident. Iată unele din dificultățile care se pot ivi:

Capacitate de prelucrare:
O parte din probleme provin din faptul că viteza de prelucrare a unui ruter este finită: de exemplu se poate întîmpla ca mai multe intrări simultan să aducă pachete cu o viteză mai mare decît ruterul poate prelucra. Din cauza asta anumite pachete pot fi pur și simplu pierdute, pentru că ruterul nu poate ține pasul cu rata de transmisiune. (Astfel de pachete vor fi probabil retransmise de către sursă mai tîrziu.)

Capacitatea legăturilor:
Dar, chiar presupunînd că ruterul poate clasifica pachete infinit de rapid, se pot ivi probleme: de exemplu imaginați-vă o rețea în care pachetele care vin pe două intrări sunt destinate pentru o singură ieșire; dacă cele trei rețele au aceeași capacitate, atunci în mod clar ruterul nu va putea trimite toate pachetele pe care le primește. Ce trebuie să facă cu pachetele pe care le primește și nu le poate transmite?

Din cauza aceasta, ruterele conțin memorii, numite buffere, în care stochează pachetele pe care nu le pot prelucra. Pachetele se așează în cozi, de unde sunt extrase unul cîte unul. Este clar însă că, oricît de multe buffere am avea, rafale mari de pachete pentru aceeași destinație pot umple cozile, cauzînd din nou pierderea pachetelor.

Calitate:
Chiar presupunînd că avem o memorie infinită în ruter, timpul de așteptare în cozi se poate dovedi inacceptabil pentru anumite aplicații. De exemplu, dacă avem o aplicație de telefonie prin Internet, dacă datele ajung după mai mult de 150ms de la un capăt la altul, se crează pauze neplăcute în conversație. Chiar mai rău este fenomenul care se petrece atunci cînd cozile variază în lungime: anumite pachete vor veni mai repede și altele mai lent, distorsionînd vorbirea la receptor. Asta ne sugerează că anumitor pachete trebuie să li se dea prioritate. De asemenea, anumite pachete pot fi preferate pentru a fi ``distruse'' (de exemplu pachetele de voce care au întîrziat deja prea mult), pentru că nu mai sunt utile.

Una peste alta, un ruter trebuie să ia următoarele decizii:

Modul în care ia aceste decizii are consecințe foarte importante pentru comportarea rețelei. În acest articol vom vedea cîteva posibilități și vom explora consecințele lor, care sunt adesea foarte diferite.

Trebuie spus însă că majoritatea covîrșitoare a disciplinelor studiate nu sunt implementate în sisteme reale, fiind deocamdată doar un subiect de cercetare. Practic toate ruterele operaționale astăzi în lume folosesc o disciplină foarte simplă: au o singură coadă pentru fiecare interfață, în care pun toate pachetele. Cînd coada este plină, noile pachete venite sunt pierdute. Această disciplină se numește FIFO: First In First Out, primul venit, primul plecat.

Din nefericire, deși extrem de simplu de implementat, FIFO are niște proprietăți indezirabile, despre care vom discuta puțin mai jos, în secțiunea despre Fair Queuing, unde ilustrăm și o alternativă. FIFO este inadecvată din multe puncte de vedere, așa că multe eforturi în cercetare sunt acum îndreptate spre studiul altor discipline.

Însă, înainte de a vedea exemple de alte discipline, vom pune sub reflector o proprietate extrem de importantă a rețelei, care este influențată de modul în care ruterele și punctele terminale operează. Este vorba despre congestie.

Congestie și controlul fluxului

Atunci cînd un segment al rețelei este înecat cu pachete pe care nu le poate transmite se produce un fenomen de congestie. Congestia este extrem de periculoasă, pentru că dacă nu este tratată corect poate periclita funcționarea întregii rețele pentru o perioadă nedeterminată.

Scenariul cel mai sumbru arată astfel: o anumită legătură primește mai multe pachete decît capacitatea ei de transport. Ca atare, după ce cozile se umplu, inevitabil ruterul care deservește acea legătură începe să piardă pachete. După o vreme sursele care au trimis acele pachete încep să dea semne de îngrijorare, pentru că nu primesc confirmarea de primire; ca atare încep să retransmită pachetele pierdute. Acestea vor cauza creșterea mai mare a congestiei, pentru că folosesc același traseu. Cu cît rețeaua este mai blocată, cu atît mai multe pachete duplicate vor apărea (din cauza retransmisiilor), și cu atît mai blocată va fi. Din această situație nu există ieșire, dacă nu se intervine imediat; rețeaua devine rapid nefuncțională.

Observați că acest lucru se poate petrece chiar dacă fiecare calculator terminal nu emite prea multe date; efectul se produce datorită agregării traficului de la mai multe conexiuni pe o legătură fizică comună. De exemplu, în figura 1, traficul între calculatoare din stînga și oricare calculator din dreapta trebuie să treacă pe linia dintre cele două rutere.

În teoria rețelelor de transmisiuni de date se face distincție între două clase de algoritmi:

Între aceste două clase există o suprapunere, pentru că o reducere a fluxului în mod implicit va reduce și congestia. De fapt secțiunea următoare discută singura soluție actualmente implementată pe scară largă în Internet, folosită de protocolul TCP, care administrează fluxuri între două calculatoare.

Soluția TCP

Soluția folosită la ora actuală de majoritatea implementărilor existente ale protocolului TCP a fost propusă de van Jacobson în 1988. Pentru a o înțelege ar trebui să studiem un tip special de protocol, numit protocolul cu fereastră glisantă (sliding window). Subiectul este deosebit de interesant, dar prea generos pentru acest articol, care are are un alt subiect. Sperăm să putem trata acest subiect într-un alt articol; pentru cititorul impacient trimitem la cartea lui Tanenbaum publicată de editura Agora, ``Rețele de Calculatoare''.

Din protocolul cu fereastră glisantă vom reține numai însușirile care ne interesează.

TCP este un protocol care garantează trimiterea cu succes a tuturor pachetelor. Pentru asta, toate pachetele care nu au fost confirmate la primire de către capătul celălalt sunt păstrate de emițător, chiar și după ce au fost deja transmise. Pentru că între trimiterea unui pachet și recepția confirmării se poate scurge mult timp, emițătorul este gata să emită mai multe pachete la rînd, chiar dacă nu a primit confirmări pentru nici unul. Numărul de pachete care se pot afla în tranzit fără confirmare se numește fereastră (window).

Fereastra glisantă este folosită de TCP cu succes și pentru controlul fluxului. Iată cum:

  1. Să zicem că la un moment dat un flux TCP are o fereastră de 30 de pachete.

  2. Să ne imaginăm că subit apare o congestie în rețea, pe o linie folosită și de fluxul nostru. Din cauza asta un ruter va pierde pachete ale acestui flux.

  3. După o vreme TCP observă ca nu a primit confirmările pentru pachetele trimise. Atunci, în loc să le retrimită pe toate, TCP reduce fereastra curentă la un pachet (primul trimis care nu a primit confirmare).

  4. TCP retransmite apoi pachetul din fereastră.

  5. După o vreme toate fluxurile care foloseau linia congestionată au pierdut pachete și și-au redus ferestrele. În rețea au început să intre din ce în ce mai puține pachete noi, deci linia cu probleme s-a decongestionat.

  6. Acum fluxurile TCP încep să primească confirmări, deci încep încet-încet să-și mărească ferestrele la loc.

  7. Acest proces se repetă permanent. Traficul pe acea legătură va oscila ușor în jurul valorii maxime posibile, fiind automat ajustat de către participanții la trafic.

Soluția aceasta este deosebit de elegantă. Principala ei calitate este că nu implică de loc rețeaua: numai nodurile terminale rulează acest algoritm, fără nici un fel de informații de la rutere. În plus, această soluție funcționează indiferent de politica după care ruterele din rețea aruncă pachete.

Problemele soluției TCP

Controlul congestiei prin variația ferestrei are mai multe dezavantaje, dar noi aici ne vom opri asupra unuia singur dintre ele. Să ne imaginăm că avem cinci fluxuri printr-o legătură congestionată. Să ne mai imaginăm că 4 dintre ele sunt implementări ``normale'' ale TCP, dar că unul dintre ele este o implementare foarte agresivă, care în loc să-și reducă traficul, îl păstrează neschimbat.

Ce se va întîmpla este ușor de imaginat: cele patru fluxuri civilizate vor reduce traficul așa cum trebuie, iar cel agresiv va folosi toată capacitatea posibilă a rețelei. În măsura în care acest flux de unul singur nu congestionează rețeaua, va funcționa bine-mersi, furînd din porția tuturor celorlalți.

Dacă toate fluxurile ar fi lacome rețeaua s-ar congestiona iremediabil, și nimeni n-ar primi nimic. Deci schema aceasta funcționează numai dacă aproape toată lumea este rezonabilă.

Partea neplăcută este că această situație este deja întîlnită, într-o formă sau alta, în Internet:

Aplicațiile de genul celor de mai sus folosesc protocolul UDP pentru transmisiune, care nu este fiabil, și nu face nici un fel de control al fluxului sau congestiei.

De altfel puteți face chiar dumneavoastră un experiment simplu dacă aveți acces la două calculatoare legate în rețea, oarecare cunoștințe de programare de rețea și o oră liberă:

  1. Deschideți între aceste calculatoare niște legături TCP pe care transmiteți date fictive; măsurați cîte pachete primiți în fiecare secundă avînd 1, 2, 3, etc. legături în paralel. În mod normal, datorită metodei de control a fluxului, aceste fluxuri vor oscila simultan între valorile maximă și minimă, oferind aceeași rată medie de transmisiune pe o perioadă mai mare de timp (cîteva secunde).

  2. Pentru ca acest experiment să funcționeze trebuie să reușiți să congestionați rețeaua. Dacă limita de transmisiune va fi impusă nu de rețea, ci de hardware-ul sau software-ul pe care îl posedați, acest experiment probabil nu va funcționa întocmai. Din momentul în care ați congestionat rețeaua, suma ratelor medii de transport ale legăturilor TCP va trebui să fie constantă (adică fie că aveți 5, fie 10, rata2 totală de transport va fi egală cu capacitatea legăturii dintre cele două calculatoare).

  3. Deschideți apoi o legătură UDP între cele două calculatoare, păstrînd legăturile TCP deschise. Pompați date în toate cu viteza maximă. Măsurați din nou traficul. Rata fiecărui flux va arăta ca în figura 2.

Figure 2: Acest grafic arată rata a 32 de fluxuri -- un flux UDP (numărul 1) și 31 de fluxuri TCP -- pe o legătură Ethernet de 10 Mbps. Observați că fluxurile TCP nici nu se văd, iar UDP consumă aproape toată capacitatea canalului, cu circa 8 Mbps.
\begin{figure}\centerline{\epsfxsize=6cm\epsffile{fifo.eps}}\end{figure}

Veți observa că legătura UDP obligă toate legăturile TCP să-și reducă traficul, folosind de una singură toată capacitatea rețelei. Suma dintre UDP și toate TCP-urile va fi constantă, TCP-urile vor fi aproximativ egale între ele, dar la o valoare mult mai scăzută decît legătura UDP.

Fair queuing: o disciplină echitabilă

De fapt, dacă ruterele tratează pachetele în ordinea sosirii (FIFO), și le aruncă pe cele de la coadă, nici nu putem face mare lucru. O disciplină simplă ca FIFO nu poate asigura o distribuție echitabilă a capacității în caz de congestie.

O soluție, propusă în literatură, pentru aceasta problema este utilizarea de discipline mai ``inteligente'' în rutere. O astfel de disciplină este Fair Queueing (``cozi echitabile'', FQ). În continuare vom descrie FQ și vom arăta că, spre deosebire de FIFO, această disciplină asigură o distribuție echitabilă a capacității, independent de algoritmii de adaptare folosiți de surse.

FQ este definit în contextul unui sistem ideal, în care traficul este transmis la nivel de biți, și nu de pachete. FQ se aplică la fiecare coadă de ieșire separat: fiecare interfață a ruterului va calcula pentru propriul ei canal. Într-un astfel de sistem, FQ se reduce la tratarea fluxurilor în mod circular (round-robin: în ordine fluxul 1, 2, 3, ..., n, 1, 2, 3, etc.). Mai precis, în cadrul fiecărei runde, FQ transmite un bit de la fiecare flux care este activ.

Spre deosebire de FIFO, FQ asigură alocarea echitabilă a capacității. În particular, capacitatea alocată unui flux nu depinde de rata fluxului. De exemplu, dacă avem o conexiune de 1 Mbps și două fluxuri, unul cu rata de 0.5 Mbps și altul de 2 Mbps, atunci fiecare flux va primi 0.5 Mbps. Aceasta se întîmplă deoarece atîta timp cît cele două fluxuri sunt active, fiecare va avea exact un bit transmis în cadrul fiecărei runde.

Evident, într-un ruter real în care transmisia se face la nivel de pachete și nu de biți, FQ nu poate fi implementată exact. O primă idee pentru implementarea FQ la nivel de pachet ar fi să utilizăm din nou round-robin. Din păcate, un moment de reflexie ne spune că această soluție nu este cea mai potrivită. Următorul exemplu ilustrează problema: dacă avem doua fluxuri, unul care transmite pachete de cîte 1000 octeți, iar celălalt care transmite pachete de cîte 100 octeți, este ușor de văzut ca aplicînd round robin la nivel de pachet are drept rezultat alocarea unei capacități de 10 ori mai mare primului flux! De aceea, în practică, pentru implementarea FQ-ului se utilizează algoritmi mai sofisticați.

Un exemplu de astfel de algoritm este următorul: pentru fiecare pachet se calculează momentul de timp la care ultimul bit al acestuia ar urma să fie transmis într-un ruter ideal, în care traficul este transmis bit cu bit; apoi pachetele sunt transmise în ordinea crescătoare a acestor timpi.

Descrierea de mai sus sugerează principalul dezavantaj al disciplinei FQ: complexitatea. În esență ruterul trebuie sa țină evidența pentru fiecare flux și sa execute următoarele operații:

  1. Să identifice cărui flux îi aparține fiecare pachet;

  2. Să hotărască ce pachet să elimine din memorie cînd aceasta este depășită;

  3. Să transmită pachetele în ordinea crescătoare a timpilor, așa cum am discutat mai sus.

Deci, pe de o parte, avem FIFO, care este foarte ușor de implementat la viteze mari, dar nu asigură o alocare echitabilă a capacității, iar pe de alta parte avem FQ care asigură o distribuție echitabilă a capacității, dar este mult mai complex de implementat. O întrebare firească este atunci dacă există o cale de mijloc, care sa îmbine simplitatea FIFO cu serviciul asigurat de FQ. Vom vedea o astfel de soluție mai jos, dar înainte vom studia mai atent care este trăsătura indezirabilă a algoritmului FQ.

Scalabilitatea

Care este diferența dintre FIFO și FQ care face FQ mai dificil de implementat? Dacă încercăm să scriem un program care să le implementeze, vom observa că FIFO manipulează structuri de date extrem de simple: de fapt o singură structură de date, care este o listă înlănțuită de pachete, stocate în ordinea sosirii. Listei i se adaugă pachete la coadă și i se iau pachete de la cap. Aceste operații se pot implementa în cîteva linii de cod, deci se pot executa extrem de rapid.

Pe de altă parte, dacă studiem FQ, o să notăm faptul că ruterul trebuie să facă contabilitate pentru fiecare flux care îl traversează. Asta înseamnă o sumedenie de lucruri destul de neplăcute:

  1. Ruterul trebuie să se uite la fiecare pachet și să observe pachetele care deschid noi conexiuni, pentru a le rezerva structuri de date; fiecare conexiune va avea propria ei coadă;

  2. Ruterul trebuie să clasifice fiecare pachet venit în funcție de conexiunea pe care circulă, și să îl pună în coada lui;

  3. Ruterul trebuie să reevalueze la trimiterea fiecărui pachet starea cozii sale, pentru a vedea cînd următorul are dreptul să plece;

  4. Ruterul trebuie să ``vîneze'' pachetele care termină conexiunea, pentru a distruge cozile respective.

Chiar dacă operațiile acestea sunt relativ simple, structurile manipulate sunt foarte complicate; avem de-a face cu cozi care cresc independent, deci ne trebuie un alocator de memorie destul de sofisticat3, care trebuie invocat la fiecare pachet venit sau plecat. În plus, numărul de structuri de date manipulate depinde de numărul de fluxuri din ruter.

Probleme extrem de greu de rezolvat apar dacă un calculator cu o conexiune deschisă cade: el nu va trimite niciodată semnalul de închiderea conexiunii, deci ruterul va păstra o coadă care teoretic nu mai dispare niciodată. Vom vedea într-un alt articol cum se rezolvă genul ăsta de neplăceri, folosind o tehnică numită soft state.

Cît de mare este numărul de fluxuri dintr-un ruter care operează în centrul rețelei? Informații de la MCI, care este unul din cei mai mari operatori de rețea din Statele Unite, arată că numărul de fluxuri active simultan într-un ruter de mare capacitate se poate apropia de un milion!

Fiecare pachet venit trebuie deci clasificat într-unul din aproape un milion de fluxuri! Asta înseamnă, chiar cu algoritmi inteligenți, o prelucrare substanțială pentru fiecare pachet venit. Asta este virtual imposibil pentru un ruter care deservește mai multe fluxuri de sute de megabiți pe secundă4: pur și simplu nu are destul timp pentru a prelucra pachetele!

Aceasta este diferența principală în ceea ce privește posibilitatea de a realiza FIFO și FQ: FQ trebuie să facă din ce în ce mai multe operații, dacă numărul de fluxuri care traversează ruterul crește. Operația lui FIFO pe de altă parte este aceeași, independent de cîte fluxuri avem.

Internetul este unul dintre cele mai mari sisteme construite vreodată de om (și în viitor va deveni probabil cel mai mare); ca atare dimensiunile cu care trebuie operat sunt uriașe în multe circumstanțe (de pildă un milion de fluxuri). Din cauza asta algoritmii executați de rutere trebuie să fie eficienți atît la un număr mic de conexiuni/pachete, cît și la valori astronomice. Această proprietate a unui algoritm se numește scalabilitate: capacitatea de a funcționa eficient la valori foarte variate ale încărcăturii de date prelucrate.

Dacă un cercetător propune pentru managementul Internetului un algoritm care nu este scalabil, poate să fie absolut sigur că acel algoritm nu va fi folosit niciodată în afara unor sisteme mici, experimentale. Din cauza asta FQ nu este un algoritm practic; ne trebuie o alternativă.

O soluție scalabilă pentru fair queuing: CSFQ

Așa cum am văzut, complexitatea algoritmului FQ se datorează în principal faptului că ruterul trebuie să mențină starea fiecărui flux. O schemă care să reducă în mod semnificativ complexitatea algoritmului FQ trebuie deci să reducă sau să elimine într-un mod sau altul această stare. Problema este că, dacă eliminăm pur și simplu starea unui flux, nu mai este posibil să ``protejăm'' acel flux atunci cînd linia este congestionată. Suntem confruntați cu următoarea dilemă: pe de o parte dorim să eliminăm starea pentru a reduce complexitatea, dar pe de altă parte avem nevoie de această stare pentru a asigura protecția fluxurilor.

Soluția se bazează pe o idee foarte simplă: în loc să menținem starea în rutere, o punem în pachete! Fiecare pachet oricum trebuie prelucrat de fiecare ruter, deci complexitatea procesării crește doar cu un factor constant (iar nu cu un factor depinzînd de numărul de fluxuri). Algoritmul general arată atunci în felul următor:

  1. Sursa, sau eventual primul nod în rețea, evaluează starea fluxului, și o pune în pachetele acestuia.

  2. Nodurile următoare tratează pachetele pe baza informației conținute în pachete.

În acest mod un nod interior nu are nevoie să mențină starea pentru fiecare flux, ceea ce face posibilă implementarea eficientă a algoritmului. Pe de altă parte, sursele sau nodurile de la marginea rețelei, care pun starea în pachete, trebuie să mențină stare pentru fiecare flux, pentru a eticheta corect pachetele. Din fericire, aceste noduri suportă un trafic mult mai mic decît nodurile interioare, și în consecință nu ridică probleme de scalabilitate. Aceasta soluție ``împinge'' starea din nodurile interioare la marginea rețelei și apoi folosește pachetele pentru a transmite această stare.

În continuare descriem pe scurt un algoritm, numit Core-Stateless Fair Queueing (CSFQ), care implementează FQ în cadrul arhitecturii prezentate mai sus. ``Core-stateless'' înseamnă că ruterele din ``miezul'' rețelei nu mențin nici un fel de informații despre starea conexiunilor. Pentru a defini algoritmul trebuie să precizăm:

În CSFQ, starea înscrisă în pachete reprezintă rata fluxului; sursa sau primul nod din rețea calculează dinamic rata fiecărui flux și o pune în pachetele acestuia. Nodurile interioare calculează pe baza traficului primit și a celui transmis rata echitabilă care trebuie sa fie alocată fiecărui flux. Intuitiv, această este rata maximă care ar fi alocată unui flux de algoritmul FQ la ruterul respectiv (în exemplul dat mai sus, cu două conexiuni pe o linie de 1 Mbps, rata echitabilă era de 0.5 Mbps). Din lipsă de spațiu nu prezentăm amănuntele calculării acestei rate. Cititorul interesat poate găsi detaliile la http://www.cs.cmu.edu/~istoica/csfq. În continuare vom prezenta numai operația executată de un ruter interior la primirea unui pachet.

Ne vom concentra asupra unei singure ieșiri din ruter; pentru fiecare ieșire vom menține o altă valoare a ratei echitabile.

Să ne uităm la un ruter pentru care rata echitabilă este rb. Această rată depinde numai de numărul de fluxuri și de capacitatea canalului de ieșire. Să zicem că acest ruter primește un pachet în care este înscrisă rata r. Atunci ruterul transmite pachetul cu probabilitatea P = max(1, rb/r) și îl ``aruncă'' cu probabilitatea 1 - P. Se poate arăta ca aceasta operație foarte simplă duce la o buna aproximare a algoritmului FQ. Intuitiv, aceasta poate fi văzut în următorul exemplu didactic:

Să presupunem că avem un flux cu pachete de lungime egală și cu o rată constantă r > rb, unde valoarea r este înscrisă în fiecare pachet de ruterele de la margine. În acest caz P = rb/r. Drept urmare, din n pachete vor fi transmise în medie numai n rb / r pachete. Deoarece fiecare pachet are aceeași lungime, rata fluxului se va reduce în medie cu rb / r, ceea ce înseamnă ca rata finală va fi r x rb / r = rb. În acest mod, fluxului îi este alocată probabilistic o rată de cel mult rb, rată care este egală cu rata echitabilă (adică cea alocată fluxului dacă FQ ar fi fost folosit)! În final, după ce aruncă din pachete, ruterul schimbă rata înscrisă în fiecare pachet cu rb. Informația din pachet rămîne astfel consistentă cu noua rată a fluxului după ce străbate ruterul congestionat.

Astfel, CSFQ este capabil să asigure o bună aproximare a algoritmului FQ la nodurile interioare, fără a menține starea fluxurilor. În fapt, complexitatea acestui algoritm la nodurile interioare este apropiată de cea a algoritmului FIFO. În concluzie, CSFQ reușește să îmbine simplitatea FIFO cu funcționalitatea algoritmului FQ.

Figura 3 arată pentru comparați valorile fluxului obținute cu un simulator, în exact condițiile din experimentul anterior cu TCP.

Figure 3: Valorile fluxurilor pentru o conexiune UDP (numărul 1) și 31 de conexiuni TCP peste o rețea de 10 Mbps, atunci cînd disciplina de coadă a ruterelor este FQ, respectiv CSFQ. Observați că fiecare legătură primește cam aceeași valoare din flux, undeva în jur de 0,30 Mbps. Între CSFQ și FQ nu există diferențe notabile, arătînd că CSFQ realizează promisiunea de a aproxima FQ pentru această situație. Valorile sunt obținute cu un simulator de rețea, care este accesibil din pagina de web a lui Ion.
\begin{figure}\centerline{\epsfxsize=12cm\epsffile{csfq.eps}}\end{figure}

Există o grămadă de alte detalii despre funcționarea CSFQ, inclusiv simulări detaliate și estimări analitice ale calității sale; acestea sunt toate descrise pe larg în articolul ``Core Stateless Fair Queueing: Achieving Approximately Fair Bandwidth Allocations in High Speed Networks'', de Ion Stoica, Scott Shenker și Hui Zhang, prezentat în 1998 la conferința SIGCOMM, a cărui copie este disponibilă din pagina de web a lui Ion.

Concluzii

În acest articol ilustrăm modul în care se poate proiecta o soluție pentru rezolvarea unei probleme specifice în Internet. Articolul însă aparent bate mult timp cîmpii, pentru că trebuie să construim o serie de concepte. Iată aici o vedere de ansamblu a schemei articolului;

  1. Întîi discutăm despre modul în care ruterele procesează pachetele pe care le înaintează în Internet. Vedem că ruterele pot decide ordinea în care deservesc cozile de pachete și ordinea în care aruncă pachetele pe care nu le pot deservi.

  2. Apoi discutăm despre situațiile în care ruterele trebuie să piardă pachete, pentru că rețeaua primește mai mult decît poate transmite pe anumite segmente. Aceasta se numește congestie.

  3. Petrecem puțin timp pentru a vedea cum algoritmii contemporani (TCP) rezolvă problema congestiei fără a avea nici un fel de ajutor din partea rețelei. Detecția congestiei se bazează pe observația că anumite pachete nu primesc confirmări.

  4. Vedem apoi că o astfel de soluție se bazează implicit pe buna-credință a tuturor participanților, și că nu este deloc protejată împotriva unor indivizi malefici, care în cazul congestiei în loc să reducă rata de transmisiune o măresc, beneficiind de reducerea făcută de toți ceilalți.

  5. Discutăm apoi o soluție pentru această problemă, care are nevoie de cooperarea ruterelor din rețea: fair queuing. Ruterele procesează pachetele nu în ordinea primirii, ci pe rînd, cîte unul de la fiecare sursă. Asta înseamnă că sursele care emit prea mult sunt penalizate mai mult.

  6. Vedem apoi că fair queuing, deși foarte dezirabil, suferă de problema scalabilității.

  7. Discutăm apoi problema scalabilității pe scurt: modul în care complexitatea unui algoritm crește în funcție de datele de intrare. Observăm că în Internet avem de-a face adesea cu probleme extrem de mari (milioane de date de intrare), deci algoritmii ineficienți sunt ne-practici.

  8. În fine, vedem o soluție de compromis, care este scalabilă și aproximează fair queuing suficient de bine.

Cred că e cinstit să stăvilim aici fluxul de informații pe care le transportă acest articol. Vom reveni.



Footnotes

... Internet1
Acest articol este disponibil din pagina sa de web.
... rata2
Rata unui flux este definită ca numărul de biți transmiși în unitatea de timp.
... sofisticat3
Vedeți și articolul lui Mihai publicat cu ceva timp în urmă în PC Report despre alocarea memoriei.
... a4
Cel puțin pentru rutere comerciale obișnuite; inginerii care construiesc supercalculatoare au mai multe tehnologii în mînecă pentru a rezolva astfel de probleme.