mirror of
https://github.com/redis/go-redis.git
synced 2025-04-16 09:23:06 +03:00
* fix: recycle connections in some Redis Cluster scenarios This issue was surfaced in a Cloud Provider solution that used for rolling out new nodes using the same address (hostname) of the nodes that will be replaced in a Redis Cluster, while the former ones once depromoted as Slaves would continue in service during some mintues for redirecting traffic. The solution basically identifies when the connection could be stale since a MOVED response will be returned using the same address (hostname) that is being used by the connection. At that moment we consider the connection as no longer usable forcing to recycle the connection.
145 lines
2.8 KiB
Go
145 lines
2.8 KiB
Go
package redis
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/go-redis/redis/v8/internal/pool"
|
|
"github.com/go-redis/redis/v8/internal/proto"
|
|
)
|
|
|
|
// ErrClosed performs any operation on the closed client will return this error.
|
|
var ErrClosed = pool.ErrClosed
|
|
|
|
type Error interface {
|
|
error
|
|
|
|
// RedisError is a no-op function but
|
|
// serves to distinguish types that are Redis
|
|
// errors from ordinary errors: a type is a
|
|
// Redis error if it has a RedisError method.
|
|
RedisError()
|
|
}
|
|
|
|
var _ Error = proto.RedisError("")
|
|
|
|
func shouldRetry(err error, retryTimeout bool) bool {
|
|
switch err {
|
|
case io.EOF, io.ErrUnexpectedEOF:
|
|
return true
|
|
case nil, context.Canceled, context.DeadlineExceeded:
|
|
return false
|
|
}
|
|
|
|
if v, ok := err.(timeoutError); ok {
|
|
if v.Timeout() {
|
|
return retryTimeout
|
|
}
|
|
return true
|
|
}
|
|
|
|
s := err.Error()
|
|
if s == "ERR max number of clients reached" {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(s, "LOADING ") {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(s, "READONLY ") {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(s, "CLUSTERDOWN ") {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(s, "TRYAGAIN ") {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func isRedisError(err error) bool {
|
|
_, ok := err.(proto.RedisError)
|
|
return ok
|
|
}
|
|
|
|
func isBadConn(err error, allowTimeout bool, addr string) bool {
|
|
switch err {
|
|
case nil:
|
|
return false
|
|
case context.Canceled, context.DeadlineExceeded:
|
|
return true
|
|
}
|
|
|
|
if isRedisError(err) {
|
|
switch {
|
|
case isReadOnlyError(err):
|
|
// Close connections in read only state in case domain addr is used
|
|
// and domain resolves to a different Redis Server. See #790.
|
|
return true
|
|
case isMovedSameConnAddr(err, addr):
|
|
// Close connections when we are asked to move to the same addr
|
|
// of the connection. Force a DNS resolution when all connections
|
|
// of the pool are recycled
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
if allowTimeout {
|
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
|
return !netErr.Temporary()
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func isMovedError(err error) (moved bool, ask bool, addr string) {
|
|
if !isRedisError(err) {
|
|
return
|
|
}
|
|
|
|
s := err.Error()
|
|
switch {
|
|
case strings.HasPrefix(s, "MOVED "):
|
|
moved = true
|
|
case strings.HasPrefix(s, "ASK "):
|
|
ask = true
|
|
default:
|
|
return
|
|
}
|
|
|
|
ind := strings.LastIndex(s, " ")
|
|
if ind == -1 {
|
|
return false, false, ""
|
|
}
|
|
addr = s[ind+1:]
|
|
return
|
|
}
|
|
|
|
func isLoadingError(err error) bool {
|
|
return strings.HasPrefix(err.Error(), "LOADING ")
|
|
}
|
|
|
|
func isReadOnlyError(err error) bool {
|
|
return strings.HasPrefix(err.Error(), "READONLY ")
|
|
}
|
|
|
|
func isMovedSameConnAddr(err error, addr string) bool {
|
|
redisError := err.Error()
|
|
if !strings.HasPrefix(redisError, "MOVED ") {
|
|
return false
|
|
}
|
|
return strings.HasSuffix(redisError, addr)
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
type timeoutError interface {
|
|
Timeout() bool
|
|
}
|