Megosztás a következőn keresztül:


A Reliable Collections használata

A Service Fabric egy állapotalapú programozási modellt kínál, amely a Reliable Collections segítségével érhető el a .NET-fejlesztők számára. A Service Fabric pontosabban megbízható szótárat és megbízható üzenetsorosztályokat biztosít. Ha ezeket az osztályokat használja, az állapot particionálva lesz (a méretezhetőség érdekében), replikálódik (a rendelkezésre állás érdekében), és egy partíción belül történik (az ACID szemantikához). Tekintsük át a megbízható szótárobjektumok tipikus használatát, és nézzük meg, hogy valójában mit is csinál.

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

A megbízható szótárobjektumokon végzett összes művelethez (a ClearAsync kivételével, amely nem vonható vissza) ITransaction objektumra van szükség. Ez az objektum társítva van vele minden olyan módosítással, amelyet egy adott partíción belül bármely megbízható szótár- és/vagy megbízható üzenetsor-objektumon próbál végrehajtani. ITransaction-objektumot a partíció StateManager CreateTransaction metódusának meghívásával szerezhet be.

A fenti kódban az ITransaction objektum egy megbízható szótár AddAsync metódusának lesz átadva. A kulcsokat elfogadó szótári metódusok belsőleg a kulccsal társított olvasói/írói zárolást használják. Ha a metódus módosítja a kulcs értékét, a metódus írási zárolást alkalmaz a kulcson, és ha a metódus csak a kulcs értékéből olvas be, akkor a rendszer olvasási zárolást alkalmaz a kulcson. Mivel az AddAsync módosítja a kulcs értékét az új, átadott értékre, a kulcs írási zárolása létrejön. Ha tehát 2 (vagy több) szál egyszerre próbál értékeket hozzáadni ugyanazzal a kulccsal, az egyik szál megkapja az írási zárolást, a többi szál pedig blokkolni fogja. Alapértelmezés szerint a metódusok legfeljebb 4 másodpercig blokkolják a zárolást; 4 másodperc után a metódusok időtúllépést jeleznek. A metódusok túlterhelései lehetővé teszik, hogy explicit időtúllépési értéket adjon át, ha szeretné.

A kódot általában úgy kell megírni, hogy reagáljon a TimeoutExceptionre, ha elkapja, és újrapróbálkozással próbálkozik a teljes művelettel (ahogy a fenti kódban látható). Ebben az egyszerű kódban csak a Task.Delay parancsot hívjuk meg, amely minden alkalommal 100 ezredmásodpercet ad át. De a valóságban jobb, ha inkább valamilyen exponenciális visszalépési késleltetést használ.

A zárolás beszerzése után az AddAsync hozzáadja a kulcs- és értékobjektum-hivatkozásokat az ITransaction objektumhoz társított belső ideiglenes szótárhoz. Ez azért történik, hogy olvasási-saját írási szemantikát biztosítson. Vagyis az AddAsync meghívása után a TryGetValueAsync egy későbbi hívása ugyanazzal az ITransaction-objektummal akkor is visszaadja az értéket, ha még nem véglegesítette a tranzakciót.

Megjegyzés:

A TryGetValueAsync új tranzakcióval való meghívása az utolsó véglegesített értékre mutató hivatkozást ad vissza. Ne módosítsa közvetlenül ezt a hivatkozást, mivel ez áthalad a módosítások megőrzésére és replikálására szolgáló mechanizmuson. Javasoljuk, hogy az értékeket írásvédetté tegye, hogy a kulcs értékének módosításának egyetlen módja a megbízható szótár API-k segítségével legyen.

Ezután az AddAsync szerializálja a kulcs- és értékobjektumokat a bájttömbökhöz, és hozzáfűzi ezeket a bájttömböket a helyi csomópont naplófájljához. Végül az AddAsync elküldi a bájttömböket az összes másodlagos replikának, így ugyanazokkal a kulcs- és értékinformációkkal rendelkeznek. Annak ellenére, hogy a kulcs-/értékadatok naplófájlba lettek írva, az információ nem tekinthető a szótár részének mindaddig, amíg a hozzájuk társított tranzakciót le nem véglegesítettük.

