• Nenhum resultado encontrado

Escalabilidade de arquiteturas P2P

148 CHAPTER 2 • APPLICATION LAYER

0

5 10 15 20 25 30

0

N

Minimum distributioin tiime

35 0.5 1.5 2.5 1.0 3.0 2.0 3.5 Client-Server P2P

Figure 2.25  Distribution time for P2P and client-server architectures

(2.3) A Figura 2.25 compara o tempo mínimo de distribuição para as arquiteturas cliente-servidor e P2P, pressu- pondo que todos os pares têm a mesma taxa de upload u. Na Figura 2.25, definimos que F/u = 1 hora, uS = 10u e

dmín ≥ uS. Assim, um par pode transmitir todo o arquivo em uma hora, sendo a taxa de transmissão do servidor 10 vezes a taxa de upload do par, e (para simplificar) as taxas de download de par são definidas grandes o suficiente de forma a não ter efeito. Vemos na Figura 2.25 que, para a arquitetura cliente-servidor, o tempo de distribuição aumenta linearmente e sem limite, conforme cresce o número de pares. No entanto, para a arquitetura P2P, o tempo mínimo de distribuição não é apenas sempre menor do que o tempo de distribuição da arquitetura clien- te-servidor; é também de menos do que uma hora para qualquer número de pares N. Assim, aplicações com a arquitetura P2P podem ter autoescalabilidade. Tal escalabilidade é uma consequência direta de pares sendo redistribuidores, bem como consumidores de bits.

BitTorrent

O BitTorrent é um protocolo P2P popular para distribuição de arquivos [Chao, 2011]. No jargão do BitTor- rent, a coleção de todos os pares que participam da distribuição de um determinado arquivo é chamada de tor-

rent. Os pares em um torrent fazem o download de blocos de tamanho igual do arquivo entre si, com um tamanho

típico de bloco de 256 KBytes. Quando um par entra em um torrent, ele não tem nenhum bloco. Com o tempo, ele acumula mais blocos. Enquanto ele faz o download de blocos, faz também uploads de blocos para outros pares. Uma vez que um par adquire todo o arquivo, ele pode (de forma egoísta) sair do torrent ou (de forma altruísta) permanecer e continuar fazendo o upload a outros pares. Além disso, qualquer par pode sair do torrent a qual- quer momento com apenas um subconjunto de blocos, e depois voltar.

Observemos agora, mais atentamente, como opera o BitTorrent. Como é um protocolo e sistema compli- cado, descreveremos apenas seus mecanismos mais importantes, ignorando alguns detalhes; isso nos permitirá ver a floresta através das árvores. Cada torrent tem um nó de infraestrutura chamado rastreador. Quando um par chega em um torrent, ele se registra com o rastreador e periodicamente informa ao rastreador que ainda está lá.

figura 2.25 temPo de distribuição Para arQuiteturas P2P e cliente-servidor

0

5 10 15 20 25 30

0

N

Tempo mínimo de distribuição

35 0,5 1,5 2,5 1,0 3,0 2,0 3,5 Cliente-Servidor P2P

Dessa forma, o rastreador mantém um registro dos pares que participam do torrent. Um determinado torrent pode ter menos de dez ou mais de mil pares participando a qualquer momento.

Como demonstrado na Figura 2.26, quando um novo par, Alice, chega, o rastreador seleciona aleatoriamen- te um subconjunto de pares (para dados concretos, digamos que sejam 50) do conjunto de pares participantes, e envia os endereços IP desses 50 pares a Alice. Com a lista de pares, ela tenta estabelecer conexões TCP simultâ- neas com todos. Chamaremos todos os pares com quem Alice consiga estabelecer uma conexão TCP de “pares vizinhos”. (Na Figura 2.26, Alice é representada com apenas três pares vizinhos. Normalmente, ela teria muito mais.) Com o tempo, alguns desses pares podem sair e outros pares (fora dos 50 iniciais) podem tentar estabele- cer conexões TCP com Alice. Portanto, os pares vizinhos de um par podem flutuar com o tempo.

