github.com/XiaoMi/Gaea@v1.2.5/util/resource_pool.go (about)

     1  /*
     2  Copyright 2017 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package util ResourcePool provides functionality to manage and reuse resources
    18  // like connections.
    19  package util
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/XiaoMi/Gaea/util/sync2"
    29  	"github.com/XiaoMi/Gaea/util/timer"
    30  )
    31  
    32  var (
    33  	// ErrClosed is returned if ResourcePool is used when it's closed.
    34  	ErrClosed = errors.New("resource pool is closed")
    35  
    36  	// ErrTimeout is returned if a resource get times out.
    37  	ErrTimeout = errors.New("resource pool timed out")
    38  )
    39  
    40  // Factory is a function that can be used to create a resource.
    41  type Factory func() (Resource, error)
    42  
    43  // Resource defines the interface that every resource must provide.
    44  // Thread synchronization between Close() and IsClosed()
    45  // is the responsibility of the caller.
    46  type Resource interface {
    47  	Close()
    48  }
    49  
    50  // ResourcePool allows you to use a pool of resources.
    51  // ResourcePool允许你使用各种资源池,需要根据提供的factory创建特定的资源,比如连接
    52  type ResourcePool struct {
    53  	resources   chan resourceWrapper
    54  	factory     Factory
    55  	capacity    sync2.AtomicInt64
    56  	idleTimeout sync2.AtomicDuration
    57  	idleTimer   *timer.Timer
    58  	capTimer    *timer.Timer
    59  
    60  	// stats
    61  	available    sync2.AtomicInt64
    62  	active       sync2.AtomicInt64
    63  	inUse        sync2.AtomicInt64
    64  	waitCount    sync2.AtomicInt64
    65  	waitTime     sync2.AtomicDuration
    66  	idleClosed   sync2.AtomicInt64
    67  	baseCapacity sync2.AtomicInt64
    68  	maxCapacity  sync2.AtomicInt64
    69  	lock         *sync.Mutex
    70  	scaleOutTime int64
    71  	scaleInTodo  chan int8
    72  	Dynamic      bool
    73  }
    74  
    75  type resourceWrapper struct {
    76  	resource Resource
    77  	timeUsed time.Time
    78  }
    79  
    80  // NewResourcePool creates a new ResourcePool pool.
    81  // capacity is the number of possible resources in the pool:
    82  // there can be up to 'capacity' of these at a given time.
    83  // maxCap specifies the extent to which the pool can be resized
    84  // in the future through the SetCapacity function.
    85  // You cannot resize the pool beyond maxCap.
    86  // If a resource is unused beyond idleTimeout, it's discarded.
    87  // An idleTimeout of 0 means that there is no timeout.
    88  // 创建一个资源池子,capacity是池子中可用资源数量
    89  // maxCap代表最大资源数量
    90  // 超过设定空闲时间的连接会被丢弃
    91  // 资源池会根据传入的factory进行具体资源的初始化,比如建立与mysql的连接
    92  func NewResourcePool(factory Factory, capacity, maxCap int, idleTimeout time.Duration) *ResourcePool {
    93  	if capacity <= 0 || maxCap <= 0 || capacity > maxCap {
    94  		panic(errors.New("invalid/out of range capacity"))
    95  	}
    96  	rp := &ResourcePool{
    97  		resources:    make(chan resourceWrapper, maxCap),
    98  		factory:      factory,
    99  		available:    sync2.NewAtomicInt64(int64(capacity)),
   100  		capacity:     sync2.NewAtomicInt64(int64(capacity)),
   101  		idleTimeout:  sync2.NewAtomicDuration(idleTimeout),
   102  		baseCapacity: sync2.NewAtomicInt64(int64(capacity)),
   103  		maxCapacity:  sync2.NewAtomicInt64(int64(maxCap)),
   104  		lock:         &sync.Mutex{},
   105  		scaleInTodo:  make(chan int8, 1),
   106  		Dynamic:      true, // 动态扩展连接池
   107  	}
   108  	for i := 0; i < capacity; i++ {
   109  		rp.resources <- resourceWrapper{}
   110  	}
   111  
   112  	if idleTimeout != 0 {
   113  		rp.idleTimer = timer.NewTimer(idleTimeout / 10)
   114  		rp.idleTimer.Start(rp.closeIdleResources)
   115  	}
   116  	rp.capTimer = timer.NewTimer(5 * time.Second)
   117  	rp.capTimer.Start(rp.scaleInResources)
   118  	return rp
   119  }
   120  
   121  // Close empties the pool calling Close on all its resources.
   122  // You can call Close while there are outstanding resources.
   123  // It waits for all resources to be returned (Put).
   124  // After a Close, Get is not allowed.
   125  func (rp *ResourcePool) Close() {
   126  	if rp.idleTimer != nil {
   127  		rp.idleTimer.Stop()
   128  	}
   129  	if rp.capTimer != nil {
   130  		rp.capTimer.Stop()
   131  	}
   132  	_ = rp.ScaleCapacity(0)
   133  }
   134  
   135  func (rp *ResourcePool) SetDynamic(value bool) {
   136  	rp.Dynamic = value
   137  }
   138  
   139  // IsClosed returns true if the resource pool is closed.
   140  func (rp *ResourcePool) IsClosed() (closed bool) {
   141  	return rp.capacity.Get() == 0
   142  }
   143  
   144  // closeIdleResources scans the pool for idle resources
   145  // 定期回收超过IdleTimeout的资源
   146  func (rp *ResourcePool) closeIdleResources() {
   147  	available := int(rp.Available())
   148  	idleTimeout := rp.IdleTimeout()
   149  
   150  	for i := 0; i < available; i++ {
   151  		var wrapper resourceWrapper
   152  		select {
   153  		case wrapper, _ = <-rp.resources:
   154  		default:
   155  			// stop early if we don't get anything new from the pool
   156  			return
   157  		}
   158  
   159  		if wrapper.resource != nil && idleTimeout > 0 && wrapper.timeUsed.Add(idleTimeout).Sub(time.Now()) < 0 {
   160  			wrapper.resource.Close()
   161  			wrapper.resource = nil
   162  			rp.idleClosed.Add(1)
   163  			rp.active.Add(-1)
   164  		}
   165  
   166  		rp.resources <- wrapper
   167  	}
   168  }
   169  
   170  // Get will return the next available resource. If capacity
   171  // has not been reached, it will create a new one using the factory. Otherwise,
   172  // it will wait till the next resource becomes available or a timeout.
   173  // A timeout of 0 is an indefinite wait.
   174  // Get会返回下一个可用的资源
   175  // 如果容量没有达到上线,它会根据factory创建一个新的资源,否则会一直等待直到资源可用或超时
   176  func (rp *ResourcePool) Get(ctx context.Context) (resource Resource, err error) {
   177  	return rp.get(ctx, true)
   178  }
   179  
   180  func (rp *ResourcePool) get(ctx context.Context, wait bool) (resource Resource, err error) {
   181  	// If ctx has already expired, avoid racing with rp's resource channel.
   182  	select {
   183  	case <-ctx.Done():
   184  		return nil, ErrTimeout
   185  	default:
   186  	}
   187  
   188  	// Fetch
   189  	var wrapper resourceWrapper
   190  	var ok bool
   191  	select {
   192  	case wrapper, ok = <-rp.resources:
   193  	default:
   194  		if rp.Dynamic {
   195  			rp.scaleOutResources()
   196  		}
   197  		if !wait {
   198  			return nil, nil
   199  		}
   200  		startTime := time.Now()
   201  		select {
   202  		case wrapper, ok = <-rp.resources:
   203  		case <-ctx.Done():
   204  			return nil, ErrTimeout
   205  		}
   206  		endTime := time.Now()
   207  		if int64(startTime.UnixNano()/100000) != int64(endTime.UnixNano()/100000) {
   208  			rp.recordWait(startTime)
   209  		}
   210  	}
   211  	if !ok {
   212  		return nil, ErrClosed
   213  	}
   214  
   215  	// Unwrap
   216  	if wrapper.resource == nil {
   217  		wrapper.resource, err = rp.factory() // 实际建立连接
   218  		if err != nil {
   219  			rp.resources <- resourceWrapper{}
   220  			return nil, err
   221  		}
   222  		rp.active.Add(1)
   223  	}
   224  	rp.available.Add(-1)
   225  	rp.inUse.Add(1)
   226  	return wrapper.resource, err
   227  }
   228  
   229  // Put will return a resource to the pool. For every successful Get,
   230  // a corresponding Put is required. If you no longer need a resource,
   231  // you will need to call Put(nil) instead of returning the closed resource.
   232  // The will eventually cause a new resource to be created in its place.
   233  func (rp *ResourcePool) Put(resource Resource) {
   234  	var wrapper resourceWrapper
   235  	if resource != nil {
   236  		wrapper = resourceWrapper{resource, time.Now()}
   237  	} else {
   238  		rp.active.Add(-1)
   239  	}
   240  	select {
   241  	case rp.resources <- wrapper:
   242  	default:
   243  		panic(errors.New("attempt to Put into a full ResourcePool"))
   244  	}
   245  	rp.inUse.Add(-1)
   246  	rp.available.Add(1)
   247  }
   248  
   249  func (rp *ResourcePool) SetCapacity(capacity int) error {
   250  	oldcap := rp.baseCapacity.Get()
   251  	rp.baseCapacity.CompareAndSwap(oldcap, int64(capacity))
   252  	if int(oldcap) < capacity {
   253  		rp.ScaleCapacity(capacity)
   254  	}
   255  	return nil
   256  }
   257  
   258  // SetCapacity changes the capacity of the pool.
   259  // You can use it to shrink or expand, but not beyond
   260  // the max capacity. If the change requires the pool
   261  // to be shrunk, SetCapacity waits till the necessary
   262  // number of resources are returned to the pool.
   263  // A SetCapacity of 0 is equivalent to closing the ResourcePool.
   264  func (rp *ResourcePool) ScaleCapacity(capacity int) error {
   265  	if capacity < 0 || capacity > int(rp.maxCapacity.Get()) {
   266  		return fmt.Errorf("capacity %d is out of range", capacity)
   267  	}
   268  
   269  	// Atomically swap new capacity with old, but only
   270  	// if old capacity is non-zero.
   271  	var oldcap int
   272  	for {
   273  		oldcap = int(rp.capacity.Get())
   274  		if oldcap == 0 {
   275  			return ErrClosed
   276  		}
   277  		if oldcap == capacity {
   278  			return nil
   279  		}
   280  		if rp.capacity.CompareAndSwap(int64(oldcap), int64(capacity)) {
   281  			break
   282  		}
   283  	}
   284  
   285  	if capacity < oldcap {
   286  		for i := 0; i < oldcap-capacity; i++ {
   287  			wrapper := <-rp.resources
   288  			if wrapper.resource != nil {
   289  				wrapper.resource.Close()
   290  				rp.active.Add(-1)
   291  			}
   292  			rp.available.Add(-1)
   293  		}
   294  	} else {
   295  		for i := 0; i < capacity-oldcap; i++ {
   296  			rp.resources <- resourceWrapper{}
   297  			rp.available.Add(1)
   298  		}
   299  	}
   300  	if capacity == 0 {
   301  		close(rp.resources)
   302  	}
   303  	return nil
   304  }
   305  
   306  // 扩容
   307  func (rp *ResourcePool) scaleOutResources() {
   308  	rp.lock.Lock()
   309  	defer rp.lock.Unlock()
   310  	if rp.capacity.Get() < rp.maxCapacity.Get() {
   311  		rp.ScaleCapacity(int(rp.capacity.Get()) + 1)
   312  		rp.scaleOutTime = time.Now().Unix()
   313  	}
   314  }
   315  
   316  // 缩容
   317  func (rp *ResourcePool) scaleInResources() {
   318  	rp.lock.Lock()
   319  	defer rp.lock.Unlock()
   320  	if rp.capacity.Get() > rp.baseCapacity.Get() && time.Now().Unix()-rp.scaleOutTime > 60 {
   321  		select {
   322  		case rp.scaleInTodo <- 0:
   323  			go func() {
   324  				rp.ScaleCapacity(int(rp.capacity.Get()) - 1)
   325  				<-rp.scaleInTodo
   326  			}()
   327  		default:
   328  			return
   329  		}
   330  	}
   331  }
   332  
   333  func (rp *ResourcePool) recordWait(start time.Time) {
   334  	rp.waitCount.Add(1)
   335  	rp.waitTime.Add(time.Now().Sub(start))
   336  }
   337  
   338  // SetIdleTimeout sets the idle timeout. It can only be used if there was an
   339  // idle timeout set when the pool was created.
   340  func (rp *ResourcePool) SetIdleTimeout(idleTimeout time.Duration) {
   341  	if rp.idleTimer == nil {
   342  		panic("SetIdleTimeout called when timer not initialized")
   343  	}
   344  
   345  	rp.idleTimeout.Set(idleTimeout)
   346  	rp.idleTimer.SetInterval(idleTimeout / 10)
   347  }
   348  
   349  // StatsJSON returns the stats in JSON format.
   350  func (rp *ResourcePool) StatsJSON() string {
   351  	return fmt.Sprintf(`{"Capacity": %v, "Available": %v, "Active": %v, "InUse": %v, "MaxCapacity": %v, "WaitCount": %v, "WaitTime": %v, "IdleTimeout": %v, "IdleClosed": %v}`,
   352  		rp.Capacity(),
   353  		rp.Available(),
   354  		rp.Active(),
   355  		rp.InUse(),
   356  		rp.MaxCap(),
   357  		rp.WaitCount(),
   358  		rp.WaitTime().Nanoseconds(),
   359  		rp.IdleTimeout().Nanoseconds(),
   360  		rp.IdleClosed(),
   361  	)
   362  }
   363  
   364  // Capacity returns the capacity.
   365  func (rp *ResourcePool) Capacity() int64 {
   366  	return rp.capacity.Get()
   367  }
   368  
   369  // Available returns the number of currently unused and available resources.
   370  func (rp *ResourcePool) Available() int64 {
   371  	return rp.available.Get()
   372  }
   373  
   374  // Active returns the number of active (i.e. non-nil) resources either in the
   375  // pool or claimed for use
   376  func (rp *ResourcePool) Active() int64 {
   377  	return rp.active.Get()
   378  }
   379  
   380  // InUse returns the number of claimed resources from the pool
   381  func (rp *ResourcePool) InUse() int64 {
   382  	return rp.inUse.Get()
   383  }
   384  
   385  // MaxCap returns the max capacity.
   386  func (rp *ResourcePool) MaxCap() int64 {
   387  	return int64(cap(rp.resources))
   388  }
   389  
   390  // WaitCount returns the total number of waits.
   391  func (rp *ResourcePool) WaitCount() int64 {
   392  	return rp.waitCount.Get()
   393  }
   394  
   395  // WaitTime returns the total wait time.
   396  func (rp *ResourcePool) WaitTime() time.Duration {
   397  	return rp.waitTime.Get()
   398  }
   399  
   400  // IdleTimeout returns the idle timeout.
   401  func (rp *ResourcePool) IdleTimeout() time.Duration {
   402  	return rp.idleTimeout.Get()
   403  }
   404  
   405  // IdleClosed returns the count of resources closed due to idle timeout.
   406  func (rp *ResourcePool) IdleClosed() int64 {
   407  	return rp.idleClosed.Get()
   408  }