Aracılığıyla paylaş


Güvenilir Koleksiyonlar ile çalışma

Service Fabric, Reliable Collections aracılığıyla .NET geliştiricilerinin kullanımına sunulan durum bilgisi olan bir programlama modeli sunar. Özellikle, Service Fabric güvenilir sözlük ve güvenilir kuyruk sınıfları sağlar. Bu sınıfları kullandığınızda durumunuz bölümlendirilir (ölçeklenebilirlik için), çoğaltılır (kullanılabilirlik için) ve bir bölüm içinde işlem yapılır (ACID semantiği için). Şimdi güvenilir bir sözlük nesnesinin tipik kullanımına bakalım ve gerçekte ne yaptığını görelim.

try
{
   // Create a new Transaction object for this partition
   using (ITransaction tx = base.StateManager.CreateTransaction())
   {
      // AddAsync takes key's write lock; if >4 secs, TimeoutException
      // Key & value put in temp dictionary (read your own writes),
      // serialized, redo/undo record is logged & sent to secondary replicas
      await m_dic.AddAsync(tx, key, value, cancellationToken);

      // CommitAsync sends Commit record to log & secondary replicas
      // After quorum responds, all locks released
      await tx.CommitAsync();
   }
   // If CommitAsync isn't called, Dispose sends Abort
   // record to log & all locks released
}
catch (TimeoutException)
{
   // choose how to handle the situation where you couldn't get a lock on the file because it was 
   // already in use. You might delay and retry the operation
   await Task.Delay(100);
}

Güvenilir sözlük nesneleri üzerindeki tüm işlemler (geri alınamaz olan ClearAsync hariç), bir ITransaction nesnesi gerektirir. Bu nesne, tek bir bölüm içindeki herhangi bir güvenilir sözlükte ve/veya güvenilir kuyruk nesnesinde yapmaya çalıştığınız tüm değişiklikleri ve tüm değişiklikleri kendisiyle ilişkilendirdi. Bölümün StateManager'ın CreateTransaction yöntemini çağırarak bir ITransaction nesnesi alırsınız.

Yukarıdaki kodda, ITransaction nesnesi güvenilir bir sözlüğün AddAsync yöntemine geçirilir. Dahili olarak, bir anahtarı kabul eden sözlük yöntemleri, anahtarla ilişkilendirilmiş bir okuyucu/yazıcı kilidi alır. yöntemi anahtarın değerini değiştirirse, yöntem anahtar üzerinde bir yazma kilidi alır ve yöntem yalnızca anahtarın değerinden okursa, anahtara bir okuma kilidi alınır. AddAsync anahtarın değerini yeni, geçirilen değerle değiştirdiğinden anahtarın yazma kilidi alınır. Bu nedenle, 2 (veya daha fazla) iş parçacığı aynı anda aynı anahtara sahip değerler eklemeyi denerse, bir iş parçacığı yazma kilidini alır ve diğer iş parçacıkları engeller. Varsayılan olarak, yöntemler kilidi almak için 4 saniyeye kadar engeller; 4 saniye sonra yöntemler bir TimeoutException oluşturur. Yöntem aşırı yüklemeleri, tercih ederseniz açık bir zaman aşımı değeri geçirmenize olanak sağlar.

Genellikle, bir TimeoutException'a tepki vermek için kodunuzu yakalar ve işlemin tamamını yeniden denersiniz (yukarıdaki kodda gösterildiği gibi). Bu basit kodda task.delay çağrısı yapıyoruz ve her seferinde 100 milisaniye geçiyoruz. Ancak gerçekte, bunun yerine bir tür üstel geri alma gecikmesi kullanmak daha iyi olabilir.

Kilit alındıktan sonra, AddAsync anahtar ve değer nesnesi başvurularını ITransaction nesnesiyle ilişkilendirilmiş bir iç geçici sözlüğe ekler. Bu, size kendi yazmalarınızı okuma semantiği sağlamak için yapılır. Diğer bir ifadeyle, AddAsync'i çağırdıktan sonra, işlemi henüz işlememiş olsanız bile aynı ITransaction nesnesini kullanan TryGetValueAsync çağrısı değeri döndürür.

Dekont

TryGetValueAsync'in yeni bir işlemle çağrılması, son işlenen değere bir başvuru döndürür. Değişiklikleri kalıcı hale getiren ve çoğaltan mekanizmayı atlayan bu başvuruyu doğrudan değiştirmeyin. Bir anahtarın değerini değiştirmenin tek yolunun güvenilir sözlük API'lerinden geçmesi için değerleri salt okunur hale getirmenizi öneririz.

