LoginSignup
0
1

More than 5 years have passed since last update.

Azure Service Fabric を使う(3)Reliable Actor

Last updated at Posted at 2016-11-29

今までの Azure 関連の記事は以下。

Azure Container Serviceを使う(1) 基本のデプロイ編
Azure Container Serviceを使う(2) 自動化してみる編
Azure Service Facricを使う(1) 利用するための環境を揃える
Azure Service Facricを使う(2) Azure Service Fabric を使う(2)Stateless Service

前回のReliable Stateless Serviceが思いの外重量級だったので、今回はライトにいきます。

Reliable Actor

Reliable Actor とは、Microsoft Azureにおいて以下のように説明されています。

アクターは、シングル スレッド実行のコンピューティングと状態の、分離されて独立したユニットです。アクター パターンは、同時実行システムまたは分散システム用のコンピューティング モデルです。このモデルでは、これらの多数のアクターが同時に、互いに独立して実行されます。アクターは、互いに通信することができ、さらにアクターを作成することができます。
https://docs.microsoft.com/ja-jp/azure/service-fabric/service-fabric-reliable-actors-introduction

Reliable Serviceの特徴に、一般的なServiceではなく、アクターモデルを組み合わせたもの、ということのようです。アクターモデルに合うようなアプリケーションも、Fabricの中でまとめて動かせるってことが利点ですね。

Azureのドキュメントにも書いていますが、実体はReliable Stateful Serviceです。なので、基本的に状態をReliable Actorランタイム内に保存することが出来ます。Actorは基本的に外部と通信しないほうがいいとされています。Actor同士の連携や、外部からのActorの呼び出しは、全てInterfaceを介して呼び出す必要があります。

すごい細かい処理が大量にあったり、関連のあんまりない独立した処理とかが、アクターモデルが適合するものとされています。

Reliable Actorを作ってみる

では早速作っていきましょう。前回のVagrant & Java環境を前提とします。

Vagrant上の任意のディレクトリで、以下のコマンドを実行します。

$ yo azuresfjava

色々聞かれますが、 Reliable Actor Service を選んでいれば、後はだいたい何でもいいかと思います。ここでは、 Application: HelloActorActor service: SampleActor という名前にしたという前提で進めます。

生成すると、こんな感じのディレクトリ構造ができます。

HelloActor
|--.yo-rc.json
|--HelloActor
| |--ApplicationManifest.xml
| |--SampleActorPkg
| | |--Code
| | | |--_readme.txt
| | | |--entryPoint.sh
| | |--Config
| | | |--Settings.xml
| | | |--_readme.txt
| | |--Data
| | | |--_readme.txt
| | |--ServiceManifest.xml
|--SampleActor
| |--build.gradle
| |--settings.gradle
| |--src
| | |--reliableactor
| | | |--SampleActorHost.java
| | | |--SampleActorImpl.java
|--SampleActorInterface
| |--build.gradle
| |--src
| | |--reliableactor
| | | |--SampleActor.java
|--SampleActorTestClient
| |--build.gradle
| |--settings.gradle
| |--src
| | |--reliableactor
| | | |--test
| | | | |--SampleActorTestClient.java
| |--testclient.sh
|--build.gradle
|--install.sh
|--settings.gradle
|--uninstall.sh

この中の SampleActor ディレクトリが、実際のReliable Actorです。Reliable Serviceと異なり、テストクライアントと、SampleActorInterfaceという見慣れないディレクトリがあるのが特徴的です。

Gradleでインポートします。コンパイルエラーが発生しないようにする設定は、前回を参照してください。ただし、今回はSampleActorInterfaceのbuild.gradleも編集する必要があります。概要は一緒なので、割愛します。

Reliable Actorの構成要素

Reliable Actorは、以下の要素で構築されています。

  • Actor interface
  • Actor service
  • Actor registration
  • Actor proxy

Actor interface

Reliable Actorは、基本的に外部へ公開するためのInterfaceを定義する必要があります。無ければ無いで動かせますが、外部から呼び出すことが出来ませんので、定義するのが基本です。

package reliableactor;

import java.util.concurrent.CompletableFuture;

import microsoft.servicefabric.actors.Actor;
import microsoft.servicefabric.actors.Readonly;

public interface SampleActor extends Actor {
    @Readonly   
    CompletableFuture<String> getResultAsync();

    CompletableFuture<?> plus(int num);

    CompletableFuture<?> minus(int num);

    CompletableFuture<?> multiply(int num);

    CompletableFuture<?> divide(int num);
}

今回は物凄いシンプルな電卓Actorにしてみます。 @Readonly アノテーションが付いているメソッドは、実装する上で状態を変化させないように求められます。(が、アノテーションなのでコンパイルエラーとかにはなりません)

Actor service

Actor serviceは、Actor interfaceの実装です。こいつがActor Id毎に複数存在することになります。

package reliableactor;

import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;

