Sdílet prostřednictvím


Migrace z Orleans verze 3.x na 7.0

Orleans 7.0 představuje několik výhodných změn, včetně vylepšení hostování, vlastní serializace, neměnnosti a abstrakce zrnitosti.

Migrace

Stávající aplikace využívající připomenutí, datové proudy nebo trvalost zrnitosti nelze snadno migrovat na Orleans verzi 7.0 kvůli změnám způsobu Orleans identifikace zrn a datových proudů. Plánujeme pro tyto aplikace přírůstkově nabídnout cestu migrace.

Aplikace s předchozími verzemi Orleans nelze hladce upgradovat prostřednictvím postupného upgradu na Orleans verzi 7.0. Proto se musí použít jiná strategie upgradu, například nasazení nového clusteru a vyřazení předchozího clusteru z provozu. Orleans 7.0 změní protokol drátu nekompatibilním způsobem, což znamená, že clustery nemohou obsahovat kombinaci Orleans hostitelů 7.0 a hostitelů s předchozími verzemi Orleans.

Už mnoho let jsme se takovým zásadním změnám vyhnuli, a to i v hlavních verzích, takže proč? Existují dva hlavní důvody: identity a serializace. Pokud jde o identity, odstupňované identity a identity datových proudů se teď skládají z řetězců, což umožňuje správně kódovat informace o obecném typu a umožnit datovým proudům snadněji mapovat do domény aplikace. Typy zrn se dříve identifikovaly pomocí komplexní datové struktury, která nemohla představovat obecná zrnka, což vedlo k rohovým případům. Toky identifikoval string obor názvů a Guid klíč, který vývojářům obtížně namapoval na svoji doménu aplikace, ale efektivně. Serializace je nyní odolné proti verzím, což znamená, že můžete upravit typy určitými kompatibilními způsoby, sledovat sadu pravidel a mít jistotu, že můžete upgradovat aplikaci bez chyb serializace. To bylo obzvláště problematické, když typy aplikací trvaly v datových proudech nebo v úložišti zrnitosti. Následující části podrobně uvádějí hlavní změny a podrobněji je probírají.

Změny balení

Pokud upgradujete projekt na Orleans verzi 7.0, budete muset provést následující akce:

Tip

Orleans Všechny ukázky byly upgradovány na Orleans verzi 7.0 a lze je použít jako referenci na provedené změny. Další informace najdete v tématu Orleans problém č. 8035 , který uvádí změny provedené v každé ukázce.

Orleansglobal using Směrnic

Všechny Orleans projekty buď přímo nebo nepřímo odkazují na Microsoft.Orleans.Sdk balíček NuGet. Orleans Pokud je projekt nakonfigurovaný tak, aby umožňoval implicitní použití (například<ImplicitUsings>enable</ImplicitUsings>), Orleans oba obory názvů se Orleans.Hosting implicitně používají. To znamená, že kód aplikace tyto direktivy nepotřebuje.

Další informace naleznete v tématu ImplicitUsings a dotnet/orleans/src/Orleans. Sdk/build/Microsoft.Orleans. Sdk.targets.

Hostování

Typ ClientBuilder byl nahrazen metodou UseOrleansClient rozšíření na IHostBuilder. Typ IHostBuilder pochází z balíčku NuGet Microsoft.Extensions.Hosting . To znamená, že můžete přidat Orleans klienta do existujícího hostitele, aniž byste museli vytvořit samostatný kontejner injektáže závislostí. Klient se připojí ke clusteru během spuštění. Po IHost.StartAsync dokončení se klient připojí automaticky. Služby přidané do registrace IHostBuilder jsou spuštěny v pořadí registrace, takže volání UseOrleansClient před voláním ConfigureWebHostDefaults zajistí, že Orleans se spustí před spuštěním ASP.NET Core, například umožníte přístup k klientovi z aplikace ASP.NET Core okamžitě.