Ardından AddAsync, anahtar ve değer nesnelerinizi bayt dizilerine seri hale getirir ve bu bayt dizilerini yerel düğümdeki bir günlük dosyasına ekler. Son olarak, AddAsync bayt dizilerini aynı anahtar/değer bilgilerine sahip olmaları için tüm ikincil çoğaltmalara gönderir. Anahtar/değer bilgileri bir günlük dosyasına yazılmış olsa da, ilişkili oldukları işlem işlenmeden bilgiler sözlüğün parçası olarak kabul edilmez.

Yukarıdaki kodda CommitAsync çağrısı, işlemin tüm işlemlerini işler. Özellikle, yerel düğümdeki günlük dosyasına işleme bilgilerini ekler ve ayrıca işleme kaydını tüm ikincil çoğaltmalara gönderir. Çoğaltmaların bir çekirdek (çoğunluğu) yanıtladıktan sonra, tüm veri değişiklikleri kalıcı olarak kabul edilir ve diğer iş parçacıklarının/işlemlerin aynı anahtarları ve değerlerini işleyebilmesi için ITransaction nesnesi aracılığıyla işlenen anahtarlarla ilişkili tüm kilitler serbest bırakılır.

CommitAsync çağrılmazsa (genellikle bir özel durum oluştuğundan), ITransaction nesnesi atılır. Service Fabric, kaydedilmemiş bir ITransaction nesnesini yok ederken, iptal bilgilerini yerel düğümün günlük dosyasına ekler ve ikincil çoğaltmaların hiçbirine hiçbir şey gönderilmez. Ardından, işlem aracılığıyla yönlendirilen anahtarlarla ilişkili tüm kilitler serbest bırakılır.

Geçici Güvenilir Koleksiyonlar

Örneğin, çoğaltılmış önbellek gibi bazı iş yüklerinde zaman zaman veri kaybına tolerans gösterilebilir. Verilerin diske kalıcı olmasını önlemek, Reliable Dictionaries'a yazarken daha iyi gecikme süreleri ve aktarım hızı sağlayabilir. Kalıcılık eksikliğinin sonucu, çekirdek kaybı oluşursa tam veri kaybı olmasıdır. Çekirdek kaybı nadir görülen bir durum olduğundan, artan performans bu iş yükleri için nadir veri kaybı olasılığına değer olabilir.

Şu anda geçici destek yalnızca Reliable Dictionaries ve Reliable Queues için kullanılabilir, ReliableConcurrentQueues için kullanılamaz. Geçici koleksiyonları kullanıp kullanmama kararınızı bildirmek için lütfen Uyarılar listesine bakın.

Hizmetinizde geçici desteği etkinleştirmek için hizmet türü bildirimindeki bayrağını falseolarak ayarlayınHasPersistedState, örneğin:

<StatefulServiceType ServiceTypeName="MyServiceType" HasPersistedState="false" />

Dekont

Mevcut kalıcı hizmetler geçici hale getirilemez ve bunun tersi de geçerlidir. Bunu yapmak isterseniz, mevcut hizmeti silmeniz ve ardından güncelleştirilmiş bayrakla hizmeti dağıtmanız gerekir. Bu, bayrağı değiştirmek HasPersistedState istiyorsanız tam veri kaybına neden olmaya istekli olmanız gerektiği anlamına gelir.

Yaygın tuzaklar ve bunlardan kaçınma

Artık güvenilir koleksiyonların dahili olarak nasıl çalıştığını anladığınıza göre, bunların bazı yaygın kötüye kullanımlarına göz atalım. Aşağıdaki koda bakın:

using (ITransaction tx = StateManager.CreateTransaction())
{
   // AddAsync serializes the name/user, logs the bytes,
   // & sends the bytes to the secondary replicas.
   await m_dic.AddAsync(tx, name, user);

   // The line below updates the property's value in memory only; the
   // new value is NOT serialized, logged, & sent to secondary replicas.
   user.LastLogin = DateTime.UtcNow;  // Corruption!

   await tx.CommitAsync();
}

Normal bir .NET sözlüğüyle çalışırken, sözlüğe bir anahtar/değer ekleyebilir ve ardından bir özelliğin değerini (LastLogin gibi) değiştirebilirsiniz. Ancak, bu kod güvenilir bir sözlükle düzgün çalışmaz. Önceki tartışmadan hatırlayacağınız gibi AddAsync çağrısı, anahtar/değer nesnelerini bayt dizilerine seri hale getirir ve ardından dizileri yerel bir dosyaya kaydeder ve bunları ikincil çoğaltmalara gönderir. Daha sonra bir özelliği değiştirirseniz, bu özellik değerini yalnızca bellekte değiştirir; yerel dosyayı veya çoğaltmalara gönderilen verileri etkilemez. İşlem kilitlenirse bellektekiler atılır. Yeni bir işlem başlatıldığında veya başka bir çoğaltma birincil olduğunda, eski özellik değeri kullanılabilir.

