タイトル

Need for Answer

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")
  }
}

0 件のコメント:

コメントを投稿