2018年12月16日日曜日

Golangのfake google searchフレームワークをjavaで書いてみた。その2

その1を投稿したあと、某友人から「CompletableFuture使ったらもっとスッキリできるよ」って情報もらった。なので、それ使って書き換えてみた。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import java.util.function.Supplier;

class FakeSearch implements Supplier<String>{
    String name = null;
    public FakeSearch(String name){
        this.name = name;
    }
    @Override    public String get() {
        try {
            Thread.sleep((new Random().nextInt(10) * 1000));
        }catch (Exception e){

        }
        return name;
    }
}

public class CmpGoChannel2 {
    public static void First(Supplier<String> supplier1,Supplier<String> supplier2) throws Exception{
        CompletableFuture<String> future1 =
                CompletableFuture.supplyAsync(supplier1);
        CompletableFuture<String> future2 =
                CompletableFuture.supplyAsync(supplier2);

        CompletableFuture<Object> future3 = CompletableFuture.anyOf(future1, future2);
        System.out.println(future3.get());
    }

    public static void main(String... args) throws Exception{
        FakeSearch web1 = new FakeSearch("Web1");
        FakeSearch web2 = new FakeSearch("Web2");
        FakeSearch image1 = new FakeSearch("Image1");
        FakeSearch image2 = new FakeSearch("Image2");
        FakeSearch video1 = new FakeSearch("Video1");
        FakeSearch video2 = new FakeSearch("Video2");


        CmpGoChannel2 c = new CmpGoChannel2();
        List<Future<String>> list = new ArrayList<>();

        First(web1,web2);
        First(image1,image2);
        First(video1,video2);

    }

}
while(true)と終了フラグで乗り切ってた汚い部分がだいぶきれいになった。

public static void First(Supplier<String> supplier1,Supplier<String> supplier2) throws Exception{
    CompletableFuture<String> future1 =
            CompletableFuture.supplyAsync(supplier1);
    CompletableFuture<String> future2 =
            CompletableFuture.supplyAsync(supplier2);
    CompletableFuture<Object> future3 = CompletableFuture.anyOf(future1, future2);
    System.out.println(future3.get());
}


行数もざっくり60行程度まで収まった。

2018年12月15日土曜日

Golangのfake google searchフレームワークをjavaで書いてみた。その1

Go concurrency patternsで触れられていたGoogle SearchのFake frameworkのコードが非常にすっきり記載されていて感動したので、これをJavaで書いたらどうなるんだろうと好奇心から書いてみた。

まず、Go言語の方のやつを、書き出してみる。

package main

import (
   "time"   "math/rand"   "fmt")

var (
   Web1 = fakeSearch("web1")
   Image1 = fakeSearch("image1")
   Video1 = fakeSearch("video1")

   Web2 = fakeSearch("web2")
   Image2 = fakeSearch("image2")
   Video2 = fakeSearch("video2")
)

type Result string

type Search func(query string) Result

func fakeSearch(kind string) Search {
   return func(query string) Result {
      time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
      return Result(fmt.Sprintf("%s result for %q\n", kind, query))
   }
}

func First(query string, replicas ...Search) Result {
   c := make(chan Result)
   searchReplica := func(i int) { c <- replicas[i](query) }
   for i := range replicas {
      go searchReplica(i)
   }
   return <-c
}

func Google(query string) (results []Result) {
   c := make(chan Result)
   go func() { c <- First(query, Web1, Web2) } ()
   go func() { c <- First(query, Image1, Image2) } ()
   go func() { c <- First(query, Video1, Video2) } ()
   timeout := time.After(80 * time.Millisecond)
   for i := 0; i < 3; i++ {
      select {
      case result := <-c:
         results = append(results, result)
      case <-timeout:
         fmt.Println("timed out")
         return      }
   }
   return}

func main() {
   rand.Seed(time.Now().UnixNano())
   start := time.Now()
   result := First("golang",
      fakeSearch("replica 1"),
      fakeSearch("replica 2"))
   elapsed := time.Since(start)
   fmt.Println(result)
   fmt.Println(elapsed)
}


コードの解説はスライドに譲るとして、First関数の中での、return <-c の使い方が素晴らしい。

で、これをJavaで書いてみた。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

class Web1 implements Callable<String>{

