Sites do Azure

Dimensionando seu aplicativo Web com os sites do Azure

Yochay Kiriaty

Baixar o código de exemplo

Surpreendentemente, o dimensionamento é geralmente um aspecto desconsiderado do desenvolvimento de aplicativo Web. Normalmente, o dimensionamento de um aplicativo Web se torna uma preocupação somente quando as coisas começam a falhar e o UX está comprometido devido à lentidão ou tempo limite na camada de apresentação. Quando um aplicativo Web começa a exibir tais déficits de desempenho, ele atingiu seu ponto de escalabilidade—o ponto em que a falta de recursos, como CPU, memória ou largura de banda, ao invés de um bug lógico no código, dificulta a sua capacidade de funcionar.

Este é o momento de dimensionar seu aplicativo Web e fornecê-lo mais recursos, quer seja mais computação, armazenamento adicional ou um banco de dados back end mais forte. A forma mais comum de dimensionar na nuvem é de forma horizontal—adicionando mais instâncias de computação que permitem que um aplicativo Web execute simultaneamente em múltiplos servidores Web (instâncias). As plataformas de nuvem, como o Microsoft Azure, tornam mais fácil dimensionar a infraestrutura subjacente que suporta seu aplicativo Web fornecendo qualquer número de servidores Web, na forma de máquinas virtuais (VMs), em um movimento de seu dedo. No entanto, se seu aplicativo Web não foi concebido para dimensionar e executar por várias instâncias, você não poderá aproveitar os recursos extras e não produzirá os resultados esperados.

Este artigo examina os padrões e conceitos de design fundamentais para dimensionar os aplicativos Web. Os exemplos e detalhes da implementação focam na execução dos aplicativos Web nos sites do Microsoft Azure. 

Antes de eu começar, é importante observar que dimensionar um aplicativo Web é muito dependente do contexto e da maneira que seu aplicativo é projetado. O aplicativo Web usado neste artigo é simples, mesmo assim ele aborda os fundamentos do dimensionamento de um aplicativo Web, especificamente abordando o dimensionamento ao executar os sites do Azure.

Há níveis diferentes de dimensionamento que atendem as diferentes necessidades comerciais. Neste artigo, eu examinarei quatro níveis diferentes de capacidades de dimensionamento, a partir de um aplicativo Web que não pode ser executado em várias instâncias, a um que pode dimensionar por várias instâncias—mesmo por várias regiões geográficas e datacenters.

Etapa um: Conheça o aplicativo

Vou começar revisando as limitações do aplicativo Web de exemplo. Esta etapa definirá a linha de base a partir da qual as modificações necessárias para melhorar a escalabilidade do aplicativo serão feitas. Eu escolhi modificar um aplicativo existente, porque na vida real, isto é geralmente o que você é solicitado a fazer, ao invés de criar um aplicativo totalmente novo do zero.

O aplicativo que vou usar para este artigo é o Modelo de Galeria de Fotos WebMatrix para as páginas da Web do ASP.NET (bit.ly/1llAJdQ). Este modelo é uma excelente maneira de aprender como usar as páginas da Web do ASP.NET para criar aplicativos Web reais. É um aplicativo Web totalmente funcional que permite aos usuários criar álbuns de fotos e carregar imagens. Qualquer pessoa pode exibir imagens, os usuários conectados podem deixar comentários. O aplicativo Web Galeria de Fotos pode ser implantado para os sites do Azure a partir do WebMatrix, ou diretamente do Portal do Azure por meio da Galeria dos sites do Azure.

Olhando de perto o código do aplicativo Web ele revela pelo menos três problemas de arquitetura significantes que limitam a escalabilidade do aplicativo: o uso de um SQL Server Express local como o banco de dados; o uso de um estado de sessão em processo (memória do servidor Web local) local; e o uso de um sistema de arquivos locais para armazenar fotos.

Eu revisarei todas estas limitações no mínimos detalhes.

O arquivo PhotoGallery.sdf, encontrado na pasta App_Data, é o banco de dados SQL Server Express padrão que é distribuído com o aplicativo. O SQL Server Express torna mais fácil iniciar o desenvolvimento de um aplicativo e oferece uma excelente experiência de aprendizagem, mas também impõe sérias restrições na capacidade de dimensionamento do aplicativo. Um banco de dados SQL Server Express é, essencialmente, um arquivo no sistema de arquivos. O aplicativo Galeria de Fotos em seu estado atual não pode dimensionar com segurança através de várias instâncias. Tentar dimensionar através de várias instâncias pode deixar você com várias instâncias do arquivo de banco de dados SQL Server Express, cada uma um arquivo local e provavelmente fora de sintonia com os outros. Mesmo se todas as instâncias do servidor Web compartilham o mesmo sistema de arquivos, o arquivo SQL Server Express pode ser bloqueado por qualquer instância em horários diferentes, fazendo com que as outras instâncias falhem.

