20200114のGoに関する記事は3件です。

Goでワーカープールを15分で実装する方法

こちらの記事は、Joseph Livni氏により2018年10月に公開された『 Write a Go Worker Pool in 15 minutes 』の和訳です。
本記事は原著者から許可を得た上で記事を公開しています。


1_ugshDOhXfC287WWhG4IfSA.jpeg

私は多くのユーザーリクエストを高速に処理するGoのサービスを構築していました。Goroutineのプールを使ってカプセル化することにより、パフォーマンスは20倍になりました。本記事では独自のワーカープールを作る方法を説明します。1

最終的な結果を知りたい場合は こちら からダウンロードしてください。2

本記事は以下の順番で説明します。

  • モックとなるジョブの作成
  • ワーカープールの作成
  • ベンチマークテストの実施

ファイル構造は以下です。

/go_worker_pool
    /work
        work.go
    /pool
        worker.go
        dispatcher.go
    bench_test.go
    main.go

上記を構築するディレクトリパスは以下のとおりです。

go/src/github.com/Lebonesco/go_worker_pool

最終的には以下のようになります。

$ go run main.go
2018/10/06 15:53:43 starting application...
2018/10/06 15:53:43 starting worker:  1
2018/10/06 15:53:43 starting worker:  2
2018/10/06 15:53:43 starting worker:  3
2018/10/06 15:53:43 starting worker:  4
2018/10/06 15:53:43 starting worker:  5
2018/10/06 15:53:43 creating jobs...
worker [2] - created hash [2376065843] from word [iCMRAjWw]
worker [4] - created hash [121297580] from word [xhxKQFDa]
worker [1] - created hash [3193224551] from word [XVlBzgba]
worker [3] - created hash [1481401259] from word [hTHctcuA]
worker [5] - created hash [166906897] from word [FpLSjFbc]
worker [5] - created hash [1752784812] from word [QYhYzRyW]
...

モックとなるジョブの作成

完了するまでに時間がかかる処理をシミュレートするために、ランダムな文字列を使って、たくさんのジョブを作成します。ジョブは何らかの処理を実行します。今回は文字列のハッシュを生成します。

work.go
package job

import (
    "fmt"
    "hash/fnv"
    "time"
    "math/rand"
    "os"
)

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

// create random string
func RandStringRunes(n int) string {
    b := make([]rune, n)
    for i := range b {
        b[i] = letterRunes[rand.Intn(len(letterRunes))]
    }
    return string(b)
}

// create list of jobs
func CreateJobs(amount int) []string {
    var jobs []string

    for i := 0; i < amount; i++ {
        jobs = append(jobs, RandStringRunes(8))
    }
    return jobs
}

// mimics any type of job that can be run concurrently
func DoWork(word string, id int) {
    h := fnv.New32a()
    h.Write([]byte(word))
    time.Sleep(time.Second)
    if os.Getenv("DEBUG") == "true" {
        fmt.Printf("worker [%d] - created hash [%d] from word [%s]\n", id, h.Sum32(), word)
    }
}

https://gist.github.com/Lebonesco/3d9758a2c248b004ea4c584796d53c75

DoWork()stringworker id を受け取って、文字列のハッシュを計算します。結果を出力する前に1秒間sleepします。ジョブの終了時に結果を表示するには、以下をセットします。

$ export DEBUG="true"

後ほどベンチマークテストをするときには、ハッシュの結果を表示することは不要であることと、テスト結果が見にくくなることから、制御できるようにしています。

ワーカープールの作成

次は、実際にワーカープールを作成していきましょう。ワーカープールは3つの要素で構成されます。dispatcher (ワーカーをインスタンス化し、ワーカーとワーカープールを接続します)と workers (処理を待っているジョブを受け取り、処理をします)と collector (ジョブの到着を待って、workers に割り当てます)です。

以下のコードで注意すべき重要な内容は WorkerChannel と Worker構造体の Start() メソッドです。

