Resent client pool when sentinel switches master

This commit is contained in:
Vladimir Mihailenco 2017-06-29 16:53:49 +03:00
parent 75ceb983b7
commit fbc8000fd1
6 changed files with 152 additions and 159 deletions

View File

@ -140,47 +140,6 @@ func (p *ConnPool) lastDialError() error {
return p._lastDialError.Load().(error) return p._lastDialError.Load().(error)
} }
func (p *ConnPool) PopFree() *Conn {
select {
case p.queue <- struct{}{}:
default:
timer := timers.Get().(*time.Timer)
timer.Reset(p.opt.PoolTimeout)
select {
case p.queue <- struct{}{}:
if !timer.Stop() {
<-timer.C
}
timers.Put(timer)
case <-timer.C:
timers.Put(timer)
atomic.AddUint32(&p.stats.Timeouts, 1)
return nil
}
}
p.freeConnsMu.Lock()
cn := p.popFree()
p.freeConnsMu.Unlock()
if cn == nil {
<-p.queue
}
return cn
}
func (p *ConnPool) popFree() *Conn {
if len(p.freeConns) == 0 {
return nil
}
idx := len(p.freeConns) - 1
cn := p.freeConns[idx]
p.freeConns = p.freeConns[:idx]
return cn
}
// Get returns existed connection from the pool or creates a new one. // Get returns existed connection from the pool or creates a new one.
func (p *ConnPool) Get() (*Conn, bool, error) { func (p *ConnPool) Get() (*Conn, bool, error) {
if p.closed() { if p.closed() {
@ -235,6 +194,17 @@ func (p *ConnPool) Get() (*Conn, bool, error) {
return newcn, true, nil return newcn, true, nil
} }
func (p *ConnPool) popFree() *Conn {
if len(p.freeConns) == 0 {
return nil
}
idx := len(p.freeConns) - 1
cn := p.freeConns[idx]
p.freeConns = p.freeConns[:idx]
return cn
}
func (p *ConnPool) Put(cn *Conn) error { func (p *ConnPool) Put(cn *Conn) error {
if data := cn.Rd.PeekBuffered(); data != nil { if data := cn.Rd.PeekBuffered(); data != nil {
internal.Logf("connection has unread data: %q", data) internal.Logf("connection has unread data: %q", data)
@ -303,17 +273,28 @@ func (p *ConnPool) closed() bool {
return atomic.LoadUint32(&p._closed) == 1 return atomic.LoadUint32(&p._closed) == 1
} }
func (p *ConnPool) Filter(fn func(*Conn) bool) error {
var firstErr error
p.connsMu.Lock()
for _, cn := range p.conns {
if fn(cn) {
if err := p.closeConn(cn); err != nil && firstErr == nil {
firstErr = err
}
}
}
p.connsMu.Unlock()
return firstErr
}
func (p *ConnPool) Close() error { func (p *ConnPool) Close() error {
if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) { if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) {
return ErrClosed return ErrClosed
} }
p.connsMu.Lock()
var firstErr error var firstErr error
p.connsMu.Lock()
for _, cn := range p.conns { for _, cn := range p.conns {
if cn == nil {
continue
}
if err := p.closeConn(cn); err != nil && firstErr == nil { if err := p.closeConn(cn); err != nil && firstErr == nil {
firstErr = err firstErr = err
} }

View File

@ -3,6 +3,7 @@ package redis_test
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"net" "net"
"os" "os"
"os/exec" "os/exec"
@ -50,6 +51,10 @@ var cluster = &clusterScenario{
clients: make(map[string]*redis.Client, 6), clients: make(map[string]*redis.Client, 6),
} }
func init() {
redis.SetLogger(log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile))
}
var _ = BeforeSuite(func() { var _ = BeforeSuite(func() {
var err error var err error

107
pubsub.go
View File

@ -19,13 +19,11 @@ import (
type PubSub struct { type PubSub struct {
base baseClient base baseClient
mu sync.Mutex mu sync.Mutex
cn *pool.Conn cn *pool.Conn
closed bool
subMu sync.Mutex
channels []string channels []string
patterns []string patterns []string
closed bool
cmd *Cmd cmd *Cmd
} }
@ -64,9 +62,6 @@ func (c *PubSub) conn() (*pool.Conn, bool, error) {
} }
func (c *PubSub) resubscribe(cn *pool.Conn) error { func (c *PubSub) resubscribe(cn *pool.Conn) error {
c.subMu.Lock()
defer c.subMu.Unlock()
var firstErr error var firstErr error
if len(c.channels) > 0 { if len(c.channels) > 0 {
if err := c._subscribe(cn, "subscribe", c.channels...); err != nil && firstErr == nil { if err := c._subscribe(cn, "subscribe", c.channels...); err != nil && firstErr == nil {
@ -81,6 +76,18 @@ func (c *PubSub) resubscribe(cn *pool.Conn) error {
return firstErr return firstErr
} }
func (c *PubSub) _subscribe(cn *pool.Conn, redisCmd string, channels ...string) error {
args := make([]interface{}, 1+len(channels))
args[0] = redisCmd
for i, channel := range channels {
args[1+i] = channel
}
cmd := NewSliceCmd(args...)
cn.SetWriteTimeout(c.base.opt.WriteTimeout)
return writeCmd(cn, cmd)
}
func (c *PubSub) putConn(cn *pool.Conn, err error) { func (c *PubSub) putConn(cn *pool.Conn, err error) {
if !internal.IsBadConn(err, true) { if !internal.IsBadConn(err, true) {
return return
@ -114,6 +121,42 @@ func (c *PubSub) Close() error {
return nil return nil
} }
// Subscribes the client to the specified channels. It returns
// empty subscription if there are no channels.
func (c *PubSub) Subscribe(channels ...string) error {
c.mu.Lock()
c.channels = appendIfNotExists(c.channels, channels...)
c.mu.Unlock()
return c.subscribe("subscribe", channels...)
}
// Subscribes the client to the given patterns. It returns
// empty subscription if there are no patterns.
func (c *PubSub) PSubscribe(patterns ...string) error {
c.mu.Lock()
c.patterns = appendIfNotExists(c.patterns, patterns...)
c.mu.Unlock()
return c.subscribe("psubscribe", patterns...)
}
// Unsubscribes the client from the given channels, or from all of
// them if none is given.
func (c *PubSub) Unsubscribe(channels ...string) error {
c.mu.Lock()
c.channels = remove(c.channels, channels...)
c.mu.Unlock()
return c.subscribe("unsubscribe", channels...)
}
// Unsubscribes the client from the given patterns, or from all of
// them if none is given.
func (c *PubSub) PUnsubscribe(patterns ...string) error {
c.mu.Lock()
c.patterns = remove(c.patterns, patterns...)
c.mu.Unlock()
return c.subscribe("punsubscribe", patterns...)
}
func (c *PubSub) subscribe(redisCmd string, channels ...string) error { func (c *PubSub) subscribe(redisCmd string, channels ...string) error {
cn, isNew, err := c.conn() cn, isNew, err := c.conn()
if err != nil { if err != nil {
@ -129,54 +172,6 @@ func (c *PubSub) subscribe(redisCmd string, channels ...string) error {
return err return err
} }
func (c *PubSub) _subscribe(cn *pool.Conn, redisCmd string, channels ...string) error {
args := make([]interface{}, 1+len(channels))
args[0] = redisCmd
for i, channel := range channels {
args[1+i] = channel
}
cmd := NewSliceCmd(args...)
cn.SetWriteTimeout(c.base.opt.WriteTimeout)
return writeCmd(cn, cmd)
}
// Subscribes the client to the specified channels. It returns
// empty subscription if there are no channels.
func (c *PubSub) Subscribe(channels ...string) error {
c.subMu.Lock()
c.channels = appendIfNotExists(c.channels, channels...)
c.subMu.Unlock()
return c.subscribe("subscribe", channels...)
}
// Subscribes the client to the given patterns. It returns
// empty subscription if there are no patterns.
func (c *PubSub) PSubscribe(patterns ...string) error {
c.subMu.Lock()
c.patterns = appendIfNotExists(c.patterns, patterns...)
c.subMu.Unlock()
return c.subscribe("psubscribe", patterns...)
}
// Unsubscribes the client from the given channels, or from all of
// them if none is given.
func (c *PubSub) Unsubscribe(channels ...string) error {
c.subMu.Lock()
c.channels = remove(c.channels, channels...)
c.subMu.Unlock()
return c.subscribe("unsubscribe", channels...)
}
// Unsubscribes the client from the given patterns, or from all of
// them if none is given.
func (c *PubSub) PUnsubscribe(patterns ...string) error {
c.subMu.Lock()
c.patterns = remove(c.patterns, patterns...)
c.subMu.Unlock()
return c.subscribe("punsubscribe", patterns...)
}
func (c *PubSub) Ping(payload ...string) error { func (c *PubSub) Ping(payload ...string) error {
args := []interface{}{"ping"} args := []interface{}{"ping"}
if len(payload) == 1 { if len(payload) == 1 {

View File

@ -387,7 +387,10 @@ func (c *Client) pubSub() *PubSub {
func (c *Client) Subscribe(channels ...string) *PubSub { func (c *Client) Subscribe(channels ...string) *PubSub {
pubsub := c.pubSub() pubsub := c.pubSub()
if len(channels) > 0 { if len(channels) > 0 {
_ = pubsub.Subscribe(channels...) err := pubsub.Subscribe(channels...)
if err != nil {
panic(err)
}
} }
return pubsub return pubsub
} }

View File

@ -132,7 +132,6 @@ func (c *sentinelClient) Sentinels(name string) *SliceCmd {
} }
type sentinelFailover struct { type sentinelFailover struct {
masterName string
sentinelAddrs []string sentinelAddrs []string
opt *Options opt *Options
@ -140,8 +139,10 @@ type sentinelFailover struct {
pool *pool.ConnPool pool *pool.ConnPool
poolOnce sync.Once poolOnce sync.Once
mu sync.RWMutex mu sync.RWMutex
sentinel *sentinelClient masterName string
_masterAddr string
sentinel *sentinelClient
} }
func (d *sentinelFailover) Close() error { func (d *sentinelFailover) Close() error {
@ -168,17 +169,30 @@ func (d *sentinelFailover) MasterAddr() (string, error) {
d.mu.Lock() d.mu.Lock()
defer d.mu.Unlock() defer d.mu.Unlock()
addr, err := d.masterAddr()
if err != nil {
return "", err
}
if d._masterAddr != addr {
d.switchMaster(addr)
}
return addr, nil
}
func (d *sentinelFailover) masterAddr() (string, error) {
// Try last working sentinel. // Try last working sentinel.
if d.sentinel != nil { if d.sentinel != nil {
addr, err := d.sentinel.GetMasterAddrByName(d.masterName).Result() addr, err := d.sentinel.GetMasterAddrByName(d.masterName).Result()
if err != nil { if err == nil {
internal.Logf("sentinel: GetMasterAddrByName %q failed: %s", d.masterName, err)
d._resetSentinel()
} else {
addr := net.JoinHostPort(addr[0], addr[1]) addr := net.JoinHostPort(addr[0], addr[1])
internal.Logf("sentinel: %q addr is %s", d.masterName, addr) internal.Logf("sentinel: master=%q addr=%q", d.masterName, addr)
return addr, nil return addr, nil
} }
internal.Logf("sentinel: GetMasterAddrByName name=%q failed: %s", d.masterName, err)
d._resetSentinel()
} }
for i, sentinelAddr := range d.sentinelAddrs { for i, sentinelAddr := range d.sentinelAddrs {
@ -193,25 +207,36 @@ func (d *sentinelFailover) MasterAddr() (string, error) {
PoolTimeout: d.opt.PoolTimeout, PoolTimeout: d.opt.PoolTimeout,
IdleTimeout: d.opt.IdleTimeout, IdleTimeout: d.opt.IdleTimeout,
}) })
masterAddr, err := sentinel.GetMasterAddrByName(d.masterName).Result() masterAddr, err := sentinel.GetMasterAddrByName(d.masterName).Result()
if err != nil { if err != nil {
internal.Logf("sentinel: GetMasterAddrByName %q failed: %s", d.masterName, err) internal.Logf("sentinel: GetMasterAddrByName master=%q failed: %s", d.masterName, err)
sentinel.Close() sentinel.Close()
continue continue
} }
// Push working sentinel to the top. // Push working sentinel to the top.
d.sentinelAddrs[0], d.sentinelAddrs[i] = d.sentinelAddrs[i], d.sentinelAddrs[0] d.sentinelAddrs[0], d.sentinelAddrs[i] = d.sentinelAddrs[i], d.sentinelAddrs[0]
d.setSentinel(sentinel) d.setSentinel(sentinel)
addr := net.JoinHostPort(masterAddr[0], masterAddr[1]) addr := net.JoinHostPort(masterAddr[0], masterAddr[1])
internal.Logf("sentinel: %q addr is %s", d.masterName, addr)
return addr, nil return addr, nil
} }
return "", errors.New("redis: all sentinels are unreachable") return "", errors.New("redis: all sentinels are unreachable")
} }
func (d *sentinelFailover) switchMaster(masterAddr string) {
internal.Logf(
"sentinel: new master=%q addr=%q",
d.masterName, masterAddr,
)
_ = d.Pool().Filter(func(cn *pool.Conn) bool {
return cn.RemoteAddr().String() != masterAddr
})
d._masterAddr = masterAddr
}
func (d *sentinelFailover) setSentinel(sentinel *sentinelClient) { func (d *sentinelFailover) setSentinel(sentinel *sentinelClient) {
d.discoverSentinels(sentinel) d.discoverSentinels(sentinel)
d.sentinel = sentinel d.sentinel = sentinel
@ -219,25 +244,25 @@ func (d *sentinelFailover) setSentinel(sentinel *sentinelClient) {
} }
func (d *sentinelFailover) resetSentinel() error { func (d *sentinelFailover) resetSentinel() error {
var err error
d.mu.Lock() d.mu.Lock()
err := d._resetSentinel() if d.sentinel != nil {
err = d._resetSentinel()
}
d.mu.Unlock() d.mu.Unlock()
return err return err
} }
func (d *sentinelFailover) _resetSentinel() error { func (d *sentinelFailover) _resetSentinel() error {
var err error err := d.sentinel.Close()
if d.sentinel != nil { d.sentinel = nil
err = d.sentinel.Close()
d.sentinel = nil
}
return err return err
} }
func (d *sentinelFailover) discoverSentinels(sentinel *sentinelClient) { func (d *sentinelFailover) discoverSentinels(sentinel *sentinelClient) {
sentinels, err := sentinel.Sentinels(d.masterName).Result() sentinels, err := sentinel.Sentinels(d.masterName).Result()
if err != nil { if err != nil {
internal.Logf("sentinel: Sentinels %q failed: %s", d.masterName, err) internal.Logf("sentinel: Sentinels master=%q failed: %s", d.masterName, err)
return return
} }
for _, sentinel := range sentinels { for _, sentinel := range sentinels {
@ -248,8 +273,8 @@ func (d *sentinelFailover) discoverSentinels(sentinel *sentinelClient) {
sentinelAddr := vals[i+1].(string) sentinelAddr := vals[i+1].(string)
if !contains(d.sentinelAddrs, sentinelAddr) { if !contains(d.sentinelAddrs, sentinelAddr) {
internal.Logf( internal.Logf(
"sentinel: discovered new %q sentinel: %s", "sentinel: discovered new sentinel=%q for master=%q",
d.masterName, sentinelAddr, sentinelAddr, d.masterName,
) )
d.sentinelAddrs = append(d.sentinelAddrs, sentinelAddr) d.sentinelAddrs = append(d.sentinelAddrs, sentinelAddr)
} }
@ -258,34 +283,6 @@ func (d *sentinelFailover) discoverSentinels(sentinel *sentinelClient) {
} }
} }
// closeOldConns closes connections to the old master after failover switch.
func (d *sentinelFailover) closeOldConns(newMaster string) {
// Good connections that should be put back to the pool. They
// can't be put immediately, because pool.PopFree will return them
// again on next iteration.
cnsToPut := make([]*pool.Conn, 0)
for {
cn := d.pool.PopFree()
if cn == nil {
break
}
if cn.RemoteAddr().String() != newMaster {
internal.Logf(
"sentinel: closing connection to the old master %s",
cn.RemoteAddr(),
)
d.pool.Remove(cn)
} else {
cnsToPut = append(cnsToPut, cn)
}
}
for _, cn := range cnsToPut {
d.pool.Put(cn)
}
}
func (d *sentinelFailover) listen(sentinel *sentinelClient) { func (d *sentinelFailover) listen(sentinel *sentinelClient) {
var pubsub *PubSub var pubsub *PubSub
for { for {
@ -312,17 +309,16 @@ func (d *sentinelFailover) listen(sentinel *sentinelClient) {
case "+switch-master": case "+switch-master":
parts := strings.Split(msg.Payload, " ") parts := strings.Split(msg.Payload, " ")
if parts[0] != d.masterName { if parts[0] != d.masterName {
internal.Logf("sentinel: ignore new %s addr", parts[0]) internal.Logf("sentinel: ignore addr for master=%q", parts[0])
continue continue
} }
addr := net.JoinHostPort(parts[3], parts[4]) addr := net.JoinHostPort(parts[3], parts[4])
internal.Logf(
"sentinel: new %q addr is %s",
d.masterName, addr,
)
d.closeOldConns(addr) d.mu.Lock()
if d._masterAddr != addr {
d.switchMaster(addr)
}
d.mu.Unlock()
} }
} }
} }

View File

@ -23,15 +23,19 @@ var _ = Describe("Sentinel", func() {
}) })
It("should facilitate failover", func() { It("should facilitate failover", func() {
// Set value on master, verify // Set value on master.
err := client.Set("foo", "master", 0).Err() err := client.Set("foo", "master", 0).Err()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
// Verify.
val, err := sentinelMaster.Get("foo").Result() val, err := sentinelMaster.Get("foo").Result()
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("master")) Expect(val).To(Equal("master"))
// Wait until replicated // Create subscription.
ch := client.Subscribe("foo").Channel()
// Wait until replicated.
Eventually(func() string { Eventually(func() string {
return sentinelSlave1.Get("foo").Val() return sentinelSlave1.Get("foo").Val()
}, "1s", "100ms").Should(Equal("master")) }, "1s", "100ms").Should(Equal("master"))
@ -59,6 +63,15 @@ var _ = Describe("Sentinel", func() {
Eventually(func() error { Eventually(func() error {
return client.Get("foo").Err() return client.Get("foo").Err()
}, "5s", "100ms").ShouldNot(HaveOccurred()) }, "5s", "100ms").ShouldNot(HaveOccurred())
// Publish message to check if subscription is renewed.
err = client.Publish("foo", "hello").Err()
Expect(err).NotTo(HaveOccurred())
var msg *redis.Message
Eventually(ch).Should(Receive(&msg))
Expect(msg.Channel).To(Equal("foo"))
Expect(msg.Payload).To(Equal("hello"))
}) })
It("supports DB selection", func() { It("supports DB selection", func() {