Bagikan melalui


Menggunakan Reliable Collection

Service Fabric menawarkan model pemrograman stateful yang tersedia untuk developer .NET melalui Reliable Collection. Secara khusus, Service Fabric menyediakan reliable dictionary dan reliable queue. Ketika Anda menggunakan kelas-kelas ini, status Anda dipartisi (untuk skalabilitas), direplikasi (untuk ketersediaan), dan ditransaksikan dalam partisi (untuk semantik ACID). Mari kita lihat penggunaan khas objek reliable dictionary dan lihat hal yang sebenarnya dilakukannya.

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);
}

Semua operasi pada objek reliable dictionary (kecuali untuk ClearAsync, yang tidak dapat dilakukan), memerlukan objek ITransaction. Objek ini telah dikaitkan dengan itu setiap dan semua perubahan yang Anda coba lakukan untuk setiap reliable dictionary dan/atau reliable queue object dalam satu partisi. Anda memperoleh objek ITransaction dengan memanggil metode CreateTransaction StateManager partisi.

Dalam kode di atas, objek ITransaction diteruskan ke metode AddAsync reliable dictionary. Secara internal, metode dictionary yang menerima key mengambil kunci pembaca/penulis yang terkait dengan kunci. Jika metode memodifikasi nilai key, metode ini mengambil kunci tulis pada key dan jika metode hanya membaca dari nilai key, kunci baca diambil pada key. Karena AddAsync memodifikasi nilai key ke nilai baru yang diteruskan, kunci tulis key diambil. Jadi, jika 2 (atau lebih) thread mencoba menambahkan nilai dengan key yang sama pada saat yang sama, satu thread akan memperoleh kunci tulis, dan thread lainnya akan memblokir. Secara default, metode memblokir hingga 4 detik untuk memperoleh kunci; setelah 4 detik, metode menampilkan TimeoutException. Metode overloads exist memungkinkan Anda untuk meneruskan nilai batas waktu eksplisit jika Anda memilihnya.

Biasanya, Anda menulis kode untuk menanggapi TimeoutException dengan menangkapnya dan mencoba kembali seluruh operasi (seperti yang ditunjukkan pada kode di atas). Dalam kode sederhana ini, kita hanya memanggil Task.Delay melewati 100 milidetik setiap kali. Tapi, pada kenyataannya, Anda mungkin lebih baik menggunakan semacam penundaan back-off eksponensial sebagai gantinya.

Setelah kunci diperoleh, AddAsync menambahkan referensi objek key dan nilai ke dictionary sementara internal yang terkait dengan objek ITransaction. Ini dilakukan untuk memberikan semantik baca-tulisan-Anda-sendiri. Artinya, setelah Anda memanggil AddAsync, panggilan selanjutnya ke TryGetValueAsync menggunakan objek ITransaction yang sama akan mengembalikan nilai sekalipun jika Anda belum melakukan transaksi.

Catatan

Memanggil TryGetValueAsync dengan transaksi baru akan mengembalikan referensi ke nilai terakhir yang diterapkan. Jangan memodifikasi referensi tersebut secara langsung karena hal tersebut melewati mekanisme untuk mempertahankan dan mereplikasi perubahan. Anda sebaiknya membuat nilai baca-saja sehingga satu-satunya cara untuk mengubah nilai key adalah melalui reliable dictionary API.

Selanjutnya, AddAsync menserialisasikan objek key dan nilai Anda ke byte array dan menambahkan byte array ini ke file log pada node lokal. Terakhir, AddAsync mengirim byte array ke semua replika sekunder sehingga mereka memiliki informasi key/nilai yang sama. Meskipun informasi key/nilai telah ditulis ke file log, informasi tersebut tidak dianggap sebagai bagian dari dictionary sampai transaksi yang terkait dengannya telah dilakukan.

Dalam kode di atas, panggilan ke CommitAsync menerapkan semua operasi transaksi. Secara khusus, hal tersebut menambahkan informasi penerapan ke file log pada node lokal dan mengirim catatan penerapan ke semua replika sekunder. Setelah kuorum (mayoritas) replika menjawab, semua perubahan data dianggap permanen dan kunci apa pun yang terkait dengan key yang dimanipulasi melalui objek ITransaction dirilis sehingga thread/transaksi lain dapat memanipulasi kunci dan nilainya yang sama.