    public String call() {
        try {
            Thread.sleep(10*1000);
        }catch (Exception e){

        }

        return "Web1";
    }
}


class Web2 implements Callable<String>{

    public String call() {

        return "Web2";
    }
}

class Image1 implements Callable<String>{

    public String call() {
        try {
            Thread.sleep(10*1000);
        }catch (Exception e){

        }

        return "Image1";
    }
}


class Image2 implements Callable<String>{

    public String call() {

        return "Image2";
    }
}

class Video1 implements Callable<String>{

    public String call() {
        try {
            Thread.sleep(10*1000);
        }catch (Exception e){

        }

        return "Video1";
    }
}


class Video2 implements Callable<String>{

    public String call() {

        return "Video2";
    }
}

public class CmpGoChannel {

    public static void First(ExecutorService executor1,Callable<String> callable1, Callable<String> callable2) throws Exception{
        List<Future<String>> list = new ArrayList<>();

        list.add(executor1.submit(callable1));
        list.add(executor1.submit(callable2));

        boolean loopDone = false;
        while(true) {
            for (Future<String> f : list) {
                System.out.print(".");
                if (f.isDone()) {
                    System.out.println(f.get());
                    loopDone = true;
                    break;
                }
            }
            if(loopDone){
                break;
            }
        }
    }

    public static void main(String... args) throws Exception{
        ExecutorService executor1 = Executors.newFixedThreadPool(2);
        ExecutorService executor2 = Executors.newFixedThreadPool(2);
        ExecutorService executor3 = Executors.newFixedThreadPool(2);

        CmpGoChannel c = new CmpGoChannel();
        List<Future<String>> list = new ArrayList<>();

        Callable<String> web1 = new Web1();
        Callable<String> web2 = new Web2();

        First(executor1,web1,web2);

        Callable<String> image1 = new Image1();
        Callable<String> image2 = new Image2();

        First(executor2,image1,image2);

        Callable<String> video1 = new Video1();
        Callable<String> video2 = new Video2();

        First(executor3,video1,video2);


        executor1.shutdown();
        executor2.shutdown();
        executor3.shutdown();
    }
}


まず気づいたのが、Javaにchannelに相当する仕組みがなかったこと。JavaのConcurrencyはそんなに人気のあるものではなかったが、そうは言ってもそれなりに使われている実績はあるので、書こうとしてみて「そういや、なかったな」みたいな気付きがあった。

なので、ここは

while(true) {
    for (Future<String> f : list) {
        System.out.print(".");
        if (f.isDone()) {
            System.out.println(f.get());
            loopDone = true;
            break;
        }
    }
    if(loopDone){
        break;
    }
}

といった感じで while(true)と終了フラグで乗り切るというちょっと汚いコードで対応した。
このあたりですでに、簡素なコードを書けるというGoの魅了が十分に表れている。



2018年11月29日木曜日

GraphQLを触ってみた

The better Restとの触れ込みのあるGraphQLを触ってみた。

GraphQLとは何か?

ここ数年、Webではスタンダードとして使われていたRest APIに対する不満から生まれたバックエンドとフロントエンド間の通信規約である。具体的には主に下記のようなものを課題として捉えている。

課題1 endpointから返されるデータを全部使わない(Over-fetching)

たとえばユーザのプロフィール画面を作りたくて、/user/profiesのエンドポイントを叩いたとする。大体の場合は、これのレスポンスに含まれるすべてのデータは必要とせずに、一部のデータのみ使うであろう。使われないデータは無駄であり、結果として効率的でない通信のやり取りに繋がる。

課題2 endpointをいくつも叩かなければいけない(Under-fetching)

Over-fetchingと表裏一体ではあるのだが、一つの画面に必要となるデータをやり取りするにも、 user/<id>を叩き、次にuser/<id>/profiles、そして最後にuser/<id>/photosの3つのendpointを叩く必要になることがある。これは一つのendpointから返されるレスポインスでは足りないがために起き、そのため複数のendpointへの通信が過剰に走ることになる。パフォーマンスの観点からは基本的にフロントエンドとバックエンド間の通信は少なければ少ないほど好ましい。

課題3 endpoint作成のコストが高い

endpointを一個作るにはフロントエンド・バックエンド側双方の調整が必要となるため、作成のコストが高い。


実際に作ってみた