O aplicativo da Galeria de Fotos é também limitado pela forma que ele gerencia o estado da sessão do usuário. Uma sessão é definida como uma série de solicitações emitidas pelo mesmo usuário, dentro de um determinado período de tempo, e é gerenciada ao associar uma ID da sessão com cada usuário exclusivo. A ID é usada para cada solicitação HTTP e é fornecida pelo cliente, na forma de um cookie ou como um fragmento especial da URL da solicitação. Os dados da sessão são armazenados no lado do servidor em um dos armazenamento do estado da sessão com suporte, que inclui memória em processo, um banco de dados SQL Server ou servidor de estado do ASP.NET.

O aplicativo Galeria de Fotos usa a classe WebMatrix WebSecurity para gerenciar o estado e login do usuário, e o WebSecurity usa o estado de sessão do provedor de associação ASP.NET padrão. Por padrão, o modo do estado de sessão do provedor da associação ASP.NET está em processo (InProc). Neste modo, os valores e variáveis do estado de sessão são armazenados na memória em uma instância de servidor Web local (VM). Tendo o estado de sessão do usuário sido armazenado localmente de acordo com o servidor Web limita a capacidade do aplicativo de executar várias instâncias porque solicitações HTTP subsequentes de um único usuário pode terminar em instâncias diferentes dos servidores Web. Porque cada instância do servidor Web mantém sua própria cópia do estado em sua própria memória, você pode terminar com objetos do estado de sessão InProc diferentes para os mesmos usuários. Isto pode levar a UXes inesperados e inconsistentes. Aqui, você pode ver a classe WebSecurity sendo usada para gerenciar um estado de usuário:

_AppStart.cshtml

@{ WebSecurity.InitializeDatabaseConnection ("PhotoGallery", "UserProfiles", "UserId", "Email", true); }

Upload.cshtml

@{ WebSecurity.RequireAuthenticatedUser(); ... ... }

A classe WebSecurity é um auxiliar, um componente que simplifica a programação nas páginas da Web do ASP.NET. Por trás dos bastidores, a classe Web­Security interage com um provedor da associação ASP.NET, que por sua vez, executa o trabalho de nível inferior necessário para executar as tarefas de segurança. O provedor da associação padrão nas páginas da Web do ASP.NET é a classe SimpleMembershipProvider e, por padrão, seu modo de estado de sessão é InProc.

Finalmente, a versão atual do aplicativo Web Galeria de Fotos armazena fotos no banco de dados, cada foto como uma matriz de bytes. Essencialmente, porque o aplicativo está usando o SQL Server Express, as fotos são salvas no disco local. Para um aplicativo da galeria de fotos, um dos principais cenários é a exibição de fotos, então o aplicativo pode precisar lidar e mostrar muitas solicitações de fotos. A leitura de fotos de um banco de dados é menos do que o ideal. Mesmo usando um banco de dados mais sofisticado, como o SQL Server ou o Banco de Dados SQL do Azure, não é ideal, principalmente porque recuperar fotos é uma operação cara.

Resumindo, esta versão da Galeria de Fotos é um aplicativo com monitoração de estado, e aplicativos com monitoração de estado não se expandem bem através de várias instâncias.

Etapa dois: Modificando a Galeria de Fotos para ser um aplicativo Web sem monitoração de estado

Agora o que eu expliquei algumas das limitações do aplicativo Galeria de Fotos com relação ao dimensionamento, vou abordá-las uma por uma para melhorar as capacidades de dimensionamento do aplicativo. Na Etapa dois, farei as alterações necessárias para converter a Galeria de Foto com monitoração de estado para sem monitoração de estado. No final da Etapa dois, o aplicativo Galeria de Fotos atualizado poderá dimensionar com segurança e executar através de várias instâncias do servidor Web (VMs).

