Windows Azure 개발: 간단한 방명록 만들기 [Step 3]

이전 글 보기: Windows Azure 개발: 간단한 방명록 만들기 [Step 2] / 소스 코드 수정


 


이전 시간에 이어서, 오늘은 방명록에 업로드한 이미지를 백그라운드에서 실시간으로 처리하는 Worker Role을 작성해보도록 하겠습니다. 방명록에 업로드할 수 있는 이미지의 종류가 다양하고, 간혹 디지털 카메라에서 촬영한, 웹에 업로드하기에는 적합하지 않은 고해상도의 이미지를 처리한다는 시나리오를 세워볼 수 있을 것입니다.


 


만들었던 Windows Azure 프로젝트를 솔루션 탐색기에서 찾아, Roles 노드를 오른쪽 버튼으로 클릭하여 새 Worker Role 추가 메뉴를 클릭한 후, 아래와 같이 Worker Role의 이름을 Guestbook_WorkerRole로 지정하고 새로 생성합니다.


 



 


Worker Role을 추가한 후에는, Storage에 만든 Table 자료를 Worker Role에서도 동일하게 액세스할 수 있도록, GuestBook_WorkerRole 프로젝트를 솔루션 탐색기에서 오른쪽 버튼으로 클릭한 후, “참조 추가” 메뉴를 클릭하여 참조 추가 대화 상자를 엽니다.


 


1. 참조 추가 대화 상자에서, “프로젝트” 탭을 선택하고, 기존에 만들어두었던 GuestBook_Data 프로젝트를 지정합니다.


2. 이미지를 처리해야 하므로, GDI+ API를 포함하는 System.Drawing 어셈블리가 필요합니다. 마찬가지로 참조 추가 대화 상자에서 System.Drawing 어셈블리를 추가합니다.


 


참조 추가를 끝낸 후에는 WorkerRole.cs 파일 상단에 네임스페이스 참조를 아래와 같이 추가합니다.


 

using System.IO;
using System.Drawing;
using GuestBook_Data;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

 


그리고 Worker Role 클래스의 멤버 변수로 아래와 같이 추가합니다.


 

private CloudQueue queue;
private CloudBlobContainer container;

 


그리고 OnStart 메서드에는 다음과 같이 구현합니다.


 

public override bool OnStart()
{
DiagnosticMonitor.Start(“DiagnosticsConnectionString”);

// Restart the role upon all configuration changes
RoleEnvironment.Changing += RoleEnvironmentChanging;

// read storage account configuration settings
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
{
configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
});
var storageAccount = CloudStorageAccount.FromConfigurationSetting(“DataConnectionString”);

// initialize blob storage
CloudBlobClient blobStorage = storageAccount.CreateCloudBlobClient();
container = blobStorage.GetContainerReference(“guestbookpics”);

// initialize queue storage
CloudQueueClient queueStorage = storageAccount.CreateCloudQueueClient();
queue = queueStorage.GetQueueReference(“guestthumbs”);

Trace.TraceInformation(“Creating container and queue…”);

bool storageInitialized = false;
while (!storageInitialized)
{
try
{
// create the blob container and allow public access
container.CreateIfNotExist();
var permissions = container.GetPermissions();
permissions.PublicAccess = BlobContainerPublicAccessType.Container;
container.SetPermissions(permissions);

  // create the message queue
  queue.CreateIfNotExist();
  storageInitialized = true;
}
catch (StorageClientException e)
{
  if (e.ErrorCode == StorageErrorCode.TransportError)
  {
    Trace.TraceError("Storage services initialization failure. "
      + "Check your storage account configuration settings. If running locally, "
      + "ensure that the Development Storage service is running. Message: '{0}'", e.Message);
    System.Threading.Thread.Sleep(5000);
  }
  else
  {
    throw;
  }
}

}

return base.OnStart();
}


 


위의 코드에서, BLOB 저장소와 큐 저장소에 대한 정보를 초기화 단계에서 확보하는 것을 볼 수 있습니다. Worker Role의 관점에서 보면, BLOB 저장소는 처리 대상 이미지와 처리 결과 이미지를 입력받거나 출력하는 대상이고, 큐 저장소를 통하여 작업을 지시받고 결과를 반환할 수 있습니다.


 