GraphQLを推進する会社にApollo社がある。Apollo社が提供するApollo Serverを使うと比較的かんたんにGraphQLを試すことができる。

Getting started

出だしに書いてあったとおり、本当に10分で雰囲気がつかめる。もしかしたら10分もいらないかも。

感想

npmで初期化するだけで、playgroundがついてくるのは便利。ただ、ドキュメントが不親切で断片的なコードしか書いてないため、ちょっとした改良をするにも試行錯誤が必要。急がばまわれ的に、まずはapollo serverを使わない素のgraphql.jsなどを触って基礎を先に身につけたほうが良さそう。


2018年10月14日日曜日

Go言語の学習に使った本・サイト


A Tour of Go
定番どころのサイト。一通りのSyntax等は学べるか、サラッと書かれてるので基本を知る以外には情報が足りない感じがある。ブラウザ上でGoの実行環境があるので、スキマ時間に理解を進めるのには役立ちそう

スターティングGo言語 (CodeZine BOOKS)   松尾 愛賀 
入門書として良書。C、Java、Pythonなどの経験がある人に向けたGoの入門書。文法を網羅的に説明しつつ、落とし穴となりそうなところをちゃんと解説している。注意点としては全くのプログラミング初心者を対象としてないところ。プログラミング未経験者がいきなり本書を読んでも理解ができる前提で書かれた本ではない。

Go言語によるWebアプリケーション開発
タイトルのまんまGo言語でWebアプリケーション開発をするために必要な知識についてまとめてある本。(読み終わったらまた書く)

Go in Action
Manning者のIn Actionシリーズ。個人的にIn Actionシリーズの大ファンなので、購入したが、これは若干いまいち。In Actionシリーズは、実際的なサンプルを作りながら、その技術セットに付いて学べるのが特徴であるが、本書に関してはGoの理解を進めるのにあまり向いている作りではなかった

2018年9月5日水曜日

メルカリの俺的活用方法

フリマアプリのメルカリ。いろんな使い方があると思うけど、俺が気に入ってる代表的な使い方を紹介してみる。


1. コミック全巻セットを買って、読んで、そしてまた売る。

コミックは場所的に嵩張るので、よっぽど気に入った本以外は家にキープしておく場所がない。解決策としてはTSUTAYAとかで宅配レンタルするとか、漫画喫茶で読むとかあるんだけど、それぞれ微妙な点がある。
  • 宅配レンタル
TSUTAYAの場合、送料まで含めると一冊あたり150円くらいするので、結構コスパが悪い。またうっかり返却日をすぎると、容赦なく延滞料金が取られる
  • 満喫

フリータイムとか利用すれば、かなりコスパは、個人的にはフリータイム3時間とか6時間とかで集中して漫画を読むのとか楽しめない。一冊読んだあと、ネット見て、またその後、次の巻を読んだり、あいた時間に一冊だけ読んだりと、ダラダラ読むのが好きなので、正直言って満喫に何時間も缶詰するとか若干苦行感ある。家でのんびりと読みたい。

ということで、メルカリとかで漫画全巻セットを買って、自分のペースで読み終わったあとに、またその漫画を売りに出すのが、おすすめとなる。大体の場合、買ったときとほぼ同じくらいの値段で売れるので、手数料+送料がランニングコストとなる。30冊くらいの漫画セットであれば、両方合わせてもせいぜい800-1200円程度である。

2. ゲームを買って、遊んで、また売る
コミックと一緒だけど、ゲームなんかも大体の場合は一度遊ぶと二度とやらないので、遊んだあとサクッと処分できると嬉しい。新作は値段の下落が早いけど、発売後数ヶ月から年単位で時間がたった古いゲームなんかは、コミック全巻セットと同様に大きく値段が下がらないので、だいたい買ったときの値段で売れることになる。ゲームは漫画に比べて小さく、当然送料も安く済むので、ランニングコスト的には更に安く上がる。手数料+送料で400-600円程度で済む。

というふうに、上げてみると、ほとんどシェアリングサービス的な使い方になった。もちろん、店で新品で買ったものをただ売ったり、逆に欲しいものをメルカリで中古で買ったりもしているが、とりわけ個人的には漫画とゲームの使い方が気に入ってる。








2018年2月4日日曜日

日経新聞をKindleで読むまでにしたこと