A qualquer momento, cada par terá um subconjunto de blocos do arquivo, com pares diferentes com sub- conjuntos diferentes. De tempos em tempos, Alice pedirá a cada um de seus pares vizinhos (nas conexões TCP) a lista de quais blocos eles têm. Caso Alice tenha L vizinhos diferentes, ela obterá L listas de blocos. Com essa informação, Alice emitirá solicitações (novamente, nas conexões TCP) de blocos que ela não tem.

Portanto, a qualquer momento, Alice terá um subconjunto de blocos e saberá quais blocos seus vizinhos têm. Com essa informação, ela terá duas decisões importantes a fazer. Primeiro, quais blocos deve solicitar de iní- cio a seus vizinhos, e segundo, a quais vizinhos deve enviar os blocos solicitados. Ao decidir quais blocos solicitar, Alice usa uma técnica chamada rarest first (o mais raro primeiro). A ideia é determinar, dentre os blocos que ela não tem, quais são os mais raros dentre seus vizinhos (ou seja, os blocos que têm o menor número de cópias repetidas) e então solicitar esses blocos mais raros primeiro. Dessa forma, os blocos mais raros são redistribuídos mais depressa, procurando (grosso modo) equalizar os números de cópias de cada bloco no torrent.

Para determinar a quais pedidos atender, o BitTorrent usa um algoritmo de troca inteligente. A ideia bá- sica é Alice dar prioridade aos vizinhos que estejam fornecendo seus dados com a maior taxa. Especificamente, para cada vizinho, Alice mede de maneira contínua a taxa em que recebe bits e determina os quatro pares que lhe fornecem na taxa mais alta. Então, ela reciprocamente envia blocos a esses mesmos quatro pares. A cada 10 s, ela recalcula as taxas e talvez modifique o conjunto de quatro pares. No jargão do BitTorrent, esses quatro pares são chamados de unchoked (não sufocado). É importante informar que a cada 30 s ela também escolhe

figura 2.26 distribuição de arQuivos com o bittorrent

Rastreador Troca de blocos Par Obter lista de pares Alice

um vizinho adicional ao acaso e envia blocos a ele. Chamaremos o vizinho escolhido de Bob. No jargão de Bit- Torrent, Bob é chamado de otimisticamente não sufocado. Como Alice envia dados a Bob, ela pode se tornar um dos quatro melhores transmissores para Bob, caso em que ele começaria a enviar dados para Alice. Caso a taxa em que Bob envie dados seja alta o suficiente, ele pode, em troca, tornar-se um dos quatro melhores transmissores para Alice. Em outras palavras, a cada 30 s, Alice escolherá ao acaso um novo parceiro de troca e a começará com ele. Caso os dois pares estejam satisfeitos com a troca, eles colocarão um ao outro nas suas listas de quatro melhores pares e continuarão a troca até que um dos pares encontre um parceiro melhor. O efeito é que pares capazes de fazer uploads em taxas compatíveis tendem a se encontrar. A seleção aleatória de vizinho também permite que novos pares obtenham blocos, de forma que possam ter algo para trocar. Todos os pares vizinhos, além desses cinco pares (quatro pares “top” e um em experiência) estão “sufocados”, ou seja, não recebem nenhum bloco de Alice. O BitTorrent tem diversos mecanismos interessantes não discutidos aqui, incluindo pedaços (miniblocos), pipelining (tubulação), primeira seleção aleatória, modo endgame (fim de jogo) e anti-snubbing (antirrejeição) [Cohen 2003].

O mecanismo de incentivo para troca descrito costuma ser chamado de tit-for-tat (olho por olho) [Chen, 2003]. Demonstrou-se que esse esquema de incentivo pode ser burlado [Liogkas, 2006; Locher, 2006; Piatek, 2007]. Não obstante, o ecossistema do BitTorrent é muito bem-sucedido, com milhões de pares simultâneos com- partilhando arquivos ativamente em centenas de milhares de torrents. Caso o BitTorrent tivesse sido projetado sem o tit-for-tat (ou uma variante), mas com o restante da mesma maneira, ele talvez nem existisse mais, visto que a maioria dos usuários são pessoas que apenas querem obter as coisas de graça [Saroiu, 2002].

