タイトル

Need for Answer

2015年7月4日土曜日

nginx_luaでhtmlを書き換えたい!のまき

レスポンスボディ、書き換えてますか!

…唐突ですが、OSSとか使ってるとちょっとした修正、加えたいってあると思うんですよ。

パッチ当てるのも面倒くさいなーという時、「リバースプロキシでレスポンスボディを書き換えてしまう」という方法もありますよーという話です。実現する選択肢は色々あるのですが、今回はnginx_luaで実現しました。

やってることは、「nginxでリバースプロキシしたhttp_bodyを、luaスクリプトで書き換え」という感じです。こんなかんじで設定すれば、Apacheで動いてるAPサーバーをSPDY対応させつつ書き換えられますね!

 server {
    listen 443 ssl spdy;

    ssl on;

    ssl_certificate your_server.crt;
    ssl_certificate_key your_server.key;

    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    add_header Alternate-Protocol 443:npn-spdy/3;
    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";

    location / {
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;


      proxy_pass http://localhost:80;

        body_filter_by_lua
        '
        replacestr = "$1http://www.google.com/url?q=$2"
        ngx.arg[1] = ngx.re.gsub(ngx.arg[1],"(href=\\")(https?.*?\\")", replacestr)
        ';
    }
 }


2015年6月19日金曜日

golangでHTTPのbodyを読み捨てたい!…のまき

golang書いてる人だったら、httpリクエストをひたすら繰り返すアタックツール、書きますよね!

…ということでソースをそのまま公開すると「アタックを繰り返すプログラムを公開し続けるだけのヤンチャなプログラマー」になっちゃうので、その際学んだことを書こうかな?というお話。

http接続処理のとこだけ抜粋するんですが、重要なのは「バッファ読み捨て」というところです。

なんでここが重要なの?という話なのですが、バッファを読み捨てないと「http接続が完了する前に、Client側でTCP接続を切断しちゃうから」なのです!

ちなみにバッファ読み捨ての行をコメントアウトしても、レスポンスコードは取得できたりします。

…なんでかはioutilのソースを読むといいんじゃないかなーと思います!

// HTTP接続
func httpRequest(tr *http.Transport) {

        // HTTP接続
        client := &http.Client{Transport: tr}
        r, err := client.Get(url)

        if err != nil {
                fmt.Println(err)
                return
        }

        // Body Close処理
        defer r.Body.Close()

        // バッファ読み捨て
        io.Copy(ioutil.Discard, r.Body)

        // レスポンスコード
        response := r.StatusCode
        fmt.Println(response)

}

2015年6月17日水曜日

golangでfluentdのストレステストツールを書きましたのまき

fluent-attacker-rpsという、Fluentdサーバーに負荷をかけるツールを作りました!fluentdサーバーにいっぱいデータ登録したいときに使って下さい!

…というと2行でお話終わっちゃうので、一応使い方とか説明しようかなーと。

go run fluent-attacker-rps.go -h 192.168.0.1

という感じで実行すると、こんな出力がされます。

=====================================
Insert Keys=> 3000 RPS Set => 300 Message Length => 1000
=====================================
0 loop - TaskSet Done.
300 loop - TaskSet Done.
600 loop - TaskSet Done.
900 loop - TaskSet Done.
1200 loop - TaskSet Done.
1500 loop - TaskSet Done.
1800 loop - TaskSet Done.
2100 loop - TaskSet Done.
2400 loop - TaskSet Done.
2700 loop - TaskSet Done.
3000 Task - All Job Done.
=====================================
Exec Time => 10.389512Sec Real RPS => 288.752743rps(96.250914%)
=====================================