Pokud chcete emulovat předchozí ClientBuilder chování, můžete vytvořit samostatný HostBuilder a nakonfigurovat ho Orleans pomocí klienta. IHostBuilder může mít nakonfigurovaného Orleans klienta nebo Orleans sila. Všechna sila registrují instanci IGrainFactory a IClusterClient kterou může aplikace používat, takže konfigurace klienta samostatně není nutná a nepodporovaná.

OnActivateAsync a OnDeactivateAsync změna podpisu

Orleans umožňuje provádění kódu během aktivace a deaktivace. Dá se použít k provádění úloh, jako je čtení stavu z úložiště nebo zpráv životního cyklu protokolu. V Orleans 7.0 se podpis těchto metod životního cyklu změnil:

  • OnActivateAsync() teď přijímá CancellationToken parametr. CancellationToken Po zrušení by se měl proces aktivace opustit.
  • OnDeactivateAsync() nyní přijímá DeactivationReason parametr a CancellationToken parametr. Označuje DeactivationReason , proč se aktivace deaktivuje. Očekává se, že vývojáři budou tyto informace používat pro účely protokolování a diagnostiky. CancellationToken Po zrušení by se měl proces deaktivace provést okamžitě. Mějte na paměti, že vzhledem k tomu, že jakýkoli hostitel může kdykoli selhat, nedoporučuje se spoléhat na OnDeactivateAsync provádění důležitých akcí, jako je zachování kritického stavu.

Představte si následující příklad přepsání těchto nových metod:

public sealed class PingGrain : Grain, IPingGrain
{
    private readonly ILogger<PingGrain> _logger;

    public PingGrain(ILogger<PingGrain> logger) =>
        _logger = logger;

    public override Task OnActivateAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("OnActivateAsync()");
        return Task.CompletedTask;
    }

    public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token)
    {
        _logger.LogInformation("OnDeactivateAsync({Reason})", reason);
        return Task.CompletedTask;
    }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

PoCO zrna a IGrainBase

Orleans Zrna už nemusí dědit ze Grain základní třídy ani z jakékoli jiné třídy. Tato funkce se označuje jako zrnka POCO . Přístup k rozšiřujícím metodám, jako je některý z následujících způsobů:

Vaše agregační interval musí buď implementovat IGrainBase , nebo dědit z Grain. Tady je příklad implementace IGrainBase třídy grain:

public sealed class PingGrain : IGrainBase, IPingGrain
{
    public PingGrain(IGrainContext context) => GrainContext = context;

    public IGrainContext GrainContext { get; }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

IGrainBase také definuje OnActivateAsync a OnDeactivateAsync s výchozími implementacemi, což umožňuje, aby se vaše podrobné údaje v případě potřeby účastnily jeho životního cyklu:

public sealed class PingGrain : IGrainBase, IPingGrain
{
    private readonly ILogger<PingGrain> _logger;

    public PingGrain(IGrainContext context, ILogger<PingGrain> logger)
    {
        _logger = logger;
        GrainContext = context;
    }

    public IGrainContext GrainContext { get; }

    public Task OnActivateAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("OnActivateAsync()");
        return Task.CompletedTask;
    }

    public Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token)
    {
        _logger.LogInformation("OnDeactivateAsync({Reason})", reason);
        return Task.CompletedTask;
    }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

Serializace

Nejtěžnější změnou ve verzi Orleans 7.0 je zavedení serializátoru odolného proti verzi. Tato změna byla provedena, protože aplikace se obvykle vyvíjejí a to vedlo k významnému nástrahám pro vývojáře, protože předchozí serializátor nemohl tolerovat přidávání vlastností do existujících typů. Na druhou stranu serializátor byl flexibilní, což vývojářům umožnilo reprezentovat většinu typů .NET bez úprav, včetně funkcí, jako jsou obecné typy, polymorfismus a sledování odkazů. Náhrada byla příliš dlouhá, ale uživatelé stále potřebují vysoce věrnou reprezentaci svých typů. Proto byl zaveden náhradní serializátor v Orleans 7.0, který podporuje vysoce věrnou reprezentaci typů .NET a zároveň umožňuje vyvíjet typy. Nový serializátor je mnohem efektivnější než předchozí serializátor, což vede až k 170% vyšší koncové propustnosti.