Variantes interessantes do protocolo BitTorrent são propostas [Guo, 2005; Piatek, 2007]. Além disso, muitas das aplicações de transmissão em tempo real P2P, como PPLive e ppstream, foram inspiradas pelo BitTorrent [Hei, 2007].

2.6.2 distributed hash tables (dhts)

Nesta seção, vamos considerar como realizar um banco de dados simples em uma rede P2P. Começamos descrevendo uma versão centralizada desse banco de dados simples, que terá apenas pares (chave, valor). Por exemplo, as chaves podem ser números de seguridade social e os valores podem ser nomes humanos correspon- dentes; nesse caso, um exemplo de dupla chave-valor é (156-45-7081, Johnny Wu). Ou as duplas podem ser no- mes de conteúdo (por exemplo, nomes de filmes, álbuns e software), e os valores podem ser endereços IP onde o conteúdo está armazenado; nesse caso, um exemplo de par chave-valor é (Led Zeppelin IV, 203.17.123.38). Pares consultam nossos bancos de dados fornecendo a chave: caso haja duplas (chave, valor) em seus bancos de dados que correspondam à chave, o banco de dados retorna as duplas correspondentes ao par solicitante. Portanto, por exemplo, se o banco de dados armazenar números de seguridade social e seus nomes humanos correspondentes, um par pode consultar um número de seguridade social e o banco de dados retornará o nome do humano que possui aquele número. Ou então, se o banco de dados armazenar os nomes de conteúdo e seus endereços IP correspondentes, podemos consultar um nome de conteúdo e o banco de dados retornará os endereços IP que armazenam aquele conteúdo.

Basear em tal banco de dados é simples com a arquitetura cliente-servidor que armazena todos os pares (chave, valor) em um servidor central. Assim, nesta seção, vamos considerar, em vez disso, como montar uma versão distribuída, P2P, desse banco de dados, que guardará os pares (chave, valor) por milhões. No sistema P2P, cada par só manterá um pequeno subconjunto da totalidade (chave, valor). Permitiremos que qualquer par con- sulte o banco de dados distribuído com uma chave em particular. O banco de dados distribuído, então, localizará os pares que possuem os pares (chave, valor) correspondentes e retornará os pares chave -valor ao consultante. Qualquer par também poderá inserir novos pares chave-valor no banco de dados. Esse banco de dados distribuído é considerado como uma tabela hash distribuída (DHT — Distributed Hash Table).

Antes de descrever como podemos criar um DHT, primeiro vamos apresentar um exemplo específico de serviço DHT no contexto do compartilhamento de arquivos P2P. Neste caso, uma chave é o nome de conteúdo e

o valor é o endereço IP de um par que tem uma cópia do conteúdo. Assim, se Bob e Charlie tiverem cada um uma cópia da distribuição Linux mais recente, então o banco de dados DHT incluirá as seguintes duplas de chave-va- lor: (Linux, IPBob) e (Linux, IPCharlie). Mais especificamente, como o banco de dados DHT é distribuído pelos pares, algum deles, digamos Dave, será responsável pela chave “Linux” e terá as duplas chave-valor correspondentes. Agora, suponha que Alice queira obter uma cópia do Linux. É claro, ela precisa saber primeiro quais pares têm uma cópia do Linux antes que possa começar a baixá-lo. Para essa finalidade, ela consulta o DHT com “Linux” como chave. O DHT, então, determina que Dave é responsável pela chave. O DHT entra em contato com Dave, obtém dele as duplas chave-valor (Linux, IPBob) e (Linux, IPCharlie), e os passa a Alice. Ela pode, então, baixar a

distribuição Linux mais recente a partir de IPBob ou de IPCharlie.

