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 }