Kindleのフォーマット(mobi)への変換


日経新聞の電子版を長らくPCで読んでたのだが、目を酷使する時間を少しでも減らそうと思いKindleで読める方法がないか探ってみた。

調べたところ、Calibreというその筋では有名なソフトウェアが存在するようなので、試してみた。

何度か試したのだが、どうやら最近の日経新聞電子版ではエラーが発生するようで、Kindle化するまでに至らなかった。

幸い、Calibreは開発をオープンソースベースで行っているようでgithubにソースが管理されていた。その為、必要な修正をpushし、本家に取り込んでもらうことができた。

Ver 3.15以上であれば日経新聞を無事読み込むことができるので、エラーに悩まされていた人はぜひとも試してほしい


Amazon Web Serviceで自動配信

Calibre本体にタイマー機能がついているので、PCを起動しっぱなしにしておけば、毎日設定した時間に日経新聞を取得し、Kindleまで配信することが可能であるが、現在私はラップトップPCしか使っていないため、毎朝手動で朝刊を取得し、Kindleまでメールで送信する必要があった。毎日のこととなるとこの単純作業も地味に辛い。


はじめはフリー枠を恒久的に用意しているHerokuなどを使いたかったのだが、Calibreの場合は本体をインストールするという作業が必要になるため、Herokuでは技術的にこれを実現できなかった。

AWSならば基本的にはLinuxをそのまま利用できるのでこちらを利用することとした。
まずは一年間のトライアル枠で利用するので料金はかからない。厳密に料金の見積もりを取ってないが、Amazon CouldWatchというこれまたAmazonのサービスを使って朝刊と夕刊の時間を取得する時間だけインスタンスを立ち上げることができたので、トライアル期間を過ぎてもおそらくそれほど多大な料金はかからないだろう。


分かる人にしかわからないレベルの粒度で、AWSの自動配信のためにやったことを書いておく。

1. EC2インスタンスを用意する。Amazon Linux AMIを利用した


2. Amazon Lambdaを利用して、EC2インスタンスを起動するFunction、停止するFunctionを用意する


起動のためのFunction(python2.7)

import boto3
# Enter the region your instances are in. Include only the region without specifying Availability Zone; e.g.; 'us-east-1'
region = 'XX-XXXXX-X'
# Enter your instances here: ex. ['X-XXXXXXXX', 'X-XXXXXXXX']
instances = ['X-XXXXXXXX']

def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name=region)
    ec2.start_instances(InstanceIds=instances)
    print 'started your instances: ' + str(instances)
停止のためのFunction
import boto3
# Enter the region your instances are in. Include only the region without specifying Availability Zone; e.g., 'us-east-1'
region = 'XX-XXXXX-X'
# Enter your instances here: ex. ['X-XXXXXXXX', 'X-XXXXXXXX']
instances = ['X-XXXXXXXX']

def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name=region)
    ec2.stop_instances(InstanceIds=instances)
    print 'stopped your instances: ' + str(instances)


3. Amazon CloudWatchを利用して、2で作成したそれぞれのFunctionを実行するEventを作成する。

例えば朝刊は午前のJSTのAM2:30に配信されるので、起動をAM2:30、停止をAM3:20にした。

4. 1で用意したEC2にCalibreをインストールする


%>yum update 
%>sudo yum install libGL
%>sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.py | sudo python -c "import sys; main=lambda:sys.stderr.write('Download failed\n'); exec(sys.stdin.read()); main()"
私の場合はcalibreのインストール前に、libGLを手動でインストールする必要があった。


5. 日経新聞をmobi化するためのコマンド、およびそれを自分のKindleに配信するためのコマンドをスクリプト化する。


%>/opt/calibre/ebook-convert '日本経済新聞(朝刊・夕刊).recipe' /tmp/nikkei.mobi --username=<日経のIDとなるメールアドレス> --password=<そのパスワード>

%>/opt/calibre/calibre-smtp -r mail.gmx.com --username <送信用のメールアドレス> --password <パスワード> --port 587 -a /tmp/nikkei.mobi <送信用のメールアドレス> <Kindleのドキュメント受付用のメールアドレス> subject

6. 5のスクリプトをcronで実行するよう設定する

35 17 * * * /home/ec2-user/calibre.sh



リファレンス