Primeiro, substituirei o SQL Server Express por um servidor de banco de dados mais poderoso—o Banco de Dados SQL do Azure, um serviço baseado na nuvem da Microsoft que oferece capacidades de armazenamento de dados como parte da plataforma de serviços do Azure. As SKUs do Banco de Dados SQL do Azure Standard e Premium oferecem recursos de continuidade comercial avançados que usarei na Etapa quatro. A partir de agora, eu apenas migrarei o banco de dados SQL Server Express para o Banco de Dados SQL do Azure. Você pode fazer isso facilmente usando a ferramenta de migração de bancos de dados do WebMatrix, ou qualquer outra ferramenta que desejar, para converter o arquivo SDF no formato de Banco de Dados SQL do Azure.

Contanto que eu já esteja migrando o banco de dados, é uma boa oportunidade para fazer algumas modificações no esquema que, mesmo pequenas, terão um impacto significativo nas capacidades de dimensionamento do aplicativo.

Primeiro, eu converterei o tipo da coluna ID de algumas tabelas (Galerias, Fotos, Perfis de Usuário e assim por diante) do INT para GUID. Esta alteração se revelará útil na Etapa quatro, quando eu atualizar o aplicativo para executar através de várias regiões e precisar manter o banco de dados e conteúdo de fotos em sincronização. É importante observar que esta modificação não força nenhuma alteração de código no aplicativo; todas as consultas SQL no aplicativo permanecem as mesmas.

Em seguida, eu vou parar de armazenar fotos como matrizes de bytes no banco de dados. Esta alteração envolve tanto a modificações de código como de esquema. Eu removerei as colunas FileContents e FileSize da tabela Fotos, armazenarei fotos diretamente no disco e usarei a ID de foto, que é agora um GUID, como uma forma de diferenciar as fotos.

O seguinte trecho de código mostra a instrução INSERT antes da alteração (observe que o fileBytes e fileBytes.Length são armazenados diretamente no banco de dados):

db.Execute(@"INSERT INTO Photos (Id, GalleryId, UserName, Description, FileTitle, FileExtension, ContentType, FileSize, UploadDate, FileContents, Likes) VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10)", guid.ToString(), galleryId, Request.GetCurrentUser(Response), "", fileTitle, fileExtension, fileUpload.ImageFormat, fileBytes.Length, DateTime.Now, fileBytes, 0);

Este é o código depois das alterações do banco de dados:

using (var db = Database.Open("PhotoGallery")) { db.Execute(@"INSERT INTO Photos (Id, GalleryId, UserName, Description, FileTitle, FileExtension, UploadDate, Likes) VALUES (@0, @1, @2, @3, @4, @5, @6, @7)", imageId, galleryId, userName, "", imageId, extension, DateTime.UtcNow, 0); }

Na Etapa três, eu vou explorar em mais detalhes como eu modifiquei o aplicativo. Por agora, basta dizer que as fotos estão salvas em um local central, como um disco compartilhado, que todos as instâncias do servidor Web podem acessar.

A última alteração que farei na Etapa dois é parar de usar o estado de sessão InProc. Como observado antes, o WebSecurity é uma classe auxiliar que interage com os provedores de associação ASP.NET. Por padrão, o modo do estado da sessão ASP.NET SimpleMembership é InProc. Há várias opções fora de processo que você pode usar com o SimpleMembership, incluindo o SQL Server e o serviço do Servidor de Estado do ASP.NET. Estas duas opções permitem que o estado da sessão seja compartilhado entre várias instâncias de servidor Web e evite a afinidade do servidor; ou seja, não é necessário que a sessão fique ligada a um servidor Web específico.

A minha abordagem também gerencia o estado fora de processo, especificamente ao usar um banco de dados ou cookie. No entanto, eu dependo de minha própria implementação ao invés de no ASP.NET, essencialmente porque eu quero manter as coisas simples. A implementação usa um cookie e armazena a ID da sessão e seu estado no banco de dados. Assim que um usuário faz login, eu atribuo um novo GUID como uma ID da sessão, que armazeno no banco de dados. Aquele GUID é também devolvido ao usuário na forma de um cookie. O código a seguir mostra o método CreateNewUser, que é chamado sempre que um usuário faz login:

private static string CreateNewUser() { var newUser = Guid.NewGuid(); var db = Database.Open("PhotoGallery"); db.Execute(@"INSERT INTO GuidUsers (UserName, TotalLikes) VALUES (@0, @1)", newUser.ToString(), 0); return newUser.ToString(); }

Ao responder por uma solicitação HTTP, o GUID é inserido na resposta HTTP como um cookie. O nome de usuário passado ao método AddUser é o produto da função CreateNewUser apenas mostrada, como:

