Saniyede çok sayıda kesici uç ile sorgulama için on milyonlarca nesneyi depolamak için etkili yöntemler?


15

Bu temelde bir p2p sohbet ağında paket sayısını ve paket türünü, vb. Sayan bir günlük / sayım uygulamasıdır. Bu, 5 dakikalık bir sürede yaklaşık 4-6 milyon pakete eşittir. Ve bu bilgilerin yalnızca bir "anlık görüntüsünü" aldığım için, her beş dakikada bir 5 dakikadan daha eski paketleri kaldırıyorum. Bu nedenle, bu koleksiyonda yer alacak maksimum öğe sayısı 10 ila 12 milyon arasındadır.

Farklı superpeers için 300 bağlantı yapmam gerektiğinden, her paketin en az 300 kez eklenmeye çalışılması olasılığı vardır (muhtemelen bu veriyi bellekte tutmak tek makul seçenektir).

Şu anda, bu bilgileri saklamak için bir Sözlük kullanıyorum. Ancak depolamaya çalıştığım çok sayıda öğe nedeniyle, büyük nesne yığınıyla ilgili sorunlar yaşıyorum ve bellek kullanımı miktarı zamanla sürekli artıyor.

Dictionary<ulong, Packet>

public class Packet
{
    public ushort RequesterPort;
    public bool IsSearch;
    public string SearchText;
    public bool Flagged;
    public byte PacketType;
    public DateTime TimeStamp;
}

Mysql kullanmayı denedim, ancak (yinelenmediğinden emin olmak için kontrol ederken) eklemek için gereken veri miktarına yetişmek mümkün değildi ve bu işlemleri kullanırken oldu.

Mongodb'u denedim, ama bunun için cpu kullanımı deliydi ve ya tutmadı.

Ana sorunum her 5 dakikada bir ortaya çıkıyor, çünkü 5 dakikadan daha eski olan tüm paketleri kaldırıyorum ve bu verilerin "anlık görüntüsünü" alıyorum. Belirli bir paket türü içeren paket sayısını saymak için LINQ sorguları kullanıyorum gibi. Ben de keyvaluepair'ın anahtarından 4 bayt (ip adresi) şerit ve anahtar değer değeri değerinde requestingport değeri ile birleştirmek ve farklı bir sayı elde etmek için kullanmak veri ayrı bir () sorgusu çağırıyorum tüm paketlerden akranları.

Uygulama şu anda yaklaşık 1,1 GB bellek kullanımı barındırıyor ve bir anlık görüntü çağrıldığında, kullanımı iki katına çıkaracak kadar ileri gidebiliyor.

Eğer çılgınca bir miktar koç varsa, bu bir sorun olmaz, ama bu çalıştığım vm şu anda 2GB koç ile sınırlıdır.

Kolay bir çözüm var mı?


Çok bellek yoğun bir senaryo ve bunun üstüne vow uygulamayı çalıştırmak için bir vm kullanıyorsunuz. Her neyse, paketleri saklamak için memcached keşfettiniz. Temel olarak ayrı bir makinede memcached çalıştırabilirsiniz ve uygulama vm kendisi üzerinde çalışmaya devam edebilirsiniz.

Hem MySQL hem de MongoDB'yi zaten denediğiniz gibi, belki de uygulamanızın gereksinimleri (doğru yapmak istiyorsanız) sadece daha fazla beygir gücüne ihtiyacınız olduğunu belirtebilir. Uygulamanız sizin için önemliyse, sunucuyu güçlendirin. Ayrıca "tasfiye" kodunuzu tekrar ziyaret etmek de isteyebilirsiniz. Uygulamanızı kullanılamaz hale getirmediği sürece, bunun için daha optimize bir yol bulabileceğinize eminim.
Matt Beckman

4
Profiliniz size ne anlatıyor?
Jasonk

Yerel yığından daha hızlı bir şey elde edemezsiniz. Benim önerim, temizlendikten sonra elle çöp toplama işlemini başlatmak olacaktır.
12'de vartec

@vartec - aslında, popüler inanışın aksine, çöp toplayıcıyı manuel olarak çağırmak, hemen, iyi ... çöp toplanmasını garanti etmez. GC, eylemi kendi gc algoritmasına göre daha sonraki bir süreye erteleyebilir. Her 5 dakikada bir çağırmak, hafifletmek yerine zorlanmaya katkıda bulunabilir. Sadece söyleyerek;)
Jas