Goでよく知られているフレーズに “Do not communicate by sharing memory; instead, share memory by communicating.3 があります。これが WorkerChannel の目的であり、ワーカーとワーカープールを通信する方法です。WorkerChannel は利用できるすべてのワーカーのチャネルを保持します。ジョブが到着すると collectorWorkerChannel からワーカーチャネルを取得し、ジョブをそのチャネルに渡します。ワーカーがそのジョブを受け取ります。逆に、ワーカーは処理が終了するとそのチャネルを WorkerChannel に戻し、次のジョブを待ちます。

worker.go
package pool

import (
    "log"
    work "github.com/Lebonesco/go_worker_pool/work"
)

type Work struct {
    ID  int
    Job string
}

type Worker struct {
    ID int
    WorkerChannel chan chan Work // used to communicate between dispatcher and workers
    Channel chan Work
    End chan bool
}

// start worker
func (w *Worker) Start() {
    go func() {
        for {
            w.WorkerChannel <-w.Channel // when the worker is available place channel in queue
            select {
            case job := <-w.Channel: // worker has received job
                work.DoWork(job.Job, w.ID) // do work
            case <-w.End:
                return 
            }
        }
    }()
}

// end worker
func (w *Worker) Stop() {
    log.Printf("worker [%d] is stopping", w.ID)
    w.End <- true
}

https://gist.github.com/Lebonesco/3fc3bb1dde81bf6ac676640ea0e5abd0

すばらしい!終わりに近づいています。ワーカープールを仕上げるために dispatchercollector を完成させましょう。その前に、要素を組み立てるという点では、ワーカープールを設計する方法はたくさんあることを知っておいてください。

コードの可読性や実装方法に応じて、よりうまくいく方法があります。workers, dispatcher, collector の要素を用いて、あなたのプロジェクトに応用できるでしょう。たとえば collectordispatcher から分ける人もいます。私の場合 dispatcher から Collector 構造体を返します。独自のワーカープールを実装したい人にとって可読性が良いと考えているためです。4

dispatcher.go
package pool

import (
    "log"
)

var WorkerChannel = make(chan chan Work)

type Collector struct {
    Work chan Work // receives jobs to send to workers
    End chan bool // when receives bool stops workers
}

func StartDispatcher(workerCount int) Collector {
    var i int
    var workers []Worker
    input := make(chan Work) // channel to recieve work
    end := make(chan bool) // channel to spin down workers
    collector := Collector{Work: input, End: end}

    for i < workerCount {
        i++
        log.Println("starting worker: ", i)
        worker := Worker{
                ID: i,
                Channel: make(chan Work),
                WorkerChannel: WorkerChannel,
                End: make(chan bool)}
        worker.Start()
        workers = append(workers, worker) // stores worker
    }

    // start collector
    go func() {
        for {
            select {
            case <-end:
                for _, w := range workers {
                    w.Stop() // stop worker
                }
                return
            case work := <-input:
                worker := <-WorkerChannel // wait for available channel
                worker <-work // dispatch work to worker
            }
        }
    }()

    return collector
}

https://gist.github.com/Lebonesco/7e696ca0a7bd487cc909b10a1385cfcd

最後に、アプリケーションを動かすドライバーを作りましょう。main() 関数がワーカープールをインスタンス化し、ジョブを作成します。

main.go
package main 

import (
    "log"
    "github.com/Lebonesco/go_worker_pool/pool"
    work "github.com/Lebonesco/go_worker_pool/work"
)

const WORKER_COUNT = 5
const JOB_COUNT = 100

func main() {
    log.Println("starting application...")
    collector := pool.StartDispatcher(WORKER_COUNT) // start up worker pool

    for i, job := range work.CreateJobs(JOB_COUNT) {
        collector.Work <-pool.Work{Job: job, ID: i}
    }
}

