Hatena::ブログ(Diary)

かれ3  かれこれ3個目のブログ このページをアンテナに追加

2013-12-15

CDP AdventCalendar 2014 - Rutherford Scaling Pattern

はじめに

CDP Advent Calendar 2014 の15日目です。

昨日はなぜか二人いたようで

@ijin さんの

Autoscalingによる自動復旧(Immutable Infrastucture)

http://ijin.github.io/blog/2013/12/14/self-healing-with-non-elb-autoscaling2/

Takuro Sasakiさんの

AutoScalingやインスタンス障害に強い 〜Stateless Serverパターン〜

http://d.hatena.ne.jp/dkfj/20131214/1387023080

の二本立てでお送りいたしました。

AutoScalingが続いているので、今日もAutoScalingでいきます。



解決したい課題

サーバをスケーリングさせるときにはいつも決まった大きさのサーバが立ち上がってしまうのが通常である。最も小さいサイズのサーバを立ち上げることで、細かいスケールアウトが出来なくは無いが、急激な負荷の上昇などの対応には遅れてしまう。

急激な負荷にも対応しつつ、細かい負荷の上昇にもコスト効率よくスケーリングしていきたい。


クラウドでの解決/パターンの説明

実装するにあたって、一つのサーバ群に対して複数のスケーリングの設定をすることで解決する。

しかし、同じ設定をしてしまうと、大きなサーバ、小さなサーバが同時に立ち上がり、同時に落ちてしまうので、コスト効率は悪くなる。

そのため、トリガーとなる値のthresholdやperiodやトリガーとなるものを変更しながら、複数のスケーリングの設定をする。

例えば、CPU使用率でのスケーリング、IOでのスケーリング、CPU使用率でも、60%でのスケーリング、80%のスケーリングなど。

構造

f:id:tottokug:20131215224849p:image:w360

一つのアプリケーションに対して複数のAutoScalingがいます。

Webアプリケーションであれば、一つのELBに対して複数のAutoScaling、

非同期なアプリケーションでアレば、SQSやSWFのWorkerが複数のAutoScalingによって立ち上がるようになります。

えっ? ElasticSearchってAWSのサービスじゃなかったんですか!?|アドカレ2013 : CFn #10で紹介されているCloudFormationはこのパターンとCustomize Scaling Metrics Patternを組み合わせて使用しています。

一つのELBに対して、CPUでのスケーリングと、Disk容量でのスケーリングをしています。

利点

利点として、一番大きいのは、細かい単位でのスケーリングができるので、うまくしきい値トリガーを調整することで、

コスト効率を最大化することが出来る。

また、スケーリングの設定が複数あるので、必ずしも全てのスケーリングの設定が恒久的なものでなくても良い。例えば、スケーリングの設定のうち一部を入札性のサーバを利用することによって、更にコスト効率をあげることが出来る。


注意点

複数のスケーリングの設定をするため、それらが同一のクラスタであり、最低限の台数を確保するために調整が必要である。

もしロードバランサーを使用するのであれば、全てを同一のロードバランサーにぶら下げるなどし、サーバ群全体のサーバ数を管理出来るようにするのが望ましい。

ある一定の規模になってくれば、この注意点は気にしなくても良い。

最後に

この構造は頭の中でなんとなく、

ラザフォードの原子模型のようにひとつの原子核に対して電子が周りを回っているイメージと

SWF,SQS,ELBの周りをEC2が複数のリングによって回っているイメージが重なったので、

このデザインパターンには

Rutherford Scaling Patternと付けたいと思います。

このCDPのイメージ

f:id:tottokug:20131215224848j:image:w360

Shadeのデータも公開します。

Download

2013-12-07

AWS Elastic Beanstalkで Amazon SWFのWorker Deciderを動かす #jawsug

JAWS-UG Advent Calendar 7日目です。

昨日は高校生でも使えるAWSでした。