Jika CommitAsync tidak dipanggil (biasanya karena pengecualian yang ditampilkan), objek ITransaction akan dibuang. Saat membuang objek ITransaction yang belum diterapkan, Service Fabric menambahkan informasi pembatalan ke file log node lokal dan tidak ada yang perlu dikirim ke salah satu replika sekunder. Dan kemudian, setiap kunci yang terkait dengan key yang dimanipulasi melalui transaksi dirilis.

Reliable Collections Volatil

Dalam beberapa beban kerja, cache yang direplikasi misalnya, kehilangan data sesekali dapat ditoleransi. Menghindari kegigihan data ke disk dapat memungkinkan latensi dan throughput yang lebih baik saat menulis ke Reliable Dictionaries. Tradeoff untuk kurangnya kegigihan adalah jika kerugian kuorum terjadi, kehilangan data penuh akan terjadi. Karena kehilangan kuorum adalah kejadian yang jarang terjadi, peningkatan kinerja mungkin sepadan dengan kemungkinan kehilangan data yang jarang terjadi untuk beban kerja tersebut.

Saat ini, dukungan volatil hanya tersedia untuk Reliable Dictionary dan Reliable Queue, dan bukan ReliableConcurrentQueues. Harap lihat daftar Caveats untuk menginformasikan keputusan Anda tentang apakah akan menggunakan koleksi yang volatil.

Untuk mengaktifkan dukungan volatil di layanan Anda, atur flag HasPersistedState dalam deklarasi jenis layanan ke false, seperti ini:

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

Catatan

Layanan persisten yang ada tidak dapat dibuat volatil, dan sebaliknya. Jika Anda ingin melakukannya, Anda harus menghapus layanan yang ada lalu menyebarkan layanan dengan flag yang diperbarui. Ini berarti bahwa Anda harus bersedia untuk menimbulkan kehilangan data penuh jika Anda ingin mengubah flag HasPersistedState.

Pitfall umum dan cara menghindarinya

Setelah Anda memahami cara kerja reliable collection secara internal, mari kita lihat beberapa penyalahgunaan umumnya. Lihat kode di bawah ini:

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();
}

Saat bekerja dengan dictionary .NET biasa, Anda dapat menambahkan key/nilai ke dictionary lalu mengubah nilai properti (seperti LastLogin). Namun, kode ini tidak akan berfungsi dengan benar dengan reliable dictionary. Dari diskusi sebelumnya, panggilan ke AddAsync menserialisasikan objek key/nilai ke byte array lalu menyimpan array ke file lokal dan juga mengirimkannya ke replika sekunder. Jika nanti Anda mengubah properti, ini hanya mengubah nilai properti dalam memori; ini tidak berdampak pada file lokal atau data yang dikirim ke replika. Jika prosesnya crash, apa yang ada dalam memori dibuang. Ketika proses baru dimulai atau jika replika lain menjadi primer, nilai properti lama adalah apa yang tersedia.

Saya tidak dapat benar-benar menekankan betapa mudahnya membuat jenis kesalahan yang ditunjukkan di atas. Dan, Anda hanya akan mempelajari tentang kesalahan jika/ketika prosesnya turun. Cara yang benar untuk menulis kode hanyalah membalikkan dua baris:

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

Berikut adalah contoh lain yang menunjukkan kesalahan umum:

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();
   }
}

Sekali lagi, dengan dictionary .NET biasa, kode di atas berfungsi dengan baik dan merupakan pola umum: developer menggunakan key untuk mencari nilai. Jika nilainya ada, developer akan mengubah nilai properti. Namun, dengan reliable collection, kode ini menunjukkan masalah yang sama seperti yang telah dibahas: Anda TIDAK BOLEH memodifikasi objek setelah memberikannya ke reliable collection.