https://gist.github.com/Lebonesco/3f725e96bdc86f71742e0b5929aa6dde

ベンチマークテストの実施

このワーカープールが実際に機能することを確認するために、簡単なベンチマークテストを動かしてみましょう。Goはテストフレームワークが組み込まれているため、とても簡単にできます。

bench_test.go
package main 

import (
    "testing"
    "github.com/Lebonesco/go_worker_pool/pool"
    work "github.com/Lebonesco/go_worker_pool/work"
)
func BenchmarkConcurrent(b *testing.B) {
    collector := pool.StartDispatcher(WORKER_COUNT) // start up worker pool

    for n := 0; n < b.N; n++ {
        for i, job := range work.CreateJobs(20) {
            collector.Work <-pool.Work{Job: job, ID: i}
        }
    }
}

func BenchmarkNonconcurrent(b *testing.B) {
    for n := 0; n < b.N; n++ {
        for _, job := range work.CreateJobs(20) {
            work.DoWork(job, 1)
        }
    }
}

https://gist.github.com/Lebonesco/9e4048e2ef7438af8fda81874850e6f7

それでは動かします。

$ go test -bench=.
starting worker:  1
starting worker:  2
starting worker:  3
starting worker:  4
starting worker:  5
goos: windows
goarch: amd64
pkg: tutorials/concurrent-limiter
BenchmarkConcurrent-4              1        3001744600 ns/op
BenchmarkNonConcurrent-4           1        20006911100 ns/op
PASS
ok      tutorials/concurrent-limiter    23.291s

結果はこのとおりです。ワーカープールによりパフォーマンスが大幅に良くなります。さらに、個々のジョブの所要時間が長くなり、動いているワーカー数が増えるほど、パフォーマンスがより良くなることがわかるでしょう。

私の記事を読んでくれてありがとう。

この記事がお役にたちましたら、教えてください???

さらに読みたい場合は、下の「フォロー」ボタンをクリックしてください。


  1. 訳注: ライブラリとして提供されているワーカープールとしては gammazero/workerpool などがあります 

  2. 訳注: done channelを用いてgoroutineを終了するようになっていますが、Go1.7からcontextが導入されたのでcontext.Done()を用いることもできます 

  3. https://blog.golang.org/share-memory-by-communicating 

  4. 訳注: その他の応用として、例えば Collector にバッファ付きチャネルを加えて、キューイングする処理を追加することもできます 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自作SORACOM InventoryのエージェントinventorydでIoTデバイスを簡単に遠隔操作する

はじめに

IoTデバイスの遠隔操作したくないですか?
したいに決まってますよね!
ということで今回お勧めするのはSORACOM Inventoryです。

1分でラズパイを遠隔再起動するよ

注意
すでにSORACOM Inventoryを使用中の場合、以下を実行すると費用が発生します。(初期費用100円、利用料金50円)
未使用の場合は150円の無料枠に入ります。


SORACOM Airで接続中のラズパイに入ってrootで以下のコマンドを実行しましょう。

curl -L -O https://github.com/1stship/inventoryd/releases/download/v0.0.1/inventoryd_0.0.1_linux_arm.tar.gz
tar zxf inventoryd_0.0.1_linux_arm.tar.gz
./inventoryd --init # 質問にはEnterのみでよい(yで回答したことになる)
echo "/sbin/reboot" > resources/3/0/4
./inventoryd -b

こんな感じで表示されればOKです。

2020/01/13 12:37:59 Start Bootstrap
2020/01/13 12:38:00 Request Bootstrap accepted
2020/01/13 12:38:01 Bootstrap finished
Bootstrap finish
2020/01/13 12:38:01 Registering...
2020/01/13 12:38:04 Register finished. Location is SKdsWwKWEq
2020/01/13 12:38:04 READ /1/0
2020/01/13 12:38:04 READ /2/0
2020/01/13 12:38:05 READ /3/0
2020/01/13 12:38:05 READ /4/0
2020/01/13 12:38:06 READ /5/0
2020/01/13 12:38:06 READ /6/0
2020/01/13 12:38:06 READ /7/0
2020/01/13 12:38:07 READ /8/0
2020/01/13 12:38:07 READ /9/0

