Skip to content

Microsoft Orleans ile Distributed Virtual Actor Model’e Giriş

Merhaba arkadaşlar.

Bu makale konumda straightforward bir şekilde concurrency problemlerini düşünmeden, distributed bir şekilde high-scale application’lar geliştirebilmemize olanak sağlayan Orleans project konusuna giriş yapmak istiyorum.

Nedir Bu Orleans Project?

Yukarıda da bahsettiğim gibi, concurrency problemlerini düşünmeden high-scale, distributed cloud-based uygulamalar geliştirebilmemiz için Microsoft’un 2014 yılında duyurmuş olduğu bir framework/platform’dur.

Orleans, concurrency ve high-scale gibi problemleri çözebilmek için altyapısında Actor Model yaklaşımını temel almaktadır ve tamamen horizontal scalability‘e odaklanmaktadır.

Actor’ler hakkında bir kaç şey söylemem gerekirse eğer:
  • Her şeyden önce message odaklıdırlar (her bir actor’un, bir mailbox’ı vardır)
  • Tamamen async bir yapıya sahiptirler
  • Her bir aktör, bir diğerinden bağımsızdır (memory share etmezler) ve birbirlerine message gönderebilirler
  • Yüksek performanslara sahiptirler
Peki actor’ler kısaca ne yapar?
  • Birden fazla actor yaratabilirler
  • Message’ı bir diğer actor’e gönderebilirler
  • Bir sonraki message ile, ne yapılacağını belirtirler

Buradan anlayabileceğimiz üzere, message’lar sayesinde actor’ler yaşamlarını sürdürebilmektedirler ve actor’ler bir yığından meydana gelmektedirler. Bu model’in özü, actor’leri temel olarak basit computing units olarak kullanmaktır diyebiliriz. Ayrıca actor’ler state’leri tutabildiği gibi, davranışları da tutabilmektedir ve diğer actor’ler ile message yoluyla iletişim kurabilmektedirler.

Orleans ise actor based bir platform’dur. Actor model, yapısı gereği high availability ve low latency‘i kolaylıkla sağlayabilmektedir. Orleans diğer actor model platform’larının aksine virtual actor abstraction’ı ile reliability ve distributed resource management gibi bir çok complexity’i kendisi handle edip, developer’a herhangi bir ek distributed system problem’leri çıkartmamaktadır.

Ayrıca actor’ler içerisinde single-threaded execution context sunduğundan dolayı, concurrency gibi problemleri düşünmeden geliştirme yapabilmeye de olanak sağlamaktadır. Bunlara ek olarak Orleans tarafında cluster içerisine dynamically olarak node’lar eklenebilmektedir ve yükü bunlara otomatik olarak distribute edebilmektedir de.

Hello World

Dilerseniz konunun devamına hello world düzeyinde örnek bir proje ile devam edelim. Örneğimizi gerçekleştirirken bazı Orleans terimlerine de değiniyor olacağız. Öncelikle buraya tıklayarak, Visual Studio üzerine Microsoft Orleans Tools’u indirelim ve ardından kurulum işlemini gerçekleştirelim. Bu paket ile bir çok initialization işlemlerinden kurtulacağız ve bir kaç wrapper’a sahip olacağız.

“OrleansHelloWorld” isminde bir blank solution oluşturalım ve içerisine aşağıdaki gibi “HelloWorldGrainInterfaces” isminde bir Orleans Grain Interface Collection projesi ekleyelim.

Ekledikten sonra otomatik olarak oluşan “IGrain1” interface’ini “IHello” olarak güncelleyerek, aşağıdaki gibi düzenleyelim.

using System.Threading.Tasks;
using Orleans;

namespace HelloWorldGrainInterfaces
{
    /// <summary>
    /// Grain interface IHello
    /// </summary>
    public interface IHello : IGrainWithIntegerKey
    {
        Task<string> SayHello(string name);
    }
}

Peki nedir bu Grain dersek eğer, Orleans içerisindeki actor’ler, Grain olarak adlandırılmaktadır. Bunun dışında eklemiş olduğumuz “SayHello” method’una dikkat edersek eğer, Orleans implemente edilecek tüm method’ları async olarak istemektedir. Su sebeple task based olarak kodlama yapmamız gerekmektedir.

Şimdi ise concrete grain’i oluşturabilmek için solution içerisine, “HelloWorldGrains” isminde bir Orleans Grain Class Collection projesi ekleyelim ve ardından “HelloWorldGrainInterfaces” projesini referans olarak gösterelim. Projenin eklenmesinden sonra default olarak elen “Grain1” isimli class’ı, “HelloGrain” olarak güncelleyelim ve aşağıdaki gibi kodlayalım.

using System.Threading.Tasks;
using Orleans;
using HelloWorldGrainInterfaces;

namespace HelloWorldGrains
{
    /// <summary>
    /// Grain implementation class HelloGrain.
    /// </summary>
    public class HelloGrain : Grain, IHello
    {
        public Task<string> SayHello(string name)
        {
            return Task.FromResult($"Hello {name}");
        }
    }
}

public class HelloGrain : Grain, IHello { public Task SayHello(string name) { return Task.FromResult($”Hello {name}”); } } }

Grain implementasyonu için “Grain” abstract class’ını ve oluşturmuş olduğumuz “IHello” interface’ini implemente ettik.

Şimdi sıra geldi grain’leri host edeceğimiz silo’yu oluşturmaya. Silo üzerinden self-hosted olarak oluşturmuş olduğumuz “HelloGrain” i active edip, invoke edeceğiz. Bunun için öncelikle “HelloWorld” isminde bir Orleans Dev/Test Host projesi oluşturalım.

