新的代码增加了同步用的条件变量,没有空闲资源时的排队不再使用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 
   
 } 
    
 
为什么要实现这两个接口呢?,因为资源池需要知道如何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函数) 
  
}


抱歉都快到电视剧的第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() 
  
}

  

在代码注释中可以看到,为了避免惊群效应,这里采用的方式是先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的各种功能是如何实现的。休息,休息一会儿。