終わりましたね!fluentdサーバーにどんなデータが来ているかといいますと、こんな感じです!
2015-06-17T11:48:19+09:00       test.tag        {"ID":"0","md5":"1cea7c2ef3b6019d774fd46a90ef127b","strings":"zrv5xl4igns0qvm6rdki5jq5qrq6wfko3gahmk41idbh5udjk6hvus3wjw1659x51fu7oe6xfc6c163s6ee7stunspm2drdcaap4mgk0khexs17chdg5hjzx4u15h7hc5ui3i256hfon1i294fh9e3dlatpfeks5o8b8lyj8lgp7oqzmsyme94qc88btpahp51ua5lbzt606p76lnxmpj7y7z0hmcb0oic8fmm0km0zg97ag4orm4scql1qvgp5b2av0o8om5k2bc5wpdutvgb22n2alf0e4tf7v42v0h9rd20ye2y3lkj5cczegurle8socvwlz9wjg7379b37w1958kpryai16yqjgxno39g7u3i1xky4oqaeuhmr2aeu0of5yhabakwgrp94oxtjlhncof5lqasudwgvmkdzh75jwia2milduhy05tf0cszg99kqoqixwpu2bsuy4vqaopu1e5x9f9ba5bsvz31a4zzt97ybs92taaalqvp4y9bsrjhc0fxwxxmdd9a89e6hldttqap08za7xmcuq8a2bvczmg1n21t0do78qeg0c79as2tpujxwyrraxijjhjsnap1kfh702jkrjlqv8v6ik6u2ys7z2b5xhh66jhtx9fnj571igv8yedtosb050omt6h62rcyaibk4gcuj6iez2g70kol9zrfiwow7o17467qrf14wwxoskavqw0t64azei1kbzsm5uncb3nspwb6tyjmyopef6yprxbvtylsx18h34rw2x3v69dh3eqv28jc1z2pnny8qgrjle5fdn2pvjuby9197f3kr3f6r031j651zb9egbqtqzgncvim02i33wbs20msvrmt1cldkwkd7qhac39e6u0hoes0e6yh9g24idnclqkwx72vbrdb5ziu425qm58j6e8rcs9nyqyevb33idwhvruhna2wo7pgkqnwgyfkv6n0laavw09t06lo1pc0s82gxexgfznroazqlc"}
2015-06-17T11:48:19+09:00       test.tag        {"strings":"j4t29f6dyejou4h320ggfxi7u4f4kzr3kzxv9u852pzqw8ft238fdqmr42eha0g826nn2vyu79n2wmjtuj17lc1n0jpbqw8bnpcie3csc34ixkiaeuy6n1xczg3uvk11h1lh3kd005xepund2bz2xpj0fha4c4h3acqxvns5x7p6wtes87trldkn8ze51qhhzrql41kx1sxcg4m6aidc406qldco4d6g6k8enbk6oblmyb6v9txdq6aigpn13thgnshi0c5yremnrw2l7bbee8jccqqvpwe035ov8oqk84xzsr2wg1kzgj4xiudg1zxajdn4q661hy4qh6f8bfqa9v4nedn2c1x5nsinkgsr3zw8t6hzdl0bxapt0aysf0qap6ak7x1k2ec09ucxz98u59yjx7b49l5tv7x8xde9bm8e503ayl51ojtmgapu5227mucnlqyr4v78xvvdta85z3160a06my9ndhmyrlboghl025cqda74dpfrbuxjguu2eamhujzeimfpcd91as0fbxj31conls96yby622i9le6cnoj54vjdstj75cg6mksguush2hg39kjs75j1nd46jjyuioxlz8cesedc00i2zg8apipzodmosp69eo0alxkxkr3xnz0u9n9hcbyan3wywiu8efr729y73afwerc8yji8ioaxivs71d3143tzk02b7toaea4j7bdz2snmqrodjtabvnwe5l90ivxl8oflae4cvkiafcvkwwi4nlh3vw98os4ftskbgvj7b6he2rtags1isqh1fszd9379upustge9gvp8pgl25gcw7xu2lre6jkx11i306gckvf5k8m6mr4iufbax80m25zyyjlac7tlty4mg24t1h7pacwo7tzf3s4vtubi7aisupo30x1w3yjlmlxiojz5ckmsp1sppntm173uqi9fw6kydxfikyzpd0iaa3sctuqigemjwq72skxsbwjhrzj8q391wjslw","ID":"1","md5":"93464e057d8babe65f77801432dc27fe"}
2015-06-17T11:48:19+09:00       test.tag        {"ID":"2","md5":"d4a115141322905aa3e21dc8ea91349c","strings":"8x0pwax2q60gbdlzbe0hq7zp5w57zt03g6qq0y6a7g9x6a9wzhjmhxx30hjtbok4wnlxgczusrenbtwbyus89ihz1czpbr75g1djbmdbfoq6c6me5xrh3w5t01bfkrccw0kiharjc1c3rb73g74d7mkvjxqk0htgkrkrr23innjjdzybn4l5q4g62d8vfpbk6reycp22jv1aavza6z8egcrh8jboewllr5dvvif8tiouqil0ncvkzpugp3s4hakzvob1b1t83lf97adwuh5177vcu54ek816vslydwqkvhseblxzpf7lwjn5te9p0t4zgoy3txhn9paswxce6rp6f8ozrldxcc0sv96o5kn1s2z6negjc12s11406z0fxpa01ybhqxtovmmiix867x8ebfoi86lcp4upo6xplwpj0kvzhaiq1o6yo2z0gh9fd6z5od0201yy6c9hk5b9eflcv72kd8wg2sric04vl43o0kv0yc3hmc4v4xcgnra0iqreipdu0fiop2je5xl8npg3gryqv27504vvu5lrtimyxhy6sqjazrrz19od263tl4mbwqlvxx0dlb7unr3563p01symis6p2pc8pkyuhgs0zb9q4lukvdqnnk2uqu42dl488drbvhs7iypxkgqxk049n5ofuj42fik7v7mmanwliw4bdei6a2q5hw1ws9kn9sl9h74dg264gtmh2ybvl0xpk0dcgxdun0ksz0aa5uoruswt49v2xs3iaf7edl3jju0u7a4cq1v8ocrb1k4hxqdninhjuxha9kdztkx2uzxiec56fpqyf66m519vnv11dff0s3rnn5f2o72amhyybl5mf5yv3bx185usi4re6rpq0sasf2dem6rb99zk9fgg2gpqrxzarm810si24bfoear4iuey45b4dqfk4p0thyypc5ii2o19a107uthif4tr0qlad44atr9xaw0nq7ehpkhawl6sy06thl6084ri13us"}