Yanıtlar:


12

Bir sözlüğe sahip olmak ve o sözlükte çok eski girişleri aramak yerine; 10 sözlük var. Her 30 saniyede bir yeni bir "geçerli" sözlük oluşturun ve hiç arama yapmadan en eski sözlüğü atın.

Daha sonra, en eski sözlüğü atarken, tüm eski nesneleri daha sonra kullanmak üzere bir FILO kuyruğuna koyun ve yeni nesneler oluşturmak için "yeni" kullanmak yerine eski bir nesneyi FILO kuyruğundan çekin ve eskisini yeniden yapılandırmak için bir yöntem kullanın. (eski nesnelerin kuyruğu boş değilse). Bu, çok fazla tahsisi ve çok fazla çöp toplama yükünü önleyebilir.


1
Zaman dilimine göre bölümleme! Tam önereceğim şey.
James Anderson

Sorun şu ki, son beş dakika içinde yapılan tüm sözlükleri sorgulamak zorunda kalacağım. 300 bağlantı olduğundan, aynı paket her birine en az bir kez ulaşacaktır. Bu yüzden aynı paketi bir kereden fazla işlememek için, bunları en az 5 dakikalık bir süre boyunca saklamalıyım.
Josh

1
Genel yapılarla ilgili sorunun bir kısmı, belirli bir amaç için özelleştirilmemesidir. Belki de Paket yapınıza bir "nextItemForHash" alanı ve bir "nextItemForTimeBucket" alanı eklemeli ve kendi karma tablonuzu uygulamalı ve Sözlüğü kullanmayı bırakmalısınız. Bu şekilde, çok eski olan tüm paketleri hızlı bir şekilde bulabilir ve bir paket takıldığında yalnızca bir kez arama yapabilirsiniz (yani, pastanızı alın ve yiyin). Ayrıca bellek yönetimi yükü için de yardımcı olur ("Sözlük" Sözlük yönetimi için fazladan veri yapıları tahsis etmeyecek / serbest bırakmayacaktır).
Brendan

@ Daha önce bir şey görüp görmediğinizi belirlemenin en hızlı yolu bir hashsettir . Zaman dilimli karma kümeleri hızlı olur ve eski öğeleri çıkarmak için arama yapmanız gerekmez. Daha önce görmediyseniz, sözlükte (y / i) saklayabilirsiniz.
Temel


3

İlk akla gelen düşünce, neden 5 dakika beklediğinizdir. Anlık görüntüleri daha sık yapabilir ve böylece 5 dakikalık sınırda gördüğünüz büyük aşırı yüklenmeyi azaltabilir misiniz?

İkincisi, LINQ özlü kod için mükemmeldir, ancak gerçekte LINQ "normal" C # üzerinde sözdizimsel şekerdir ve en uygun kodu üreteceğine dair bir garanti yoktur. Bir alıştırma olarak, LINQ ile sıcak noktaları yeniden yazmayı deneyebilir, performansı geliştiremeyebilirsiniz, ancak ne yaptığınızı daha iyi anlayacaksınız ve profil oluşturma işini kolaylaştıracaktır.

Bakılması gereken bir diğer şey de veri yapıları. Verilerinizle ne yaptığınızı bilmiyorum, ancak sakladığınız verileri herhangi bir şekilde basitleştirebilir misiniz? Bir dize veya bayt dizisi kullanabilir ve daha sonra, ihtiyacınız olan parçaları ilgili öğelerden çıkarabilir misiniz? Bir sınıf yerine bir yapı kullanabilir ve hatta bellek ayırmak ve GC çalışmalarını önlemek için stackalloc ile kötü bir şey yapabilir misiniz?


1
Bir dize / bayt dizisi kullanmayın, Bit biti gibi bir şey kullanın: msdn.microsoft.com/en-us/library/… manuel olarak bit-twiddle kullanmaktan kaçınmak için. Aksi takdirde, bu iyi bir cevaptır, daha iyi algoritmalar, daha fazla donanım veya daha iyi donanımdan başka gerçekten kolay bir seçenek yoktur.
Ed James

1
Beş dakikalık şey, bu 300 bağlantının aynı paketi alabilmesinden kaynaklanmaktadır. Bu yüzden, zaten ele aldığım şeyleri takip etmeliyim ve 5 dakika, paketlerin bu belirli ağdaki tüm düğümlere tam olarak yayılması için geçen süredir.
Josh