Další informace najdete v následujících článcích, které se týkají Orleans verze 7.0:

Identity agregačního intervalu

Každý zrna má jedinečnou identitu, která se skládá z typu zrnka a jeho klíče. Předchozí verze použitého Orleans složeného typu pro GrainIds pro podporu klíčů zrnitosti:

To zahrnuje určitou složitost, pokud jde o práci s klíči odstupňované. Identity agregace se skládají ze dvou součástí: typu a klíče. Součást typu se dříve skládala z číselného kódu typu, kategorie a 3 bajtů obecných informací o typu.

Odstupňované identity teď mají tvar type/key , ve kterém type jsou řetězce i key řetězce. Nejčastěji používané rozhraní klíče zrnitosti IGrainWithStringKeyje . To výrazně zjednodušuje fungování odstupňované identity a zlepšuje podporu obecných typů agregačních typů.

Rozhraní zrnitosti jsou nyní také reprezentována pomocí čitelného názvu člověka místo kombinace kódu hash a řetězcové reprezentace jakýchkoli parametrů obecného typu.

Nový systém je lépe přizpůsobitelný a tato přizpůsobení můžou být řízena atributy.

  • GrainTypeAttribute(String) v odstupňovaném intervalu classurčuje část typu id jeho agregace.
  • DefaultGrainTypeAttribute(String)v agregačním intervalu určuje typ agregaceinterface, který IGrainFactory se má ve výchozím nastavení vyřešit při získání odkazu na agregační interval. Například při volání IGrainFactory.GetGrain<IMyGrain>("my-key"), grain factory vrátí odkaz na zrno "my-type/my-key" , pokud IMyGrain má výše uvedený atribut zadaný.
  • GrainInterfaceTypeAttribute(String) umožňuje přepsání názvu rozhraní. Zadání názvu explicitně pomocí tohoto mechanismu umožňuje přejmenování typu rozhraní bez narušení kompatibility s existujícími odkazy na agregace. Upozorňujeme, že vaše rozhraní by mělo mít AliasAttribute také v tomto případě, protože jeho identita může být serializována. Další informace o zadání aliasu typu najdete v části o serializaci.

Jak je uvedeno výše, přepsání výchozí třídy zrnitosti a názvů rozhraní pro vaše typy umožňuje přejmenovat základní typy bez narušení kompatibility s existujícími nasazeními.

Streamování identit

Při Orleans prvním vydání datových proudů lze datové proudy identifikovat pouze pomocí Guid. To bylo efektivní z hlediska přidělování paměti, ale pro uživatele bylo obtížné vytvářet smysluplné identity datových proudů, často vyžadují určité kódování nebo nepřímé určení vhodné identity streamu pro daný účel.

Ve Orleans verzi 7.0 jsou teď streamy identifikovány pomocí řetězců. Obsahuje Orleans.Runtime.StreamIdstruct tři vlastnosti: a StreamId.NamespaceStreamId.Key, a a StreamId.FullKey. Tyto hodnoty vlastností jsou kódované řetězce UTF-8. Například StreamId.Create(String, String).

Výměna SimpleMessage Toky pomocí BroadcastChannel

SimpleMessageStreams (označuje se také jako SMS) byla odebrána ve verzi 7.0. SMS měla stejné rozhraní jako Orleans.Providers.Streams.PersistentStreams, ale jeho chování bylo velmi odlišné, protože se spoléhalo na přímé volání od zrnitosti. Aby nedošlo k nejasnostem, SMS byla odstraněna a byla zavedena nová výměna Orleans.BroadcastChannel .