RPSとか投稿数とか調整できますので、「fluentdサーバーのスペック決めるのどーすんだろ?」とか「ログがロストしてないんだろか?」とか実験するといいんじゃないかなーと思います!

追記)念のため言っておきますが、Newで作った接続を使いまわしていないのは「大量のTCP接続来たらどうなるのかなー?」という試験のためです!アホの子をみるような目でソースを見るのはやめてね!

2015年6月12日金曜日

golangでRPSをそれなりっぽく実装するのまき

golang書いている4割位の人が悩む問題として、「RPS(Requests Per Second)をどうやって実装するか?」があると思います。…多分。

ということでこんなかんじで実装しましたよ、というお話。
ちなみにコードのほとんどはmattn氏のBig Sky :: Golang の channel の使い所 : の引用です。

以下サンプルコード。

// RPSから間隔を計算
func calcInt(x int) int64 {
        return int64(1 / float64(x) *1000 *1000)
}

func main() {
    // Channel宣言
    task := make(chan int)        // TaskQue用
    taskquit := make(chan bool)   // SetTask停止用
    workerquit := make(chan bool) // JobWorker停止用

    // WaitGroup作成
    wg := &sync.WaitGroup{}

    // Job Worker
    go func() {
    loop:
        for {
            select {
            case <-taskquit:
                workerquit <- true
                break loop
            case job := <-task:
                wg.Add(1)
                なんかの処理(job)
                wg.Done()
            }
        }
    }()

    // SetTask Worker
    go func() {

        // RPS間隔計算
        interval := calcInt(rps)
        sleepWait := time.Duration(interval) * time.Microsecond

        for i := 0; i < key; i++ {
            task <- i
            time.Sleep(sleepWait)

            // Log Print
            if i%rps == 0 {
                fmt.Printf("%d loop - TaskSet Done.\n",i)
            }
        }
        taskquit <- true
    }()

    // WaitGroup終了まで待つ
    wg.Wait()

    <-workerquit
}