今日はみんなが大好きで仕方ないAmazon SimpleWorkflowService(以下SWF)のWorkerとDeciderをAWS Elastic Beanstalk(以下Beanstalk)で動かす方法についてです。

SWFといえば、分散処理をするために非常に便利なもので、Amazon SQSに比べて正確な制御がし易いという特徴を持っています。

しかしSWFでもめんどくさいところがあります。

それは新しいバージョンのデプロイの作業です。

SWFでは各ActivityとWorkflowにバージョンを持っていて、ExternalClientによって呼び出されるバージョンと、Workflowのバージョンが合っていないと実行されなかったり、当然WorkflowからActivityを呼び出すときもClientのバージョンと実装のバージョンが合っていなくても実行されません。


この管理は意外に手間で、キチンと管理してあげないとサービスが全て止まってしまうなんて事もありえます。

そこで、この管理の手間を簡単に、かつ完璧にこなすためにElastic Beanstalkを使ってみようと思います。

Beanstalkを使うことで、SWFアプリケーションのバージョンを上げるときも、新しいEnvironmentを作りそちらにデプロイすることで、新旧両方のアプリケーションを同時稼働させる事が可能です。

そして旧バージョンでのExecutionがなくなったタイミングで古いEnvironmentをTerminateするだけで良いのです。

SWFアプリケーションはBeanstalkで動かすのが楽ちんでよいです。


というわけで、BeanstalkでSWFアプリケーションを動かすための方法を紹介しますが、

BeanstalkでSWFアプリケーションを動かす方法は、いくつか有ります。

jsvcを使う方法。

Tomcatの中でThreadとして動かしてしまう方法

色々なやり方があるとは思いますが、今回は.ebextensionsとかもあまりいじらなくても良い

なんか無駄遣いしているような気がしますが、、、Tomcatの中で起動してしまう方法でやってみます。

ActivityとDeciderを動かすものを普通に作る。

SWFで普段動かしているのと同じような感じで作れば大丈夫です。

私はThreadを継承したAbstractなものを一つ作ってDeciderとActivityとそれぞれが継承してやるのが好きなので、こんな感じで作っています。


package com.tottokug.host;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.WorkerBase;
import com.tottokug.common.Config;

public abstract class AbstractHost extends Thread {
  static Logger logger = LoggerFactory.getLogger(AbstractHost.class);
  AmazonSimpleWorkflow swfService;
  WorkerBase worker;
  boolean test;
  boolean halt_;
  static final String TEST_TASKLIST = "JUNIT";

  String taskList = null;

  public AbstractHost() {
    this.test = false;
    this.init();
  }

  public AbstractHost(boolean test) {
    this.test = test;
    this.init();
  }

  protected void init() {
    this.swfService = new AmazonSimpleWorkflowClient(new ClasspathPropertiesFileCredentialsProvider());
    this.activeTaskList = new ArrayList<String>();
    this.swfService.setRegion(Config.getRegion());
    this.halt_ = false;
    if (this.test) {
      this.taskList = TEST_TASKLIST;
    }
  }

  abstract protected WorkerBase getWorker();

  public void halt() {
    this.halt_ = true;
    interrupt();
  }

  public abstract void destroy();

  protected abstract List<String> getActiveTaskLists();

  List<String> activeTaskList;

  @Override
  public void run() {
    logger.info(this.getClass().getName() + " starting");
    this.worker = getWorker();
    this.worker.start();
    while (true) {
      try {
        sleep(1000);
      } catch (InterruptedException e) {
      }
      if (this.halt_()) {
        break;
      }
    }
  }

  protected void updateActiveTasklist() {

  }

  static enum TaskListStatus {
    STOP, START, STAY
  }

  protected Map<TaskListStatus, String> tasklistCompare(List<String> newer, List<String> older) {
    Map<TaskListStatus, String> tasklistStatuses = new HashMap<AbstractHost.TaskListStatus, String>();
    return tasklistStatuses;
  }