SORACOMコンソールにログインして、SORACOM Inventoryのデバイス管理メニューに入ります。

スクリーンショット 2020-01-13 21.12.00.png

デバイスを選択して、詳細をクリックします。

スクリーンショット 2020-01-13 21.16.35.png

Reboot /3/0/4を探して、実行ボタンをクリックします。

スクリーンショット 2020-01-13 21.25.47.png

「コマンド実行」をクリックします。

スクリーンショット 2020-01-13 21.27.03.png

2020/01/13 12:38:58 EXECUTE /3/0/4
Connection to 192.168.2.3 closed by remote host.
Connection to 192.168.2.3 closed.

はい、ラズパイが再起動しました。やったね!完!

SORACOM Inventoryとは

完!でもよかったのですが、上でやったことは何なのか説明をします。

皆さん、SORACOM Inventory使ってますか?というかそもそも知ってますか?
ぶっちゃけ使ってない人多いんじゃないかと思います。Qiitaのキーワード0件でしたし。(記事自体はありました)

SORACOM Inventoryの公式の説明は以下の通り。

Inventory は、OMA LightweightM2M(LwM2M)をベースにしたデバイス管理のためのフレームワークを提供するサービスです。SORACOM Air と連携したデバイスの自動登録が可能です。

LwM2Mというプロトコルを使ってデバイスを管理するためのサービスです。LwM2Mで必要になるサーバはSORACOMにてフルマネージドで提供されています。そのため、自前でサーバやAWSなどのクラウドを用意することなく、デバイス管理や遠隔操作ができるようになります。

「I」のサービスである「Inventory」は、「H」のサービスである「Harvest」の次に出たサービスです。Harvest以前のサービスのBeamやFunnelは他のサービスと連携することが前提になっていましたが、他のクラウドサービスが必要なくSORACOMだけで完結できるHarvestはとっつきやすく、IoTの入り口として最適ですよね。そしてHarvestによってデータの蓄積や可視化(これはInventoryの翌年出たLagoonで強化される)ができるようになってくると、それをもとにデバイスに制御をかけたい、というのは当然の流れ、、そしてデバイス管理サービスのInventoryが登場します。これもSORACOMだけで完結できる、という点はHarvestと同じで、ユーザーの用意するものはデバイスとSORACOM Airの回線のみです。

使ってみると、デバイスのステータスを取得したり再起動したりということが、SORACOMのコンソールやAPI経由で簡単にできるようになるので、デバイス管理や遠隔操作にはとっても便利です。

でもちょっと大変なところがありまして、LwM2Mのエージェントを自分で作らなければならないんですよね。そのため導入のハードルが高いと思われがちです。(SORACOM HarvestのとりあえずTCP/UDPでデータ投げときゃOKからの落差がすごい)

そもそもLwM2Mって何?というところから始まって、WakaamaLeshanなどの参照実装や、懇切丁寧なREADMEのあるSORACOM Inventory agent for Javaはあるものの、これを読んで自分のデバイスに合わせてソースを改変してビルドするのはまあ大変です。なんか簡単に扱えるようにしたい。

SORACOM Inventoryのエージェントを自作しよう

というところで色々あって僕はSORACOM Inventoryには思い入れがあるので、なんかやろうかなと思いまして、扱いやすいSORACOM Inventoryのエージェントを自作することにしました。というか2019年のゴールデンウイークを丸ごと費やして作って、inventorydという名前でGitHubに公開していたのですが、当時はブログを書いたりしていなかったのでここで改めて紹介します。

inventoryd
SORACOM Inventory access tool in Golang
https://github.com/1stship/inventoryd

