vitess源码阅读笔记cache系列之用go实现通用资源池
2012-04-06 14:34 ngaut 阅读(2628) 评论(0) 编辑 收藏 举报更新日志:
更新至2012.4.8的vitess代码
新的代码增加了同步用的条件变量,没有空闲资源时的排队不再使用channel来同步(使用其它编程语言的同学可以方便的移植这个代码了),
转而使用condition variable。不再使用mu.RLock,统一使用Lock,不再纠结。 整体代码清晰了许多。
为了进一步提高性能和代码复用,vitess还提供了通用的池管理,RoundRobin.go中实现了通用的资源池,方便管理池内资源总数,超时。
先上第一道菜:RoundRobin在整个vitess架构中扮演的什么角色?
个人觉得RoundRobin的重要性在vitess中怎么着也能算个丐帮的几个长老之一吧,作为其它几个pool的核心基础
如cache_pool.go就是RoundRobin + memcache client实现的,而conn_pool.go则是用RoundRobin实现的连接池。
先看看RoundRobin.go的基本原理,老规矩,还是从数据结构入手
// Factory is a function that can be used to create a resource.
type Factory func() (Resource, error) //工厂方法,资源创建函数,如connection
// Every resource needs to suport the Resource interface.
type Resource interface {
Close()
IsClosed() bool
}
这里要注意的是使用RoundRobin资源池来管理的对象必须实现Resouce接口 。type Factory func() (Resource, error) //工厂方法,资源创建函数,如connection
// Every resource needs to suport the Resource interface.
type Resource interface {
Close()
IsClosed() bool
}
为什么要实现这两个接口呢?,因为资源池需要知道如何Close超时的资源,
以及哪些资源是空闲的,哪些已经关闭了。
// RoundRobin allows you to use a pool of resources in a round robin fashion.
type RoundRobin struct {
// mu controls resources & factory
// Use Lock to modify, RLock otherwise
mu sync.RWMutex
resources chan fifoWrapper //用chan来模拟一个FIFO的队列,先来先服务
factory Factory
// Use sync/atomic to access the following vars
size int64 //LQ: 池的总大小
waitCount int64 //LQ: 还有多少人在等池内出现空闲资源
waitTime int64 //LQ: 等待空闲资源总共花了多少时间
idleTimeout int64 //LQ: 最多允许资源空闲多长时间,超过则close空闲资源
}
type fifoWrapper struct {
resource Resource
timeUsed time.timeUsed //LQ: 用于控制超时,初始值为资源上次进入池内的时间(见Put函数)
}
type RoundRobin struct {
// mu controls resources & factory
// Use Lock to modify, RLock otherwise
mu sync.RWMutex
resources chan fifoWrapper //用chan来模拟一个FIFO的队列,先来先服务
factory Factory
// Use sync/atomic to access the following vars
size int64 //LQ: 池的总大小
waitCount int64 //LQ: 还有多少人在等池内出现空闲资源
waitTime int64 //LQ: 等待空闲资源总共花了多少时间
idleTimeout int64 //LQ: 最多允许资源空闲多长时间,超过则close空闲资源
}
type fifoWrapper struct {
resource Resource
timeUsed time.timeUsed //LQ: 用于控制超时,初始值为资源上次进入池内的时间(见Put函数)
}
抱歉都快到电视剧的第N集了才看到golang里面的重量级组件channel,简单的理解可以认为channel是个
支持生产者消费者模型的同步队列,而且是个不需要销毁的队列,golang会自动将不再使用的channel当垃圾回收掉。
其实池也是生产者消费者模型。所以需要外部提供生产资源的方法,也就是上面的Factory接口。这里的可以简单理解为
c语言里面的函数指针。
RoundRobin的实现逻辑是这样的,如果池内有资源,则可以Get成功,如果没有,但还有容量,则用Factory创建一个资源。
如果池内已经没有空闲资源,则傻等,知道有空闲资源可用位置。为了了解资源池的运作状态,还记录了等待的客户有多少。
总共等了多少时间,有了这些就可以方便的评估RoundRobin的效果。源代码也是按照这个思路来写的。
不多说,上代码,详细内容见标注的代码注释
// Get will return the next available resource. If none is available, and capacity
// has not been reached, it will create a new one using the factory. Otherwise,
// it will indefinitely wait till the next resource becomes available.
func (self *RoundRobin) Get() (resource Resource, err error) {
return self.get(true)
}
// TryGet will return the next available resource. If none is available, and capacity
// has not been reached, it will create a new one using the factory. Otherwise,
// it will return nil with no error.
func (self *RoundRobin) TryGet() (resource Resource, err error) {
return self.get(false)
}
func (self *RoundRobin) get(wait bool) (resource Resource, err error) {
self.mu.Lock()
defer self.mu.Unlock()
// Any waits in this loop will release the lock, and it will be
// reacquired before the waits return.
for {
select {
case fw := <-self.resources:
// Found a free resource in the channel
if self.idleTimeout > 0 && fw.timeUsed.Add(self.idleTimeout).Sub(time.Now()) < 0 {
// resource has been idle for too long. Discard & go for next.
go fw.resource.Close()
self.size--
continue
}
return fw.resource, nil
default:
// resource channel is empty
if self.size >= int64(cap(self.resources)) {
// The pool is full
if wait {
start := time.Now()
self.available.Wait() //没有空闲资源了,等着吧,不如上一版本的代码自然啊
self.recordWait(start)
continue
}
return nil, nil
}
// Pool is not full. Create a resource.
if resource, err = self.waitForCreate(); err == nil {
// Creation successful. Account for this by incrementing size.
self.size++
}
return resource, err
}
}
panic("unreachable")
}
func (self *RoundRobin) recordWait(start time.Time) {
self.waitCount++
self.waitTime += time.Now().Sub(start)
}
//LQ: 这里的increment和decrement应该是多余的,没看明白作者是什么目的,和惊群有啥关系
//为了避免self.factory()比较耗时,执行self.factory时unlock还是有必要的
func (self *RoundRobin) waitForCreate() (resource Resource, err error) {
// Prevent thundering herd: increment size before creating resource, and decrement after.
self.size++
self.mu.Unlock()
defer func() {
self.mu.Lock()
self.size--
}()
return self.factory()
}
// has not been reached, it will create a new one using the factory. Otherwise,
// it will indefinitely wait till the next resource becomes available.
func (self *RoundRobin) Get() (resource Resource, err error) {
return self.get(true)
}
// TryGet will return the next available resource. If none is available, and capacity
// has not been reached, it will create a new one using the factory. Otherwise,
// it will return nil with no error.
func (self *RoundRobin) TryGet() (resource Resource, err error) {
return self.get(false)
}
func (self *RoundRobin) get(wait bool) (resource Resource, err error) {
self.mu.Lock()
defer self.mu.Unlock()
// Any waits in this loop will release the lock, and it will be
// reacquired before the waits return.
for {
select {
case fw := <-self.resources:
// Found a free resource in the channel
if self.idleTimeout > 0 && fw.timeUsed.Add(self.idleTimeout).Sub(time.Now()) < 0 {
// resource has been idle for too long. Discard & go for next.
go fw.resource.Close()
self.size--
continue
}
return fw.resource, nil
default:
// resource channel is empty
if self.size >= int64(cap(self.resources)) {
// The pool is full
if wait {
start := time.Now()
self.available.Wait() //没有空闲资源了,等着吧,不如上一版本的代码自然啊
self.recordWait(start)
continue
}
return nil, nil
}
// Pool is not full. Create a resource.
if resource, err = self.waitForCreate(); err == nil {
// Creation successful. Account for this by incrementing size.
self.size++
}
return resource, err
}
}
panic("unreachable")
}
func (self *RoundRobin) recordWait(start time.Time) {
self.waitCount++
self.waitTime += time.Now().Sub(start)
}
//LQ: 这里的increment和decrement应该是多余的,没看明白作者是什么目的,和惊群有啥关系
//为了避免self.factory()比较耗时,执行self.factory时unlock还是有必要的
func (self *RoundRobin) waitForCreate() (resource Resource, err error) {
// Prevent thundering herd: increment size before creating resource, and decrement after.
self.size++
self.mu.Unlock()
defer func() {
self.mu.Lock()
self.size--
}()
return self.factory()
}
在代码注释中可以看到,为了避免惊群效应,这里采用的方式是先increment,本人也不太明白,为什么这样能避免惊群效应。
还请熟悉的朋友不吝赐教。
看完了Get发现排队等待是那么的自然,一行代码的事情。再来看Put函数,我们会发现唤醒也是那么的简洁。
// Put will return a resource to the pool. You MUST return every resource to the pool,
// even if it's closed. If a resource is closed, Put will discard it. Thread synchronization
// between Close() and IsClosed() is the caller's responsibility.
func (self *RoundRobin) Put(resource Resource) {
self.mu.Lock()
defer self.mu.Unlock()
defer self.available.Signal() //LQ: 排队的兄弟该醒醒了
if self.size > int64(cap(self.resources)) {
go resource.Close()
self.size--
} else if resource.IsClosed() {
self.size--
} else {
self.resources <- fifoWrapper{resource, time.Now()}
}
}
接下来的系列将会分析其它依赖RoundRobin的几个池的实现,然后分析vitess的各种功能是如何实现的。休息,休息一会儿。