BroadcastChannel podporuje pouze implicitní předplatná a v tomto případě může být přímým nahrazením. Pokud potřebujete explicitní předplatná nebo potřebujete použít PersistentStream rozhraní (například jste používali SMS v testech při používání EventHub v produkčním prostředí), pak MemoryStream je pro vás nejlepším kandidátem.

BroadcastChannel bude mít stejné chování jako SMS, zatímco MemoryStream se bude chovat jako ostatní poskytovatelé datových proudů. Představte si následující příklad použití kanálu broadcastu:

// Configuration
builder.AddBroadcastChannel(
    "my-provider",
    options => options.FireAndForgetDelivery = false);

// Publishing
var grainKey = Guid.NewGuid().ToString("N");
var channelId = ChannelId.Create("some-namespace", grainKey);
var stream = provider.GetChannelWriter<int>(channelId);

await stream.Publish(1);
await stream.Publish(2);
await stream.Publish(3);

// Simple implicit subscriber example
[ImplicitChannelSubscription]
public sealed class SimpleSubscriberGrain : Grain, ISubscriberGrain, IOnBroadcastChannelSubscribed
{
    // Called when a subscription is added to the grain
    public Task OnSubscribed(IBroadcastChannelSubscription streamSubscription)
    {
        streamSubscription.Attach<int>(
          item => OnPublished(streamSubscription.ChannelId, item),
          ex => OnError(streamSubscription.ChannelId, ex));

        return Task.CompletedTask;

        // Called when an item is published to the channel
        static Task OnPublished(ChannelId id, int item)
        {
            // Do something
            return Task.CompletedTask;
        }

        // Called when an error occurs
        static Task OnError(ChannelId id, Exception ex)
        {
            // Do something
            return Task.CompletedTask;
        }
    }
}

MemoryStream Migrace bude jednodušší, protože pouze konfigurace se musí změnit. Zvažte následující MemoryStream konfiguraci:

builder.AddMemoryStreams<DefaultMemoryMessageBodySerializer>(
    "in-mem-provider",
    _ =>
    {
        // Number of pulling agent to start.
        // DO NOT CHANGE this value once deployed, if you do rolling deployment
        _.ConfigurePartitioning(partitionCount: 8);
    });

OpenTelemetry

Systém telemetrie byl aktualizován ve verzi Orleans 7.0 a předchozí systém byl odebrán ve prospěch standardizovaných rozhraní .NET API, jako jsou metriky .NET pro metriky a ActivitySource trasování.

V rámci této funkce byly stávající Microsoft.Orleans.TelemetryConsumers.* balíčky odebrány. Zvažujeme zavedení nové sady balíčků, abychom zjednodušili proces integrace metrik vygenerovaných Orleans do zvoleného řešení monitorování. Jako vždy vítáme zpětnou vazbu a příspěvky.

Nástroj dotnet-counters nabízí monitorování výkonu pro monitorování stavu ad hoc a prošetření výkonu na první úrovni. U Orleans čítačů lze nástroj dotnet-counters použít k jejich monitorování:

dotnet counters monitor -n MyApp --counters Microsoft.Orleans

Podobně metriky OpenTelemetry můžou přidat Microsoft.Orleans měřiče, jak je znázorněno v následujícím kódu:

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics
        .AddPrometheusExporter()
        .AddMeter("Microsoft.Orleans"));

Pokud chcete povolit distribuované trasování, nakonfigurujete OpenTelemetry, jak je znázorněno v následujícím kódu:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.SetResourceBuilder(ResourceBuilder.CreateDefault()
            .AddService(serviceName: "ExampleService", serviceVersion: "1.0"));

        tracing.AddAspNetCoreInstrumentation();
        tracing.AddSource("Microsoft.Orleans.Runtime");
        tracing.AddSource("Microsoft.Orleans.Application");

        tracing.AddZipkinExporter(options =>
        {
            options.Endpoint = new Uri("http://localhost:9411/api/v2/spans");
        });
    });