public static class ResponseExtensions { public static void AddUser(this HttpResponseBase response, string userName) { var userCookie = new HttpCookie("GuidUser") { Value = userName, Expires = DateTime.UtcNow.AddYears(1) }; response.Cookies.Add(userCookie); } }

Ao lidar com uma solicitação de entrada HTTP, primeiro eu tento extrair a ID do usuário, representada como um GUID, do cookie GuidUser. Em seguida, eu procuro por aquela ID de usuário (GUID) no banco de dados e extraio qualquer informação específica do usuário. Figura 1mostra parte da implementação Get­CurrentUser.

Figura 1 GetCurrentUser

public static string GetCurrentUser(this HttpRequestBase request, HttpResponseBase response = null) { string userName; try { if (request.Cookies["GuidUser"] != null) { userName = request.Cookies["GuidUser"].Value; var db = Database.Open("PhotoGallery"); var guidUser = db.QuerySingle( "SELECT * FROM GuidUsers WHERE UserName = @0", userName); if (guidUser == null || guidUser.TotalLikes > 5) { userName = CreateNewUser(); } } ... ... }

O CreateNewUser e o GetCurrentUser fazem parte da classe RequestExtensions. De maneira semelhante, o AddUser faz parte da classe ResponseExtensions. Ambas as classes se conectam na solicitação que está processando o pipeline do ASP.NET, manipulando solicitações e respostas, respectivamente.

A minha abordagem para gerenciar o estado da sessão é uma bastante ingênua, visto que não é segura e não reforça nenhuma autenticação. No entanto, ela mostra o benefício de gerenciar sessões fora de processo e dimensiona. Quando você implementa seu próprio gerenciamento de estado de sessão, quer esteja baseado no ASP.NET ou não, certifique-se de usar uma solução segura que inclui autenticação e uma forma segura para criptografar o cookie que você devolve.

Neste ponto, eu posso afirmar com segurança que o aplicativo de Galeria de Fotos atualizado é agora um aplicativo Web sem monitoração de estado. Ao substituir a implementação do banco de dados SQL Server Express local pelo Banco de Dados SQL do Azure SQL e alterar a implementação do estado do banco de dados do InProc para fora de processo, usando um cookie e um banco de dados, eu converti com sucesso o aplicativo com monitoração de estado para sem monitoração de estado, como ilustrado na Figura 2.

Logical Representation of the Modified Photo Gallery Application
Figura 2 Representação lógica do aplicativo Galeria de Fotos modificado

Tomando as medidas necessárias para garantir que seu aplicativo Web esteja sem monitoração de estado é provavelmente a tarefa mais significativa durante o desenvolvimento de um aplicativo Web. A capacidade de executar através de várias instâncias do servidor Web, sem preocupações sobre o estado do usuário, corrupção de dados ou correção funcional, é um dos fatores mais importantes no dimensionamento de um aplicativo Web.

Etapa três: Melhorias adicionais que continuam ao longo do caminho

Alterações feitas ao aplicativo Web da Galeria de Fotos na Etapa dois assegura que o aplicativo esteja sem monitoração de estado e se dimensionar com segurança através de várias instâncias do servidor Web. Agora, vou fazer melhorias adicionais que podem melhorar mais as características de escalabilidade, permitindo ao aplicativo Web lidar com cargas maiores com menos recursos. Nesta etapa, eu revisarei as estratégias de armazenamento e abordarei os padrões de design assíncronos que melhoram o desempenho do UX.

Uma das alterações discutidas na Etapa dois foi salvar fotos para um local central, como um disco compartilhado que todas as instâncias do servidor Web podem acessar ao invés de salvar em um banco de dados. A arquitetura dos sites do Azure assegura que todas as instâncias de um aplicativo Web que são executadas através de vários servidores compartilhem o mesmo disco, como a Figura 3 ilustra.

With Microsoft Azure Web Sites, All Instances of a Web Application See the Same Shared Disk
Figura 3 Com sites do Microsoft Azure, todas as instâncias do aplicativo Web veem o mesmo disco compartilhado

A partir da perspectiva do aplicativo Web Galeria de Fotos, “disco compartilhado” significa quando uma foto é carregada por um usuário, é salva para a …/pasta carregada, que parece como uma pasta local. No entanto, quando a imagem é gravada no disco, ela não é salva “localmente” no servidor Web específico que trata da solicitação HTTP, mas ao invés ela é salva em um local central que todos os servidores Web podem acessar. No entanto, qualquer servidor pode gravar qualquer foto no disco compartilhado e todos os outros servidores Web podem ler aquela imagem. Os metadados da foto são armazenados no banco de dados e usados pelo aplicativo para ler a ID da foto—um GUID—e retornar a URL da imagem como parte da resposta HTML. O seguinte trecho de código faz parte do view.cshtml, que é a página que usei para permitir a visualização das imagens:

<img class="large-photo" src="@ImagePathHelper.GetFullImageUrl(photoId, photo.FileExtension.ToString())" alt="@Html.AttributeEncode(photo.FileTitle)" />

A origem do elemento HTML da imagem é populada pelo valor retornado da função auxiliar GetFullImageUrl, que recebe uma ID da foto e uma extensão de arquivo (.jpg, .png e assim por diante) e retorna uma cadeia de caracteres representando a URL da imagem.

Ao salvar as fotos para um local central assegura que o aplicativo Web não tenha monitoração de estado. No entanto, com a implementação atual, uma determinada imagem é servida diretamente de um dos servidores Web que está executando o aplicativo Web. Especificamente, a URL de cada origem de imagem aponta para a URL do aplicativo Web. Como resultado, a própria imagem é servida de um dos servidores Web que está executando o aplicativo Web, o que significa que o número real de bytes da imagem são enviados como uma resposta HTTP do servidor Web. Isto significa que seu servidor Web, além de lidar com páginas da Web dinâmicas, também serve o conteúdo estático, como imagens. Os servidores Web podem servir muitos conteúdos estáticos em grande escala, mas ao fazer isto impõe um custo nos recursos, incluindo CPU, IO e memória. Se você pudesse assegurar que apenas o conteúdo estático, como fotos, não seja servido diretamente do servidor Web que está executando seu aplicativo Web, mas ao invés de algum outro local, você poderia reduzir o número de solicitações HTTP atingindo os servidores Web. Ao fazer isto, você liberaria recursos no servidor Web para lidar com solicitações HTTP mais dinâmicas.

A primeira alteração a fazer é usar o armazenamento de blob do Azure (bit.ly/TOK3yb) para armazenar e distribuir fotos de usuário. Quando um usuário solicita para ver uma imagem, a URL retornada pelo GetFullImageUrl atualizado aponta para um blob do Azure. O resultado final parece com o seguinte HTML, onde a URL da imagem aponta para o armazenamento de blob:

<img class="large-photo" alt="764beb6b-1988-42d7-9900-03ee8a60749b" src="http://photogalcontentwestus.blob.core.windows.net/ full/764beb6b-1988-42d7-9900-03ee8a60749b.jpg">

Isto significa que uma imagem é servida diretamente do armazenamento de blob e não dos servidores Web que estão executando o aplicativo Web.

Ao contrário, o seguinte mostra fotos salvas em um disco compartilhado dos sites do Azure:

<img class="large-photo" alt="764beb6b-1988-42d7-9900-03ee8a60749b" src="http:// builddemophotogal2014.websites.net/ full/764beb6b-1988-42d7-9900-03ee8a60749b.jpg">

O aplicativo Web Galeria de Fotos usa dois contêineres, completo e miniatura. Como você poderia esperar, completo armazena fotos nos tamanhos originais, enquanto as miniaturas armazenam as imagens menores que são mostradas na exibição da galeria.

public static string GetFullImageUrl(string imageId, string imageExtension) { return String.Format("{0}/full/{1}{2}", Environment.ExpandEnvironmentVariables("%AZURE_STORAGE_BASE_URL%"), imageId, imageExtension); }

O AZURE_STORAGE_BASE_URL é uma variável de ambiente que contém a URL base para o blob do Azure, neste caso, - http://­photogalcontentwestus.blob.core.windows.net. Esta variável de ambiente pode ser definida no Portal do Azure na guia Configuração do site, ou pode fazer parte do aplicativo web.config. Configurar as variáveis do ambiente do Portal do Azure fornece a você mais flexibilidade, no entanto, porque é mais fácil alterar sem precisar reimplantar.

O Armazenamento do Azure é usado quase da mesma maneira que uma rede de distribuição de conteúdo (CDN), principalmente porque as solicitações HTTP para imagens não estão sendo servidas de servidores Web dos aplicativos, mas diretamente do contêiner de armazenamento do Azure. Isto reduz substancialmente a quantidade de tráfego da solicitação HTTP estático que sempre alcança seus servidores Web, permitindo aos servidores Web lidar com solicitações mais dinâmicas. Observe também que o Armazenamento do Azure pode lidar com muito mais tráfego do que seu servidor Web médio—um único contêiner pode dimensionar para o servidor várias dezenas de milhares de solicitações por segundo.

Além de usar o armazenamento de blob para o conteúdo estático, você pode também adicionar o CDN do Microsoft Azure. Adicionando um CDN por cima de seu aplicativo Web melhora ainda mais o desempenho, visto que o CDN servirá todos os conteúdos estáticos. Solicitações para uma foto já armazenada em cache no CDN não atingirá o armazenamento de blob. Além do mais, um CDN também melhora o desempenho percebido, visto que o CDN normalmente tem um servidor de borda mais próximo ao cliente final. Os detalhes da adição de um CDN a um aplicativo de exemplo estão além do escopo deste artigo, visto que as alterações são na maioria em torno da configuração e do registro DNS. Mas quando você está abordando uma produção em escala e deseja assegurar que seus clientes aproveitarão uma interface de usuário responsiva e rápida, você deve considerar usar o CDN.

Eu não examinei o código que lida com as imagens carregadas do usuário, mas esta é uma ótima oportunidade para abordar um padrão assíncrono básico que melhora o desempenho do aplicativo Web e o UX. Isto também ajudará na sincronização dos dados entre duas regiões diferentes, como você verá na Etapa quatro.

A próxima alteração que farei ao aplicativo Web Galeria de Fotos é adicionar uma fila de armazenamento do Azure, como uma forma de separar o front end de um aplicativo (o site) da lógica comercial back-end (Trabalho Web + banco de dados). Sem uma fila, o código de Galeria de Fotos lida com o front end e back end, já que o código carregado salvou a imagem inteira no armazenamento, e atualizou o banco de dados SQL Server. Durante esse tempo, o usuário esperou por uma resposta. Com a introdução de uma fila de armazenamento do Azure, no entanto, o front end apenas escreve uma mensagem para a fila e imediatamente retorna uma resposta ao usuário. Um processo em segundo plano, o Trabalho Web (bit.ly/1mw0A3w), recebe a mensagem da fila e executa a lógica comercial back-end. Para a Galeria de Fotos, isto inclui a manipular imagens, salvá-las no local correto e atualizar o banco de dados. Figura 4 ilustra as alterações realizadas na Etapa três, incluindo o uso do Armazenamento do Azure e a adição de uma Fila.

Logical Representation of Photo Gallery Post Step Three
Figura 4 Representação lógica da Galeria de Fotos após a Etapa três

Agora que eu tenho uma Fila, preciso alterar o código. No código a seguir você pode ver que ao invés de realizar uma lógica comercial complicada com a manipulação da imagem, eu uso o StorageHelper para enfileirar uma mensagem (a mensagem inclui a ID da foto, a extensão do arquivo de foto e a ID da galeria):

var file = Request.Files[i]; var fileExtension = Path.GetExtension(file.FileName).Trim(); guid = Guid.NewGuid(); using var fileStream = new FileStream( Path.Combine( HostingEnvironment.MapPath("~/App_Data/Upload/"), guid + fileExtension), FileMode.Create)) { file.InputStream.CopyTo(fileStream); StorageHelper.EnqueueUploadAsync( Request.GetCurrentUser(Response), galleryId, guid.ToString(), fileExtension); }

