Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Orleans O 7.0 introduz várias alterações benéficas, incluindo melhorias na hospedagem, serialização personalizada, imutabilidade e abstrações de grãos.
Migração
Devido a alterações na forma como Orleans identifica grãos e fluxos, migrar aplicativos existentes usando lembretes, fluxos ou persistência de grãos para Orleans a versão 7.0 não é fácil atualmente.
Não é possível atualizar sem problemas aplicativos que executam versões anteriores Orleans por meio de uma atualização contínua para Orleans a versão 7.0. Portanto, use uma estratégia de atualização diferente, como implantar um novo cluster e desativar o anterior. Orleans 7.0 altera o protocolo wire de forma incompatível, o que significa que os clusters não podem conter uma mistura de Orleans hosts 7.0 e hosts executando versões anteriores Orleans .
Tais alterações significativas foram evitadas por muitos anos, mesmo em lançamentos principais. Porquê agora? Há duas razões principais: identidades e serialização. Em relação às identidades, as identidades de grain e stream agora consistem em strings. Isso permite que os grains codifiquem informações de tipos genéricos corretamente e facilita o mapeamento de fluxos para o domínio da aplicação. Anteriormente, Orleans identificava tipos de grãos usando uma estrutura de dados complexa que não podia representar grãos genéricos, levando a casos extremos. Os fluxos foram identificados por um string namespace e uma Guid chave, que era eficiente, mas difícil de mapear para o domínio do aplicativo. A serialização agora é tolerante a versões. Isso significa que os tipos podem ser modificados de determinadas maneiras compatíveis, seguindo um conjunto de regras, com a confiança de que o aplicativo pode ser atualizado sem erros de serialização. Esta capacidade é especialmente útil quando os tipos de aplicações persistem em fluxos ou armazenamento de grãos. As seções a seguir detalham as principais mudanças e as discutem mais adiante.
Alterações de embalagem
Ao atualizar um projeto para Orleans 7.0, execute as seguintes ações:
- Todos os clientes devem fazer referência à Microsoft.Orleans. Cliente.
- Todos os silos (servidores) devem fazer referência a Microsoft.Orleans. Servidor.
- Todos os outros pacotes devem fazer referência à Microsoft.Orleans. Sdk.
- Os pacotes de cliente e servidor incluem uma referência à Microsoft.Orleans. Sdk.
- Remova todas as referências a
Microsoft.Orleans.CodeGenerator.MSBuildeMicrosoft.Orleans.OrleansCodeGenerator.Build.- Substitua os usos de
KnownAssemblypor GenerateCodeForDeclaringAssemblyAttribute. - O
Microsoft.Orleans.Sdkpacote faz referência ao pacote C# Source Generator (Microsoft.Orleans.CodeGenerator).
- Substitua os usos de
- Remova todas as referências a
Microsoft.Orleans.OrleansRuntime.-
Microsoft.Orleans. Os pacotes do servidor fazem referência à sua substituição,
Microsoft.Orleans.Runtime.
-
Microsoft.Orleans. Os pacotes do servidor fazem referência à sua substituição,
- Remover chamadas para
ConfigureApplicationParts. Os componentes da aplicação foram removidos. O C# Source Generator for Orleans é adicionado a todos os pacotes (incluindo o cliente e o servidor) e gera automaticamente o equivalente a Application Parts. - Substitua referências a
Microsoft.Orleans.OrleansServiceBuspor Microsoft.Orleans. Streaming.EventHubs. - Se estiver a usar lembretes, adicione uma referência a Microsoft.Orleans.Reminders.
- Se estiver usando fluxos, adicione uma referência a Microsoft.Orleans. Streaming.
Sugestão
Todas as Orleans amostras foram atualizadas para Orleans a versão 7.0 e podem ser usadas como referência para as alterações feitas. Para obter mais informações, consulte Orleans a edição #8035 que discrimina as alterações feitas em cada exemplo.
Orleans Diretivas de utilização global
Todos os Orleans projetos fazem referência direta ou indireta ao Microsoft.Orleans.Sdk pacote NuGet. Quando um Orleans projeto é configurado para habilitar usos implícitos (por exemplo, <ImplicitUsings>enable</ImplicitUsings>), o projeto usa implicitamente os Orleans namespaces e Orleans.Hosting . Isso significa que o código do aplicativo não precisa dessas using diretivas.
Para obter mais informações, consulte ImplicitUsings e dotnet/orleans/src/Orleans.Sdk/build/Microsoft.Orleans.Sdk.targets.
Alojamento
O tipo ClientBuilder é substituído pelo método de extensão UseOrleansClient em IHostBuilder. O tipo IHostBuilder é proveniente do pacote NuGet Microsoft.Extensions.Hosting. Isso significa que um Orleans cliente pode ser adicionado a um host existente sem criar um contêiner de injeção de dependência separado. O cliente se conecta ao cluster durante a inicialização. Uma vez IHost.StartAsync concluído, o cliente se conecta automaticamente. Serviços adicionados ao IHostBuilder começam na ordem de registo. Executar UseOrleansClient antes de executar ConfigureWebHostDefaults, por exemplo, garante que Orleans seja iniciado antes do ASP.NET Core, permitindo acesso imediato ao cliente a partir da aplicação ASP.NET Core.
Para emular o comportamento anterior ClientBuilder , crie um separado HostBuilder e configure-o com um Orleans cliente. Um IHostBuilder pode ser configurado com um Orleans cliente ou um Orleans silo. Todos os silos registram uma instância de IGrainFactory e IClusterClient que o aplicativo pode usar, portanto, configurar um cliente separadamente é desnecessário e não tem suporte.
OnActivateAsync e OnDeactivateAsync mudança de assinatura
Orleans Permite que os grãos executem código durante a ativação e desativação. Use esse recurso para executar tarefas como ler o estado do armazenamento ou registrar mensagens do ciclo de vida. Na Orleans versão 7.0, a assinatura desses métodos de ciclo de vida mudou:
- OnActivateAsync() agora aceita um CancellationToken parâmetro. Quando o CancellationToken for cancelado, abandone o processo de ativação.
-
OnDeactivateAsync() agora aceita um DeactivationReason parâmetro e um
CancellationTokenparâmetro. ODeactivationReasonindica por que a ativação está sendo desativada. Use essas informações para fins de registro e diagnóstico. Quando oCancellationTokenfor cancelado, conclua o processo de desativação imediatamente. Note que, uma vez que qualquer host pode falhar a qualquer momento, não é recomendado confiar emOnDeactivateAsyncpara executar ações importantes, como persistir o estado crítico.
Considere o seguinte exemplo de um grão substituindo esses novos métodos:
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;
}
Grãos POCO e IGrainBase
Os grãos em Orleans não precisam mais herdar da classe base Grain ou de qualquer outra classe. Esta funcionalidade é conhecida como grãos POCO . Para acessar métodos de extensão, como qualquer um dos seguintes:
- DeactivateOnIdle
- AsReference
- Cast
- GetPrimaryKey
- GetReminder
- GetReminders
- RegisterOrUpdateReminder
- UnregisterReminder
- GetStreamProvider
O grão deve implementar IGrainBase ou herdar de Grain. Aqui está um exemplo de implementação de IGrainBase numa classe de 'grain':
public sealed class PingGrain : IGrainBase, IPingGrain
{
public PingGrain(IGrainContext context) => GrainContext = context;
public IGrainContext GrainContext { get; }
public ValueTask Ping() => ValueTask.CompletedTask;
}
IGrainBase também define OnActivateAsync e OnDeactivateAsync com implementações padrão, permitindo que o grão participe de seu ciclo de vida se desejado:
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;
}
Serialização
A mudança mais onerosa no Orleans 7.0 é a introdução do serializador tolerante à versão. Essa alteração foi feita porque os aplicativos tendem a evoluir, o que levou a uma armadilha significativa para os desenvolvedores, já que o serializador anterior não podia tolerar a adição de propriedades aos tipos existentes. Por outro lado, o serializador anterior era flexível, permitindo a representação da maioria dos tipos .NET sem modificação, incluindo recursos como genéricos, polimorfismo e rastreamento de referência. A substituição já devia ter sido feita há muito tempo, mas a representação de tipos de alta fidelidade ainda é necessária. Portanto, o 7.0 introduz um serializador de substituição que suporta a representação de alta fidelidade de tipos .NET e, ao mesmo tempo, Orleans permite que os tipos evoluam. O novo serializador é muito mais eficiente do que o anterior, resultando em até 170% a mais na taxa de throughput de ponta a ponta.
Para obter mais informações, consulte os seguintes artigos relacionados à Orleans versão 7.0:
Identidades de cereais
Cada grão tem uma identidade única composta pelo tipo do grão e sua chave. As versões anteriores Orleans utilizavam um tipo composto para GrainIds, permitindo suportar chaves de granularidade:
Esta abordagem envolve alguma complexidade ao lidar com chaves de grão. As identidades de grãos consistem em dois componentes: um tipo e uma chave. O componente de tipo consistia anteriormente em um código de tipo numérico, uma categoria e 3 bytes de informações de tipo genéricas.
As identidades de grain agora assumem a forma type/key, onde tanto type quanto key são strings. A interface de chave de grão mais usada é IGrainWithStringKey. Isso simplifica muito como a identidade de grãos funciona e melhora o suporte para tipos de grãos genéricos.
As interfaces de grain agora também são representadas usando um nome fácil de ler por humanos, em vez de uma combinação de um código hash e uma representação de cadeia de caracteres de quaisquer parâmetros de tipo genéricos.
O novo sistema é mais personalizável, e essas personalizações podem ser conduzidas com atributos.
-
GrainTypeAttribute(String) em um grão
classespecifica a porção Tipo de seu ID de grão. -
DefaultGrainTypeAttribute(String) on a grain
interfaceespecifica o tipo do grão que IGrainFactory deve ser resolvido por padrão ao obter uma referência de grão. Por exemplo, ao chamarIGrainFactory.GetGrain<IMyGrain>("my-key"), a fábrica de grãos retorna uma referência ao grão"my-type/my-key"seIMyGraintiver o atributo acima especificado acima. - GrainInterfaceTypeAttribute(String) Permite substituir o nome da interface. Especificar um nome explicitamente usando esse mecanismo permite renomear o tipo de interface sem quebrar a compatibilidade com as referências de grão existentes. Observe que a interface também deve ter o AliasAttribute neste caso, uma vez que sua identidade pode ser serializada. Para obter mais informações sobre como especificar um alias de tipo, consulte a seção sobre serialização.
Como mencionado acima, substituir a classe grain padrão e os nomes de interface para tipos permite renomear os tipos subjacentes sem quebrar a compatibilidade com implantações existentes.
Identidades de fluxo
Quando os fluxos Orleans foram lançados pela primeira vez, os fluxos só podiam ser identificados usando um Guid. Essa abordagem foi eficiente em termos de alocação de memória, mas dificultou a criação de identidades de fluxo significativas, muitas vezes exigindo alguma codificação ou indireção para determinar a identidade de fluxo apropriada para uma determinada finalidade.
Na Orleans versão 7.0, os fluxos são identificados usando strings. O Orleans.Runtime.StreamIdstruct contém três propriedades: StreamId.Namespace, StreamId.Key, e StreamId.FullKey. Esses valores de propriedade são cadeias de caracteres UTF-8 codificadas. Por exemplo, veja StreamId.Create(String, String).
Substituição de SimpleMessageStreams com BroadcastChannel
SimpleMessageStreams (também chamado de SMS) é removido na versão 7.0. O SMS tinha a mesma interface que Orleans.Providers.Streams.PersistentStreams, mas o seu comportamento era muito diferente porque dependia de chamadas diretas grão a grão. Para evitar confusão, o SMS foi removido e um novo substituto chamado foi introduzido Orleans.BroadcastChannel .
BroadcastChannel suporta apenas subscrições implícitas e pode ser um substituto direto neste caso. Se forem necessárias assinaturas explícitas ou se a interface tiver de ser usada (por exemplo, se o PersistentStream SMS foi usado em testes enquanto EventHub foi usado na produção), então MemoryStream é o melhor candidato.
BroadcastChannel tem os mesmos comportamentos que o SMS, enquanto MemoryStream se comporta como outros provedores de fluxo. Considere o seguinte exemplo de uso do canal de transmissão:
// 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;
}
}
}
A migração para MemoryStream é mais fácil, pois apenas a configuração precisa ser alterada. Considere a seguinte MemoryStream configuração:
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);
});
OpenTelemetria
O sistema de telemetria é atualizado na Orleans versão 7.0 e o sistema de telemetria anterior é removido em favor de APIs .NET padronizadas, como .NET Metrics para métricas e o ActivitySource para rastreamento.
Como parte disso, os pacotes existentes Microsoft.Orleans.TelemetryConsumers.* são removidos. Um novo conjunto de pacotes está a ser considerado para agilizar a integração de métricas emitidas pela Orleans na solução de monitorização escolhida. Como sempre, comentários e contribuições são bem-vindos.
A dotnet-counters ferramenta apresenta monitoramento de desempenho para monitoramento de integridade ad-hoc e investigação de desempenho de primeiro nível. Para Orleans contadores, use a ferramenta dotnet-counters para monitorá-los :
dotnet counters monitor -n MyApp --counters Microsoft.Orleans
Da mesma forma, adicione os Microsoft.Orleans contadores às métricas do OpenTelemetry, como ilustrado no código seguinte.
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddPrometheusExporter()
.AddMeter("Microsoft.Orleans"));
Para habilitar o rastreamento distribuído, configure o OpenTelemetry conforme mostrado no código a seguir:
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");
});
});
No código anterior, OpenTelemetry é configurado para monitorar:
Microsoft.Orleans.RuntimeMicrosoft.Orleans.Application
Para propagar a atividade, chame AddActivityPropagation:
builder.Host.UseOrleans((_, clientBuilder) =>
{
clientBuilder.AddActivityPropagation();
});
Refatorar funcionalidades do pacote principal para pacotes separados
Em Orleans versão 7.0, as extensões foram divididas em pacotes separados que não dependem de Orleans.Core. Ou seja, Orleans.Streaming, Orleans.Reminderse Orleans.Transactions foram separados do núcleo. Isso significa que esses pacotes são inteiramente pagos pelo que é usado, e nenhum código no Orleans núcleo é dedicado a esses recursos. Essa abordagem reduz a superfície da API principal e o tamanho da montagem, simplifica o núcleo e melhora o desempenho. Em relação ao desempenho, as transações em Orleans anteriormente exigiam alguma execução de código para cada método para coordenar transações potenciais. Essa lógica de coordenação é agora transferida para uma base por método.
Esta é uma mudança que quebra a compilação. O código existente que interage com lembretes ou streams ao chamar métodos previamente definidos na classe base Grain pode deixar de funcionar porque estes agora são métodos de extensão. Atualize essas chamadas que não especificam this (por exemplo, GetReminders) para incluir this (por exemplo, this.GetReminders()) porque os métodos de extensão devem ser qualificados. Um erro de compilação ocorre se essas chamadas não forem atualizadas e a alteração de código necessária pode não ser óbvia sem saber o que mudou.
Cliente da transação
Orleans 7.0 introduz uma nova abstração para coordenar transações: Orleans.ITransactionClient. Anteriormente, apenas os grãos podiam coordenar as transações. Com ITransactionClient, disponível via injeção de dependência, os clientes também podem coordenar transações sem precisar de um intermediário. O exemplo a seguir retira créditos de uma conta e os deposita em outra em uma única transação. Chame este código de dentro de um grain ou de um cliente externo que recuperou o ITransactionClient do contentor de injeção de dependência.
await transactionClient.RunTransaction(
TransactionOption.Create,
() => Task.WhenAll(from.Withdraw(100), to.Deposit(100)));
Para transações coordenadas pelo cliente, o cliente deve adicionar os serviços necessários durante a configuração:
clientBuilder.UseTransactions();
O exemplo BankAccount demonstra o uso do ITransactionClient. Para obter mais informações, consulte Orleans transações.
Reentrância da cadeia de chamadas
Os grãos são de thread único e processam solicitações uma a uma, do início à conclusão, por padrão. Em outras palavras, os grãos não são reentrantes por padrão. Adicionar o ReentrantAttribute a uma classe de grão permite que o grão processe várias solicitações simultaneamente de forma intercalada enquanto ainda é de thread único. Esse recurso pode ser útil para grãos que não possuem estado interno ou executam muitas operações assíncronas, como emitir chamadas HTTP ou gravar em um banco de dados. É necessário um cuidado extra quando as solicitações podem ser intercaladas: é possível que o estado de um grão observado antes de uma instrução await mude no momento em que a operação assíncrona é concluída e o método retoma a execução.
Por exemplo, o grão a seguir representa um contador. Está marcado Reentrant, permitindo que várias chamadas sejam intercaladas. O Increment() método deve incrementar o contador interno e retornar o valor observado. No entanto, como o corpo do método Increment() observa o estado do grão antes de um ponto await e o atualiza depois, várias execuções intercaladas de Increment() podem resultar num número _value inferior ao total de chamadas Increment() recebidas. Este é um erro introduzido pelo uso indevido de reentrancy.
Remover o ReentrantAttribute é suficiente para corrigir esse problema.
[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;
}
}
Para evitar tais erros, os grãos não são reentrantes por padrão. A desvantagem é a taxa de transferência reduzida para grãos que executam operações assíncronas em sua implementação, uma vez que o grão não pode processar outras solicitações enquanto aguarda a conclusão de uma operação assíncrona. Para aliviar este problema, Orleans oferece várias opções para permitir a reentrância em certos casos.
- Para uma classe inteira: Colocar o ReentrantAttribute sobre o grão permite que qualquer pedido para o grão intercale com qualquer outro pedido.
- Para um subconjunto de métodos: Colocar o AlwaysInterleaveAttribute método na interface do grain permite que as solicitações para esse método se misturem com qualquer outra solicitação e permite que qualquer outra solicitação se misture com solicitações para esse método.
- Para um subconjunto de métodos: Colocar o ReadOnlyAttribute método na interface de grão permite que as solicitações para esse método intercalem com qualquer outra
ReadOnlysolicitação e permite que qualquer outraReadOnlysolicitação intercale solicitações para esse método. Nesse sentido, é uma forma mais restrita deAlwaysInterleave. - Para qualquer solicitação dentro de uma cadeia de chamadas: RequestContext.AllowCallChainReentrancy() e RequestContext.SuppressCallChainReentrancy() permitem decidir se as solicitações posteriores podem voltar a acessar o recurso. Ambas as chamadas retornam um valor que deve ser descartado ao sair da solicitação. Portanto, use-os da seguinte maneira:
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;
Adesão para reentrância de cadeia de chamada por grão, por cadeia de chamada. Por exemplo, considere dois elementos, A e B. Se o elemento A possibilitar a reentrada na cadeia de chamadas antes de chamar o elemento B, então o elemento B pode retornar ao elemento A nessa chamada. No entanto, o grão A não pode chamar de volta para o grão B se o grão B também não tiver habilitado a reentrância da cadeia de chamadas. Ele é ativado por grão, por cadeia de chamada.
Os grãos também podem suprimir informações de reentrância da cadeia de chamada de fluir para baixo de uma cadeia de chamada usando using var _ = RequestContext.SuppressCallChainReentrancy(). Isso impede que chamadas subsequentes sejam reinseridas.
ADO.NET scripts de migração
Para garantir a compatibilidade futura com Orleans clustering, persistência e lembretes que dependem de ADO.NET, o script de migração SQL apropriado é necessário.
Selecione os arquivos para o banco de dados usado e aplique-os em ordem.