3

Basit yaklaşım: Memcached'i deneyin .

  • Bunun gibi görevleri çalıştırmak için optimize edilmiştir.
  • Yedek belleği yalnızca özel kutunuzda değil, daha az meşgul kutularda da yeniden kullanabilir.
  • Tembel yani hıçkırık yerleşik önbellek son kullanma mekanizması vardır.

Dezavantajı, bellek tabanlı olması ve herhangi bir kalıcılığı olmamasıdır. Bir örnek bozulursa veriler kaybolur. Kalıcılığa ihtiyacınız varsa, verileri kendiniz serileştirin.

Daha karmaşık bir yaklaşım: Redis'i deneyin .

  • Bunun gibi görevleri çalıştırmak için optimize edilmiştir.
  • Bu yerleşik önbellek son kullanma mekanizması .
  • Kolayca ölçeklenir / kırılır.
  • Kalıcılığı vardır.

Dezavantajı biraz daha karmaşık olmasıdır.


1
Memcached kullanılabilir ram miktarını artırmak için makineler arasında bölünebilir. Dosya sistemine veri serileştiren ikinci bir sunucunuz olabilir, böylece bir mesaj kutusu kapanırsa bir şeyleri kaybetmezsiniz. Memcache API'sinin kullanımı çok basittir ve farklı yerlerde farklı yığınlar kullanmanıza izin veren herhangi bir dilden çalışır.
Michael Shopsin

1

Bahsettiğiniz sorguların tüm paketlerini saklamanız gerekmez. Örneğin - paket türü sayacı:

İki diziye ihtiyacınız var:

int[] packageCounters = new int[NumberOfTotalTypes];
int[,] counterDifferencePerMinute = new int[6, NumberOfTotalTypes];

İlk dizi, farklı tiplerde kaç paket olduğunu takip eder. İkinci dizi, dakika başına kaç paketin eklendiğini izler, böylece her dakika aralığında kaç paketin kaldırılması gerektiğini bilirsiniz. Umarım ikinci dizinin yuvarlak FIFO kuyruğu olarak kullanıldığını söyleyebilirsiniz.

Yani her paket için aşağıdaki işlemler gerçekleştirilir:

packageCounters[packageType] += 1;
counterDifferencePerMinute[current, packageType] += 1;
if (oneMinutePassed) {
  current = (current + 1) % 6;
  for (int i = 0; i < NumberOfTotalTypes; i++) {
    packageCounters[i] -= counterDifferencePerMinute[current, i];
    counterDifferencePerMinute[current, i] = 0;
}

Herhangi bir zamanda, paket sayaçları endeks tarafından anında alınabilir ve tüm paketleri saklamayız.


Yaptığım verileri saklamak zorunda olmanın temel nedeni, bu 300 bağlantının tam olarak aynı paketi alabilmesidir. Bu yüzden, bir kereden fazla işlemediğimden / saymadığımdan emin olmak için görülen her paketi en az beş dakika tutmam gerekiyor. Sözlük anahtarı için ulong bunun içindir.
Josh

1

(Bunun eski bir soru olduğunu biliyorum, ancak ikinci nesil çöp toplama geçişinin uygulamayı birkaç saniye duraklattığı benzer bir soruna bir çözüm ararken karşılaştım, bu yüzden benzer durumdaki diğer insanlar için kayıt).

Verileriniz için bir sınıf yerine bir yapı kullanın (ancak kopyala anlambilimiyle bir değer olarak değerlendirildiğini unutmayın). Bu, gc'nin her işaret geçişini yapması gereken bir arama düzeyini alır.

Dizileri (depoladığınız verilerin boyutunu biliyorsanız) veya dizileri dahili olarak kullanan Liste'yi kullanın. Gerçekten hızlı rasgele erişime ihtiyacınız varsa, dizi indeksleri sözlüğü kullanın. Bu, gc'nin aramak zorunda kalması için başka birkaç düzey (veya bir SortedDictionary kullanıyorsanız bir düzine veya daha fazla) alır.

Yaptığınız işe bağlı olarak, bir yapı listesinde arama, söz konusu uygulamanız için profil aramasından (belleğin yerelleştirilmesi nedeniyle) daha hızlı olabilir.

Struct & list kombinasyonu, hem bellek kullanımını hem de çöp toplayıcı taramasının boyutunu önemli ölçüde azaltır.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.