RateLimiter Uygulaması

RateLimiter Uygulaması

Diyelim ki X saniyede Y işlem yaptırmanız gerekiyor. Bunu C# ta nasıl yaparsınız?

Rate Limiter'i Türkçe'ye "hız sınırlayıcı" olarak çevirebiliriz. Amacımız belirli bir süre içerisinde yapılacak işlem sayısını sınırlamak.

"Peki bunu nerede kullanabilirim?" dediğinizi duyuyorum. Örnek olarak Amazon Web Servisleri'nden (AWS) Simple Email Service'i (SES) verebilirim.

http://docs.aws.amazon.com/ses/latest/DeveloperGuide/limits.html adresinde de belirtildiği üzere AWS SES kullanıcılarına tanımlanan bir "Maximum send rate" özelliği var. Saniyede gönderilebilecek email sayısını sınırlamaktadır. Bu limitin üzerinde gönderim yapamazsınız.

Biz de buna benzer senaryolarda işlem sayımızı, süreye bağlı sınırlamak durumunda kalabiliriz.

Bunun için ben http://www.jackleitch.net/2010/10/better-rate-limiting-with-dot-net/ adresindeki RateGate sınıfını (üzerinde ufak değişiklikler yaparak) kullanıyorum. Bu yazıda sadece kullanım örneklerini paylaşacağım. Sınıfı incelemek isteyenler, yazının sonundaki linkten örnek projeyi indirerek inceleyebilirler.

Önce kendimiz bir deneyelim. X saniyede Y işlem yapmak istiyoruz.

Gelin daha da somutlaştıralım, 1 - 20 arasındaki sayıları, saniyede 4 tanesi olacak şekilde ekrana yazdırmayı deneyelim.

Yazacağımız kod şuna benzeyecektir:

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();
    Console.WriteLine("{0,5} {1,10}""Adım""Süre");
    sw.Start();
    for (int i = 0; i < 20; i++)
    {
        if (i % 4 == 0 && i > 1)
            Thread.Sleep(1000);
        BisiyYap();
        Console.WriteLine("{0,5} {1,10}", i + 1, sw.ElapsedMilliseconds);
    }
    sw.Stop();
 
}
 
private static void BisiyYap()
{
    Thread.Sleep(100);
}

BisiyYap() metodunda yapmak istediğimiz işlem(ler)i yapıyoruz. Ben sadece sistemi 100 ms bekletiyorum.

(Not: Stopwatch sınıfı kullanarak program adımlarında geçen süreyi takip edebiliriz.)

Ekran çıktısı:

Yazdırılacak 20 sayımız varsa ve saniyede 4 sayı yazdırmak istiyorsak işlem süresi yaklaşık olarak ~4,4  sn (her bir adımda BisiyYap() metodunda 100 ms beklettiğimiz için) sürmeliydi. Ama birazcık ıskaladık sanki :)

Şimdi aynı işlemi RateGate sınıfını kullanarak yapalım:

static void Main(string[] args)
{
    using (RateGate rg = new RateGate(4, TimeSpan.FromSeconds(1)))
    {
        Stopwatch sw = new Stopwatch();
        Console.WriteLine("{0,5} {1,10}""Adım""Süre");
        sw.Start();
        for (int i = 0; i < 20; i++)
        {
            rg.WaitToProceed();
            BisiyYap();
            Console.WriteLine("{0,5} {1,10}", i + 1, sw.ElapsedMilliseconds);
        }
        sw.Stop();
    }
}
 
private static void BisiyYap()
{
    Thread.Sleep(100);
}

Ekran çıktısı:

Beklediğimiz sonuca yakın bir süre karşımıza çıktı. Hiç fena değil. :)