그리고 아래와 같이 Run 메서드를 작성합니다.


 

public override void Run()
{
Trace.TraceInformation(“Listening for queue messages…”);

while (true)
{
try
{
// retrieve a new message from the queue
CloudQueueMessage msg = queue.GetMessage();
if (msg != null)
{
// parse message retrieved from queue
var messageParts = msg.AsString.Split(new char[] { ‘,’ });
var uri = messageParts[0];
var partitionKey = messageParts[1];
var rowkey = messageParts[2];
Trace.TraceInformation(“Processing image in blob ‘{0}’.”, uri);

    // download original image from blob storage
    CloudBlockBlob imageBlob = container.GetBlockBlobReference(uri);
    MemoryStream image = new MemoryStream();
    imageBlob.DownloadToStream(image);
    image.Seek(0, SeekOrigin.Begin);

    // create a thumbnail image and upload into a blob
    string thumbnailUri = String.Concat(Path.GetFileNameWithoutExtension(uri), "_thumb.jpg");
    CloudBlockBlob thumbnailBlob = container.GetBlockBlobReference(thumbnailUri);
    thumbnailBlob.UploadFromStream(CreateThumbnail(image));

    // update the entry in table storage to point to the thumbnail
    var ds = new GuestBookEntryDataSource();
    ds.UpdateImageThumbnail(partitionKey, rowkey, thumbnailBlob.Uri.AbsoluteUri);

    // remove message from queue
    queue.DeleteMessage(msg);

    Trace.TraceInformation("Generated thumbnail in blob '{0}'.", thumbnailBlob.Uri);
  }
  else
  {
    System.Threading.Thread.Sleep(1000);
  }
}
catch (StorageClientException e)
{
  Trace.TraceError("Exception when processing queue item. Message: '{0}'", e.Message);
  System.Threading.Thread.Sleep(5000);
}

}
}


 


while 구문을 통하여, 큐로부터 메시지를 수신하고, BLOB 스토리지로부터 이미지 데이터를 로드하여 처리하는 과정을 코드로 작성한 것입니다. 처리가 끝난 후에는, 큐에 쌓여있는 메시지를 제거하고 다음 메시지를 수신할 때 까지 대기하게 됩니다.


 


만약 윗 부분의 코드를 비동기 패턴으로 작성한다면, 좀 더 효율적으로 동작하는 Worker Role 인스턴스를 만들 수도 있을 것입니다. (Worker Role 하나가 처리할 수 있는 가용성의 범위는 VM Size에 따라 달라질 수 있지만 보통의 경우, 많은 사용자가 접속을 하게 되더라도 거의 무리 없이 소화가 가능할 것입니다.)


 


그리고, 위의 코드에서 실제 썸네일 생성을 담당하는 코드는 아래와 같습니다.


 

private Stream CreateThumbnail(Stream input)
{
var orig = new Bitmap(input);
int width;
int height;

if (orig.Width > orig.Height)
{
width = 128;
height = 128 * orig.Height / orig.Width;
}
else
{
height = 128;
width = 128 * orig.Width / orig.Height;
}

var thumb = new Bitmap(width, height);

using (Graphics graphic = Graphics.FromImage(thumb))
{
graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphic.DrawImage(orig, 0, 0, width, height);
var ms = new MemoryStream();
thumb.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Seek(0, SeekOrigin.Begin);
return ms;
}
}


 


이로서, Worker Role에 대한 구현도 끝이 났습니다. 아직 한 가지가 더 남았는데, 바로 Web Role과 Worker Role을 이어주기 위하여 Web Role에도 큐에 대한 처리가 들어가야 한다는 점입니다. 이제 다시 Web Role 프로젝트로 돌아가서 코드를 조금 수정해보도록 하겠습니다.


 


메인 웹 페이지 (Default.aspx)의 코드 비하인드에, 아래와 같이 정적 변수를 추가합니다.


 

private static CloudQueueClient queueStorage;

 


그리고, 파일을 업로드할 때의 버튼 클릭 이벤트에, 기존에 작성하였던 BLOB에 이미지를 저장하는 코드 아랫 부분에, 큐에 메시지를 넣는 부분을 좀 더 추가합니다. 아래와 같을 것입니다.


 