O StorageHelper.EnqueueUploadAsync simplesmente cria um CloudQueueMessage e de forma assíncrona carrega-o para a Fila de Armazenamento do Azure: 

public static Task EnqueueUploadAsync (string userName, string galleryId, string imageId, string imageExtension) { return UploadQueue.AddMessageAsync( new CloudQueueMessage(String.Format("{0}, {1}, {2}, {3}", userName, galleryId, imageId, imageExtension))); }

O Trabalho Web é agora responsável para a lógica comercial back-end. O recurso Trabalho Web dos sites do Azure fornece uma maneira fácil de executar programas, tais como tarefas em segundo plano ou serviços em um site. O Trabalho Web escuta alterações na Fila e recebe qualquer nova mensagem. O método ProcessUploadQueueMessages, mostrado na Figura 5, é chamado sempre que há pelo menos uma mensagem na Fila. O atributo QueueInput faz parte da SDK do Trabalho Web do Microsoft Azure (bit.ly/1cN9eCx), que é uma estrutura que simplifica a tarefa de adição do processamento em segundo plano para os sites do Azure. A SDK do Trabalho Web está fora do escopo deste artigo, mas tudo que você realmente precisa saber é que a SDK do Trabalho Web permite que você se vincule facilmente a uma Fila, no meu caso a fila de carregamento, e ouça às mensagens de entrada.

