Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Existují široce dva druhy serializace používané v Orleans:
- Serializace zrnitého volání: Slouží k serializaci objektů předaných a z zrn.
- Serializace zrnitého úložiště: Slouží k serializaci objektů do a ze systémů úložiště.
Většina tohoto článku se zaměřuje na serializaci odstupňované volání prostřednictvím rozhraní serializace, který je součástí Orleans. Část serializátorů úložiště obilí popisuje serializaci obilného úložiště.
Použití Orleans serializace
Orleans zahrnuje pokročilou a rozšiřitelnou serializační architekturu, která se označuje jako Orleans. Serializace. Architektura serializace, která Orleans je součástí, je navržena tak, aby splňovala následující cíle:
- Vysoký výkon: Serializátor je navržený a optimalizovaný pro výkon. Další podrobnosti jsou k dispozici v této prezentaci.
- Vysoká věrnost: Serializátor věrně představuje většinu typu systémů .NET, včetně podpory obecných typů, polymorfismu, hierarchií dědičnosti, identity objektů a cyklických grafů. Ukazatele nejsou podporované, protože nejsou přenositelné mezi procesy.
- Flexibilita: Serializátor můžete přizpůsobit tak, aby podporoval knihovny třetích stran vytvořením náhradních dotazů nebo delegováním do externích serializačních knihoven, jako jsou System.Text.Json, Newtonsoft.Json a Google.Protobuf.
-
Tolerance verzí: Serializátor umožňuje, aby se typy aplikací v průběhu času vyvíjely a podporovaly:
- Přidávání a odebírání členů
- Podtřídy
- Číselné rozšíření a zužování (např.
intna/zlong,floatna/zdouble) - Přejmenování typů
Reprezentace typů s vysokou věrností je pro serializátory poměrně neobvyklá, takže některé body vyžadují další vysvětlení:
Dynamické typy a libovolný polymorfismus: Orleans nevynucuje omezení na typy předávané při volání objektů grain a zachovává dynamickou povahu skutečného datového typu. To znamená, že pokud je například metoda v rozhraní zrní deklarována tak, aby přijímala IDictionary, ale za běhu odesílatel předá SortedDictionary<TKey,TValue>, příjemce skutečně získá
SortedDictionary(i když "statické kontrakt"/grain rozhraní neurčilo toto chování).Údržba identity objektu: Pokud je stejný objekt předán vícekrát v argumentech volání agregace nebo nepřímo odkazuje na více než jednou z argumentů, Orleans serializuje ho pouze jednou. Na straně příjemce Orleans obnoví všechny odkazy správně, takže dva ukazatele na stejný objekt stále odkazují na stejný objekt po deserializaci. Zachování identity objektu je důležité ve scénářích, jako je následující: Představte si, že částice A odesílá slovník se 100 položkami do částice B a 10 klíčů ve slovníku odkazuje na stejný objekt
objna straně A. Bez zachování identity objektu by B obdržel slovník 100 položek s těmito 10 klíči ukazující na 10 různých klonůobj. Když je identita objektu zachovaná, slovník na straně B vypadá přesně stejně jako na straně A s těmito 10 klíči ukazující na jeden objektobj. Všimněte si, že vzhledem k tomu, že implementace výchozího kódu hash řetězců v .NET jsou náhodné pro jednotlivé procesy, nemusí se pořadí hodnot ve slovníkech a sadách hodnot hash (například) zachovat.
Aby bylo možné podporovat toleranci verzí, serializátor vyžaduje explicitní určení typů a členů, které jsou serializovány. Snažili jsme se to udělat co nejméně bolestivé. Označte všechny serializovatelné typy pomocí Orleans.GenerateSerializerAttribute, aby se Orleans mohl vygenerovat kód serializátoru pro váš typ. Jakmile to uděláte, můžete pomocí zahrnuté opravy kódu přidat požadované Orleans.IdAttribute do serializovatelných členů vašich typů, jak je zde znázorněno:
Zde je příklad serializovatelného typu , Orleansdemonstrující, jak použít atributy.
[GenerateSerializer]
public class Employee
{
[Id(0)]
public string Name { get; set; }
}
Orleans podporuje dědičnost a serializuje jednotlivé vrstvy v hierarchii samostatně, což jim umožňuje mít jedinečné ID členů.
[GenerateSerializer]
public class Publication
{
[Id(0)]
public string Title { get; set; }
}
[GenerateSerializer]
public class Book : Publication
{
[Id(0)]
public string ISBN { get; set; }
}
V předchozím kódu si všimněte, že oba Publication a Book mají členy s [Id(0)], i když Book je odvozen od Publication. Toto je doporučený postup, Orleans protože identifikátory členů jsou vymezeny na úroveň dědičnosti, nikoli na typ jako celek. Členy můžete přidávat a odebírat jak z Publication, tak z Book nezávisle, ale po nasazení aplikace není možné do hierarchie vložit novou základní třídu bez zvláštního uvážení.
Orleans také podporuje serializaci typů s internal, privatea readonly členy, například v tomto příkladu typ:
[GenerateSerializer]
public struct MyCustomStruct
{
public MyCustom(int intProperty, int intField)
{
IntProperty = intProperty;
_intField = intField;
}
[Id(0)]
public int IntProperty { get; }
[Id(1)] private readonly int _intField;
public int GetIntField() => _intField;
public override string ToString() => $"{nameof(_intField)}: {_intField}, {nameof(IntProperty)}: {IntProperty}";
}
Ve výchozím nastavení Orleans serializuje váš typ pomocí zakódování jeho celého názvu. Můžete to přepsat přidáním .Orleans.AliasAttribute Výsledkem je serializace typu pomocí názvu odolného proti přejmenování základní třídy nebo jeho přesunutí mezi sestaveními. Aliasy typu jsou globálně vymezeny a v aplikaci nemůžete mít dva aliasy se stejnou hodnotou. V případě obecných typů musí hodnota aliasu obsahovat počet obecných parametrů předcházených zpětným apostrofem; například MyGenericType<T, U> může mít alias [Alias("mytype`2")].
Serializace record typů
Členy definované v primárním konstruktoru záznamu mají ve výchozím nastavení implicitní ID. Jinými slovy, Orleans podporuje serializaci record typů. To znamená, že nemůžete změnit pořadí parametrů pro již nasazený typ, protože tím dojde k narušení kompatibility s předchozími verzemi aplikace (ve scénáři postupného upgradu) a serializovanými instancemi tohoto typu v úložišti a datových proudech. Členové definované v těle typu záznamu nesdílí identity s primárními parametry konstruktoru.
[GenerateSerializer]
public record MyRecord(string A, string B)
{
// ID 0 won't clash with A in primary constructor as they don't share identities
[Id(0)]
public string C { get; init; }
}
Pokud nechcete, aby parametry primárního konstruktoru byly automaticky zahrnuty jako serializovatelná pole, použijte [GenerateSerializer(IncludePrimaryConstructorParameters = false)].
Náhradník pro serializaci cizích typů
Někdy může být potřeba předat typy mezi zrny, u kterých nemáte úplnou kontrolu. V těchto případech může být ruční převod na a z vlastního definovaného typu v kódu aplikace nepraktický. Orleans nabízí řešení pro tyto situace: náhradní typy. Náhradní náhražky jsou serializovány místo jejich cílového typu a mají funkce pro převod na cílový typ a z cílového typu. Představte si následující příklad cizího typu a odpovídajícího náhradního a převaděče:
// This is the foreign type, which you do not have control over.
public struct MyForeignLibraryValueType
{
public MyForeignLibraryValueType(int num, string str, DateTimeOffset dto)
{
Num = num;
String = str;
DateTimeOffset = dto;
}
public int Num { get; }
public string String { get; }
public DateTimeOffset DateTimeOffset { get; }
}
// This is the surrogate which will act as a stand-in for the foreign type.
// Surrogates should use plain fields instead of properties for better performance.
[GenerateSerializer]
public struct MyForeignLibraryValueTypeSurrogate
{
[Id(0)]
public int Num;
[Id(1)]
public string String;
[Id(2)]
public DateTimeOffset DateTimeOffset;
}
// This is a converter that converts between the surrogate and the foreign type.
[RegisterConverter]
public sealed class MyForeignLibraryValueTypeSurrogateConverter :
IConverter<MyForeignLibraryValueType, MyForeignLibraryValueTypeSurrogate>
{
public MyForeignLibraryValueType ConvertFromSurrogate(
in MyForeignLibraryValueTypeSurrogate surrogate) =>
new(surrogate.Num, surrogate.String, surrogate.DateTimeOffset);
public MyForeignLibraryValueTypeSurrogate ConvertToSurrogate(
in MyForeignLibraryValueType value) =>
new()
{
Num = value.Num,
String = value.String,
DateTimeOffset = value.DateTimeOffset
};
}
V předchozím kódu:
-
MyForeignLibraryValueTypeje typ mimo vaši kontrolu, definovaný v klientské knihovně. -
MyForeignLibraryValueTypeSurrogateje mapovací typ náhradníka proMyForeignLibraryValueType. -
RegisterConverterAttribute určuje, že
MyForeignLibraryValueTypeSurrogateConverterfunguje jako převaděč pro mapování mezi těmito dvěma typy. Třída implementuje IConverter<TValue,TSurrogate> rozhraní.
Orleans podporuje serializaci typů v hierarchiích typů (typy odvozené z jiných typů). Pokud se cizí typ může objevit v hierarchii typů (např. jako základní třída pro jeden z vašich vlastních typů), musíte rozhraní navíc implementovat Orleans.IPopulator<TValue,TSurrogate> . Představte si následující příklad:
// The foreign type is not sealed, allowing other types to inherit from it.
public class MyForeignLibraryType
{
public MyForeignLibraryType() { }
public MyForeignLibraryType(int num, string str, DateTimeOffset dto)
{
Num = num;
String = str;
DateTimeOffset = dto;
}
public int Num { get; set; }
public string String { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
// The surrogate is defined as it was in the previous example.
[GenerateSerializer]
public struct MyForeignLibraryTypeSurrogate
{
[Id(0)]
public int Num;
[Id(1)]
public string String;
[Id(2)]
public DateTimeOffset DateTimeOffset;
}
// Implement the IConverter and IPopulator interfaces on the converter.
[RegisterConverter]
public sealed class MyForeignLibraryTypeSurrogateConverter :
IConverter<MyForeignLibraryType, MyForeignLibraryTypeSurrogate>,
IPopulator<MyForeignLibraryType, MyForeignLibraryTypeSurrogate>
{
public MyForeignLibraryType ConvertFromSurrogate(
in MyForeignLibraryTypeSurrogate surrogate) =>
new(surrogate.Num, surrogate.String, surrogate.DateTimeOffset);
public MyForeignLibraryTypeSurrogate ConvertToSurrogate(
in MyForeignLibraryType value) =>
new()
{
Num = value.Num,
String = value.String,
DateTimeOffset = value.DateTimeOffset
};
public void Populate(
in MyForeignLibraryTypeSurrogate surrogate, MyForeignLibraryType value)
{
value.Num = surrogate.Num;
value.String = surrogate.String;
value.DateTimeOffset = surrogate.DateTimeOffset;
}
}
// Application types can inherit from the foreign type, assuming they're not sealed
// since Orleans knows how to serialize it.
[GenerateSerializer]
public sealed class DerivedFromMyForeignLibraryType : MyForeignLibraryType
{
public DerivedFromMyForeignLibraryType() { }
public DerivedFromMyForeignLibraryType(
int intValue, int num, string str, DateTimeOffset dto) : base(num, str, dto)
{
IntValue = intValue;
}
[Id(0)]
public int IntValue { get; set; }
}
Pravidla správy verzí
Tolerance verzí se podporuje, pokud při úpravách typů dodržujete sadu pravidel. Pokud znáte systémy, jako jsou Vyrovnávací paměti protokolu Google (Protobuf), budou tato pravidla známá.
Složené typy (class & struct)
- Dědičnost se podporuje, ale změna hierarchie dědičnosti objektu není podporovaná. Základní třídu třídy nelze přidat, změnit ani odebrat.
- S výjimkou některých číselných typů popsaných v části Numerics níže nelze změnit typy polí.
- Pole můžete přidat nebo odebrat v libovolném bodě v hierarchii dědičnosti.
- ID polí nelze změnit.
- ID polí musí být jedinečná pro každou úroveň v hierarchii typů, ale je možné je znovu použít mezi základními třídami a podtřídami. Třída může například
Basedeklarovat pole s ID0aSub : Basetřída může deklarovat jiné pole se stejným ID,0.
Numerické výpočty
-
Podpis číselného pole nelze změnit.
- Převody mezi
int&uintjsou neplatné.
- Převody mezi
- Šířku číselného pole můžete změnit.
- Podporují se například převody z
intdolongneboulongdoushort. - Převody, které zmenší šířku, vyvolají výjimku, pokud hodnota pole za běhu dojde k přetečení.
- Převod z
ulongnaushortje podporován pouze v případě, že hodnota modulu runtime je menší nežushort.MaxValue. - Převody z
doublenafloatjsou podporovány pouze v případě, že je hodnota modulu runtime mezifloat.MinValueafloat.MaxValue. - Podobně pro
decimal, který má užší rozsah než obadoubleafloat.
- Podporují se například převody z
Kopírky
Orleans podporuje bezpečnost ve výchozím nastavení, včetně ochrany před některými typy chyb souběžnosti. Konkrétně Orleans okamžitě kopíruje objekty předané voláním grainů ve výchozím nastavení. Orleans. Serializace usnadňuje kopírování. Když použijete Orleans.CodeGeneration.GenerateSerializerAttribute na typ, Orleans také vygeneruje kopírovací funkce pro tento typ. Orleans zabraňuje kopírování typů nebo jednotlivých členů označených pomocí ImmutableAttribute. Další podrobnosti naleznete v tématu Serializace neměnných typů v Orleans.
Osvědčené postupy serializace
✅ Udělte svým typům aliasy pomocí atributu
[Alias("my-type")]. Typy s aliasy je možné přejmenovat bez narušení kompatibility.❌ Neměňte
recordběžnouclassnebo naopak. Záznamy a třídy nejsou reprezentovány identicky, protože záznamy mají primární konstruktory kromě běžných členů; proto nejsou zaměnitelné.❌ Nepřidávejte nové typy do existující hierarchie typů pro serializovatelný typ. Do existujícího typu nesmíte přidat novou základní třídu. Do existujícího typu můžete bezpečně přidat novou podtřídu.
✅ Nahraďte použití SerializableAttribute odpovídajícími GenerateSerializerAttribute deklaracemiIdAttribute.
✅ Pro každý typ začněte všechna ID členů od nuly. ID v podtřídě a její základní třídě se mohou bezpečně překrývat. Obě vlastnosti v následujícím příkladu mají ID rovna
0.[GenerateSerializer] public sealed class MyBaseClass { [Id(0)] public int MyBaseInt { get; set; } } [GenerateSerializer] public sealed class MySubClass : MyBaseClass { [Id(0)] public int MyBaseInt { get; set; } }✅ Podle potřeby rozšiřte číselné typy členů. Můžete rozšířit
sbytenashortintlong.- Typy číselných členů můžete zúžit, ale výsledkem je výjimka za běhu, pokud pozorované hodnoty nelze správně reprezentovat zúženým typem. Například
int.MaxValuenelze reprezentovat polemshort, takže zúžení poleintnashortmůže vést k běhové výjimce, pokud je zjištěna taková hodnota.
- Typy číselných členů můžete zúžit, ale výsledkem je výjimka za běhu, pokud pozorované hodnoty nelze správně reprezentovat zúženým typem. Například
❌ Neměňte signičnost člena číselného typu. Typ člena nesmíte změnit například na
uintintnebointnauint.
Serializátory úložiště zrnitosti
Orleans zahrnuje model trvalosti založené na poskytovateli pro zrnka, který je přístupný prostřednictvím State vlastnosti, nebo vložením jedné nebo více IPersistentState<TState> hodnot do agregace. Před Orleans verzí 7.0 měl každý poskytovatel jiný mechanismus konfigurace serializace. Ve Orleans verzi 7.0 je nyní rozhraní serializátoru stavu pro všestranné použití, IGrainStorageSerializer, které nabízí konzistentní způsob přizpůsobení serializace stavu pro každého zprostředkovatele. Podporovaní poskytovatelé úložiště implementují model zahrnující nastavení IStorageProviderSerializerOptions.GrainStorageSerializer vlastnosti ve třídě možností poskytovatele, například:
- DynamoDBStorageOptions.GrainStorageSerializer
- AzureBlobStorageOptions.GrainStorageSerializer
- AzureTableStorageOptions.GrainStorageSerializer
- GrainStorageSerializer
Serializace úložiště agregačních hodnot se v současné době standardně Newtonsoft.Json serializuje do stavu serializace. Tuto vlastnost můžete nahradit úpravou této vlastnosti v době konfigurace. Následující příklad ukazuje použití OptionsBuilder<TOptions>:
siloBuilder.AddAzureBlobGrainStorage(
"MyGrainStorage",
(OptionsBuilder<AzureBlobStorageOptions> optionsBuilder) =>
{
optionsBuilder.Configure<IMySerializer>(
(options, serializer) => options.GrainStorageSerializer = serializer);
});
Další informace naleznete v tématu OptionsBuilder API.
Orleans má pokročilou a rozšiřitelnou architekturu serializace. Orleans serializuje datové typy předané v žádostech a odpovědích zpráv typu grain, stejně jako přetrvávající stavové objekty typu grain. V rámci této architektury Orleans automaticky generuje kód serializace pro tyto datové typy. Kromě generování efektivnější serializace/deserializace pro typy, které jsou již .NET-serializovatelné, se Orleans také snaží generovat serializátory pro typy používané v rozhraní grainů, které nejsou .NET-serializovatelné. Tato architektura obsahuje také sadu efektivních předdefinovaných serializátorů pro často používané typy: seznamy, slovníky, řetězce, primitivy, pole atd.
Dvě důležité vlastnosti serializátoru Orleans ho odlišují od mnoha dalších systémů serializace třetích stran: dynamické typy/libovolný polymorfismus a identita objektu.
Dynamické typy a libovolný polymorfismus: Orleans nevynucuje omezení na typy předávané při volání objektů grain a zachovává dynamickou povahu skutečného datového typu. To znamená, že pokud je například metoda v rozhraní zrní deklarována tak, aby přijímala IDictionary, ale za běhu odesílatel předá SortedDictionary<TKey,TValue>, příjemce skutečně získá
SortedDictionary(i když "statické kontrakt"/grain rozhraní neurčilo toto chování).Údržba identity objektu: Pokud je stejný objekt předán vícekrát v argumentech volání agregace nebo nepřímo odkazuje na více než jednou z argumentů, Orleans serializuje ho pouze jednou. Na straně příjemce Orleans obnoví všechny odkazy správně, takže dva ukazatele na stejný objekt stále odkazují na stejný objekt po deserializaci. Zachování identity objektu je důležité ve scénářích, jako je následující: Představte si, že částice A odesílá slovník se 100 položkami do částice B a 10 klíčů ve slovníku odkazuje na stejný objekt
objna straně A. Bez zachování identity objektu by B obdržel slovník 100 položek s těmito 10 klíči ukazující na 10 různých klonůobj. Když je identita objektu zachovaná, slovník na straně B vypadá přesně stejně jako na straně A s těmito 10 klíči ukazující na jeden objektobj.
Standardní binární serializátor .NET poskytuje výše uvedené dvě chování, takže bylo pro nás důležité podporovat i toto standardní a známé chování Orleans .
Generované serializátory
Orleans používá následující pravidla k rozhodnutí, jaké serializátory generovat.
- Prohledejte všechny typy ve všech sestaveních odkazujících na základní Orleans knihovnu.
- Z těchto sestavení vygenerujte serializátory pro typy přímo odkazované v podpisech metod grainového rozhraní, v podpisech pro stavové třídy, nebo pro jakýkoli typ označený SerializableAttribute.
- Kromě toho může projekt rozhraní grain nebo implementace odkazovat na libovolné typy pro generování serializace přidáním atributů na úrovni sestavení KnownTypeAttribute nebo KnownAssemblyAttribute. Tyto informace říkají generátoru kódu, aby vygeneroval serializátory pro konkrétní typy nebo všechny způsobilé typy v rámci sestavení. Další informace o atributech na úrovni sestavení naleznete v tématu Použití atributů na úrovni sestavení.
Náhradní serializace
Orleans podporuje přenos libovolných typů za běhu. Proto integrovaný generátor kódu nemůže předem určit celou sadu typů, které se budou přenášet předem. Kromě toho některé typy nemohou mít serializátory vygenerované pro ně, protože jsou nepřístupné (např private. ) nebo mají nepřístupná pole (např readonly. ). Proto je potřeba serializace typů v reálném čase, které byly neočekávané nebo pro něž nemohly být serializátory vygenerovány předem. Serializátor zodpovědný za tyto typy se nazývá náhradní serializátor.
Orleans dodává se dvěma náhradními serializátory:
- Orleans.Serialization.BinaryFormatterSerializer, který používá . net ; BinaryFormattera
-
Orleans.Serialization.ILBasedSerializer, který generuje instrukce CIL za běhu k vytvoření serializátorů, které využívají Orleans' serializace framework k serializaci každé pole. To znamená, že pokud nepřístupný typ
MyPrivateTypeobsahuje poleMyType, které má vlastní serializátor, tento vlastní serializátor se použije k serializaci.
Nakonfigurujte náhradní serializátor pomocí vlastnosti FallbackSerializationProvider jak na ClientConfiguration (klientovi), tak na GlobalConfiguration (úložištích).
// Client configuration
var clientConfiguration = new ClientConfiguration();
clientConfiguration.FallbackSerializationProvider =
typeof(FantasticSerializer).GetTypeInfo();
// Global configuration
var globalConfiguration = new GlobalConfiguration();
globalConfiguration.FallbackSerializationProvider =
typeof(FantasticSerializer).GetTypeInfo();
Alternativně zadejte náhradního zprostředkovatele serializace v konfiguraci XML:
<Messaging>
<FallbackSerializationProvider
Type="GreatCompany.FantasticFallbackSerializer, GreatCompany.SerializerAssembly"/>
</Messaging>
Jedná se BinaryFormatterSerializer o výchozí záložní serializátor.
Upozorňující
Binární serializace s BinaryFormatter může být nebezpečná. Další informace najdete v průvodci zabezpečením BinaryFormatter a průvodce migrací BinaryFormatter.
Serializace výjimek
Výjimky jsou serializovány pomocí náhradní serializátoru. Ve výchozí konfiguraci BinaryFormatter je záložní serializátor. Proto je nutné postupovat podle vzoru ISerializable, aby byla zajištěna správná serializace všech vlastností v typu výjimky.
Tady je příklad typu výjimky s správně implementovanou serializací:
[Serializable]
public class MyCustomException : Exception
{
public string MyProperty { get; }
public MyCustomException(string myProperty, string message)
: base(message)
{
MyProperty = myProperty;
}
public MyCustomException(string transactionId, string message, Exception innerException)
: base(message, innerException)
{
MyProperty = transactionId;
}
// Note: This is the constructor called by BinaryFormatter during deserialization
public MyCustomException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
MyProperty = info.GetString(nameof(MyProperty));
}
// Note: This method is called by BinaryFormatter during serialization
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(nameof(MyProperty), MyProperty);
}
}
Osvědčené postupy serializace
Serializace slouží dvěma primárním účelům v Orleans:
- Jako formát drátu pro přenos dat mezi zrnky a klienty za běhu.
- Jako formát úložiště pro uchování dlouhodobých dat pro pozdější načtení.
Serializátory generované Orleans jsou vhodné pro první účel z důvodu jejich flexibility, výkonu a všestrannosti. Nejsou tak vhodné pro druhý účel, protože nejsou explicitně odolné vůči verzím. Pro trvalá data doporučujeme nakonfigurovat serializátor odolný proti verzím, jako jsou vyrovnávací paměti protokolu. Vyrovnávací paměti protokolu jsou podporovány prostřednictvím Orleans.Serialization.ProtobufSerializer microsoft.Orleans. Balíček NuGet OrleansGoogleUtils. Postupujte podle osvědčených postupů pro zvolený serializátor, abyste zajistili odolnost proti verzi. Nakonfigurujte serializátory třetích stran pomocí SerializationProviders vlastnosti konfigurace, jak je popsáno výše.