  protected boolean halt_() {
    if (this.halt_) {
      logger.info("halt signal received");
      logger.info("shutting down worker thread. wating 1000 ms ...");
      try {
        this.worker.shutdownAndAwaitTermination(30000, TimeUnit.MILLISECONDS);
      } catch (InterruptedException e) {
        this.worker.shutdown();
      }
      logger.info("shutting down host");
      return true;
    }
    return false;
  }
}

JUnitでテストする時もJUnitの中でこのHostを立ち上げるので、それ用にタスクリストを分けていたりします。


package com.tottokug.host;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.WorkerBase;
import com.tottokug.common.Config;

public abstract class AbstractHost extends Thread {
  static Logger logger = LoggerFactory.getLogger(AbstractHost.class);
  AmazonSimpleWorkflow swfService;
  WorkerBase worker;
  boolean test;
  boolean halt_;
  static final String TEST_TASKLIST = "JUNIT";

  String taskList = null;

  public AbstractHost() {
    this.test = false;
    this.init();
  }

  public AbstractHost(boolean test) {
    this.test = test;
    this.init();
  }

  protected void init() {
    this.swfService = new AmazonSimpleWorkflowClient(new ClasspathPropertiesFileCredentialsProvider());
    this.activeTaskList = new ArrayList<String>();
    this.swfService.setRegion(Config.getRegion());
    this.halt_ = false;
    if (this.test) {
      this.taskList = TEST_TASKLIST;
    }
  }

  abstract protected WorkerBase getWorker();

  public void halt() {
    this.halt_ = true;
    interrupt();
  }

  public abstract void destroy();

  protected abstract List<String> getActiveTaskLists();

  List<String> activeTaskList;

  @Override
  public void run() {
    logger.info(this.getClass().getName() + " starting");
    this.worker = getWorker();
    this.worker.start();
    while (true) {
      try {
        sleep(1000);
      } catch (InterruptedException e) {
      }
      if (this.halt_()) {
        break;
      }
    }
  }

  protected void updateActiveTasklist() {

  }

  static enum TaskListStatus {
    STOP, START, STAY
  }

  protected Map<TaskListStatus, String> tasklistCompare(List<String> newer, List<String> older) {
    Map<TaskListStatus, String> tasklistStatuses = new HashMap<AbstractHost.TaskListStatus, String>();
    return tasklistStatuses;
  }

  protected boolean halt_() {
    if (this.halt_) {
      logger.info("halt signal received");
      logger.info("shutting down worker thread. wating 30000 ms ...");
      try {
        this.worker.shutdownAndAwaitTermination(30000, TimeUnit.MILLISECONDS);
      } catch (InterruptedException e) {
        this.worker.shutdown();
      }
      logger.info("shutting down host");
      return true;
    }
    return false;
  }

}


こんな感じで出来たら後は簡単で、Servletのロード時にこれがスレッドで立ち上がれば良いのです。

Servlet作る

次に適当にServlet作ります。

Servletのinitメソッドで無理やりThreadを立ち上げてみます。

public class WorkerStarter extends HttpServlet {
 // 略
 ActivityHost a = new ActivityHost();
 DeciderHost d = new DeciderHost();
   
 public void init(ServletConfig config) throws ServletException {
    a.start();
    d.start();
  }
  
  public void destroy() {
    a.interrupt();
    d.interrupt();
  }
  // 略  
}

後はこのServletTomcat起動時に自動的に読み込まれるようにすればOKです。

load-on-startupを書いておくことで、アクセスがなくてもロードされます。

<servlet>
	<description></description>
	<display-name>WorkerStarter</display-name>
	<servlet-name>WorkerStarter</servlet-name>
	<servlet-class>com.tottokug.WorkerStarter</servlet-class>
	<load-on-startup>3</load-on-startup>
</servlet>

デプロイ

必要なライブラリ類もWEB-INF/libに入れて、設定ファイル、log系の設定なんかも詰め込んでwarにして、

Elastic Beanstalkにデプロイすれば、ElasticBeanstalkでSWFアプリケーションが動き始めます。