Projenin oluşturulmasının ardından “HelloWorldGrainInterfaces” ve “HelloWorldGrains” projelerini referans olarak ekleyelim. “Program.cs” i açarsanız otomatik olarak Orleans tarafından, Orleans test silo host template’i geldiğini görebilirsiniz.

using System;

using Orleans;
using Orleans.Runtime.Configuration;
using HelloWorldGrainInterfaces;

namespace HelloWorld
{
    /// <summary>
    /// Orleans test silo host
    /// </summary>
    public class Program
    {
        static void Main(string[] args)
        {
            // The Orleans silo environment is initialized in its own app domain in order to more
            // closely emulate the distributed situation, when the client and the server cannot
            // pass data via shared memory.
            AppDomain hostDomain = AppDomain.CreateDomain("OrleansHost", null, new AppDomainSetup
            {
                AppDomainInitializer = InitSilo,
                AppDomainInitializerArguments = args,
            });

            var config = ClientConfiguration.LocalhostSilo();
            GrainClient.Initialize(config);

            // TODO: once the previous call returns, the silo is up and running.
            //       This is the place your custom logic, for example calling client logic
            //       or initializing an HTTP front end for accepting incoming requests.

            Console.WriteLine("Orleans Silo is running.\nPress Enter to terminate...");

            var user = GrainClient.GrainFactory.GetGrain<IHello>(0);
            Console.WriteLine(user.SayHello("Gökhan").Result);

            Console.ReadLine();

            hostDomain.DoCallBack(ShutdownSilo);
        }

        static void InitSilo(string[] args)
        {
            hostWrapper = new OrleansHostWrapper(args);

            if (!hostWrapper.Run())
            {
                Console.Error.WriteLine("Failed to initialize Orleans silo");
            }
        }

        static void ShutdownSilo()
        {
            if (hostWrapper != null)
            {
                hostWrapper.Dispose();
                GC.SuppressFinalize(hostWrapper);
            }
        }

        private static OrleansHostWrapper hostWrapper;
    }
}

Template’e dikkat edersek, “OrleansHost” isminde kendi appdomain’ini oluşturuyor ve init kısmında ise “InitSilo” method’u ile, “OrleansHostWrapper” üzerinden silo’nun initializing işlemlerini gerçekleştiriyor.

Aşağıdaki kod satırlarına dikkat edersek eğer burada oluşturmuş olduğumuz grain’i, “GrainClient” üzerinden active edip invoke ediyoruz.

var user = GrainClient.GrainFactory.GetGrain(0);
Console.WriteLine(user.SayHello("Gökhan").Result);

“HelloWorld” projesini çalıştıralım ve sonucuna bir bakalım.

İlk akış içerisinde dikkat edersek, en aşağıda “Successfully started Orleans silo ‘GOKGOKALP’ as a Primary node.” mesajını göreceğiz. Test silo host’unu kendi process’imiz üzerinde başlattığımız için, otomatik bir şekilde primary node olarak belirlemiştir. Akışın devamına baktığımızda ise:

Silo active olmasının ardından, “HelloGrain” i active edip invoke etmiştir. Bunun sonucunda ise “Hello Gökhan” mesaj’ını görebilmekteyiz.

Orleans’a giriş konusuna göre, umarım yeterli bilgiler paylaşabilmişimdir. Orleans üzerindeki çalışmalarım devam ediyor ve sonraki makalelerimde ise stateful bir grain nasıl tasarlanır, bir grain down olursa eğer leaf grain ile process kaldığı yerden nasıl devam edebilir gibi örnekleri gerçekleştirmeye çalışacağım.

Örnek projeye buradan erişebilirsiniz.

Ayrıca Orleans ile ilgili bazı örneklere, kendi github hesapları üzerinden de erişebilirsiniz.

https://github.com/dotnet/orleans

Takipte kalın…

Published in.NETActor Programming ModelArchitectural

2 Comments

  1. Ali Aldemir Ali Aldemir

    Merhaba hocam,

    Bir yeri anlayamadım. Oluşturduğumuz grains nerede siloya register oluyor? Yani silo GetGrain(0) çağırıldığı zaman HelloGrain sınıfını oluşturacağını nasıl anlıyor?

    Birden fazla domain(product, order, category vb.) olduğu zaman hepsini ayrı ayrı projeler olarak mı geliştirmeli yoksa interfaces ve grains şeklinde hepsini bir yere mi toplamalı yönetilmesi açısından hangisi daha iyi olur. Sonraki makaleler de gerçek dünyadan örneklerle anlatır mısınız?

    • Merhaba, bu ilk giriş örneğinde ben silo ile, grain’leri aynı console üzerinde yaptığım için sanırım anlamada karışıklık olmuş. Bahsettiğiniz grain’in hangi silo’da ayağa kalkacağını, host üzerindeki cluster ayarlarından belirtiyorsunuz. Yani senin silo adresin budur… gibi. Buradaki örneğe bakarsanız daha açıklayıcı olacaktır: https://github.com/GokGokalp/orleans-product-api-sample/blob/master/ProductAPI.Host/Startup.cs

      Microservice’ler olarak domain’lere bölmekte sizin elinizde. Yani “product” domain’i başlı başına büyük bir domain’dir aggregate’leri ile beraber. Siz domain olarak bölüp, içerisinde actor’lerle koşturabilirsiniz.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.