基本的な考えは以下の2つです。

・ビルド済のシングルバイナリで動作する
・リソース対応のためにリビルドが必要なく、ファイルの配置で対応できる

要はApache HTTP Server(httpd)と同じ感じで使えるようにしたい、ということです。名前もそれっぽくしています。

多くの人はWebサーバ(HTTPサーバ)を立てようとした時、HTTPサーバのソースを改変してビルドしたりしないと思うんですよね。大抵は、httpdやnginxをビルド済のパッケージをインストールして、公開用のディレクトリにファイルを置いたり、PHPなどのスクリプトファイルを置いたりするところから始めると思います。そんな感じで使えるものであれば、ソースコードを読んだり改変したりビルドしたりしない非開発者の人でもSORACOM Inventoryを使えるようになるかもしれない。

そんなものを目指して作りました。Go言語で書いているのはビルド済みのシングルバイナリで提供するのが一番ユーザー側に負担がかからず簡単に導入できるだろう、ということと、当時Go言語に興味があって何か作ってみようという思いがあったからです。(Go言語で書いた最初のプログラムなのでクオリティはお察し。。プロトタイプ的なものと思っていただければ)

inventorydの基本的な考え方

LwM2Mでは、デバイスへの問い合わせや書き込み、実行などをオブジェクトモデルというもので定義し、そのモデルをパスに割り当てています。

例えば先ほどの「Reboot /3/0/4」というのは、Deviceというモデルの仕様にObjectIDが3と定義されており、次の0は最初のリソースを表し、(同じ定義のモデルが複数ある場合は/3/1/4、3/2/4と2個目の数字が上がっていく)、その中のItem ID="4"としてRebootが以下のように記載されています。

<Item ID="4">
  <Name>Reboot</Name>
  <Operations>E</Operations>
  <MultipleInstances>Single</MultipleInstances>
  <Mandatory>Mandatory</Mandatory>
  <Type/>
  <RangeEnumeration/>
  <Units/>
  <Description>
    <![CDATA[ Reboot the LwM2M Device to restore the Device from unexpected firmware failure. ]]>
  </Description>
</Item>

従って、/3/0/4はDeviceというモデルの最初のリソースに対してRebootというExecuteを実行するもの、ということになります。HTTPのRESTのパスであれば、
POST /Device/0/Reboot
のようになっても良さそうなものですが、LwM2Mは通信が軽量なプロトコルを目指しており、上のような仕様がサーバ、デバイス間で共有されているものとして、番号で対象を伝えることになっています。

このパスと、デバイス内のファイルを一致させるというのがinventorydの基本的な考えです。

./inventoryd --init

を実行した時に、resourcesディレクトリが作成され、その中にはオブジェクトモデルに応じたリソースのファイルが作成されています。そしてこのファイルに対してREADするとそのファイルの中身が返り、WRITEするとファイルが書き換えられ、EXECUTEするとそのファイルがスクリプトとして実行される、という動作をします。

init直後はリソースはデフォルト値を返すだけのものになっており、あまり役に立ちません。このファイルを適切な値やスクリプトで置き換えたり、WRITEされた値を他のプログラムで使用するなどすることによって、ちゃんとしたデバイス管理ができることになります。最初の例では/3/0/4を/sbin/rebootとしましたが、これにより/3/0/4(LwM2Mで定義されたReboot)をラスパイの再起動(/sbin/reboot)と結びつけることができました。

このようなファイルによる対応付けであれば、CやJavaのコードを書いてビルドすることなくファイルの読み書きやシェルスクリプトの記載で対応できるので、比較的簡単に対応できそうですよね。

また、READやWRITEをファイル読み書きでは無く、スクリプトとして実行させたいという場合は、ファイル名の.read、.writeとつけて、実行権をもったスクリプトとすることで対応できるようにしています。

