Java 中的 Reliable Services 入门Get started with Reliable Services in Java

本文介绍 Azure Service Fabric Reliable Services 的基础知识,并演示如何创建和部署以 Java 编写的简单 Reliable Service 应用程序。This article explains the basics of Azure Service Fabric Reliable Services and walks you through creating and deploying a simple Reliable Service application written in Java.

安装和设置Installation and setup

在开始之前,请确保已在计算机上设置 Service Fabric 开发环境。Before you start, make sure you have the Service Fabric development environment set up on your machine. 如果需要设置环境,请转到在 Mac 上开始使用在 Linux 上开始使用If you need to set it up, go to getting started on Mac or getting started on Linux.

基本概念Basic concepts

了解几个基本概念,即可开始使用 Reliable Services:To get started with Reliable Services, you only need to understand a few basic concepts:

  • 服务类型:这是你的服务实现。Service type: This is your service implementation. 它由编写的可扩展 StatelessService 的类、其中使用的任何其他代码或依赖项以及名称和版本号定义。It is defined by the class you write that extends StatelessService and any other code or dependencies used therein, along with a name and a version number.
  • 命名服务实例:若要运行服务,需要创建服务类型的命名实例,就像创建类类型的对象实例一样。Named service instance: To run your service, you create named instances of your service type, much like you create object instances of a class type. 事实上,服务实例是编写的服务类的对象实例化。Service instances are in fact object instantiations of your service class that you write.
  • 服务宿主:创建的命名服务实例需在宿主中运行。Service host: The named service instances you create need to run inside a host. 服务宿主是可以运行服务实例的进程。The service host is just a process where instances of your service can run.
  • 服务注册:通过注册可将所有对象融合在一起。Service registration: Registration brings everything together. 只有在服务宿主中将服务类型注册 Service Fabric 运行时,Service Fabric 才能创建该类型的可运行实例。The service type must be registered with the Service Fabric runtime in a service host to allow Service Fabric to create instances of it to run.

创建无状态服务Create a stateless service

首先创建 Service Fabric 应用程序。Start by creating a Service Fabric application. 适用于 Linux 的 Service Fabric SDK 包括一个 Yeoman 生成器,它为包含无状态服务的 Service Fabric 应用程序提供基架。The Service Fabric SDK for Linux includes a Yeoman generator to provide the scaffolding for a Service Fabric application with a stateless service. 首先,请运行以下 Yeoman 命令:Start by running the following Yeoman command:

$ yo azuresfjava

按照说明创建 可靠无状态服务Follow the instructions to create a Reliable Stateless Service. 本教程将应用程序命名为“HelloWorldApplication”,将服务命名为“HelloWorld”。For this tutorial, name the application "HelloWorldApplication" and the service "HelloWorld". 结果包含 HelloWorldApplicationHelloWorld 的目录。The result includes directories for the HelloWorldApplication and HelloWorld.

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

服务注册Service registration

必须将服务类型注册到 Service Fabric 运行时。Service types must be registered with the Service Fabric runtime. 服务类型在 ServiceManifest.xml 中以及实现 StatelessService 的服务类中定义。The service type is defined in the ServiceManifest.xml and your service class that implements StatelessService. 服务注册在进程主入口点中执行。Service registration is performed in the process main entry point. 在本示例中,进程主入口点为 HelloWorldServiceHost.javaIn this example, the process main entry point is 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;
    }
}

实现服务Implement the service

打开 HelloWorldApplication/HelloWorld/src/statelessservice/HelloWorldService.javaOpen HelloWorldApplication/HelloWorld/src/statelessservice/HelloWorldService.java. 此类定义服务类型,可以运行任何代码。This class defines the service type, and can run any code. 服务 API 为代码提供两个入口点:The service API provides two entry points for your code:

  • 名为 runAsync()的开放式入口点方法,可在其中开始执行任何工作负荷,包括长时间运行的计算工作负荷。An open-ended entry point method, called runAsync(), where you can begin executing any workloads, including long-running compute workloads.
@Override
protected CompletableFuture<?> runAsync(CancellationToken cancellationToken) {
    ...
}
  • 一个通信入口点,可在其中插入选择的通信堆栈。A communication entry point where you can plug in your communication stack of choice. 这就是可以开始接收来自用户和其他服务请求的位置。This is where you can start receiving requests from users and other services.
