Formação
Módulo
Crie seu primeiro aplicativo de Orleans com o ASP.NET Core 8.0 - Training
Saiba como criar aplicativos distribuídos nativos da nuvem com Orleans.
Este browser já não é suportado.
Atualize para o Microsoft Edge para tirar partido das mais recentes funcionalidades, atualizações de segurança e de suporte técnico.
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.
Os aplicativos existentes que usam lembretes, fluxos ou persistência de grãos não podem ser facilmente migrados para a Orleans versão 7.0 devido a alterações na forma como Orleans identifica grãos e fluxos. Planejamos oferecer incrementalmente um caminho de migração para esses aplicativos.
Os aplicativos que executam versões anteriores do não podem ser atualizados sem problemas por meio de Orleans uma atualização contínua para a Orleans versão 7.0. Portanto, uma estratégia de atualização diferente deve ser usada, como implantar um novo cluster e desativar o cluster anterior. Orleans 7.0 altera o protocolo de conexão 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 do Orleans.
Temos evitado tais mudanças por muitos anos, mesmo em grandes lançamentos, então por que agora? Há duas razões principais: identidades e serialização. Em relação às identidades, as identidades de grãos e fluxos agora são compostas por cadeias de caracteres, permitindo que os grãos codificem informações de tipo genéricas corretamente e permitindo que os fluxos sejam mapeados mais facilmente para o domínio do aplicativo. Os tipos de grãos foram previamente identificados usando uma estrutura de dados complexa que não poderia representar grãos genéricos, levando a casos de esquina. Os fluxos eram identificados por um string
namespace e uma Guid chave, o que era difícil para os desenvolvedores mapear para seu domínio de aplicativo, por mais eficiente que fosse. A serialização agora é tolerante à versão, o que significa que você pode modificar seus tipos de determinadas maneiras compatíveis, seguindo um conjunto de regras, e ter certeza de que pode atualizar seu aplicativo sem erros de serialização. Isso era especialmente problemático quando os tipos de aplicação persistiam em fluxos ou armazenamento de grãos. As seções a seguir detalham as principais mudanças e as discutem mais detalhadamente.
Se você estiver atualizando um projeto para Orleans a versão 7.0, precisará executar as seguintes ações:
Microsoft.Orleans.CodeGenerator.MSBuild
e Microsoft.Orleans.OrleansCodeGenerator.Build
.
KnownAssembly
por GenerateCodeForDeclaringAssemblyAttribute.Microsoft.Orleans.Sdk
pacote faz referência ao pacote C# Source Generator (Microsoft.Orleans.CodeGenerator
).Microsoft.Orleans.OrleansRuntime
.
Microsoft.Orleans.Runtime
.ConfigureApplicationParts
.
As peças de aplicação foram removidas . O C# Source Generator for Orleans é adicionado a todos os pacotes (incluindo o cliente e o servidor) e gerará o equivalente a Application Parts automaticamente.Microsoft.Orleans.OrleansServiceBus
Microsoft.Orleans. Streaming.EventHubsGorjeta
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.
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>
), os Orleans
namespaces e Orleans.Hosting
são usados implicitamente. Isso significa que o código do seu aplicativo não precisa dessas diretivas.
Para obter mais informações, consulte ImplicitUsings e dotnet/orleans/src/Orleans. Sdk/build/Microsoft.Orleans. Sdk.targets.
O ClientBuilder tipo foi substituído por um método de UseOrleansClient extensão em IHostBuilder. O IHostBuilder
tipo vem do pacote NuGet Microsoft.Extensions.Hosting . Isso significa que você pode adicionar um Orleans cliente a um host existente sem ter que 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 será conectado automaticamente. Os serviços adicionados ao IHostBuilder
são iniciados na ordem de registro, portanto, ligue UseOrleansClient
antes de ligar ConfigureWebHostDefaults para garantir Orleans que seja iniciado antes que ASP.NET Core comece, por exemplo, permitindo que você acesse o cliente do seu aplicativo ASP.NET Core imediatamente.
Se desejar emular o comportamento anterior ClientBuilder
, você pode criar um separado HostBuilder
e configurá-lo com um Orleans cliente. IHostBuilder
pode ter um Orleans cliente ou um Orleans silo configurado. 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.
Orleans Permite que os grãos executem código durante a ativação e desativação. Isso pode ser usado 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:
CancellationToken
parâmetro. O DeactivationReason
indica por que a ativação está sendo desativada. Espera-se que os desenvolvedores usem essas informações para fins de registro e diagnóstico. Quando o CancellationToken
é cancelado, o processo de desativação deve ser concluído imediatamente. Observe que, como qualquer host pode falhar a qualquer momento, não é recomendável confiar para OnDeactivateAsync
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;
}
Os grãos não Orleans precisam mais herdar da Grain classe base ou de qualquer outra classe. Esta funcionalidade é conhecida como grãos POCO . Para acessar métodos de extensão, como qualquer um dos seguintes:
Seu grão deve implementar IGrainBase ou herdar de Grain. Aqui está um exemplo de implementação IGrainBase
em uma classe de grão:
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 seu 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;
}
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 e isso 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 era flexível, permitindo que os desenvolvedores representassem a maioria dos tipos .NET sem modificação, incluindo recursos como genéricos, polimorfismo e rastreamento de referência. A substituição já deveria ter sido feita há muito tempo, mas os usuários ainda precisam da representação de alta fidelidade de seus tipos. Portanto, um serializador de substituição foi introduzido no 7.0 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 serializador anterior, resultando em uma taxa de transferência de ponta a ponta até 170% maior.
Para obter mais informações, consulte os seguintes artigos relacionados à Orleans versão 7.0:
Cada grão tem uma identidade única que é composta pelo tipo do grão e sua chave. As versões anteriores do Orleans usavam um tipo composto para GrainId
s para suportar chaves de grão de:
Isso envolve alguma complexidade quando se trata de 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 grãos agora assumem a forma type/key
onde ambas e type
key
são cordas. A interface de chave de grão mais usada é o 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 grãos também são agora representadas usando um nome legível 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 impulsionadas por atributos.
class
especifica a porção Tipo de seu id de grão.interface
especifica o tipo do grão que IGrainFactory deve ser resolvido por padrão ao obter uma referência de grão. Por exemplo, ao chamar IGrainFactory.GetGrain<IMyGrain>("my-key")
, a fábrica de grãos retornará uma referência ao grão "my-type/my-key"
se IMyGrain
tiver o atributo acima especificado acima.Como mencionado acima, substituir a classe de grão padrão e os nomes de interface para seus tipos permite que você renomeie os tipos subjacentes sem quebrar a compatibilidade com implantações existentes.
Quando Orleans os fluxos foram lançados pela primeira vez, os fluxos só podiam ser identificados usando um Guidarquivo . Isso foi eficiente em termos de alocação de memória, mas era difícil para os usuários criar 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 agora são identificados usando strings. O Orleans.Runtime.StreamId struct
contém três propriedades: a StreamId.Namespace, a StreamId.Keye a StreamId.FullKey. Esses valores de propriedade são cadeias de caracteres UTF-8 codificadas. Por exemplo, StreamId.Create(String, String).
SimpleMessageStreams
(também chamado de SMS) foi removido na versão 7.0. O SMS tinha a mesma interface Orleans.Providers.Streams.PersistentStreamsdo , mas seu comportamento era muito diferente, já que dependia de chamadas diretas grão-a-grão. Para evitar confusão, o SMS foi removido e um novo substituto chamado Orleans.BroadcastChannel foi introduzido.
BroadcastChannel
suporta apenas subscrições implícitas e pode ser um substituto direto neste caso. Se você precisa de assinaturas explícitas ou precisa usar a PersistentStream
interface (por exemplo, você estava usando SMS em testes enquanto usava EventHub
na produção), então MemoryStream
é o melhor candidato para você.
BroadcastChannel
terá os mesmos comportamentos que o SMS, enquanto MemoryStream
se comportará 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
será mais fácil, uma vez que apenas a configuração precisa mudar. 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);
});
O sistema de telemetria foi atualizado na Orleans versão 7.0 e o sistema anterior foi removido em favor de APIs .NET padronizadas, como .NET Metrics para métricas e ActivitySource rastreamento.
Como parte disso, os pacotes existentes Microsoft.Orleans.TelemetryConsumers.*
foram removidos. Estamos considerando introduzir um novo conjunto de pacotes para agilizar o processo de integração das métricas emitidas pela Orleans sua solução de monitoramento de escolha. 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, a ferramenta dotnet-counters pode ser usada para monitorá-los:
dotnet counters monitor -n MyApp --counters Microsoft.Orleans
Da mesma forma, as métricas OpenTelemetry podem adicionar os Microsoft.Orleans
medidores, conforme mostrado no código a seguir:
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.Runtime
Microsoft.Orleans.Application
Para propagar a atividade, chame AddActivityPropagation:
builder.Host.UseOrleans((_, clientBuilder) =>
{
clientBuilder.AddActivityPropagation();
});
Na Orleans versão 7.0, fizemos um esforço para incluir extensões em pacotes separados que não dependem Orleans.Coredo . Ou seja, Orleans.Streaming, Orleans.Reminders, e Orleans.Transactions foram separados do núcleo. Isso significa que esses pacotes são inteiramente pagos pelo que você usa e nenhum código no núcleo do Orleans é dedicado a esses recursos. Isso 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 algum código que era executado para cada método para coordenar transações potenciais. Desde então, isso foi transferido para o método por método.
Esta é uma mudança que quebra a compilação. Você pode ter código existente que interage com lembretes ou fluxos chamando métodos que foram definidos anteriormente na classe base, Grain mas agora são métodos de extensão. Tais chamadas que não especificam this
(por exemplo GetReminders) precisarão ser atualizadas para incluir this
(por exemplo this.GetReminders()
) porque os métodos de extensão devem ser qualificados. Haverá um erro de compilação se você não atualizar essas chamadas e a alteração de código necessária pode não ser óbvia se você não souber o que mudou.
Orleans 7.0 introduz uma nova abstração para coordenar transações, Orleans.ITransactionClient. Anteriormente, as transações só podiam ser coordenadas por grãos. Com ITransactionClient
o , que está disponível por meio de 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. Esse código pode ser chamado de dentro de um grão ou de um cliente externo que recuperou o ITransactionClient
do contêiner 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 o tempo de configuração:
clientBuilder.UseTransactions();
O exemplo BankAccount demonstra o uso do ITransactionClient
. Para obter mais informações, consulte Orleans transações.
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 várias solicitações sejam processadas simultaneamente, de forma intercalada, enquanto ainda são single-threaded. Isso 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. Cuidado extra precisa ser tomado quando as solicitações podem intercalar: é possível que o estado de um grão seja observado antes que uma await
instrução seja alterada 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. Foi marcado Reentrant
, permitindo que várias chamadas intercalem. O Increment()
método deve incrementar o contador interno e retornar o valor observado. No entanto, como o corpo do Increment()
método observa o estado do grão antes de um await
ponto e o atualiza depois, é possível que várias execuções de intercalação possam Increment()
resultar em um _value
número menor do que o total de Increment()
chamadas recebidas. Este é um erro introduzido pelo uso indevido de reentrancy.
Remover o ReentrantAttribute é suficiente para corrigir o 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 disso é a taxa de transferência reduzida para grãos que executam operações assíncronas em sua implementação, uma vez que outras solicitações não podem ser processadas enquanto o grão aguarda a conclusão de uma operação assíncrona. Para aliviar isso, Orleans oferece várias opções para permitir a reentrância em certos casos:
ReadOnly
solicitação e que as solicitações para esse método sejam intercaladas por qualquer outra ReadOnly
solicitação. Neste sentido, é uma forma mais restrita de AlwaysInterleave
.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;
A reentrância da cadeia de chamadas deve ser optada por grão, por cadeia de chamada. Por exemplo, considere dois grãos, grão A ou grão B. Se o grão A permite a reentrância da cadeia de chamada antes de chamar o grão B, o grão B pode chamar de volta para o grão 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 não tiver também habilitado a reentrância da cadeia de chamada. É 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 a reentrada de chamadas subsequentes.
Para garantir a compatibilidade direta com Orleans clustering, persistência e lembretes que dependem de ADO.NET, você precisará do script de migração SQL apropriado:
Selecione os arquivos para o banco de dados usado e aplique-os em ordem.
Comentários do .NET
O .NET é um projeto código aberto. Selecione um link para fornecer comentários:
Formação
Módulo
Crie seu primeiro aplicativo de Orleans com o ASP.NET Core 8.0 - Training
Saiba como criar aplicativos distribuídos nativos da nuvem com Orleans.