Cara yang benar untuk memperbarui nilai dalam reliable collection, adalah dengan mendapatkan referensi ke nilai yang ada dan menganggap objek yang dimaksud berdasarkan referensi ini tidak dapat diubah. Kemudian, buat objek baru yang merupakan salinan persis dari objek asli. Sekarang, Anda dapat memodifikasi status objek baru ini dan menulis objek baru ke dalam koleksi sehingga akan diserialisasikan ke byte array, ditambahkan ke file lokal dan dikirim ke replika. Setelah menerapkan perubahan, objek dalam memori, file lokal, dan semua replika memiliki status yang sama persis. Semua berjalan dengan baik!

Kode di bawah ini menunjukkan cara yang benar untuk memperbarui nilai dalam reliable collection:

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();
   }
}

Tentukan jenis data yang tidak dapat diubah untuk mencegah kesalahan programmer

Idealnya, kita ingin compiler melaporkan kesalahan ketika Anda secara tidak sengaja menghasilkan kode yang mengubah status objek yang seharusnya Anda anggap tidak dapat diubah. Tapi, kompilasi C# tidak memiliki kemampuan untuk melakukan ini. Jadi, untuk menghindari bug programmer potensial, kami sangat menyarankan Anda untuk menentukan jenis yang Anda gunakan dengan reliable collection untuk menjadi jenis yang tidak dapat diubah. Secara khusus, ini berarti Anda tetap berpegang pada jenis nilai inti (seperti angka [Int32, UInt64, dll.], DateTime, Guid, TimeSpan, dan sejenisnya). Anda juga dapat menggunakan String. Anda sebaiknya menghindari properti koleksi karena melakukan serialisasi dan deserialisasi terhadapnya kerap merusak kinerja. Namun, jika Anda ingin menggunakan properti koleksi, kami sangat menyarankan Anda untuk menggunakan . Pustaka koleksi .NET yang tidak dapat diubah (System.Collections.Immutable). Pustaka ini dapat diunduh dari https://nuget.org. Kami juga merekomendasikan Anda untuk menyegel kelas dan menjadikan bidang baca-saja jika memungkinkan.

Jenis UserInfo di bawah ini menunjukkan cara menentukan jenis yang tidak dapat diubah dengan memanfaatkan rekomendasi tersebut.

[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));
   }
}

Jenis ItemId juga merupakan jenis yang tidak dapat diubah seperti yang ditunjukkan di sini:

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

Pembuatan versi skema (peningkatan)

Secara internal, Reliable Collection menserialisasikan objek Anda menggunakan . DataContractSerializer .NET. Objek berserial tetap berada di disk lokal replika utama dan juga ditransmisikan ke replika sekunder. Saat layanan Anda usang, Anda mungkin ingin mengubah jenis data (skema) yang diperlukan layanan Anda. Lakukan pembuatan data Anda dengan sangat hati-hati. Pertama dan yang terpenting, Anda harus selalu dapat mendeserialisasi data lama. Secara khusus, ini berarti kode deserialisasi Anda harus kompatibel ke belakang tanpa batas: Versi 333 dari kode layanan Anda harus dapat beroperasi pada data yang ditempatkan dalam reliable collection dengan versi 1 dari kode layanan Anda 5 tahun yang lalu.

Selain itu, kode layanan ditingkatkan satu domain peningkatan pada satu waktu. Jadi, selama peningkatan, Anda memiliki dua versi berbeda dari kode layanan Anda yang berjalan secara bersamaan. Anda harus menghindari versi baru kode layanan Anda menggunakan skema baru sebagai versi lama kode layanan Anda mungkin tidak dapat menangani skema baru. Jika memungkinkan, Anda harus mendesain setiap versi layanan Anda agar kompatibel dengan satu versi. Secara khusus, ini berarti bahwa V1 dari kode layanan Anda harus dapat mengabaikan elemen skema apa pun yang tidak ditangani secara eksplisit. Namun, itu harus dapat menyimpan data apa pun yang tidak diketahuinya secara eksplisit dan menulisnya kembali ketika memperbarui key atau nilai dictionary.

Peringatan

Meskipun Anda dapat mengubah skema kunci, Anda harus memastikan bahwa algoritma kesetaraan dan perbandingan kunci Anda stabil. Perilaku koleksi yang dapat diandalkan setelah perubahan dalam salah satu algoritma ini tidak terdefinisi dan dapat menyebabkan kerusakan data, kehilangan, dan crash layanan. .NET Strings dapat digunakan sebagai key tetapi gunakan string tersebut sebagai key --jangan gunakan hasil String.GetHashCode sebagai key.