ログの出し方だけ、気をつけてあげましょう。catalina.outに出してしまうのが楽かと思います。



さいごに

試してはいませんが、全く同じ方法でKinesis Applicationも動かせるはずです。

後は今のままではAutoScalingがまともに動かないはずなので、別のAutoScalingに差し替えませう。

SWFアプリケーションを運用する上で手間である新旧並行稼動、旧バージョンの終了等がElastic Beanstalkで動かす事によってすごくやりやすくなります。

まさにBeanstalkのEnvironmentとVersion管理機能はSWFにぴったりの機能です。

SWFを使っている人はBeanstalkでの管理をして、

今Beanstalkを使っている人はWebアプリケーションだけでなくSWFのWorkerたちも載せてみると面白いと思います。

今度もし、機会があったら、無理やりTomcat上で動かすではなく、

jsvcで動かす方法を.ebextensionと一緒に紹介するかもしれません。

2013-11-07

JavaScriptで次のページを読みに行く

Scrapingの過程で次のページに遷移したいとき


var client = e.createMitsubachiClient();
var request = new Mitsubachi.HttpFetchRequest();
// 遷移先のURLを指定。 ハードコーディングじゃなくその場で取得したURLももちろん可能
request.setUrl("http://example.com/foo/bar/index?hoge=fuga");
// 遷移先のパージをscrapingするスクリプト
request.setScriptName('example.js');
client.httpFetch(request);

公式のドキュメントに載ってない。

http://docs.mushikago.org/mitsubachi/scripting/javascript/index.html

2012-12-13 AWS Advent Calendar

SQSってなんじゃ?(SQSに関する考察)

はじめに

AWS初心者ですが、わからないなりに、SQSという物を調べてみました。

特に重複について


先日SQSのアップデートがあったみたいです。

エヴァンジェリスト 堀内さんのブログにはものすごく大事な事が書かれていました。

”重要な副作用として、ロングポーリングはメッセージのための全てのSQSホストをチェックします(定期的なポーリングがサブセットをチェックします)。ロングポーリングが空のメッセージセットを返すことにより、未処理のメッセージがキューに存在しないことが確認できます。”

これは。。。

SQSの重複を緩和できる可能性が出てきました。


ここからは初心者ながら知ったかぶって書くことにします。


SQSの重複発生の理由の想像

そもそもSQSでなぜ重複が起こっていたかというと、

VisivilityTimeoutになる前にreceiveしてしまったからでは無いかと考えられます。

マイスターシリーズで公開された資料を見てください。

f:id:tottokug:20121213065739p:image



この図の中で注目すべき所はVisibilityTimeoutの青色の左端の所です。



f:id:tottokug:20121213065740p:image

この赤いまるで囲んだ部分で、ReceiveからVisibilityTimeoutまでの時間が0では無いのではないかと考えられます。

とても短い間隔で2回Receiveしてしまうと、VisibilityTimeoutの情報が伝搬する前に取得する事ができてしまうから重複が発生するはずです。

1つのクライアントからではありえない間隔だとしても、複数のクライアントがあった場合は、どんなタイミングでreceiveするかなんてわかりません。

ここを拡大すると、こうなってます。

f:id:tottokug:20121213065738p:image

receiveしてからVsibilityTimeoutとなるまでの間に重複発生時間が有ると推測できます。


なんで、ここに重複発生時間が存在するのかというところですが、SQSがサービスとして提供していて、堅牢性を確保する為に、

一台のマシンで処理しているのではないからです。


1つのQueueに対しても複数台のホストを使って、メッセージを保存しているはずです。


SQSのホストたちはメッセージをreceiveされると、receiveされたことを他のサブセットに含まれるノードにお知らせしなくてはいけないはずなので、

そのタイムラグが重複発生時間の原因になると考えられます。



この辺りの説明は

Amazon.com CTO Wernerのブログ

を見たらより理解出来ると思います。

続きを読む