@Override
protected List<ServiceInstanceListener> createServiceInstanceListeners() {
    ...
}

本教程将重点放在 runAsync() 入口点方法上。This tutorial focuses on the runAsync() entry point method. 可在其中立即开始运行代码。This is where you can immediately start running your code.

RunAsyncRunAsync

当服务实例已放置并且可以执行时,平台将调用此方法。The platform calls this method when an instance of a service is placed and ready to execute. 对于无状态服务,这表示打开服务实例的时间。For a stateless service, that means when the service instance is opened. 需要关闭服务实例时,将提供取消标记进行协调。A cancellation token is provided to coordinate when your service instance needs to be closed. 在 Service Fabric 中,服务的整个生存期内可能多次出现服务实例的这一打开-关闭循环。In Service Fabric, this open/close cycle of a service instance can occur many times over the lifetime of the service as a whole. 发生这种情况的原因多种多样,包括:This can happen for various reasons, including:

  • 系统可能会移动服务实例以实现资源平衡。The system moves your service instances for resource balancing.
  • 代码中发生错误。Faults occur in your code.
  • 应用程序或系统升级。The application or system is upgraded.
  • 基础硬件遇到中断。The underlying hardware experiences an outage.

Service Fabric 会管理此业务流程,以便保持服务的高度可用和适当均衡。This orchestration is managed by Service Fabric to keep your service highly available and properly balanced.

runAsync() 不应以同步方式阻止。runAsync() should not block synchronously. RunAsync 实现应返回 CompletableFuture,以允许运行时继续执行。Your implementation of runAsync should return a CompletableFuture to allow the runtime to continue. 如果工作负荷需要实现应在 CompletableFuture 内部完成的长时间运行任务。If your workload needs to implement a long running task that should be done inside the CompletableFuture.

取消Cancellation

取消工作负荷是一项由所提供的取消标记协调的协同操作。Cancellation of your workload is a cooperative effort orchestrated by the provided cancellation token. 系统会等任务结束后(成功完成、取消或出现故障)再执行下一步操作。The system waits for your task to end (by successful completion, cancellation, or fault) before it moves on. 当系统请求取消时,请务必接受取消标记,完成所有任务,并尽快退出 runAsync()It is important to honor the cancellation token, finish any work, and exit runAsync() as quickly as possible when the system requests cancellation. 以下示例演示如何处理取消事件:The following example demonstrates how to handle a cancellation event:

@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){}
        }
    });
}

在此无状态服务示例中,计数存储在本地变量中。In this stateless service example, the count is stored in a local variable. 不过,由于这是无状态服务,因此,所存储的值仅在其所在服务实例的当前生命周期中存在。But because this is a stateless service, the value that's stored exists only for the current lifecycle of its service instance. 当服务移动或重新启动时,值就会丢失。When the service moves or restarts, the value is lost.

创建有状态服务Create a stateful service

Service Fabric 引入了一种新的有状态服务。Service Fabric introduces a new kind of service that is stateful. 有状态服务能够可靠地在服务本身内部保持状态,并与使用它的代码共置。A stateful service can maintain state reliably within the service itself, co-located with the code that's using it. Service Fabric 无需将状态保存到外部存储,便可实现状态的高可用性。State is made highly available by Service Fabric without the need to persist state to an external store.

要将计数器值从无状态转换为即使在服务移动或重新启动时仍高度可用并持久存在,你需要有状态服务。To convert a counter value from stateless to highly available and persistent, even when the service moves or restarts, you need a stateful service.

在 HelloWorld 应用程序所在的同一个目录中,可通过运行 yo azuresfjava:AddService 命令添加新服务。In the same directory as the HelloWorld application, you can add a new service by running the yo azuresfjava:AddService command. 选择“可靠的有状态服务”作为框架,并将服务命名为“HelloWorldStateful”。Choose the "Reliable Stateful Service" for your framework and name the service "HelloWorldStateful".

应用程序现在应该有两个服务:无状态服务 HelloWorld 和有状态服务 HelloWorldStateful。Your application should now have two services: the stateless service HelloWorld and the stateful service HelloWorldStateful.

