зеркало из https://github.com/github/vitess-gh.git
Refactor DNS refresh changes
Signed-off-by: Jacques Grove <aquarapid@gmail.com>
This commit is contained in:
Родитель
101563b99d
Коммит
2d024326fa
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
package netutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
|
@ -189,3 +190,60 @@ func ResolveIPv4Addrs(addr string) ([]string, error) {
|
|||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func dnsLookup(host string) ([]net.IP, error) {
|
||||
addrs, err := net.LookupHost(host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error looking up dns name [%v]: (%v)", host, err)
|
||||
}
|
||||
naddr := make([]net.IP, len(addrs))
|
||||
for i, a := range addrs {
|
||||
naddr[i] = net.ParseIP(a)
|
||||
}
|
||||
sort.Slice(naddr, func(i, j int) bool {
|
||||
return bytes.Compare(naddr[i], naddr[j]) < 0
|
||||
})
|
||||
return naddr, nil
|
||||
}
|
||||
|
||||
// DNSTracker is a closure that persists state for
|
||||
// tracking changes in the DNS resolution of a target dns name
|
||||
// returns true if the DNS name resolution has changed
|
||||
// If there is a lookup problem, we pretend nothing has changed
|
||||
func DNSTracker(host string) func() (bool, error) {
|
||||
dnsName := host
|
||||
var addrs []net.IP
|
||||
if dnsName != "" {
|
||||
addrs, _ = dnsLookup(dnsName)
|
||||
}
|
||||
|
||||
return func() (bool, error) {
|
||||
if dnsName == "" {
|
||||
return false, nil
|
||||
}
|
||||
newaddrs, err := dnsLookup(dnsName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(newaddrs) == 0 { // Should not happen, but just in case
|
||||
return false, fmt.Errorf("Connection DNS for %s reporting as empty, ignoring", dnsName)
|
||||
}
|
||||
if !addrEqual(addrs, newaddrs) {
|
||||
addrs = newaddrs
|
||||
return true, fmt.Errorf("Connection DNS for %s has changed; old: [%v] new: [%v]", dnsName, addrs, newaddrs)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func addrEqual(a, b []net.IP) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for idx, v := range a {
|
||||
if !net.IP.Equal(v, b[idx]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"vitess.io/vitess/go/vt/log"
|
||||
|
||||
"context"
|
||||
|
||||
"vitess.io/vitess/go/sync2"
|
||||
|
@ -52,7 +54,7 @@ type Factory func(context.Context) (Resource, error)
|
|||
|
||||
// RefreshCheck is a function used to determine if a resource pool should be
|
||||
// refreshed (i.e. closed and reopened)
|
||||
type RefreshCheck func() bool
|
||||
type RefreshCheck func() (bool, error)
|
||||
|
||||
// Resource defines the interface that every resource must provide.
|
||||
// Thread synchronization between Close() and IsClosed()
|
||||
|
@ -80,11 +82,11 @@ type ResourcePool struct {
|
|||
idleTimer *timer.Timer
|
||||
logWait func(time.Time)
|
||||
|
||||
refreshCheck RefreshCheck
|
||||
refreshFrequency time.Duration
|
||||
refreshStop chan struct{}
|
||||
refreshTicker *time.Ticker
|
||||
refreshWg sync.WaitGroup
|
||||
refreshCheck RefreshCheck
|
||||
refreshInterval time.Duration
|
||||
refreshStop chan struct{}
|
||||
refreshTicker *time.Ticker
|
||||
refreshWg sync.WaitGroup
|
||||
}
|
||||
|
||||
type resourceWrapper struct {
|
||||
|
@ -103,8 +105,9 @@ type resourceWrapper struct {
|
|||
// An idleTimeout of 0 means that there is no timeout.
|
||||
// A non-zero value of prefillParallelism causes the pool to be pre-filled.
|
||||
// The value specifies how many resources can be opened in parallel.
|
||||
// TODO: document refreshCheck and refreshFrequency
|
||||
func NewResourcePool(factory Factory, refreshCheck RefreshCheck, refreshFrequency time.Duration, capacity, maxCap int, idleTimeout time.Duration, prefillParallelism int, logWait func(time.Time)) *ResourcePool {
|
||||
// refreshCheck is a function we consult at refreshInterval
|
||||
// intervals to determine if the pool should be drained and reopened
|
||||
func NewResourcePool(factory Factory, capacity, maxCap int, idleTimeout time.Duration, prefillParallelism int, logWait func(time.Time), refreshCheck RefreshCheck, refreshInterval time.Duration) *ResourcePool {
|
||||
if capacity <= 0 || maxCap <= 0 || capacity > maxCap {
|
||||
panic(errors.New("invalid/out of range capacity"))
|
||||
}
|
||||
|
@ -154,8 +157,8 @@ func NewResourcePool(factory Factory, refreshCheck RefreshCheck, refreshFrequenc
|
|||
rp.idleTimer.Start(rp.closeIdleResources)
|
||||
}
|
||||
|
||||
if refreshCheck != nil && refreshFrequency > 0 {
|
||||
rp.refreshFrequency = refreshFrequency
|
||||
if refreshCheck != nil && refreshInterval > 0 {
|
||||
rp.refreshInterval = refreshInterval
|
||||
rp.refreshCheck = refreshCheck
|
||||
rp.startRefreshTicker()
|
||||
}
|
||||
|
@ -164,7 +167,7 @@ func NewResourcePool(factory Factory, refreshCheck RefreshCheck, refreshFrequenc
|
|||
}
|
||||
|
||||
func (rp *ResourcePool) startRefreshTicker() {
|
||||
rp.refreshTicker = time.NewTicker(rp.refreshFrequency)
|
||||
rp.refreshTicker = time.NewTicker(rp.refreshInterval)
|
||||
rp.refreshStop = make(chan struct{})
|
||||
rp.refreshWg.Add(1)
|
||||
go func() {
|
||||
|
@ -172,7 +175,11 @@ func (rp *ResourcePool) startRefreshTicker() {
|
|||
for {
|
||||
select {
|
||||
case <-rp.refreshTicker.C:
|
||||
if rp.refreshCheck() {
|
||||
val, err := rp.refreshCheck()
|
||||
if err != nil {
|
||||
log.Info(err)
|
||||
}
|
||||
if val {
|
||||
go rp.reopen()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ func TestOpen(t *testing.T) {
|
|||
count.Set(0)
|
||||
waitStarts = waitStarts[:0]
|
||||
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 6, 6, time.Second, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 6, 6, time.Second, 0, logWait, nil, 0)
|
||||
p.SetCapacity(5)
|
||||
var resources [10]Resource
|
||||
|
||||
|
@ -218,12 +218,12 @@ func TestOpen(t *testing.T) {
|
|||
func TestPrefill(t *testing.T) {
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 5, 5, time.Second, 1, logWait)
|
||||
p := NewResourcePool(PoolFactory, 5, 5, time.Second, 1, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
if p.Active() != 5 {
|
||||
t.Errorf("p.Active(): %d, want 5", p.Active())
|
||||
}
|
||||
p = NewResourcePool(FailFactory, nil, 0, 5, 5, time.Second, 1, logWait)
|
||||
p = NewResourcePool(FailFactory, 5, 5, time.Second, 1, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
if p.Active() != 0 {
|
||||
t.Errorf("p.Active(): %d, want 0", p.Active())
|
||||
|
@ -238,7 +238,7 @@ func TestPrefillTimeout(t *testing.T) {
|
|||
defer func() { prefillTimeout = saveTimeout }()
|
||||
|
||||
start := time.Now()
|
||||
p := NewResourcePool(SlowFailFactory, nil, 0, 5, 5, time.Second, 1, logWait)
|
||||
p := NewResourcePool(SlowFailFactory, 5, 5, time.Second, 1, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
if elapsed := time.Since(start); elapsed > 20*time.Millisecond {
|
||||
t.Errorf("elapsed: %v, should be around 10ms", elapsed)
|
||||
|
@ -254,7 +254,7 @@ func TestShrinking(t *testing.T) {
|
|||
count.Set(0)
|
||||
waitStarts = waitStarts[:0]
|
||||
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 5, 5, time.Second, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
|
||||
var resources [10]Resource
|
||||
// Leave one empty slot in the pool
|
||||
for i := 0; i < 4; i++ {
|
||||
|
@ -396,7 +396,7 @@ func TestClosing(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 5, 5, time.Second, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
|
||||
var resources [10]Resource
|
||||
for i := 0; i < 5; i++ {
|
||||
r, err := p.Get(ctx)
|
||||
|
@ -444,10 +444,10 @@ func TestReopen(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
refreshCheck := func() bool {
|
||||
return true
|
||||
refreshCheck := func() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
p := NewResourcePool(PoolFactory, refreshCheck, 500*time.Millisecond, 5, 5, time.Second, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, refreshCheck, 500*time.Millisecond)
|
||||
var resources [10]Resource
|
||||
for i := 0; i < 5; i++ {
|
||||
r, err := p.Get(ctx)
|
||||
|
@ -487,7 +487,7 @@ func TestIdleTimeout(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 1, 1, 10*time.Millisecond, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 1, 1, 10*time.Millisecond, 0, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
|
||||
r, err := p.Get(ctx)
|
||||
|
@ -598,7 +598,7 @@ func TestIdleTimeoutCreateFail(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 1, 1, 10*time.Millisecond, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 1, 1, 10*time.Millisecond, 0, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
r, err := p.Get(ctx)
|
||||
if err != nil {
|
||||
|
@ -619,7 +619,7 @@ func TestCreateFail(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(FailFactory, nil, 0, 5, 5, time.Second, 0, logWait)
|
||||
p := NewResourcePool(FailFactory, 5, 5, time.Second, 0, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
if _, err := p.Get(ctx); err.Error() != "Failed" {
|
||||
t.Errorf("Expecting Failed, received %v", err)
|
||||
|
@ -635,7 +635,7 @@ func TestCreateFailOnPut(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 5, 5, time.Second, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 5, 5, time.Second, 0, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
_, err := p.Get(ctx)
|
||||
if err != nil {
|
||||
|
@ -652,7 +652,7 @@ func TestSlowCreateFail(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(SlowFailFactory, nil, 0, 2, 2, time.Second, 0, logWait)
|
||||
p := NewResourcePool(SlowFailFactory, 2, 2, time.Second, 0, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
ch := make(chan bool)
|
||||
// The third Get should not wait indefinitely
|
||||
|
@ -674,7 +674,7 @@ func TestTimeout(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 1, 1, time.Second, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 1, 1, time.Second, 0, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
r, err := p.Get(ctx)
|
||||
if err != nil {
|
||||
|
@ -693,7 +693,7 @@ func TestTimeout(t *testing.T) {
|
|||
func TestExpired(t *testing.T) {
|
||||
lastID.Set(0)
|
||||
count.Set(0)
|
||||
p := NewResourcePool(PoolFactory, nil, 0, 1, 1, time.Second, 0, logWait)
|
||||
p := NewResourcePool(PoolFactory, 1, 1, time.Second, 0, logWait, nil, 0)
|
||||
defer p.Close()
|
||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-1*time.Second))
|
||||
r, err := p.Get(ctx)
|
||||
|
|
|
@ -44,7 +44,7 @@ type RPCPool struct {
|
|||
// will not be called).
|
||||
func NewRPCPool(size int, waitTimeout time.Duration, logWait func(time.Time)) *RPCPool {
|
||||
return &RPCPool{
|
||||
rp: NewResourcePool(rpcResourceFactory, nil, 0, size, size, 0, size, logWait),
|
||||
rp: NewResourcePool(rpcResourceFactory, size, size, 0, size, logWait, nil, 0),
|
||||
waitTimeout: waitTimeout,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,19 +22,18 @@ object to pool these DBConnections.
|
|||
package dbconnpool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"vitess.io/vitess/go/netutil"
|
||||
|
||||
"context"
|
||||
|
||||
"vitess.io/vitess/go/pools"
|
||||
"vitess.io/vitess/go/stats"
|
||||
"vitess.io/vitess/go/vt/dbconfigs"
|
||||
"vitess.io/vitess/go/vt/log"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -90,57 +89,6 @@ func (cp *ConnectionPool) pool() (p *pools.ResourcePool) {
|
|||
return p
|
||||
}
|
||||
|
||||
func lookup(host string) []net.IP {
|
||||
addrs, err := net.LookupHost(host)
|
||||
if err != nil {
|
||||
log.Errorf("Error looking up dns name [%v]: (%v)\n", host, err)
|
||||
return nil
|
||||
}
|
||||
naddr := make([]net.IP, len(addrs))
|
||||
for i, a := range addrs {
|
||||
naddr[i] = net.ParseIP(a)
|
||||
}
|
||||
sort.Slice(naddr, func(i, j int) bool {
|
||||
return bytes.Compare(naddr[i], naddr[j]) < 0
|
||||
})
|
||||
return naddr
|
||||
}
|
||||
|
||||
// DNSTracker is a closure that persists state for
|
||||
// tracking changes in the DNS resolution of a target dns name
|
||||
func DNSTracker(host string) func() bool {
|
||||
dnsName := host
|
||||
var addrs []net.IP
|
||||
if dnsName != "" {
|
||||
addrs = lookup(dnsName)
|
||||
}
|
||||
|
||||
return func() bool {
|
||||
if dnsName == "" {
|
||||
return false
|
||||
}
|
||||
newaddrs := lookup(dnsName)
|
||||
if !addrEqual(addrs, newaddrs) {
|
||||
log.Infof("Connection DNS has changed; old: [%v] new: [%v]\n", addrs, newaddrs)
|
||||
addrs = newaddrs
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func addrEqual(a, b []net.IP) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if !net.IP.Equal(v, b[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Open must be called before starting to use the pool.
|
||||
//
|
||||
// For instance:
|
||||
|
@ -149,18 +97,17 @@ func addrEqual(a, b []net.IP) bool {
|
|||
// ...
|
||||
// conn, err := pool.Get()
|
||||
// ...
|
||||
// TODO: fix comment
|
||||
func (cp *ConnectionPool) Open(info dbconfigs.Connector) {
|
||||
var f pools.RefreshCheck
|
||||
var refreshCheck pools.RefreshCheck
|
||||
if net.ParseIP(info.Host()) == nil {
|
||||
f = DNSTracker(info.Host())
|
||||
refreshCheck = netutil.DNSTracker(info.Host())
|
||||
} else {
|
||||
f = nil
|
||||
refreshCheck = nil
|
||||
}
|
||||
cp.mu.Lock()
|
||||
defer cp.mu.Unlock()
|
||||
cp.info = info
|
||||
cp.connections = pools.NewResourcePool(cp.connect, f, cp.resolutionFrequency, cp.capacity, cp.capacity, cp.idleTimeout, 0, nil)
|
||||
cp.connections = pools.NewResourcePool(cp.connect, cp.capacity, cp.capacity, cp.idleTimeout, 0, nil, refreshCheck, cp.resolutionFrequency)
|
||||
}
|
||||
|
||||
// connect is used by the resource pool to create a new Resource.
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"vitess.io/vitess/go/netutil"
|
||||
|
||||
"context"
|
||||
|
||||
"vitess.io/vitess/go/pools"
|
||||
|
@ -114,12 +116,12 @@ func (cp *Pool) Open(appParams, dbaParams, appDebugParams dbconfigs.Connector) {
|
|||
|
||||
var refreshCheck pools.RefreshCheck
|
||||
if net.ParseIP(appParams.Host()) == nil {
|
||||
refreshCheck = dbconnpool.DNSTracker(appParams.Host())
|
||||
refreshCheck = netutil.DNSTracker(appParams.Host())
|
||||
} else {
|
||||
refreshCheck = nil
|
||||
}
|
||||
|
||||
cp.connections = pools.NewResourcePool(f, refreshCheck, *mysqlctl.PoolDynamicHostnameResolution, cp.capacity, cp.capacity, cp.idleTimeout, cp.prefillParallelism, cp.getLogWaitCallback())
|
||||
cp.connections = pools.NewResourcePool(f, cp.capacity, cp.capacity, cp.idleTimeout, cp.prefillParallelism, cp.getLogWaitCallback(), refreshCheck, *mysqlctl.PoolDynamicHostnameResolution)
|
||||
cp.appDebugParams = appDebugParams
|
||||
|
||||
cp.dbaPool.Open(dbaParams)
|
||||
|
|
Загрузка…
Ссылка в новой задаче