Introdução ao Reliable Services em Java

Este artigo explica as noções básicas do Azure Service Fabric Reliable Services e explica-lhe como criar e implementar uma aplicação Reliable Service simples escrita em Java.

Veja esta página para ver um vídeo de formação que lhe mostra como criar um serviço Reliable sem estado:

Instalação e configuração

Antes de começar, certifique-se de que tem o ambiente de desenvolvimento do Service Fabric configurado no seu computador. Se precisar de configurá-lo, aceda a começar a utilizar o Mac ou a começar a utilizar o Linux.

Conceitos básicos

Para começar a utilizar o Reliable Services, só precisa de compreender alguns conceitos básicos:

  • Tipo de serviço: esta é a implementação do serviço. É definido pela classe que escreve que expande e quaisquer outros códigos StatelessService ou dependências aí utilizados, juntamente com um nome e um número de versão.
  • Instância de serviço nomeada: para executar o seu serviço, crie instâncias nomeadas do seu tipo de serviço, tal como cria instâncias de objeto de um tipo de classe. As instâncias de serviço são, de facto, instâncias de objetos da sua classe de serviço que escreve.
  • Anfitrião de serviço: as instâncias de serviço nomeadas que criar têm de ser executadas dentro de um anfitrião. O anfitrião do serviço é apenas um processo em que as instâncias do seu serviço podem ser executadas.
  • Registo do serviço: o registo reúne tudo. O tipo de serviço tem de ser registado no runtime do Service Fabric num anfitrião de serviços para permitir que o Service Fabric crie instâncias do mesmo para ser executado.

Criar um serviço sem estado

Comece por criar uma aplicação do Service Fabric. O SDK do Service Fabric para Linux inclui um gerador Yeoman para fornecer os andaimes de uma aplicação do Service Fabric com um serviço sem estado. Comece por executar o seguinte comando Yeoman:

$ yo azuresfjava

Siga as instruções para criar um Reliable Stateless Service. Neste tutorial, atribua o nome "HelloWorldApplication" à aplicação e ao serviço "HelloWorld". O resultado inclui diretórios para e HelloWorldApplicationHelloWorld.

HelloWorldApplication/
├── build.gradle
├── HelloWorld
│   ├── build.gradle
│   └── src
│       └── statelessservice
│           ├── HelloWorldServiceHost.java
│           └── HelloWorldService.java
├── HelloWorldApplication
│   ├── ApplicationManifest.xml
│   └── HelloWorldPkg
│       ├── Code
│       │   ├── entryPoint.sh
│       │   └── _readme.txt
│       ├── Config
│       │   └── _readme.txt
│       ├── Data
│       │   └── _readme.txt
│       └── ServiceManifest.xml
├── install.sh
├── settings.gradle
└── uninstall.sh

Registo do serviço

Os tipos de serviço têm de ser registados no runtime do Service Fabric. O tipo de serviço é definido na classe e do ServiceManifest.xml serviço que implementa StatelessService. O registo de serviço é efetuado no ponto de entrada principal do processo. Neste exemplo, o ponto de entrada principal do processo é HelloWorldServiceHost.java:

public static void main(String[] args) throws Exception {
    try {
        ServiceRuntime.registerStatelessServiceAsync("HelloWorldType", (context) -> new HelloWorldService(), Duration.ofSeconds(10));
        logger.log(Level.INFO, "Registered stateless service type HelloWorldType.");
        Thread.sleep(Long.MAX_VALUE);
    }
    catch (Exception ex) {
        logger.log(Level.SEVERE, "Exception in registration:", ex);
        throw ex;
    }
}

Implementar o serviço

Abra HelloWorldApplication/HelloWorld/src/statelessservice/HelloWorldService.java. Esta classe define o tipo de serviço e pode executar qualquer código. A API de serviço fornece dois pontos de entrada para o seu código:

  • Um método de ponto de entrada aberto, denominado runAsync(), onde pode começar a executar quaisquer cargas de trabalho, incluindo cargas de trabalho de computação de execução prolongada.
@Override
protected CompletableFuture<?> runAsync(CancellationToken cancellationToken) {
    ...
}
  • Um ponto de entrada de comunicação onde pode ligar a sua pilha de comunicação à sua escolha. É aqui que pode começar a receber pedidos de utilizadores e outros serviços.
@Override
protected List<ServiceInstanceListener> createServiceInstanceListeners() {
    ...
}

Este tutorial centra-se no método de runAsync() ponto de entrada. É aqui que pode começar imediatamente a executar o código.

RunAsync