RateGate rg = new RateGate(4, TimeSpan.FromSeconds(1);

Burada 4 işlem sayısını, TimeSpan.FromSeconds(1) de 1 sn lik süreyi gösteriyor. Siz dilerseniz bu parametrelerle oynayabilirsiniz.

Ayrıca RateGate sınıfı IDisposable interface inden türediği için bu tanımlamayı rahatlıkla using bloğu içerisinde kullanabildik.

Şimdi dilerseniz başka nasıl kullanabiliriz bu sınıfı, beraber inceleyelim.

LINQ ve RateGate

LINQ sorgularında kullanabilmek için extension methods (genişletme metotları) lardan faydalanabiliriz:

public static class EnumerableExtensions
{
    public static IEnumerable<T> LimitRate<T>(this IEnumerable<T> source, int count, TimeSpan timeUnit)
    {
        using (var rateGate = new RateGate(count, timeUnit))
        {
            foreach (var item in source)
            {
                rateGate.WaitToProceed();
                yield return item;
            }
        }
    }
 
    public static IEnumerable<T> LimitRate<T>(this IEnumerable<T> source, RateGate rateGate)
    {
        foreach (var item in source)
        {
            rateGate.WaitToProceed();
            yield return item;
        }
    }
}

Örnek kullanım:

static void Main(string[] args)
{
    using (RateGate rg = new RateGate(4, TimeSpan.FromSeconds(1)))
    {
        Console.WriteLine("{0,5} {1,10}""Adım""Süre");
        // siz bilgileri EF ile veri tabanından da getirebilirsiniz.
        var bilgi = Enumerable.Range(1, 20);
        Stopwatch sw = new Stopwatch();
        sw.Start();
        foreach (var i in bilgi.LimitRate(rg))
        {
            BisiyYap();
            Console.WriteLine("{0,5} {1,10}", i, sw.ElapsedMilliseconds);
        }
        sw.Stop();
    }
}
 
private static void BisiyYap()
{
    Thread.Sleep(100);
}

Veya;

static void Main(string[] args)
{
    Console.WriteLine("{0,5} {1,10}""Adım""Süre");
    // siz bilgilerin veri tabanından da getirebilirsiniz.
    var bilgi = Enumerable.Range(1, 20);
    Stopwatch sw = new Stopwatch();
    sw.Start();
    foreach (var i in bilgi.LimitRate(4, TimeSpan.FromSeconds(1)))
    {
        BisiyYap();
        Console.WriteLine("{0,5} {1,10}", i, sw.ElapsedMilliseconds);
    }
    sw.Stop();
}
 
private static void BisiyYap()
{
    Thread.Sleep(100);
}

Ekran çıktısı:

Paralel Programlama ve RateGate

RateGate sınıfını dilerseniz paralel programlamada da kullanabilirsiniz. (Yani multi threading uygulamalarda.)

Örnek kullanım:

static void Main(string[] args)
{
    using (RateGate rg = new RateGate(4, TimeSpan.FromSeconds(1)))
    {
        Console.WriteLine("{0,5} {1,10}""Adım""Süre");
        // siz bilgilerin veri tabanından da getirebilirsiniz.
        var bilgi = Enumerable.Range(1, 20);
        Stopwatch sw = new Stopwatch();
        sw.Start();
        Parallel.For(0, 20, new ParallelOptions { MaxDegreeOfParallelism = 4 }, i =>
            {
                rg.WaitToProceed();
                BisiyYap();
                Console.WriteLine("{0,5} {1,10}", i, sw.ElapsedMilliseconds);
            });
        sw.Stop();
    }
}
 
private static void BisiyYap()
{
    Thread.Sleep(100);
}

Ekran çıktısı:

1 sn içerisinde yapılan işlemler arasındaki sürenin nasıl azaldığını görüyorsunuz. Saniyede 4 sayıyı neredeyse aynı anda yazdırıyoruz. Ancak bu kod multi threading in doğası gereği her çalıştırılışta farklı sonuçlar verecektir.

Ekran çıktısı 2:

gibi...

Yazımızın sonuna geldik.

Örnek projeyi buradan indirebilirsiniz.

Herkese iyi çalışmalar diliyorum. :)