A fenti kódban a CommitAsync hívása véglegesíti a tranzakció összes műveletét. Pontosabban hozzáfűzi a véglegesítési adatokat a helyi csomópont naplófájljába, és elküldi a véglegesítési rekordot az összes másodlagos replikának. Miután a replikák kvóruma (többsége) válaszolt, a rendszer minden adatmódosítást állandónak tekint, és az ITransaction objektumon keresztül manipulált kulcsokhoz társított zárolások felszabadulnak, hogy más szálak/tranzakciók is manipulálhassák ugyanazokat a kulcsokat és értékeiket.

Ha a CommitAsync meghívása nem történik meg (általában kivétel miatt), akkor az ITransaction objektumot a rendszer megsemmisíti. A nem véglegesített ITransaction-objektum eltávolításakor a Service Fabric megszakított adatokat fűz a helyi csomópont naplófájljába, és semmit sem kell küldeni a másodlagos replikáknak. Ezután a tranzakción keresztül manipulált kulcsokhoz társított zárolások felszabadulnak.

Volatilis megbízható gyűjtemények

Bizonyos számítási feladatokban, például egy replikált gyorsítótárban például az alkalmi adatvesztés elviselhető. A lemezeken tárolt adatok megőrzésének elkerülése jobb késéseket és átviteli sebességet tehet lehetővé a Reliable Dictionariesbe való íráskor. Az állandóság hiányának kompromisszuma az, hogy kvórumveszteség esetén teljes adatvesztés következik be. Mivel a kvórumveszteség ritkán fordul elő, a megnövekedett teljesítmény megéri az adatvesztés ritka lehetőségét az ilyen számítási feladatok esetében.

Jelenleg az ingatag támogatás csak a Reliable Dictionaries és a Reliable Queues esetében érhető el, a ReliableConcurrentQueues nem. Tekintse meg a kikötések listáját, hogy tájékoztassa a döntéséről, hogy használ-e illékony gyűjteményeket.

A szolgáltatásban az ingatag támogatás engedélyezéséhez állítsa a HasPersistedState szolgáltatástípus-deklarációban szereplő jelzőt a következőhöz hasonló értékre false:

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

Megjegyzés:

A meglévő tartós szolgáltatások nem tehetők volatilissé, és fordítva. Ha ezt szeretné tenni, törölnie kell a meglévő szolgáltatást, majd telepítenie kell a szolgáltatást a frissített jelzővel. Ez azt jelenti, hogy teljes adatvesztést kell vállalnia, ha módosítani szeretné a jelzőt HasPersistedState .

Gyakori buktatók és azok elkerülése

Most, hogy megismerte a megbízható gyűjtemények belső működését, vessünk egy pillantást néhány gyakori visszaélésre. Lásd az alábbi kódot:

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

Ha normál .NET-szótárral dolgozik, hozzáadhat egy kulcsot/értéket a szótárhoz, majd módosíthatja egy tulajdonság (például LastLogin) értékét. Ez a kód azonban nem fog megfelelően működni egy megbízható szótárral. Ne feledje, hogy a korábbi vita során az AddAsync hívása szerializálja a kulcs/érték objektumokat a bájttömbökre, majd menti a tömböket egy helyi fájlba, és elküldi őket a másodlagos replikáknak is. Ha később módosít egy tulajdonságot, az csak a memóriában módosítja a tulajdonság értékét; nincs hatással a helyi fájlra vagy a replikáknak küldött adatokra. Ha a folyamat összeomlik, a memóriában lévő események elvesznek. Amikor egy új folyamat indul el, vagy ha egy másik replika lesz az elsődleges, akkor a régi tulajdonságérték áll rendelkezésre.

Nem tudom eléggé hangsúlyozni, milyen könnyű, hogy a fenti hibát. És csak akkor ismerheti meg a hibát, ha/amikor a folyamat leáll. A kód írásának helyes módja a két sor megfordítása:

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

Íme egy másik példa, amely egy gyakori hibát mutat be:

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

A normál .NET-szótárak esetében a fenti kód is jól működik, és gyakori minta: a fejlesztő egy kulccsal keres egy értéket. Ha az érték létezik, a fejlesztő módosítja egy tulajdonság értékét. A megbízható gyűjtemények esetén azonban ez a kód ugyanazzal a problémával szembesül, mint amit már említettünk: nem kell módosítania egy objektumot, miután megbízható gyűjteménynek adta.