2015年6月4日木曜日

ErastiCache for RedisでFailOverが発生すると何がおこるのか?のまき

これからタイトルどおりのお話をするのですが、Multi-AZ構成を取っていたとしても何がおこるか知らない人多いんじゃないでしょうか?AWSのドキュメント見ても、挙動の記載が無いのです。

結論から言うと、2パターンあります。
  1. Read Replicaがprimaryに昇格し、旧primaryはread replicaとして同期
  2. 自身の持っているイメージからデータを復元(read Replicaが使えない場合)
1から説明しますと、Multi-AZのRead Replicaが存在する場合は素直に切り替わります。これはDNSで切り替わるため、Primary Endpointに紐付いたNodeが、新しいprimaryに切り替わリます。その際、降格したread replicaはデータが空の状態で同期を開始します。

問題は2です。primaryがread replicaに降格するのですが、そのタイミングでデータをローカルにバックアップします。正確に言うと、こんなかんじの流れです。
  1. 障害検知したタイミングで、データをローカルにバックアップ
  2. 再起動
  3. データが空の状態でredisが起動
  4. 新primaryに問題ない場合はread replicaとして起動し、primaryからデータ同期
  5. 新primaryに問題がある場合はprimaryとして起動し、1.からデータを復元
何が問題か?という話なのですが、1~5の間は『read,writeがロックされた状態』になります。つまり長時間redisにアクセス出来ない状態になります。

…大変っすね!


2015年5月28日木曜日

New ReicのScalability analysisって何?

New Relic使ってますか!

というか、New Relicの「REPORTS」項目、見てますか!
今回はその中の、「Scalability analysis」について解説しようかな、というお話。

…「Scalability analysis」とは、読んでそのままの意味でスケーラビリティ分析をしてくれるものです。つまり「インフラがボトルネックになっているか?」がわかるのです!すごい!

では、読み方。2つのキャプチャを見てみましょう。




このグラフから何を読みとくか?というと、「rpm(Requests Per Minutes)が上昇しても、応答速度にムラが無いか?」です。横軸がrpm、縦軸が平均応答速度です。「右端のカラーチャート」と「グラフ内の点」を照らし合わせると、「いつのアクセスなのか?」がわかります。つまり何時頃にはどれくらいのrpmだったのか?がわかります。

そもそもの話ですが、rpmはユーザーアクセス数に比例します。具体的には「夜中は少なく、昼は多い」ということになります。こういった時刻ごとの変化も一目瞭然ですね。

さて「rpmが上昇」ということは、「1台のサーバーで対応するユーザー数が上昇」ということです。…ということは「インフラリソースが不足すると、応答速度が遅くなる」と考えられます。逆に考えると、「応答速度が安定しているということは、インフラリソースは充足している」事になります。

ということで結論としては、「線グラフが水平」「点が集中して上下の幅が少ない」というのが、インフラリソースが充足しているいい感じ、になります。

右肩上がりや、点がバラバラになっている場合は、どこかにボトルネックがあるという事ですね。

2015年5月23日土曜日

golangでredisに超大量にデータを入れ続けるプログラムを書きましたのまき

件名通りなのですが、redisにダミーデータを入れ続けるだけのgolangプログラムを書きました!
みんなもつかってね!