import microsoft.servicefabric.actors.ActorServiceAttribute;
import microsoft.servicefabric.actors.ReliableActor;
import microsoft.servicefabric.actors.StatePersistence;
import microsoft.servicefabric.actors.StatePersistenceAttribute;

@ActorServiceAttribute(name = "SampleActorService")
@StatePersistenceAttribute(statePersistence = StatePersistence.Persisted)
public class SampleActorImpl extends ReliableActor implements SampleActor {
    private Logger logger = Logger.getLogger(this.getClass().getName());

    @Override
    protected CompletableFuture<?> onActivateAsync() {
        logger.log(Level.INFO, "onActivateAsync");

        return this.stateManager().tryAddStateAsync("result", 0);
    }

    @Override
    public CompletableFuture<Integer> getResultAsync() {
        return this.stateManager().getStateAsync("result");
    }

    @Override
    public CompletableFuture<?> plus(int num) {
        this.stateManager().addOrUpdateStateAsync("result", num, (key, v) -> v + num);
        return CompletableFuture.runAsync(() -> {});
    }

    @Override
    public CompletableFuture<?> minus(int num) {
        this.stateManager().addOrUpdateStateAsync("result", num, (key, v) -> v - num);
        return CompletableFuture.runAsync(() -> {});
    }

    @Override
    public CompletableFuture<?> multiply(int num) {
        this.stateManager().addOrUpdateStateAsync("result", num, (key, v) -> v * num);
        return CompletableFuture.runAsync(() -> {});
    }

    @Override
    public CompletableFuture<?> divide(int num) {
        this.stateManager().addOrUpdateStateAsync("result", num, (key, v) -> v / num);
        return CompletableFuture.runAsync(() -> {});
    }
}

物凄いシンプルです。演算順序とかゼロ除算とか何も気にしません。

Actor serviceでは、 ActorServiceAttributeStatePersistenceAttribute アノテーションが重要になります。特に ActorServiceAttribute が重要で、後で出てくるActor proxyを使って呼び出すときに利用します。

ActorにおけるStateは、 this.stateManager() 経由で操作できます。原則として全て非同期を前提とした実装にする必要があります。何故かというと、ActorのStateは、複数のNodeにレプリケーションされて保存されるため、その処理は本質的に非同期になるからです。

Actor registration

Reliable Actorは、ActorRuntime(こいつ自体はReliable Serviceの中で管理されています)に、Actor serviceを登録しないと、外部から呼び出すことが出来ません。

package reliableactor;

import java.time.Duration;

import java.util.logging.Level;
import java.util.logging.Logger;

import microsoft.servicefabric.actors.ActorRuntime;
import microsoft.servicefabric.actors.ActorServiceImpl;

public class SampleActorHost {

private static final Logger logger = Logger.getLogger(SampleActorHost.class.getName());

    public static void main(String[] args) throws Exception {

        try {
            ActorRuntime.registerActorAsync(SampleActorImpl.class, (context, actorType) -> new ActorServiceImpl(context, actorType, ()-> new SampleActorImpl()), Duration.ofSeconds(10));
            Thread.sleep(Long.MAX_VALUE);
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Exception occured", e);
            throw e;
        }
    }
}

テンプレートから生成されたmainはこんな形になります。 ActorRuntime.registerActorAsync で、このServiceが管理するActorのInterfaceを登録します。複数のActorを登録することも出来ます。

Actor proxy

Actorは、基本的にHTTPとかから呼び出すことが出来ません。じゃあどうするのか、という問いの答えがActor Proxyになります。これのサンプルもテンプレートに含まれています。


public class SampleActorTestClient {

    public static void main(String[] args) throws URISyntaxException, InterruptedException, ExecutionException {
        SampleActor actorProxy = ActorProxyBase.create(new ActorId("From Actor 1"), new URI("fabric:/HelloActor/SampleActorService"), SampleActor.class);
        // ...
    }
}

ActorProxyBase.create を利用することで、指定したインターフェースのActorを取得することが出来ます。Actor IDが同一であれば、状態を共有したActorに対するProxyオブジェクトを使って操作を行うことが出来ます。

サンプルだとコマンドラインアプリケーションとして実装してますが、実際だと他のReliable Serviceから呼び出される形になるんじゃないかと思います。

インストール

Reliable Serviceと一緒です。この辺の課題はReliable Serviceと変わらないですね。

まとめ

Reliable Actorを実装してみました。Stateを扱うメソッドについては、〜Asyncというsuffixがついていないメソッドもあり、そちらの挙動も気になるところですが、いかんせんJavadocもないので、違いについてはなんとなくで察するしかないっていうのが切ないところです。

Actorの実装自体は、ActorRuntimeが管理するライフサイクルなど、Fabric特有の制約はありますが、かなりシンプルに作ることが出来るかと思います。他のJVM言語でも結構簡単に作れると思うので、Kotlinとかそういうもので作ってみるのも面白そうです。

次回は、Reliable Actorのバックエンドでもある、Reliable Stateful Serviceを実装してみようと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1