わかりやすいところで説明すると、Current Time /3/0/13はinit直後の状態ではタイムスタンプが0(1970/01/01 00:00:00 UTC)を返すだけですが、/3/0/13.readを

date +%s

として記載し、

chmod 755 resources/3/0/13.read

として実行権を付与することで、現在時刻を返すようになります。

初期状態(1970/01/01 00:00:00 UTC)
スクリーンショット 2020-01-13 22.56.51.png

スクリプト設置後(現在時刻が表示される)
スクリーンショット 2020-01-13 22.58.48.png

同様に、/3/0/13.writeに時刻を設定するスクリプトを記載することで、時刻をOSに反映させることができます。(値は標準入力で渡すので、readで受け取れます)

read TIMESTAMP
date -s "@$TIMESTAMP"

設定します。
スクリーンショット 2020-01-13 23.16.07.png

これで時刻の設定ができました。(ちなみにタイムスタンプ型はコンソールで読み出すと、「Mon Jan 13 14:14:38 UTC 2020」のような形になるのですが、書き込む時にはYYYYmmddTHHMMSS.fff(UTCからの差、日本時間だと+09)にしないと送信できないという罠があります)

このように、比較的簡単なスクリプトを設置することで、動的な値にも対応できるようになっています。ここではコンソールからの操作をしていますが、当然Web APIも用意されています。

https://dev.soracom.io/jp/docs/api/#/Device

WebAPIを使うことで、管理しているデバイスから値を収集したり、特定の処理を実行させたりを自動化するのも簡単に実現できますね。

ちょっと高度な使い方: Observe

LwM2MにはObserveという面白い仕組みがありまして、サーバから監視対象として設定したリソースは、サーバから読み出さなくてもデバイスから自発的に送信する、というものです。

例えばメモリの使用量を記録するとしましょう。
Memory Freeは/3/0/10なので、/3/0/10.readを以下のスクリプトにします。

free | grep 'Mem' | tr -s ' ' | cut -d" " -f7

freeコマンドのavailableのメモリを取得しています。LwM2Mの仕様を見ると単位はkBなのですが、変化がわかりにくくなるのでとりあえずbyte単位です。

コンソールから/3/0/10をObserveすると、5秒に1回Notifyというイベントが発生しているのがわかります。

2020/01/13 14:51:37 OBSERVE /3/0/10
2020/01/13 14:51:37 Notify /3/0/10
2020/01/13 14:51:42 Notify /3/0/10
2020/01/13 14:51:47 Notify /3/0/10

inventorydではObserveされたリソースの値を5秒に1回確認し、値が変わっていればサーバーに通知する、という動作をします。そしてこのNotifyはSORACOMのアプリケーションサービスのBeam、Funnel、Harvest、Funk、そしてUnified Endpointに送ることができます。

分かりやすいところでHarvestに連携させるとこんな感じです。

スクリーンショット 2020-01-13 23.58.22.png

メモリ量の推移がharvestに記録されていることがわかりますね。これ結構簡単じゃないですか?やったことはinventorydをダウンロード、初期設定、起動したことと、メモリを取得するスクリプトをちょっとググって入れただけです。それだけでもうこの状態になる。ある意味HarvestにTCP/UDPでデータ送るより簡単ですよ。

また、inventorydはObserveの際、値が変わっていればNotifyという動作をするため、例えば何かのエラーフラグとかを監視させておいて、それがtrueになったらFunkに通知させる、といった使い方もできそうです。

SORACOM Inventory、何だかすごく便利そうじゃないですか?

インターネットからでも使用可能

SORACOM Inventoryの他のアプリケーションサービスにない特長として、インターネットからソラコム回線なしで使用可能、というのがあります。

以下のページの方法でデバイスID、キーを払い出して、
https://dev.soracom.io/jp/start/inventory_registration_with_keys/

inventory実行時のオプションを以下のように変えるだけです。

inventoryd --identity <払い出されたデバイスID> --psk <払い出されたシークレットキー(base64)>