A plataforma chama este método quando uma instância de um serviço é colocada e pronta para ser executada. Para um serviço sem estado, isso significa quando a instância de serviço é aberta. É fornecido um token de cancelamento para coordenar quando a instância de serviço precisa de ser fechada. No Service Fabric, este ciclo de abertura/fecho de uma instância de serviço pode ocorrer muitas vezes ao longo da duração do serviço como um todo. Isto pode acontecer por vários motivos, incluindo:

  • O sistema move as instâncias de serviço para balanceamento de recursos.
  • Ocorrem falhas no código.
  • A aplicação ou o sistema é atualizado.
  • O hardware subjacente está indisponível.

Esta orquestração é gerida pelo Service Fabric para manter o seu serviço altamente disponível e devidamente equilibrado.

runAsync() não deve bloquear de forma síncrona. A implementação de runAsync deve devolver um CompleteableFuture para permitir que o runtime continue. Se a carga de trabalho precisar de implementar uma tarefa de execução prolongada que deve ser efetuada no CompleteableFuture.

Cancelamento

O cancelamento da carga de trabalho é um esforço cooperativo orquestrado pelo token de cancelamento fornecido. O sistema aguarda que a tarefa termine (por conclusão, cancelamento ou falha com êxito) antes de avançar. É importante respeitar o token de cancelamento, concluir qualquer trabalho e sair runAsync() o mais rapidamente possível quando o sistema pedir o cancelamento. O exemplo seguinte demonstra como lidar com um evento de cancelamento:

@Override
protected CompletableFuture<?> runAsync(CancellationToken cancellationToken) {

    // TODO: Replace the following sample code with your own logic
    // or remove this runAsync override if it's not needed in your service.

    return CompletableFuture.runAsync(() -> {
        long iterations = 0;
        while(true)
        {
        cancellationToken.throwIfCancellationRequested();
        logger.log(Level.INFO, "Working-{0}", ++iterations);

        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex){}
        }
    });
}

Neste exemplo de serviço sem estado, a contagem é armazenada numa variável local. No entanto, como se trata de um serviço sem estado, o valor armazenado existe apenas para o ciclo de vida atual da instância de serviço. Quando o serviço é movido ou reiniciado, o valor é perdido.

Criar um serviço com estado

O Service Fabric apresenta um novo tipo de serviço com monitorização de estado. Um serviço com monitorização de estado pode manter o estado de forma fiável dentro do próprio serviço, colocalizado com o código que o está a utilizar. O Estado é altamente disponível pelo Service Fabric sem a necessidade de manter o estado num arquivo externo.

Para converter um valor de contador de sem estado para altamente disponível e persistente, mesmo quando o serviço é movido ou reiniciado, precisa de um serviço com estado.

No mesmo diretório da aplicação HelloWorld, pode adicionar um novo serviço ao executar o yo azuresfjava:AddService comando . Escolha o "Reliable Stateful Service" para a sua arquitetura e dê ao serviço o nome "HelloWorldStateful".

A sua aplicação deverá ter agora dois serviços: o serviço sem estado HelloWorld e o serviço com monitorização de estado HelloWorldStateful.

Um serviço com estado tem os mesmos pontos de entrada que um serviço sem estado. A principal diferença é a disponibilidade de um fornecedor de estado que pode armazenar o estado de forma fiável. O Service Fabric inclui uma implementação de fornecedor de estado denominada Reliable Collections, que lhe permite criar estruturas de dados replicadas através do Reliable State Manager. Um Reliable Service com monitorização de estado utiliza este fornecedor de estado por predefinição.

Abra HelloWorldStateful.java em HelloWorldStateful -> src, que contém o seguinte método RunAsync:

@Override
protected CompletableFuture<?> runAsync(CancellationToken cancellationToken) {
    Transaction tx = stateManager.createTransaction();
    return this.stateManager.<String, Long>getOrAddReliableHashMapAsync("myHashMap").thenCompose((map) -> {
        return map.computeAsync(tx, "counter", (k, v) -> {
            if (v == null)
                return 1L;
            else
                return ++v;
            }, Duration.ofSeconds(4), cancellationToken)
                .thenCompose((r) -> tx.commitAsync())
                .whenComplete((r, e) -> {
            try {
                tx.close();
            } catch (Exception e) {
                logger.log(Level.SEVERE, e.getMessage());
            }
        });
    });
}

RunAsync

RunAsync() funciona de forma semelhante em serviços com monitorização de estado e sem estado. No entanto, num serviço com monitorização de estado, a plataforma realiza trabalho adicional em seu nome antes de executar RunAsync(). Este trabalho pode incluir garantir que o Reliable State Manager e o Reliable Collections estão prontos a utilizar.

Reliable Collections e Reliable State Manager

ReliableHashMap<String,Long> map = this.stateManager.<String, Long>getOrAddReliableHashMapAsync("myHashMap")