V předchozím kódu je OpenTelemetry nakonfigurovaná tak, aby monitorovala:

  • Microsoft.Orleans.Runtime
  • Microsoft.Orleans.Application

Pokud chcete aktivitu rozšířit, zavolejte AddActivityPropagation:

builder.Host.UseOrleans((_, clientBuilder) =>
{
    clientBuilder.AddActivityPropagation();
});

Refaktoring funkcí ze základního balíčku do samostatných balíčků

V Orleans 7.0 jsme se snažili zohlednit rozšíření do samostatných balíčků, které se nespoléhá na Orleans.Core. Konkrétně , Orleans.Streaminga Orleans.RemindersOrleans.Transactions byly odděleny od jádra. To znamená, že tyto balíčky jsou zcela platit za to, co používáte, a žádný kód v jádru Orleans je vyhrazený pro tyto funkce. Tím se zklidní plocha a velikost základního rozhraní API, zjednoduší se jádro a zlepší výkon. Pokud jde o výkon, transakce v Orleans minulosti vyžadovaly nějaký kód, který byl proveden pro každou metodu ke koordinaci potenciálních transakcí. To bylo od té doby přesunuto na jednotlivé metody.

Jedná se o změnu způsobující kompilaci. Možná máte existující kód, který komunikuje s připomenutími nebo datovými proudy voláním metod, které byly dříve definovány v Grain základní třídě, ale nyní jsou rozšiřující metody. Taková volání, která nezadávají this (například GetReminders) budou muset být aktualizována tak, aby zahrnovala this (například this.GetReminders()), protože metody rozšíření musí být kvalifikované. Pokud tato volání neaktualizujete, dojde k chybě kompilace a požadovaná změna kódu nemusí být zřejmé, pokud nevíte, co se změnilo.

Klient transakcí

Orleans7.0 zavádí novou abstrakci pro koordinaci transakcí , Orleans.ITransactionClient Dříve mohly být transakce koordinovány pouze zrny. S ITransactionClient, který je k dispozici prostřednictvím injektáže závislostí, klienti mohou také koordinovat transakce bez nutnosti zprostředkujícího agregace. Následující příklad stáhne kredity z jednoho účtu a uloží je do jiné v rámci jedné transakce. Tento kód lze volat z agregačního intervalu nebo z externího klienta, který načetl ITransactionClient kontejner injektáž závislostí.

await transactionClient.RunTransaction(
  TransactionOption.Create,
  () => Task.WhenAll(from.Withdraw(100), to.Deposit(100)));

V případě transakcí koordinovaných klientem musí klient během konfigurace přidat požadované služby:

clientBuilder.UseTransactions();

Ukázka BankAccount ukazuje použití ITransactionClient. Další informace naleznete v tématu Orleans transakce.

Opětovné zařazení řetězu volání

Zrnka jsou ve výchozím nastavení jednovláknová a zpracovávají jeden po druhém. Jinými slovy, zrnka nejsou ve výchozím nastavení znovu prováděná. ReentrantAttribute Přidání do třídy zrnitosti umožňuje souběžné zpracování více požadavků, a to v prokládání, zatímco stále je jednovláknové. To může být užitečné pro zrnka, která neobsahují žádný vnitřní stav nebo provádějí velké množství asynchronních operací, jako je vydávání volání HTTP nebo zápis do databáze. Při prokládání požadavků je potřeba věnovat zvláštní pozornost: je možné, že stav agregace je pozorován před await změnou příkazu v době, kdy se asynchronní operace dokončí, a metoda pokračuje v provádění.

Například následující agregační interval představuje čítač. Bylo označeno Reentrant, což umožňuje více volání prokládání. Metoda Increment() by měla zvýšit vnitřní čítač a vrátit pozorovanou hodnotu. Vzhledem k tomu, že Increment() tělo metody sleduje stav zrnitosti před await bodem a následně ho aktualizuje, je možné, že více prokládání provádění Increment() může mít za následek _value menší než celkový počet přijatých Increment() volání. Jedná se o chybu, kterou představuje nesprávné použití opětovného použití.