A megbízható gyűjtemények értékeinek frissítésének helyes módja, ha a meglévő értékre mutató hivatkozást kér le, és a hivatkozás által hivatkozott objektumot megváltoztathatatlannak tekinti. Ezután hozzon létre egy új objektumot, amely az eredeti objektum pontos másolata. Most módosíthatja az új objektum állapotát, és beírhatja az új objektumot a gyűjteménybe, hogy szerializálva legyen a bájttömbökre, hozzáfűzve a helyi fájlhoz, és elküldje a replikáknak. A módosítás(ok) véglegesítése után a memóriában lévő objektumok, a helyi fájl és az összes replika azonos állapotú. Minden jó!

Az alábbi kód a megbízható gyűjtemények értékeinek frissítésének helyes módját mutatja be:

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

Nem módosítható adattípusok definiálása a programozói hiba elkerülése érdekében

Ideális esetben azt szeretnénk, hogy a fordító jelentse a hibákat, ha véletlenül olyan kódot hoz létre, amely egy olyan objektum állapotát mutálja, amelyet módosíthatatlannak kell tekinteni. A C#-fordító azonban nem képes erre. Ezért a potenciális programozói hibák elkerülése érdekében javasoljuk, hogy a megbízható gyűjteményekkel használt típusokat módosíthatatlan típusokként határozza meg. Ez azt jelenti, hogy ragaszkodik az alapvető értéktípusokhoz (például számokhoz [Int32, UInt64 stb.], DateTime, Guid, TimeSpan és hasonlók). Sztringet is használhat. A legjobb, ha elkerüli a gyűjtemény tulajdonságait, mivel a szerializálás és a deszerializálás gyakran ronthatja a teljesítményt. Ha azonban gyűjteménytulajdonságokat szeretne használni, erősen javasoljuk a használatát. A NET nem módosítható gyűjtemények könyvtára (System.Collections.Immutable). Ez a kódtár letölthető innen https://nuget.org: . Azt is javasoljuk, hogy zárja be az osztályokat, és a mezőket írásvédetté tegye, amikor csak lehetséges.

Az alábbi UserInfo típus bemutatja, hogyan definiálhat nem módosítható típust, kihasználva a fent említett javaslatokat.

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

Az ItemId típus szintén nem módosítható, ahogy az itt látható:

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

Séma verziószámozása (frissítések)

A Reliable Collections belsőleg szerializálja az objektumokat a . NET DataContractSerializer. A szerializált objektumok megmaradnak az elsődleges replika helyi lemezén, és a másodlagos replikákra is továbbítódnak. Ahogy a szolgáltatás kiforrott, valószínűleg módosítania kell a szolgáltatás által igényelt adattípust (sémát). Nagy gonddal közelítheti meg az adatok verziószámozását. Mindenekelőtt mindig képesnek kell lennie a régi adatok deszerializálására. Ez azt jelenti, hogy a deszerializálási kódnak végtelenül visszamenőlegesen kompatibilisnek kell lennie: a szolgáltatáskód 333-es verziójának képesnek kell lennie arra, hogy a szolgáltatáskód 1. verziójával megbízható gyűjteménybe helyezett adatokon működjön 5 évvel ezelőtt.

A szolgáltatáskód emellett egyszerre egy frissítési tartományt is frissít. A frissítés során tehát a szolgáltatáskód két különböző verziója fut egyszerre. El kell kerülnie, hogy a szolgáltatáskód új verziója használja az új sémát, mivel előfordulhat, hogy a szolgáltatáskód régi verziói nem tudják kezelni az új sémát. Ha lehetséges, a szolgáltatás minden verzióját úgy kell megterveznie, hogy az egy verzióval kompatibilis legyen. Ez azt jelenti, hogy a szolgáltatáskód V1-jének képesnek kell lennie figyelmen kívül hagyni azokat a sémaelemeket, amelyeket nem kezel explicit módon. Azonban képesnek kell lennie arra, hogy mentsen minden olyan adatot, amelyről nem tud explicit módon, és vissza kell írnia őket egy szótárkulcs vagy érték frissítésekor.

Figyelmeztetés:

Bár módosíthatja egy kulcs sémáját, gondoskodnia kell arról, hogy a kulcs egyenlőségi és összehasonlító algoritmusai stabilak legyenek. A megbízható gyűjtemények viselkedése ezen algoritmusok bármelyikének módosítása után nem definiált, és adatsérüléshez, veszteséghez és szolgáltatás összeomlásához vezethet. A .NET-sztringek használhatók kulcsként, de magát a sztringet használják kulcsként – ne használja a String.GetHashCode eredményét kulcsként.