Yukarıda gösterilen hatanın ne kadar kolay olduğunu yeterince vurgulayamıyorum. Ayrıca, hata hakkında yalnızca süreç çökerse/olduğunda öğreneceksiniz. Kodu yazmanın doğru yolu yalnızca iki satırı ters çevirmektir:

using (ITransaction tx = StateManager.CreateTransaction())
{
   user.LastLogin = DateTime.UtcNow;  // Do this BEFORE calling AddAsync
   await m_dic.AddAsync(tx, name, user);
   await tx.CommitAsync();
}

Yaygın bir hatayı gösteren başka bir örnek aşağıda verilmiştir:

using (ITransaction tx = StateManager.CreateTransaction())
{
   // Use the user's name to look up their data
   ConditionalValue<User> user = await m_dic.TryGetValueAsync(tx, name);

   // The user exists in the dictionary, update one of their properties.
   if (user.HasValue)
   {
      // The line below updates the property's value in memory only; the
      // new value is NOT serialized, logged, & sent to secondary replicas.
      user.Value.LastLogin = DateTime.UtcNow; // Corruption!
      await tx.CommitAsync();
   }
}

Yine, normal .NET sözlükleri ile yukarıdaki kod düzgün çalışır ve yaygın bir desendir: geliştirici bir değeri aramak için bir anahtar kullanır. Değer varsa, geliştirici bir özelliğin değerini değiştirir. Ancak, güvenilir koleksiyonlarda, bu kod daha önce açıklandığı gibi aynı sorunu gösterir: Bir nesneyi güvenilir bir koleksiyona verdikten sonra değiştirmemelisiniz.

Güvenilir bir koleksiyondaki bir değeri güncelleştirmenin doğru yolu, var olan değere başvuru almak ve bu başvuru tarafından başvuruda bulunulan nesneyi sabit olarak değerlendirmektir. Ardından, özgün nesnenin tam kopyası olan yeni bir nesne oluşturun. Şimdi, bu yeni nesnenin durumunu değiştirebilir ve yeni nesneyi koleksiyona yazarak bayt dizilerine seri hale getirilebilir, yerel dosyaya eklenir ve çoğaltmalara gönderilir. Değişiklikleri işledikten sonra bellek içi nesneler, yerel dosya ve tüm çoğaltmalar tam olarak aynı duruma sahiptir. Her şey yolunda!

Aşağıdaki kod, güvenilir bir koleksiyondaki bir değeri güncelleştirmenin doğru yolunu gösterir:

using (ITransaction tx = StateManager.CreateTransaction())
{
   // Use the user's name to look up their data
   ConditionalValue<User> currentUser = await m_dic.TryGetValueAsync(tx, name);

   // The user exists in the dictionary, update one of their properties.
   if (currentUser.HasValue)
   {
      // Create new user object with the same state as the current user object.
      // NOTE: This must be a deep copy; not a shallow copy. Specifically, only
      // immutable state can be shared by currentUser & updatedUser object graphs.
      User updatedUser = new User(currentUser);

      // In the new object, modify any properties you desire
      updatedUser.LastLogin = DateTime.UtcNow;

      // Update the key's value to the updateUser info
      await m_dic.SetValue(tx, name, updatedUser);
      await tx.CommitAsync();
   }
}

Programcı hatasını önlemek için sabit veri türleri tanımlama

İdeal olarak, derleyicinin yanlışlıkla sabit kabul etmeniz gereken bir nesnenin durumunu sessize alan bir kod ürettiğiniz zaman hataları bildirmesini istiyoruz. Ancak, C# derleyicisi bunu yapma yeteneğine sahip değildir. Bu nedenle, olası programcı hatalarından kaçınmak için, güvenilir koleksiyonlarla kullandığınız türleri sabit türler olacak şekilde tanımlamanızı kesinlikle öneririz. Bu, özellikle çekirdek değer türlerine (sayı [Int32, UInt64 vb.], DateTime, Guid, TimeSpan ve benzeri) bağlı olduğunuz anlamına gelir. Dize de kullanabilirsiniz. Seri hale getirme ve seri durumdan çıkarmanın performansa sıklıkla zarar verebileceği için koleksiyon özelliklerinden kaçınmak en iyisidir. Ancak, koleksiyon özelliklerini kullanmak istiyorsanız, kullanılmasını kesinlikle öneririz. NET'in sabit koleksiyon kitaplığı (System.Collections.Immutable). Bu kitaplık adresinden https://nuget.orgindirilebilir. Ayrıca mümkün olduğunda sınıflarınızı mühürlemenizi ve alanları salt okunur hale getirmenizi öneririz.