ReentrantAttribute Odstranění problému stačí k vyřešení problému.

[Reentrant]
public sealed class CounterGrain : Grain, ICounterGrain
{
    int _value;
    
    /// <summary>
    /// Increments the grain's value and returns the previous value.
    /// </summary>
    public Task<int> Increment()
    {
        // Do not copy this code, it contains an error.
        var currentVal = _value;
        await Task.Delay(TimeSpan.FromMilliseconds(1_000));
        _value = currentVal + 1;
        return currentValue;
    }
}

Chcete-li těmto chybám zabránit, nejsou zrnka ve výchozím nastavení znovu prováděná. Nevýhodou je snížení propustnosti zrna, která provádějí asynchronní operace v jejich implementaci, protože jiné požadavky nelze zpracovat, zatímco agregační interval čeká na dokončení asynchronní operace. Chcete-li to zmírnit, Orleans nabízí několik možností, jak v některých případech umožnit opakované zařazení:

  • Pro celou třídu: umístění ReentrantAttribute na zrno umožňuje jakékoli žádosti o prokládání s jakýmkoli jiným požadavkem.
  • Pro podmnožinu metod: umístění AlwaysInterleaveAttribute metody podrobného rozhraní umožňuje požadavkům této metody prokládání s jakýmkoli jiným požadavkem a pro požadavky na danou metodu prokládání jinými požadavky.
  • Pro podmnožinu metod: umístění ReadOnlyAttribute metody podrobného rozhraní umožňuje požadavkům této metody prokládání s jakýmkoli jiným ReadOnly požadavkem a pro požadavky na danou metodu prokládání jinými ReadOnly požadavky. V tomto smyslu je to omezenější forma AlwaysInterleave.
  • Pro všechny žádosti v rámci řetězce volání: RequestContext.AllowCallChainReentrancy() a <xref:Orleans. Runtime.RequestContext.SuppressCallChainReentrancy?displayProperty=nameWithType umožňuje výslovný souhlas a odhlášení, aby podřízené požadavky mohly znovu zadávat zpět do agregace. Obě volání vrátí hodnotu, která musí být uvolněna při ukončení požadavku. Správné použití je proto následující:
public Task<int> OuterCall(IMyGrain other)
{
    // Allow call-chain reentrancy for this grain, for the duration of the method.
    using var _ = RequestContext.AllowCallChainReentrancy();
    await other.CallMeBack(this.AsReference<IMyGrain>());
}

public Task CallMeBack(IMyGrain grain)
{
    // Because OuterCall allowed reentrancy back into that grain, this method 
    // will be able to call grain.InnerCall() without deadlocking.
    await grain.InnerCall();
}

public Task InnerCall() => Task.CompletedTask;

Opětovná architektura řetězu volání musí být opted-in per-grain, per-call-chain. Představte si například dvě zrnka, zrno A a B. Pokud agregační interval A umožňuje znovu zavolat řetěz volání před voláním agregační agregační interval B, může v daném volání zavolat zpět do agregační hodnoty A. Agregační agregační interval A však nemůže volat zpět do agregační hodnoty B, pokud agregační interval B také nepovolil převázání řetězu volání. Jedná se o agregační řetězec podle volání.

Zrna mohou také potlačit informace o zpětném řetězu volání od toku z řetězu volání pomocí using var _ = RequestContext.SuppressCallChainReentrancy(). Tím se zabrání opětovnému zavádění následných volání.

skripty migrace ADO.NET

Abyste zajistili kompatibilitu s clusteringem Orleans , trvalostmi a připomenutími, která se spoléhají na ADO.NET, budete potřebovat příslušný skript migrace SQL:

Vyberte soubory pro použitou databázi a použijte je v pořadí.