Másik lehetőségként többfázisú frissítést is végrehajthat.

  1. A szolgáltatás frissítése egy új verzióra, amely
    • rendelkezik az eredeti V1-sel és a szolgáltatáskódcsomagban szereplő adatszerződések új V2-es verziójával;
    • szükség esetén regisztrálja az egyéni V2-állapotszerializálókat;
    • Az eredeti, V1-gyűjteményen végzett összes műveletet a V1 adatszerződések használatával hajtja végre.
  2. A szolgáltatás frissítése egy új verzióra, amely
    • létrehoz egy új, V2-gyűjteményt;
    • minden egyes hozzáadási, frissítési és törlési műveletet egyetlen tranzakcióban hajt végre az első V1- és v2-gyűjteményeken;
    • csak a V1-gyűjtemény olvasási műveleteit hajtja végre.
  3. Másolja az összes adatot a V1 gyűjteményből a V2-gyűjteménybe.
    • Ezt egy háttérfolyamatban a 2. lépésben üzembe helyezett szolgáltatásverzió végezheti el.
    • A V1 gyűjtemény összes kulcsának újrapróbálása. A rendszer alapértelmezés szerint az IsolationLevel.Snapshot használatával végzi az enumerálást, hogy elkerülje a gyűjtemény zárolását a művelet időtartama alatt.
    • Minden kulcshoz használjon egy külön tranzakciót a
      • TryGetValueAsync a V1 gyűjteményből.
      • Ha az érték már el lett távolítva a V1 gyűjteményből a másolási folyamat elindítása óta, a kulcsot ki kell hagyni, és nem szabad újraéleszteni a V2-gyűjteményben.
      • Próbálja meg a V2-gyűjteményre szinkronizálni az értéket.
      • Ha az értéket már hozzáadták a V2-gyűjteményhez a másolási folyamat elindítása óta, a kulcsot ki kell hagyni.
      • A tranzakciót csak akkor kell véglegesíteni, ha a TryAddAsync visszaadott érték .true
      • Az értékhozzáférési API-k alapértelmezés szerint a IsolationLevel.ReadRepeatable függvényt használják, és a zárolásra támaszkodva garantálják, hogy az értékeket egy másik hívó csak a tranzakció véglegesítéséig vagy megszakításáig módosítja.
  4. A szolgáltatás frissítése egy új verzióra, amely
    • olvasási műveleteket csak a V2-gyűjteményen hajt végre;
    • továbbra is végrehajtja az egyes hozzáadási, frissítési és törlési műveletet az első V1- és v2-gyűjteményeken, hogy fenntartsa a V1-be való visszagördülés lehetőségét.
  5. A szolgáltatás átfogó tesztelése és annak ellenőrzése, hogy a várt módon működik-e.
    • Ha elmulasztott minden olyan értékhozzáférési műveletet, amely nem lett frissítve a V1 és a V2 gyűjtemény működésére, hiányzó adatokat észlelhet.
    • Ha valamelyik adat hiányzik, térjen vissza az 1. lépéshez, távolítsa el a V2-gyűjteményt, és ismételje meg a folyamatot.
  6. A szolgáltatás frissítése egy új verzióra, amely
    • az összes műveletet csak a V2-gyűjteményen hajtja végre;
    • Az 1. V1-re való visszalépés már nem lehetséges szolgáltatás-visszaállítással, és a 2–4. fordított lépésekkel történő továbblépést igényelné.
  7. A szolgáltatás frissítése olyan új verzióra, amely
  8. Várjon a napló csonkítására.
    • Alapértelmezés szerint ez minden 50 MB-os írási (hozzáadási, frissítési és eltávolítási) művelet esetén megtörténik a megbízható gyűjtemények számára.
  9. A szolgáltatás frissítése egy új verzióra, amely
    • A szolgáltatáskódcsomag már nem tartalmazza a V1 adatszerződéseket.

Következő lépések

A továbbítással kompatibilis adatszerződések létrehozásáról további információt a Továbbítással kompatibilis adatszerződések című témakörben talál .

Az adatszerződések verziószámozásával kapcsolatos ajánlott eljárásokért lásd : Adatszerződések verziószámozása

A verziótűrő adatszerződések implementálásához lásd : Verziótűrő szerializálási visszahívások

Az IExtensibleDataObject című cikkből megtudhatja, hogyan biztosíthat olyan adatstruktúrát, amely több verzióban is képes együttműködni

A megbízható gyűjtemények konfigurálásáról további információt a Replicator Konfiguráció című témakörben talál .