ReliableHashMap é uma implementação de dicionário que pode utilizar para armazenar de forma fiável o estado no serviço. Com o Service Fabric e o Reliable HashMaps, pode armazenar dados diretamente no seu serviço sem a necessidade de um arquivo persistente externo. Os HashMaps fiáveis tornam os seus dados altamente disponíveis. O Service Fabric consegue-o ao criar e gerir múltiplas réplicas do seu serviço por si. Também fornece uma API que abstrai as complexidades da gestão dessas réplicas e das respetivas transições de estado.

As Reliable Collections podem armazenar qualquer tipo de Java, incluindo os seus tipos personalizados, com algumas limitações:

  • O Service Fabric torna o seu estado altamente disponível ao replicar o estado entre nós e o Reliable HashMap armazena os seus dados no disco local em cada réplica. Isto significa que tudo o que está armazenado no Reliable HashMaps tem de ser serializável.

  • Os objetos são replicados para elevada disponibilidade quando consolida transações no Reliable HashMaps. Os objetos armazenados no Reliable HashMaps são mantidos na memória local no seu serviço. Isto significa que tem uma referência local ao objeto.

    É importante que não muta instâncias locais desses objetos sem efetuar uma operação de atualização na coleção fiável numa transação. Isto acontece porque as alterações a instâncias locais de objetos não serão replicadas automaticamente. Tem de voltar a inserir o objeto no dicionário ou utilizar um dos métodos de atualização no dicionário.

O Reliable State Manager gere o Reliable HashMaps por si. Pode pedir ao Reliable State Manager uma coleção fiável por nome em qualquer altura e em qualquer local do seu serviço. O Reliable State Manager garante que obtém uma referência de volta. Não recomendamos que guarde referências a instâncias de coleção fiáveis em propriedades ou variáveis de membros da classe. Tem de ter especial cuidado para garantir que a referência está sempre definida como uma instância no ciclo de vida do serviço. O Reliable State Manager processa este trabalho por si e está otimizado para visitas repetidas.

Operações transacionais e assíncronas

return map.computeAsync(tx, "counter", (k, v) -> {
    if (v == null)
        return 1L;
    else
        return ++v;
    }, Duration.ofSeconds(4), cancellationToken)
        .thenCompose((r) -> tx.commitAsync())
        .whenComplete((r, e) -> {
    try {
        tx.close();
    } catch (Exception e) {
        logger.log(Level.SEVERE, e.getMessage());
    }
});

As operações no Reliable HashMaps são assíncronas. Isto deve-se ao facto de as operações de escrita com Coleções Fiáveis efetuarem operações de E/S para replicar e manter os dados no disco.

As operações hashMap fiáveis são transacionais, para que possa manter o estado consistente em várias HashMaps fiáveis e operações. Por exemplo, pode obter um item de trabalho de um Dicionário Fiável, executar uma operação no mesmo e guardar o resultado noutro HashMap Fiável, tudo numa única transação. Isto é tratado como uma operação atómica e garante que toda a operação será bem-sucedida ou toda a operação será revertida. Se ocorrer um erro depois de desativar o item, mas antes de guardar o resultado, toda a transação é revertida e o item permanece na fila para processamento.

Criar a aplicação

O estruturamento Yeoman inclui um script de gradação para criar a aplicação e scripts bash para implementar e remover a aplicação. Para executar a aplicação, crie primeiro a aplicação com gradle:

$ gradle

Isto produz um pacote de aplicações do Service Fabric que pode ser implementado com a CLI do Service Fabric.

Implementar a aplicação

Depois de criada a aplicação, pode implementá-la no cluster local.

  1. Ligue ao cluster do Service Fabric local.

    sfctl cluster select --endpoint http://localhost:19080
    
  2. Execute o script de instalação fornecido no modelo para copiar o pacote de aplicação para o arquivo de imagens do cluster, registar o tipo de aplicação e criar uma instância da mesma.

    ./install.sh
    

A implementação da aplicação criada é igual à de qualquer outra aplicação do Service Fabric. Veja a documentação sobre como gerir uma aplicação do Service Fabric com a CLI do Service Fabric para obter instruções detalhadas.

Os parâmetros desses comandos encontram-se nos manifestos gerados dentro do pacote de aplicação.

Após a implementação da aplicação, abra um browser e navegue até Service Fabric Explorer em http://localhost:19080/Explorer. Em seguida, expanda o nó Aplicações e repare que há, agora, uma entrada para o tipo de aplicação e outra para a primeira instância desse tipo.

Importante

Para implementar a aplicação num cluster do Linux seguro no Azure, tem de configurar um certificado para validar a sua aplicação com o runtime do Service Fabric. Ao fazê-lo, permite que os seus serviços do Reliable Services comuniquem com as APIs de runtime do Service Fabric subjacentes. Para saber mais, veja Configurar uma aplicação reliable Services para ser executada em clusters do Linux.

Passos seguintes