Redigoを使う(4) コネクションプールを使う


はじめに

RedisのGo言語向けクライアントライブラリRedigoの使い方を見ます。
本記事ではコネクションプールの使い方を見ていきます。

環境

基本の流れ

コネクションプールとは、Redisサーバへ接続するときにコネクションを再利用することで接続を高速化する機能です。

さっそくですが、Redigoでコネクションプールを利用する典型的なコードを以下に示します。

main.go
package main

import (
    "fmt"
    "time"

    "github.com/gomodule/redigo/redis"
)

func newPool(addr string) *redis.Pool {
    return &redis.Pool{
        MaxIdle:     3,
        MaxActive:   0,
        IdleTimeout: 240 * time.Second,
        Dial:        func() (redis.Conn, error) { return redis.Dial("tcp", addr) },
    }
}

func main() {
    // コネクションプールの作成
    pool := newPool("localhost:6379")

    // コネクションの取得
    conn := pool.Get()
    defer conn.Close()

    // 値の書き込み
    w, err := conn.Do("SET", "temperature", 20)
    if err != nil {
        panic(err)
    }
    fmt.Println(w) // OK

    // 値の取得
    r, err := redis.Int(conn.Do("GET", "temperature"))
    if err != nil {
        panic(err)
    }
    fmt.Println(r) // 20
}

redis.Pool構造体を始めに作成しておき、Redisの操作が必要になるごとにpool.Get()でプールからコネクションを取得し、使い終わったらconn.Close()する、というのが基本的な使い方です。

ウェブアプリのサーバで使うときは、起動時にプールを作成してグローバル変数などで持っておいて、リクエストをハンドルする関数の先頭でpool.Get()defer conn.Close()を行うという書き方になると思います。

プールのコネクション管理

プールのコネクション管理の仕方と、パラメータについて理解しておきましょう。

プールの起動直後は、プールはコネクションを1つも持たない状態です。pool.Getが呼ばれると、プールはサーバとの間で新たなコネクションを確立し、戻り値として返します。そしてそのコネクションでconn.Closeが呼ばれたとき、プールはサーバとの間のコネクションを切断せずに、「アイドル状態」のコネクションとして蓄えておきます。その後で再度pool.Getが呼ばれたときは、プールはアイドル状態のコネクションを「利用中」に変えて戻り値として返します。もしアイドル状態のコネクションがなければ、新しいコネクションを確立します。

デフォルトでは、リソースが許す限りいくつでもコネクションを確立できます。パラメータMaxActiveを使うと、個数に上限を設けることができます。その場合、MaxActiveを超えてコネクションをpool.Getしたとき、そのコネクションでconn.Doなどを呼ぶとErrPoolExhaustedエラーが返ってきます。あるいは、パラメータWaitをtrueにしておくと、コネクション数がMaxActiveを下回るまで、pool.Getの呼び出しをブロッキングで待たせられます。

また、パラメータMaxIdleでは、アイドル状態のコネクション数の上限を設けることができます。コネクションのconn.Closeが呼ばれたとき、既に上限数のアイドルのコネクションが存在していれば、プールはコネクションをアイドル状態にせず、サーバから切断します。

以下は、上記の挙動を理解するためのコードです。アイドルのコネクションの上限数をMaxIdle = 3、コネクションの総数の上限をMaxActive = 6とした状態で、10個のコネクションの確立と終了を1個ずつ試み、pool.Statsで得たプールの状態をつど出力しています。

main.go
package main

import (
    "fmt"
    "time"

    "github.com/gomodule/redigo/redis"
)

func newPool(addr string) *redis.Pool {
    return &redis.Pool{
        MaxIdle:     3,
        MaxActive:   6,
        IdleTimeout: 240 * time.Second,
        Dial:        func() (redis.Conn, error) { return redis.Dial("tcp", addr) },
    }
}

func printStats(pool *redis.Pool) {
    s := pool.Stats()
    fmt.Printf("Active: %d, Idle: %d, InUse: %d\n",
        s.ActiveCount, s.IdleCount, s.ActiveCount-s.IdleCount)
}

func main() {
    // コネクションプールの作成
    pool := newPool("localhost:6379")

    printStats(pool)
    fmt.Println("------")

    // コネクションを作成
    conns := make([]redis.Conn, 10, 10)
    for i := 0; i < 10; i++ {
        conns[i] = pool.Get()
        printStats(pool)
    }
    fmt.Println("------")

    // コネクションをクローズ
    for i := 0; i < 10; i++ {
        conns[i].Close()
        printStats(pool)
    }
}

実行結果は以下のようになります。アクティブ(=使用中+アイドル)のコネクション数が6を超えておらず、またアイドルの数が3を超えることがない点を確認し下さい。

Active: 0, Idle: 0, InUse: 0
------
Active: 1, Idle: 0, InUse: 1
Active: 2, Idle: 0, InUse: 2
Active: 3, Idle: 0, InUse: 3
Active: 4, Idle: 0, InUse: 4
Active: 5, Idle: 0, InUse: 5
Active: 6, Idle: 0, InUse: 6
Active: 6, Idle: 0, InUse: 6
Active: 6, Idle: 0, InUse: 6
Active: 6, Idle: 0, InUse: 6
Active: 6, Idle: 0, InUse: 6
------
Active: 6, Idle: 1, InUse: 5
Active: 6, Idle: 2, InUse: 4
Active: 6, Idle: 3, InUse: 3
Active: 5, Idle: 3, InUse: 2
Active: 4, Idle: 3, InUse: 1
Active: 3, Idle: 3, InUse: 0
Active: 3, Idle: 3, InUse: 0
Active: 3, Idle: 3, InUse: 0
Active: 3, Idle: 3, InUse: 0
Active: 3, Idle: 3, InUse: 0

なお、デフォルトでは、アイドル状態のコネクションは永久にクローズされません。IdleTimeoutまたはMaxConnLifetimeのパラメータを設定することで、期限を決めてクローズさせることができます。アイドル状態になってからIdleTimeoutの時間が経過したコネクションはクローズされます。また確立されてからMaxConnLifetimeの時間が経過したコネクションもクローズされます。

おわりに

Redigoのコネクションプールの使い方を見ました。

参考