Atau, Anda dapat melakukan peningkatan multi-fase.

  1. Meningkatkan layanan ke versi baru yang
    • memiliki V1 asli, dan versi V2 baru dari kontrak data yang disertakan dalam paket kode layanan;
    • mendaftarkan serializer status V2 kustom, jika diperlukan;
    • melakukan semua operasi pada koleksi V1 asli menggunakan kontrak data V1.
  2. Meningkatkan layanan ke versi baru yang
    • membuat koleksi V2 baru;
    • melakukan setiap operasi tambahkan, perbarui, dan hapus pada koleksi V1 pertama dan kemudian V2 dalam satu transaksi;
    • melakukan operasi baca pada koleksi V1 saja.
  3. Salin semua data dari koleksi V1 ke koleksi V2.
    • Ini dapat dilakukan dalam proses latar belakang oleh versi layanan yang disebarkan di langkah 2.
    • Pertahankan semua kunci dari koleksi V1. Enumerasi dilakukan dengan IsolationLevel.Snapshot secara default untuk menghindari penguncian koleksi selama durasi operasi.
    • Untuk setiap kunci, gunakan transaksi terpisah untuk
      • CobaGetValueAsync dari koleksi V1.
      • Jika nilai telah dihapus dari koleksi V1 sejak proses penyalinan dimulai, kunci harus dilewati dan tidak dibuat ulang dalam koleksi V2.
      • TryAddAsync nilai ke koleksi V2.
      • Jika nilai telah ditambahkan ke koleksi V2 sejak proses penyalinan dimulai, kunci harus dilewati.
      • Transaksi harus dilakukan hanya jika mengembalikan TryAddAsynctrue.
      • API akses nilai menggunakan IsolationLevel.ReadRepeatable secara default dan mengandalkan penguncian untuk menjamin bahwa nilai tidak dimodifikasi oleh pemanggil lain hingga transaksi dilakukan atau dibatalkan.
  4. Meningkatkan layanan ke versi baru yang
    • melakukan operasi baca pada koleksi V2 saja;
    • masih melakukan setiap operasi tambahkan, perbarui, dan hapus pada koleksi V1 pertama dan kemudian V2 untuk mempertahankan opsi mengembalikan ke V1.
  5. Uji layanan secara komprehensif dan konfirmasikan berfungsi seperti yang diharapkan.
    • Jika Anda melewatkan operasi akses nilai apa pun yang tidak diperbarui untuk bekerja pada koleksi V1 dan V2, Anda mungkin melihat data yang hilang.
    • Jika ada data yang hilang, gulung balik ke Langkah 1, hapus koleksi V2 dan ulangi prosesnya.
  6. Meningkatkan layanan ke versi baru yang
    • melakukan semua operasi hanya pada koleksi V2;
    • kembali ke V1 tidak lagi dimungkinkan dengan pembatalan layanan dan akan memerlukan rolling forward dengan langkah terbalik 2-4.
  7. Meningkatkan layanan versi baru yang
  8. Tunggu pemotongan log.
    • Secara default, ini terjadi setiap 50MB penulisan (menambahkan, memperbarui, dan menghapus) ke koleksi yang andal.
  9. Meningkatkan layanan ke versi baru yang
    • tidak lagi memiliki kontrak data V1 yang disertakan dalam paket kode layanan.

Langkah berikutnya

Untuk mempelajari tentang membuat kontrak data yang kompatibel ke depan, lihat Kontrak Data yang Kompatibel Ke Depan

Untuk mempelajari praktik terbaik tentang pembuatan kontrak data versi, lihat Pembuatan Versi Kontrak Data

Untuk mempelajari cara menerapkan kontrak data dengan toleransi versi, lihat Pengguliran Kembali Serialisasi Dengan Toleransi Terhadap Versi

Untuk mempelajari cara menyediakan struktur data yang dapat beroperasi di beberapa versi, lihat IExtensibleDataObject

Untuk mempelajari cara mengonfigurasi koleksi yang andal, lihat Konfigurasi Replikator