Figura 5 Lendo uma mensagem de uma Fila e atualizando o banco de dados

public static void ProcessUploadQueueMessages ([QueueInput(“uploadqueue”)] string queueMessage, IBinder binder) { var splited = queueMessage .Split(‘,’).Select(m => m.Trim()).ToArray(); var userName = splited[0]; var galleryId = splited[1]; var imageId = splited[2]; var extension = splited[3]; var filePath = Path.Combine(ImageFolderPath, imageId + extension); UploadFullImage(filePath, imageId + extension, binder); UploadThumbnail(filePath, imageId + extension, binder); SafeGuard(() => File.Delete(filePath)); using (var db = Database.Open(“PhotoGallery”)) { db.Execute(@”INSERT INTO Photos Id, GalleryId, UserName, Description, FileTitle, FileExtension, UploadDate, Likes) VALUES @0, @1, @2, @3, @4, @5, @6, @7)”, imageId, galleryId, userName, “”, imageId, extension, DateTime.UtcNow, 0); } }

Cada mensagem é decodificada, separando a cadeia de caracteres de entrada em suas partes individuais. Em seguida, o método chama duas funções auxiliares para manipular e carregar as imagens para os contêiners do blob. Por último, o banco de dados é atualizado.

Neste momento, o aplicativo Web Galeria de Fotos atualizado pode lidar com milhares de solicitações HTTP por dia.