ちなみにこれまで実行した際に指定していた-bというオプションは、LwM2Mのブートストラップという仕組みを使用したもので、これを使うとSORACOM Airの回線で簡単・安全に認証情報の取得ができます。デバイスごとにパスワードを発行・保存しなくても良いのでとても簡単・便利です。逆に認証情報の発行・保存さえすれば、SORACOM Airの回線でなくとも使用できます。この場合、価格的にはInventoryの使用料金50円だけで済むのでとても安価ですね。

使いどころあるかわかりませんが、PCやサーバに入れても動いて、値の取得や遠隔操作できるようになりますよ(外向きのUDP 5684ポートが空いていれば)

他の遠隔操作方法との比較

ソラコムを使っていると遠隔操作には色々な方法があります。
最近出たSORACOM Napterはとても強力で、遠隔操作はこれで十分、という考えもあると思います。
また、SORACOM BeamとAWS IoTを組み合わせ、MQTTでコマンドを送る、ということもされていると思います。

比較ポイントはいくつかありますが、以下のように比較してみました。

項目 Napter Beam + AWS IoT Inventory
価格 300円/月 0.0018円 * コマンド数(AWS IoT部分除く) 50円/月
同期/非同期 同期 非同期 同期
必要サービス SORACOMで完結 AWSと連携 SORACOMで完結
コマンド発行方法 デバイスにサービスが必要(SSHやWebAPIなど) AWSのWebAPIもしくはMQTT SORACOMのWebAPI
コマンド発行結果 デバイスのサービス次第 仕組みを考える必要あり WebAPIの実行結果
SORACOM Air回線 必須 必須(BeamではなくKryptonとの組み合わせなら要らない) 必須ではない

価格としてはコマンド発行数が少なければBeam + AWS IoT、コマンド発行数が多ければInventoryが有利です。Napterは必要な時のみ使用するという考えなので、使用が前提となっているサービスより割高と考えられます。

コマンドの発行や結果の受け取りはInventoryが一番簡単だと考えています。SORACOMのAPIを使えばよく、応答も同期的に返ってきます。一方AWS IoTは非同期で、応答を返す場合は応答用のトピックを用意して、それを受信する何らかのサブスクライバを用意して受信する、といったことが必要になり、かなり面倒です。逆にAWS IoTは仕組みさえできていれば、多数のデバイスに非同期にメッセージを送り、非同期に返されたメッセージをまとめて収集することができます。同期処理はデバイスが多くなってくると、ひとつずつメッセージを送って応答を取得するのに時間がかかると予想されます。

また、最初の方にも上げましたが、SORACOM InventoryはSORACOMだけで完結できるサービスです。Napterもそうですね。BeamはAWS IoTと組み合わせることになるので、クラウド用意するの大変、となる可能性があります。AWS IoTはそこそこ難しいサービスですしね。逆にクラウドに慣れているのであれば、Beam + AWS IoTはAWSの様々なサービスと連携できる長所を活かせます。

ということで、所感としてそれぞれの構成で向いているのは、以下のような感じかと思います。

SORACOM Napter - デバイスに人が直接アクセスしてのアドホックな操作
SORACOM Inventory - デバイスの項目を指定しての取得や監視、設定値の変更、定型的な操作
SORACOM Beam + AWS IoT - 定常的な値の取得や多数のデバイスに対する一斉操作

すでに他の方法で運用されているところに無理に入れることもないですが、今Harvestで値をとっているだけのところに、ちょっとした制御を入れたい、というような場合にはベストチョイスなのでは無いかと思っています。

おわりに

SORACOM InventoryはIoTデバイスを簡単に管理できるサービスということが感じられましたでしょうか?
inventorydは正直去年公開してからほぼ手つかずですが、また折を見てアップデートしていきたいと思っています。ご要望あればなんらかの形でお伝えください。(このブログへのコメント、GitHubへのIssueなど)そのうち対応するかも知れません。