Aşağıdaki UserInfo türü, yukarıda belirtilen önerilerden yararlanarak sabit bir türün nasıl tanımlanacağı gösterilmektedir.

[DataContract]
// If you don't seal, you must ensure that any derived classes are also immutable
public sealed class UserInfo
{
   private static readonly IEnumerable<ItemId> NoBids = ImmutableList<ItemId>.Empty;

   public UserInfo(String email, IEnumerable<ItemId> itemsBidding = null) 
   {
      Email = email;
      ItemsBidding = (itemsBidding == null) ? NoBids : itemsBidding.ToImmutableList();
   }

   [OnDeserialized]
   private void OnDeserialized(StreamingContext context)
   {
      // Convert the deserialized collection to an immutable collection
      ItemsBidding = ItemsBidding.ToImmutableList();
   }

   [DataMember]
   public readonly String Email;

   // Ideally, this would be a readonly field but it can't be because OnDeserialized
   // has to set it. So instead, the getter is public and the setter is private.
   [DataMember]
   public IEnumerable<ItemId> ItemsBidding { get; private set; }

   // Since each UserInfo object is immutable, we add a new ItemId to the ItemsBidding
   // collection by creating a new immutable UserInfo object with the added ItemId.
   public UserInfo AddItemBidding(ItemId itemId)
   {
      return new UserInfo(Email, ((ImmutableList<ItemId>)ItemsBidding).Add(itemId));
   }
}

ItemId türü de burada gösterildiği gibi sabit bir türdür:

[DataContract]
public struct ItemId
{
   [DataMember] public readonly String Seller;
   [DataMember] public readonly String ItemName;
   public ItemId(String seller, String itemName)
   {
      Seller = seller;
      ItemName = itemName;
   }
}

Şema sürümü oluşturma (yükseltmeler)

Dahili olarak, Reliable Collections kullanarak nesnelerinizi seri hale getirmektedir. NET DataContractSerializer. Serileştirilmiş nesneler birincil çoğaltmanın yerel diskinde kalıcı hale getirilir ve ikincil çoğaltmalara da iletilir. Hizmetiniz büyüdükçe, hizmetinizin gerektirdiği veri (şema) türünü değiştirmek isteyebilirsiniz. Verilerinizin sürümüne büyük bir dikkatle yaklaşın. Her şeyden önce, her zaman eski verilerin seri durumdan çıkarılabilmesi gerekir. Bu, seri durumdan çıkarma kodunuzun sonsuz geri uyumlu olması gerektiği anlamına gelir: Hizmet kodunuzun 333 sürümü, 5 yıl önce hizmet kodunuzun 1. sürümüne göre güvenilir bir koleksiyona yerleştirilen veriler üzerinde çalışabilmelidir.

Ayrıca, hizmet kodu bir kerede bir yükseltme etki alanı yükseltilir. Bu nedenle, yükseltme sırasında hizmet kodunuzun aynı anda çalışan iki farklı sürümü vardır. Hizmet kodunuzun eski sürümleri yeni şemayı işleyemeyebileceği için, hizmet kodunuzun yeni sürümünün yeni şemayı kullanmasını önlemeniz gerekir. Mümkün olduğunda, hizmetinizin her sürümünü bir sürümle ileriye doğru uyumlu olacak şekilde tasarlamanız gerekir. Bu, hizmet kodunuzun V1'inin açıkça işlemediği tüm şema öğelerini yoksayabileceği anlamına gelir. Ancak, açıkça bilmediği tüm verileri kaydedebilmeli ve sözlük anahtarını veya değerini güncelleştirirken geri yazabilmelidir.

Uyarı

Bir anahtarın şemasını değiştirebilirsiniz ancak anahtarınızın eşitlik ve karşılaştırma algoritmalarının kararlı olduğundan emin olmanız gerekir. Bu algoritmalardan herhangi birinde yapılan bir değişiklik sonrasında güvenilir koleksiyonların davranışı tanımlanmamıştır ve veri bozulmasına, kaybına ve hizmet kilitlenmelerine yol açabilir. .NET Dizeleri anahtar olarak kullanılabilir, ancak anahtar olarak dizenin kendisini kullanır; anahtar olarak String.GetHashCode sonucunu kullanmayın.