Etapa quatro: Alcance global

Já fiz melhorias enormes na capacidade de dimensionamento do aplicativo Web Galeria de Fotos. Como observei, o aplicativo pode agora lidar com milhares de solicitações HTTP usando somente alguns servidores grandes nos sites do Azure. Atualmente, todos estes servidores estão localizados em um único datacenter do Azure. Enquanto executar a partir de um único datacenter não é exatamente uma limitação de escala—pelo menos não pela definição padrão de escala—se seus clientes do mundo todo necessitam de latência baixa, você precisará executar seu aplicativo Web a partir de mais de um datacenter. Isto também melhora a durabilidade do aplicativo Web e as capacidades da continuidade comercial. Nos poucos casos em que um datacenter passar por uma interrupção, seu aplicativo Web continuará a servir o tráfego do segundo local.

Nesta etapa, eu farei as alterações ao aplicativo que permitirão que ele execute através de vários datacenters. Para este artigo, eu vou me concentrar na execução a partir de dois locais em um modo ativo-ativo, onde o aplicativo em ambos os datacenters permite que o usuário visualize fotos, bem como carregue fotos e comentários.

Lembre-se disto porque eu entendo o contexto do aplicativo Web Galeria de Fotos, eu sei que a maioria das operações de usuário são operações de leitura, mostrando fotos. Somente um pequeno número de solicitações de usuário envolve o carregamento de novas fotos ou a atualização de comentários. Para o aplicativo Web Galeria de Fotos, eu posso afirmar com segurança que a razão ler/escrever é pelo menos 95 por cento leituras. Isto permite que eu faça suposições, por exemplo, que tendo eventual consistência por todo o sistema é aceitável, visto que é uma resposta mais lenta para escrever operações.

É importante entender que estas suposições são sensíveis ao contexto e dependem das características específicas de um determinado aplicativo, e provavelmente irão mudar de um aplicativo para outro.

Supreendentemente, a quantidade de trabalho exigida para executar a Galeria de Fotos a partir de dois locais diferentes é pequena, visto que a maioria do trabalho pesado foi realizado na Etapa dois e na Etapa três. Figura 6 mostra um diagrama de bloco de alto nível da topologia do aplicativo sendo executado a partir de dois datacenters diferentes. O aplicativo no Oeste dos EUA é o aplicativo “principal” e basicamente tem a saída da Etapa três. O aplicativo no Leste dos EUA é o site “secundário” e o Gerenciador de Tráfego do Windows Azure é colocado em cima de ambos. O Gerenciador de Tráfego do Windows Azure tem várias opções de configuração. Usarei a opção Desempenho, que faz com que o Gerenciador de Tráfego monitore ambos os sites para latência em suas respectivas regiões e encaminhe o tráfego com base na latência mais baixa. Neste caso, os clientes de Nova York (costa leste) serão direcionados ao Leste dos EUA e os clientes de São Francisco (costa oeste) serão direcionados ao Oeste dos EUA. Ambos sites estão ativos ao mesmo tempo, servindo o tráfego. Caso o aplicativo em uma região enfrentar problemas de desempenho, por qualquer motivo, o Gerenciador de Tráfego encaminhará o tráfego para outro aplicativo. Porque os dados são sincronizados, os dados não devem ser perdidos.