次回はinventorydを開発の技術的な面を書く予定です。
予定タイトル:「SORACOM Inventoryエージェント開発に向けてDTLS、CoAP、LwM2MをGo言語で実装する」

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インクリメント・デクリメントの書き方のまとめ(Scala、Java、Rust、C言語、C++、Go言語、PHP、Perl、Python、Ruby、JavaScript)

いろんな言語を触っていると、言語の細かい仕様がだんだんごっちゃになってきてしまいますので、メモです。

インクリメント・デクリメントの有無

あり: Java、C言語、C++、Go言語△、PHP、Perl、JavaScript
なし: Scala、Rust、Python、Ruby

Go言語は式を構成する演算子ではなく文(statement)という扱いにすることで、インクリメントの演算子としての問題を回避していて、個人的にはちょうどいい仕様に感じます。

ついでに代入演算子も確認しましたが、こちらはだいたいの言語にあるようです。

Scala

  • インクリメント・デクリメント演算子はない
  • 代入演算子はある
i += 1
i -= 1

i += 1 などは i = i + 1 などのシンタックスシュガー。

参考

Assignment Operators - Expressions | Scala 2.13

Scalaでは、なぜインクリメントやデクリメントができないのか?

Java

  • インクリメント・デクリメント演算子は前置・後置ともにある
  • 式であり値を返す
  • 代入演算子もある
++i;
--i;
i++;
i--;
i += 1;
i -= 1;

参考

Prefix Increment Operator ++ - Java Language Specification

Rust

  • インクリメント・デクリメント演算子はない
  • 代入演算子はある
i += 1;
i -= 1;

参考

Compound assignment expressions - Operator expressions - The Rust Reference

なぜインクリメント演算子がないのか?
Why doesn't Rust have increment and decrement operators?

C言語、C++

  • インクリメント・デクリメント演算子は前置・後置ともにある
  • 式であり値を返す
  • 代入演算子もある
++i;
--i;
i++;
i--;
i += 1;
i -= 1;

Go言語

  • C言語でいうインクリメント・デクリメント演算子は後置のみ
  • 式ではなく文の扱いなので、式の中には埋め込めない
  • 代入演算子もある
i++
i--
i += 1
i -= 1

参考

IncDec statements - The Go Programming Language Specification

++--が演算子ではない件
演算子とステートメント — プログラミング言語 Go | text.Baldanders.info

PHP

  • インクリメント・デクリメント演算子は前置・後置ともにある
  • 式であり値を返す
  • 代入演算子もある
++$i;
--$i;
$i++;
$i--;
$i += 1;
$i -= 1;

参考

加算子/減算子 | PHP Manual

Perl

  • インクリメント・デクリメント演算子は前置・後置ともにある
  • 式であり値を返す
  • 代入演算子もある
++$i;
--$i;
$i++;
$i--;
$i += 1;
$i -= 1;

参考

インクリメントとデクリメント - perlop - Perl の演算子と優先順位 - perldoc.jp

Python

  • インクリメント・デクリメント演算子はない
  • 代入演算子はある
i += 1
i -= 1

累算代入文というらしい。

参考

累算代入文 (augmented assignment statement) - 単純文 (simple statement) — Python 3.8.0 ドキュメント

Ruby

  • インクリメント・デクリメント演算子はない
  • 代入演算子はある
i += 1
i -= 1

自己代入というらしい。

参考

演算子式 (Ruby 2.6.0)

Ruby にインクリメント演算子のようなものが無い理由 - fugafuga.write

Rubyのインクリメント速度のバージョンごとの比較 - Qiita

JavaScript

  • インクリメント・デクリメント演算子は前置・後置ともにある
  • 式であり値を返す
  • 代入演算子もある
++i;
--i;
i++;
i--;
i += 1;
i -= 1;

参考

Update Expressions - ECMAScript® 2019 Language Specification

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む