有状态服务具有与无状态服务相同的入口点。A stateful service has the same entry points as a stateless service. 主要区别是是否有能够可靠地存储状态的状态提供程序。The main difference is the availability of a state provider that can store state reliably. Service Fabric 附带名为 Reliable Collections 的状态提供程序实现,可让你通过 Reliable State Manager 创建复制的数据结构。Service Fabric comes with a state provider implementation called Reliable Collections, which lets you create replicated data structures through the Reliable State Manager. 有状态可靠服务默认使用此状态提供程序。A stateful Reliable Service uses this state provider by default.

打开 HelloWorldStateful -> src 中的 HelloWorldStateful.java,该文件包含以下 RunAsync 方法:Open HelloWorldStateful.java in HelloWorldStateful -> src, which contains the following RunAsync method:

@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());
            }
        });
    });
}

RunAsyncRunAsync

RunAsync() 在有状态服务和无状态服务中的运行方式类似。RunAsync() operates similarly in stateful and stateless services. 只不过在有状态服务中,平台将先代表用户执行额外的工作,再执行 RunAsync()However, in a stateful service, the platform performs additional work on your behalf before it executes RunAsync(). 这项工作可能包括确保可靠状态管理器和可靠集合随时可供使用。This work can include ensuring that the Reliable State Manager and Reliable Collections are ready to use.

可靠集合与可靠状态管理器Reliable Collections and the Reliable State Manager

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

ReliableHashMap 是一种字典实现,可用于将状态可靠地存储在服务中。ReliableHashMap is a dictionary implementation that you can use to reliably store state in the service. 利用 Service Fabric 和 Reliable HashMaps,可以将数据直接存储在服务中而无需外部持久性存储。With Service Fabric and Reliable HashMaps, you can store data directly in your service without the need for an external persistent store. Reliable HashMaps 可让数据具备高可用性。Reliable HashMaps make your data highly available. Service Fabric 通过创建和管理服务的多个 副本 来实现此目的。Service Fabric accomplishes this by creating and managing multiple replicas of your service for you. 它还提供一个抽象 API,消除了管理这些副本及其状态转换所存在的复杂性。It also provides an API that abstracts away the complexities of managing those replicas and their state transitions.

Reliable Collections 可以存储任何 Java 类型(包括自定义类型),但需要注意以下几点:Reliable Collections can store any Java type, including your custom types, with a couple of caveats:

  • Service Fabric 通过跨节点复制状态,使状态具备高可用性;而 Reliable HashMap 会将数据存储到每个副本上的本地磁盘中。Service Fabric makes your state highly available by replicating state across nodes, and Reliable HashMap stores your data to local disk on each replica. 这意味着 Reliable HashMaps 中存储的所有内容都必须可序列化。This means that everything that is stored in Reliable HashMaps must be serializable.

  • 在 Reliable HashMaps 上提交事务时,将复制对象以实现高可用性。Objects are replicated for high availability when you commit transactions on Reliable HashMaps. 存储在 Reliable HashMaps 中的对象保留在服务的本地内存中。Objects stored in Reliable HashMaps are kept in local memory in your service. 这意味着对对象进行本地引用。This means that you have a local reference to the object.

    切勿转变这些对象的本地实例而不在事务中的可靠集合上执行更新操作。It is important that you do not mutate local instances of those objects without performing an update operation on the reliable collection in a transaction. 这是因为对对象的本地实例的更改不会自动复制。This is because changes to local instances of objects will not be replicated automatically. 必须将对象重新插回字典中,或在字典上使用其中一个更新方法。You must reinsert the object back into the dictionary or use one of the update methods on the dictionary.

可靠状态管理器自动管理 Reliable HashMaps。The Reliable State Manager manages Reliable HashMaps for you. 无论何时何地,都可以根据名称向可靠状态管理器请求服务中的某个可靠集合。You can ask the Reliable State Manager for a reliable collection by name at any time and at any place in your service. 可靠状态管理器可确保能取回引用。The Reliable State Manager ensures that you get a reference back. 建议不要将可靠集合实例的引用保存到类成员变量或属性中。We don't recommend that you save references to reliable collection instances in class member variables or properties. 请特别小心,确保在服务生命周期中始终将引用设置为某个实例。Special care must be taken to ensure that the reference is set to an instance at all times in the service lifecycle. 可靠状态管理器会代为处理此工作,且已针对重复访问对其进行优化。The Reliable State Manager handles this work for you, and it's optimized for repeat visits.