…なーんてプログラム使う人居ないと思うので、書いてた時に学んだことを共有しようかなというお話。


■コンソール出力すると極端に遅くなる
golangが早いのはご存知なことだと思うのですが、思ったよりコンソール出力が遅いです。
超大量データを処理する際、毎行ログ出力するのは無駄が多いです。

100行に一回出力とか間引きした方がいいですよ、ということですね。


■Network I/Oはコネクションプーリング等の工夫が必要
処理が早いということで、CPU以外の部分にもボトルネックが来る可能性があります。
redisへのアクセスを毎回切断していたら、あっという間にTCP接続を使い切ってしまいました。

コネクションを使いまわす、を早い段階で考えたほうがいいですね。


■ifでgoroutineを呼び出すときに、ループ数が極端に多いと落ちる
goroutineはループに入る前にスレッド(みたいなもの)を事前に用意するっぽいです。10万ループとかをgoroutineで非同期化すると、「signal: killed」で落ちます。

…ということでどうするかという話なのですが、「gorc」いうパッケージを使うと回避できます。goroutineの同時並列実行数に制限がかけられるため、リソースを使い切るのが回避できます。すごい。

※追記)sync.Poolとかsync.Cond使うとsyncでもスロットル制御出来る気がします!書いたこと無いけど!


package main

import (
  "crypto/md5"
  "crypto/rand"
  "encoding/hex"
  "flag"
  "fmt"
  "github.com/garyburd/redigo/redis"
  "github.com/mr51m0n/gorc"
  "math/big"
  "runtime"
  "strconv"
  "time"
)

var (
  pool *redis.Pool
  host string = "localhost:6379" // redis-server接続情報
  key int = 150000 // 投入するKEYの数
  val int = 10000 // 文字列の長さ
)

// redis ConnectionPooling
func newPool(server string) *redis.Pool {
  return &redis.Pool{

    MaxIdle: 3,
    IdleTimeout: 240 * time.Second,

    Dial: func() (redis.Conn, error) {
      c, err := redis.Dial("tcp", server)

      if err != nil {
      return nil, err
    }
    return c, err
  },

  TestOnBorrow: func(c redis.Conn, t time.Time) error {
    _, err := c.Do("PING")
    return err
    },
  }
}


// val用にrandomな文字列を生成する
func random(length int) string {
  const base = 36
  size := big.NewInt(base)
  n := make([]byte, length)
  for i, _ := range n {
    c, _ := rand.Int(rand.Reader, size)
    n[i] = strconv.FormatInt(c.Int64(), base)[0]
  }
  return string(n)
}


// gorc
var gorc0 gorc.Gorc


// メイン処理
func main() {
  runtime.GOMAXPROCS(runtime.NumCPU())

  flag.Parse()

  // Timer Set
  start := time.Now()

  // redis Pooling Start
  pool = newPool(host)

  // Redis DataInsert
  for i := 0; i < key; i++ {
    gorc0.Inc()
    go setredis(i, val)

    gorc0.WaitLow(100)
  }


  // Log Print
  fmt.Print(key)
  fmt.Printf("Keys - Set Done.\n")

  // Timer Print
  end := time.Now()
  fmt.Printf("%f秒\n", (end.Sub(start)).Seconds())
}


// stringからmd5から作る
func GetMD5Hash(text string) string {
  hash := md5.Sum([]byte(text))
  return hex.EncodeToString(hash[:])
}


func init() {
  gorc0.Init()
}


// redisにデータを投入するだけの処理
func setredis(k, v int) {

  c := pool.Get()
  defer c.Close()
  defer gorc0.Dec()


  vals := random(v)
  keys := GetMD5Hash(vals)


  c.Do("SET", keys, vals)


  // 100処理ごとにログ表示(非同期なので順番が適当)
  if k%100 == 0 {
    fmt.Print(k)
    fmt.Printf("Keys - Set Done.\n")
  }
}