Logical Representation of Photo Gallery Post Step Four
Figura 6 Representação lógica da Galeria de Fotos após a Etapa quatro

Vou considerar as alterações ao aplicativo do Oeste dos EUA. A única alteração ao código é ao Trabalho Web ouvindo as mensagens na Fila. Ao invés de salvar fotos para um blob, o Trabalho Web salva fotos para o armazenamento de blob local e “remoto”. Na Figura 5, o UploadFullImage é um método auxiliar que salva fotos para o armazenamento de blob. Para permitir a cópia de uma foto para um blob remoto, bem como para um blob local, eu adicionei a função auxiliar ReplicateBlob no final do UploadFullImage, como você pode ver aqui:

private static void UploadFullImage( string imagePath, string blobName, IBinder binder) { using (var fileStream = new FileStream(imagePath, FileMode.Open)) { using (var outputStream = binder.Bind<Stream>(new BlobOutputAttribute( String.Format("full/{0}", blobName)))) { fileStream.CopyTo(outputStream); } } RemoteStorageManager.ReplicateBlob("full", blobName); }

O método ReplicateBlob no seguinte código tem uma linha importante—a última linha chama o método StartCopyFromBlob, que solicita o serviço para copiar todos os conteúdos, propriedades e metadados de um blob para um novo blob (eu deixo a SDK do Azure e o serviço de armazenamento cuidar do resto):

public static void ReplicateBlob(string container, string blob) { if (sourceBlobClient == null || targetBlobClient == null) return; var sourceContainer = sourceBlobClient.GetContainerReference(container); var targetContainer = targetBlobClient.GetContainerReference(container); if (targetContainer.CreateIfNotExists()) { targetContainer.SetPermissions(sourceContainer.GetPermissions()); } var targetBlob = targetContainer.GetBlockBlobReference(blob); targetBlob.StartCopyFromBlob(sourceContainer.GetBlockBlobReference(blob)); }

No Leste dos EUA, o método ProcessLikeQueueMessages não processa nada; ele simplesmente empurra a mensagem para a Fila do Oeste dos EUA. A mensagem será processada no Oeste dos EUA, as imagens serão replicadas como explicado anteriormente e o banco de dados são sincronizados, como eu explicarei agora.

Esta é a última peça perdida da mágica—sincronizar o banco de dados. Para atingir isto eu usarei o recurso de visualização Replicação Geográfica Ativa (cópia contínua) do Banco de Dados SQL do Azure. Com este recurso, você pode ter réplicas de somente leitura secundárias do banco de dados mestre. Os dados gravados no banco de dados mestre são copiados automaticamente para o banco de dados secundário. O banco de dados mestre é configurado como um banco de dados somente leitura e todos os banco de dados secundários são somente leitura, este é o motivo no meu cenário no qual as mensagens são empurradas para a Fila do Leste dos EUA para o Oeste dos EUA. Logo que você configurar a Replicação Geográfica Ativa (por meio do Portal), os banco de dados estarão sincronizados. Não são necessárias alterações ao código além do que já foi abordado.

Conclusão

O Microsoft Azure permite que você desenvolva aplicativos Web que podem ser dimensionados bastante com muito pouco esforço. Neste artigo, eu mostrei como, em apenas algumas etapas, você pode modificar um aplicativo Web a partir de um que realmente não pode ser dimensionado de forma alguma porque não pode ser executado em várias instâncias, para um que pode executar não apenas através de várias instâncias, mas através de várias regiões, lidando com milhões (mais de dezenas de milhares) de solicitações HTTP. Os exemplos são específicos a um aplicativo em particular, mas o conceito é válido e pode ser implementado em qualquer aplicativo Web.

Yochay Kiriaty é um gerenciador de programa principal líder da equipe do Microsoft Azure e trabalha nos sites do Azure. Entre em contato com ele pelo email yochay@microsoft.com e siga-o no Twitter em twitter.com/yochayk.

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Mohamed Ameen Ibrahim