Agora, vamos retornar ao problema de projetar um DHT para duplas gerais de chave -valor. Uma técnica ingênua para a criação de um DHT é espalhar ao acaso as duplas (chave, valor) por todos os pares e fazer cada um manter uma lista dos endereços IP de todos os pares. Nesse esquema, o par consultante envia sua consulta a todos os outros, e aqueles contendo as duplas (chave, valor) que combinam com a chave podem responder com suas duplas correspondentes. Essa técnica é totalmente não escalável, é claro, pois exigiria que cada par não apenas soubesse sobre todos os outros (talvez milhões deles!), mas, pior ainda, cada consulta deveria ser enviada a todos os pares.

Agora descreveremos uma abordagem elegante para o projeto de um DHT. Para isso, primeiro designare- mos um identificador a cada par, em que cada identificador é um número inteiro na faixa [0, 2n– 1] de algum

n fixo. Observe que cada identificador pode ser expresso por uma representação com n bits. Vamos também

exigir que cada chave seja um número inteiro na mesma faixa. O leitor atento pode ter observado que as cha- ves de exemplo descritas (números de seguridade social e nomes de conteúdo) não são números inteiros. Para criar números inteiros a partir delas, precisaremos usar uma função hash que mapeie cada chave (por exemplo, número de seguridade social) em um número inteiro na faixa [0, 2n– 1]. Uma função de hash é uma função de

muitos-para-um para a qual duas entradas diferentes podem ter a mesma saída (mesmo número inteiro), mas a probabilidade de terem a mesma saída é extremamente pequena. (Leitores não familiarizados com funções de

hash podem querer consultar o Capítulo 8, que as discute em detalhes.) A função de hash é considerada publi-

camente disponível a todos os pares no sistema. Daqui em diante, quando nos referirmos à “chave”, estaremos nos referindo ao hash da chave original. Portanto, por exemplo, caso a chave original seja “Led Zeppelin IV”, a chave usada no DHT será o número inteiro que corresponda ao hash de “Led Zeppelin IV”. Como você já deve ter percebido, é por isso que “Hash” é usado no termo “Distributed Hash Table”.

Consideraremos agora o problema de armazenar as duplas (chave, valor) no DHT. A questão central aqui é definir uma regra para designar chaves a pares. Considerando que cada par tenha um identificador de número inteiro e cada chave também seja um número inteiro na mesma faixa, uma abordagem natural é designar cada dupla (chave, valor) ao par cujo identificador está mais próximo da chave. Para executar esse es- quema, precisaremos definir o que significa “mais próximo”, o que admite muitas convenções. Por conveniên- cia, definiremos que o par mais próximo é o sucessor imediato da chave. Para entender melhor, observaremos um exemplo. Considere que n = 4, portanto, todos os identificadores de par e chave estarão na faixa de [0, 15]. Suponha ainda que haja oito pares no sistema com identificadores 1, 3, 4, 5, 8, 10, 12 e 15. Por fim, imagine que queiramos armazenar a dupla chave-valor (11, Johnny Wu) em um dos oito pares. Mas em qual? Usando nossa convenção de mais próximo, como o par 12 é o sucessor imediato da chave 11, armazenaremos, por- tanto, a dupla (11, Johnny Wu) no par 12. [Para concluir nossa definição de mais próximo, caso a chave seja idêntica a um dos identificadores do par, armazenaremos a dupla (chave-valor) em um par correspondente; e caso seja maior do que todos os identificadores de par, usaremos uma convenção módulo-2n, que armazena

a dupla (chave-valor) no par com o menor identificador.]

Suponha agora que um par, Alice, queira inserir uma dupla (chave-valor) no DHT. Na concepção, é um processo objetivo: ela primeiro determina o par cujo identificador é o mais próximo da chave; então envia uma mensagem a esse par, instruindo-o a armazenar a dupla (chave, valor). Mas como Alice determina o par mais próximo da chave? Se ela rastreasse todos os pares no sistema (IDs de par e endereços IP correspondentes), pode- ria determinar localmente o par mais próximo. Mas essa abordagem requer que cada par rastreie todos os outros pares no DHT — o que é completamente impraticável para um sistema de grande escala com milhões de pares.