事务和异步操作Transactional and asynchronous operations

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());
    }
});

Reliable HashMaps 上的操作是异步的。Operations on Reliable HashMaps are asynchronous. 这是因为可靠集合的写入操作执行 I/O 操作,以将数据复制并保存到磁盘。This is because write operations with Reliable Collections perform I/O operations to replicate and persist data to disk.

Reliable HashMap 操作是事务性的,因此可以跨多个 Reliable HashMaps 和操作保持状态一致。Reliable HashMap operations are transactional, so that you can keep state consistent across multiple Reliable HashMaps and operations. 例如,可以在单个事务中,从一个可靠字典中获取工作项、对其执行操作并将结果保存在另一个 Reliable HashMap 中。For example, you may get a work item from one Reliable Dictionary, perform an operation on it, and save the result in another Reliable HashMap, all within a single transaction. 事务被视为基本操作,它可以保证整个操作要么成功,要么回滚。This is treated as an atomic operation, and it guarantees that either the entire operation will succeed or the entire operation will roll back. 如果将项取消排队之后、保存结果之前发生错误,则会回滚整个事务,并将该项保留在队列中待处理。If an error occurs after you dequeue the item but before you save the result, the entire transaction is rolled back and the item remains in the queue for processing.

构建应用程序Build the application

Yeoman 基架包含用于生成应用程序的 gradle 脚本,以及用于部署和删除应用程序的 bash 脚本。The Yeoman scaffolding includes a gradle script to build the application and bash scripts to deploy and remove the application. 若要运行应用程序,请先使用 gradle 构建应用程序:To run the application, first build the application with gradle:

$ gradle

这会生成可以使用 Service Fabric CLI 部署的 Service Fabric 应用程序包。This produces a Service Fabric application package that can be deployed using Service Fabric CLI.

部署应用程序Deploy the application

生成应用程序后,可以将其部署到本地群集。Once the application is built, you can deploy it to the local cluster.

  1. 连接到本地 Service Fabric 群集。Connect to the local Service Fabric cluster.

    sfctl cluster select --endpoint http://localhost:19080
    
  2. 运行模板中提供的安装脚本可将应用程序包复制到群集的映像存储区、注册应用程序类型和创建应用程序实例。Run the install script provided in the template to copy the application package to the cluster's image store, register the application type, and create an instance of the application.

    ./install.sh
    

部署生成的应用程序时,其方式与部署任何其他 Service Fabric 应用程序相同。Deploying the built application is the same as any other Service Fabric application. 如需详细的说明,请参阅相关文档,了解如何使用 Service Fabric CLI 管理 Service Fabric 应用程序See the documentation on managing a Service Fabric application with the Service Fabric CLI for detailed instructions.

这些命令的参数可以在应用程序包内的生成清单中找到。Parameters to these commands can be found in the generated manifests inside the application package.

应用程序部署完以后,请打开浏览器并导航到 Service Fabric Explorer,其地址为 http://localhost:19080/ExplorerOnce the application has been deployed, open a browser and navigate to Service Fabric Explorer at http://localhost:19080/Explorer. 然后,展开“应用程序”节点,注意现在有一个条目是用于应用程序类型,另一个条目用于该类型的第一个实例。Then, expand the Applications node and note that there is now an entry for your application type and another for the first instance of that type.

重要

必须将证书配置为向 Service Fabric 运行时验证应用程序,才能将应用程序部署到 Azure 中的安全 Linux 群集。To deploy the application to a secure Linux cluster in Azure, you need to configure a certificate to validate your application with the Service Fabric runtime. 这样做可允许 Reliable Services 服务与基础 Service Fabric 运行时 API 通信。Doing so enables your Reliable Services services to communicate with the underlying Service Fabric runtime APIs. 若要了解详细信息,请参阅将 Reliable Services 应用程序配置为在 Linux 群集上运行To learn more, see Configure a Reliable Services app to run on Linux clusters.

后续步骤Next steps