redigoを使用してredis proxyが踏んだ穴を共有する
4275 ワード
最近プロジェクトを開発する時、redis関連の穴を踏んで、今みんなに分かち合います.
使用するサードパーティ製ライブラリはredigoで、
接続されたredisアドレスはproxyで、
まず、redigoもredis proxyもいいもので、redigoのソースコードがはっきり見えて、redis proxyも素晴らしいです.ナイフはいいナイフですが、姿勢が悪いと自分を傷つけます.
このピットはredigo接続プールについてです.
上図は古いプロジェクトのredigo redis poolですが、どう見ても問題ありません.
MaxIdle:プール内の最大アイドル接続
IdleTimeout:このdurationの空き接続を超えると閉じられます
TestOnBorrow:この接続が健康かどうかを調べる前に
問題はこのDialです
Dial is an application supplied function for creating and configuring a connection.
pool構造体のこのDialは、redis接続を作成し、構成するために使用されます.
ピットはここにあります.redisコマンドを実行するのはconnectionオブジェクトのDoメソッドです.
上記のソースコードでtimeoutの値を入力しないと、デフォルトの0値では、この2つのset deadlineの論理はスキップされます.の
read/write timeoutを設定しないとどのような問題が発生しますか?もしネットワークに変動があれば、redisコマンドを実行するとき、サーバーの応答を受信していないと、今回の要求が戻ってこないので、そこに干してしまいます.Redisサーバが設定したタイムアウト時間になるまで、接続を閉じ、EOFのエラーが表示されます.単点redisの場合、MaxActiveを設定しないと、redis poolの接続数に上限がなく、問題は露呈しません.これは私たちのサービスにとって、影響も大きくありません.エラーログには、redis関連のEOFログがいくつかありますが、これは本当に大丈夫ですか.もちろん問題がありますが、redisからメッセージを読んでread timeoutを設定していないと、ずっと読めません.この協程はそこに詰まっていて、なかなか応答してくれないので、ユーザーにとってよくありません. クラスタモード、一般redis_Proxyは接続数を制限するのでredis poolはMaxActiveで池の最大接続数を制限すべきで、このときread/write timeoutを設定しないと問題が来て、池の接続はなくなるまで少なくなります.
だから、その状況にかかわらず、私たちはredisにあげるべきだ.Dialという方法は,3つのタイムアウト時間,DialConnectTimeout,DialReadTimeout,DialWriteTimeoutを伝達する.
今使っているredigoのredis poolを添付します.
前はずっとredisと思っていた.Dialという方法ではデフォルトのタイムアウト時間がありますが、結果は事実上ないことを証明します.
コードを書くときは必ず「と思う」ことはできません.
実はredigoのせいではありません.このライブラリの下部にはgolangのnetライブラリが使われています.default timeoutは0ですね.which is interpreted as「no timeout」...
使用するサードパーティ製ライブラリはredigoで、
接続されたredisアドレスはproxyで、
まず、redigoもredis proxyもいいもので、redigoのソースコードがはっきり見えて、redis proxyも素晴らしいです.ナイフはいいナイフですが、姿勢が悪いと自分を傷つけます.
このピットはredigo接続プールについてです.
RedisPool = &redis.Pool{
MaxIdle: 5,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", address)
if err != nil {
println(err)
return nil, err
}
_, err = c.Do("SELECT", db)
if err != nil {
println(err)
return nil, err
}
return c, nil
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
if err != nil {
println(err)
}
return err
},
}
上図は古いプロジェクトのredigo redis poolですが、どう見ても問題ありません.
MaxIdle:プール内の最大アイドル接続
IdleTimeout:このdurationの空き接続を超えると閉じられます
TestOnBorrow:この接続が健康かどうかを調べる前に
問題はこのDialです
Dial is an application supplied function for creating and configuring a connection.
pool構造体のこのDialは、redis接続を作成し、構成するために使用されます.
ピットはここにあります.redisコマンドを実行するのはconnectionオブジェクトのDoメソッドです.
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
c.mu.Lock()
pending := c.pending
c.pending = 0
c.mu.Unlock()
if cmd == "" && pending == 0 {
return nil, nil
}
if c.writeTimeout != 0 {
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
}
if cmd != "" {
if err := c.writeCommand(cmd, args); err != nil {
return nil, c.fatal(err)
}
}
if err := c.bw.Flush(); err != nil {
return nil, c.fatal(err)
}
if c.readTimeout != 0 {
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
}
if cmd == "" {
reply := make([]interface{}, pending)
for i := range reply {
r, e := c.readReply()
if e != nil {
return nil, c.fatal(e)
}
reply[i] = r
}
return reply, nil
}
var err error
var reply interface{}
for i := 0; i <= pending; i++ {
var e error
if reply, e = c.readReply(); e != nil {
return nil, c.fatal(e)
}
if e, ok := reply.(Error); ok && err == nil {
err = e
}
}
return reply, err
}
上記のソースコードでtimeoutの値を入力しないと、デフォルトの0値では、この2つのset deadlineの論理はスキップされます.の
read/write timeoutを設定しないとどのような問題が発生しますか?もしネットワークに変動があれば、redisコマンドを実行するとき、サーバーの応答を受信していないと、今回の要求が戻ってこないので、そこに干してしまいます.Redisサーバが設定したタイムアウト時間になるまで、接続を閉じ、EOFのエラーが表示されます.
だから、その状況にかかわらず、私たちはredisにあげるべきだ.Dialという方法は,3つのタイムアウト時間,DialConnectTimeout,DialReadTimeout,DialWriteTimeoutを伝達する.
今使っているredigoのredis poolを添付します.
redis.Pool{
MaxIdle: cf.StatsRedis.MaxIdle,
MaxActive: cf.StatsRedis.MaxActive,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", cf.StatsRedis.ProxyAddress,
redis.DialConnectTimeout(time.Duration(cf.StatsRedis.ConnectTimeout) * time.Millisecond),
redis.DialReadTimeout(time.Duration(cf.StatsRedis.ReadTimeout) * time.Millisecond),
redis.DialWriteTimeout(time.Duration(cf.StatsRedis.WriteTimeout) * time.Millisecond))
if err != nil {
l4g.Error(err)
return nil, err
}
return c, nil
},
// Use the TestOnBorrow function to check the health of an idle connection
// before the connection is returned to the application.
TestOnBorrow: func(c redis.Conn, t time.Time) error {
if time.Since(t) < time.Minute {
return nil
}
_, err := c.Do("PING")
return err
},
IdleTimeout: 300 * time.Second,
// If Wait is true and the pool is at the MaxActive limit,
// then Get() waits for a connection to be returned to the pool before returning
Wait: true,
},
前はずっとredisと思っていた.Dialという方法ではデフォルトのタイムアウト時間がありますが、結果は事実上ないことを証明します.
コードを書くときは必ず「と思う」ことはできません.
実はredigoのせいではありません.このライブラリの下部にはgolangのnetライブラリが使われています.default timeoutは0ですね.which is interpreted as「no timeout」...