protected void SignButton_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
InitializeStorage();

...

// create a new entry in table storage
GuestBookEntry entry = new GuestBookEntry() { GuestName = NameTextBox.Text, Message = MessageTextBox.Text, PhotoUrl = blob.Uri.ToString(), ThumbnailUrl = blob.Uri.ToString() };
GuestBookEntryDataSource ds = new GuestBookEntryDataSource();
ds.AddGuestBookEntry(entry);
System.Diagnostics.Trace.TraceInformation("Added entry {0}-{1} in table storage for guest '{2}'", entry.PartitionKey, entry.RowKey, entry.GuestName);



// added code

// queue a message to process the image
var queue = queueStorage.GetQueueReference("guestthumbs");
var message = new CloudQueueMessage(String.Format("{0},{1},{2}", uniqueBlobName, entry.PartitionKey, entry.RowKey));
queue.AddMessage(message);


System.Diagnostics.Trace.TraceInformation("Queued message to process blob '{0}'", uniqueBlobName);

}

NameTextBox.Text = “”;
MessageTextBox.Text = “”;

DataList1.DataBind();
}


 


그리고, InitializeStorage 메서드도 조금 수정하면 끝이 납니다.


 

private void InitializeStorage()
{

try
{
  ...

  // configure container for public access
  var permissions = container.GetPermissions();
  permissions.PublicAccess = BlobContainerPublicAccessType.Container;
  container.SetPermissions(permissions);



  // added code

  // create queue to communicate with worker role
  queueStorage = storageAccount.CreateCloudQueueClient();
  CloudQueue queue = queueStorage.GetQueueReference("guestthumbs");
  queue.CreateIfNotExist();
}
catch (WebException)
{
   ...
}

storageInitialized = true;

}
}


 


이제 완성된 Cloud Application을 직접 테스트해보겠습니다. F5 키를 눌러서 디버그를 시작합니다. 코드 상에 오류가 있을 경우 컴파일 오류나 경고가 발생할 수 있으며, 이러한 부분들을 모두 해결해야 합니다. 정상적으로 디버그 모드에 들어가게 되면, 작업 표시줄의 트레이 아이콘 영역에 아래와 같이 Development Fabric 트레이 아이콘이 나타납니다.


 



 


Web Role 프로젝트가 있으므로, Internet Explorer 또한 같이 시작될 것입니다. Web Role의 실행 결과를 보여주는 Internet Explorer 창이 아래와 같이 나타나는지 확인합니다.


 



 


방명록을 작성하고, 첨부할 사진으로 고해상도의 사진을 업로드한 후, 연필 모양의 이미지를 클릭하면 업로드가 됩니다. 테스트해볼만한 고해상도 사진을 찾으려면, Windows XP 이상의 운영 체제들은 모두 %windir%WebWallpapers 폴더의 이미지를 사용하면 됩니다.


 



 


이미지를 업로드한 직후에는 이미지가 있는 그대로 표시됩니다. 하지만 타이머를 이용하여 주기적으로 Refresh 하도록 페이지를 구성하였기 때문에, 잠시 후 (약 5초 후)에는 아래와 같이 알맞게 크기 조절된 이미지가 대신 표시되는 것을 볼 수 있습니다.


 



 


세 번의 포스팅에 걸쳐서 Windows Azure로 만드는 간단한 방명록 예시를 살펴보았습니다. 2010년 2월 27일에 있을 Exploring Windows Azure 세미나 ([세미나] Exploring Windows Azure)를 통하여 실제로 프로그래밍하는 모습을 보여드릴 수 있을 것이니 많은 참석 부탁드립니다.


 


좀 더 다양한 Windows Azure 관련 예제를 필요로 하신다면, Windows Azure Hands-on-Lab을 이용하실 수 있습니다. Windows Azure Hands-on-Lab은 http://go.microsoft.com/fwlink/?LinkID=130354 에서 다운로드 가능합니다.


 


긴 글을 읽어주셔서 감사드리며, 궁금하신 점은 rkttu nospam rkttu dot com을 통하여 연락하여 주십시오. 2월 27일 세미나에서 뵙겠습니다. 감사합니다. 🙂

댓글 남기기