Alternatif olarak, çok aşamalı yükseltme gerçekleştirebilirsiniz.

  1. Hizmeti yeni bir sürüme yükseltme
    • hem özgün V1'e hem de hizmet kodu paketine dahil edilen veri sözleşmelerinin yeni V2 sürümüne sahiptir;
    • gerekirse özel V2 durum serileştiricilerini kaydeder;
    • V1 veri sözleşmelerini kullanarak özgün V1 koleksiyonundaki tüm işlemleri gerçekleştirir.
  2. Hizmeti yeni bir sürüme yükseltme
    • yeni bir V2 koleksiyonu oluşturur;
    • tek bir işlemde ilk V1 ve ardından V2 koleksiyonlarında her ekleme, güncelleştirme ve silme işlemini gerçekleştirir;
    • yalnızca V1 koleksiyonunda okuma işlemleri gerçekleştirir.
  3. V1 koleksiyonundaki tüm verileri V2 koleksiyonuna kopyalayın.
    • Bu işlem, 2. adımda dağıtılan hizmet sürümü tarafından bir arka plan işleminde yapılabilir.
    • V1 koleksiyonundaki tüm anahtarları yeniden kullanın. Sabit listesi, işlemin süresince koleksiyonu kilitlemekten kaçınmak için varsayılan olarak IsolationLevel.Snapshot ile gerçekleştirilir.
    • Her anahtar için ayrı bir işlem kullanarak
      • V1 koleksiyonundan TryGetValueAsync .
      • Kopyalama işlemi başlatıldıktan sonra değer V1 koleksiyonundan zaten kaldırılmışsa anahtar atlanmalıdır ve V2 koleksiyonunda yeniden yapılandırılmamalıdır.
      • TryAddA değeri V2 koleksiyonuna eşitleyin .
      • Kopyalama işlemi başlatıldıktan sonra değer V2 koleksiyonuna zaten eklenmişse anahtar atlanmalıdır.
      • İşlem yalnızca döndürürse TryAddAsynctrueişlenmelidir.
      • Değer erişim API'leri varsayılan olarak IsolationLevel.ReadRepeatable'ı kullanır ve işlem tamamlanana veya durdurulana kadar değerlerin başka bir çağıran tarafından değiştirilmediğini garanti etmek için kilitlemeye güvenir.
  4. Hizmeti yeni bir sürüme yükseltme
    • yalnızca V2 koleksiyonunda okuma işlemleri gerçekleştirir;
    • yine de V1'e geri dönme seçeneğini korumak için ilk V1 ve ardından V2 koleksiyonlarında her ekleme, güncelleştirme ve silme işlemini gerçekleştirir.
  5. Hizmeti kapsamlı bir şekilde test edin ve beklendiği gibi çalıştığını onaylayın.
    • Hem V1 hem de V2 koleksiyonunda çalışacak şekilde güncelleştirilmeyen herhangi bir değer erişim işlemini kaçırdıysanız eksik veriler fark edebilirsiniz.
    • Eksik veriler varsa 1. Adım'a dönün, V2 koleksiyonunu kaldırın ve işlemi yineleyin.
  6. Hizmeti yeni bir sürüme yükseltme
    • yalnızca V2 koleksiyonundaki tüm işlemleri gerçekleştirir;
    • V1'e geri dönmek artık bir hizmet geri alma işlemiyle mümkün değildir ve 2-4 arası geri alınmış adımlarla ileriye doğru ilerlemeyi gerektirir.
  7. Hizmeti yeni bir sürüme yükseltin
  8. Günlük kesilmesini bekleyin.
    • Varsayılan olarak bu, güvenilir koleksiyonlara her 50 MB yazma işlemi (ekler, güncelleştirir ve kaldırır) gerçekleşir.
  9. Hizmeti yeni bir sürüme yükseltme
    • artık hizmet kodu paketine V1 veri sözleşmeleri dahil değildir.

Sonraki adımlar

İleriye dönük uyumlu veri sözleşmeleri oluşturma hakkında bilgi edinmek için bkz . İletme Uyumlu Veri Sözleşmeleri

Veri sözleşmelerini sürüm oluşturmayla ilgili en iyi yöntemleri öğrenmek için bkz. Veri Sözleşmesi Sürüm Oluşturma

Sürüme dayanıklı veri sözleşmelerini uygulamayı öğrenmek için bkz . Sürüme Dayanıklı Serileştirme Geri Çağırmaları

Birden çok sürüm arasında birlikte çalışabilen bir veri yapısı sağlamayı öğrenmek için bkz . IExtensibleDataObject

Güvenilir koleksiyonları yapılandırmayı öğrenmek için bkz . Çoğaltıcı Yapılandırması