yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/concurrent/balancer.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package concurrent
    16  
    17  import (
    18  	"container/heap"
    19  	"time"
    20  )
    21  
    22  // Balancer is a type that can balance load among a set of workers
    23  //
    24  type Balancer struct {
    25  	errorChan              chan error   // The channel used by all workers to report error
    26  	requestHandledChan     chan *Worker // The channel used by a worker to signal balancer that a work has been executed
    27  	tearDownChan           chan bool    // The channel that all workers listening for force quit signal
    28  	workerFinishedChan     chan *Worker // The channel that all worker used to signal balancer that it exiting
    29  	allWorkersFinishedChan chan bool    // The channel this balancer signals once all worker signals it's exit on workerFinishedChan
    30  	pool                   Pool         // Pool of workers that this load balancer balances
    31  	workerCount            int          // The number of workers
    32  }
    33  
    34  // The size of work channel associated with each worker this balancer manages.
    35  //
    36  const workerQueueSize int = 3
    37  
    38  // NewBalancer creates a new instance of Balancer that needs to balance load between 'workerCount' workers
    39  //
    40  func NewBalancer(workerCount int) *Balancer {
    41  	balancer := &Balancer{
    42  		workerCount: workerCount,
    43  		pool: Pool{
    44  			Workers: make([]*Worker, workerCount),
    45  		},
    46  	}
    47  	return balancer
    48  }
    49  
    50  // Init initializes all channels and start the workers.
    51  //
    52  func (b *Balancer) Init() {
    53  	b.errorChan = make(chan error, 0)
    54  	b.requestHandledChan = make(chan *Worker, 0)
    55  	b.workerFinishedChan = make(chan *Worker, 0)
    56  	b.allWorkersFinishedChan = make(chan bool, 0)
    57  	b.tearDownChan = make(chan bool, 0)
    58  	for i := 0; i < b.workerCount; i++ {
    59  		b.pool.Workers[i] = NewWorker(i, workerQueueSize, &(b.pool), b.errorChan, b.requestHandledChan, b.workerFinishedChan)
    60  		(b.pool.Workers[i]).Run(b.tearDownChan)
    61  	}
    62  }
    63  
    64  // TearDownWorkers sends a force quit signal to all workers, which case worker to quit as soon as possible,
    65  // workers won't drain it's request channel in this case.
    66  //
    67  func (b *Balancer) TearDownWorkers() {
    68  	close(b.tearDownChan)
    69  }
    70  
    71  // Run read request from the request channel identified by the parameter requestChan and dispatch it the worker
    72  // with least load. This method returns two channels, a channel to communicate error from any worker back to
    73  // the consumer of balancer and second channel is used by the balancer to signal consumer that all workers has
    74  // been finished executing.
    75  //
    76  func (b *Balancer) Run(requestChan <-chan *Request) (<-chan error, <-chan bool) {
    77  	// Request dispatcher
    78  	go func() {
    79  		for {
    80  			requestToHandle, ok := <-requestChan
    81  			if !ok {
    82  				b.closeWorkersRequestChannel()
    83  				return
    84  			}
    85  			b.dispatch(requestToHandle)
    86  		}
    87  	}()
    88  
    89  	// listener for worker status
    90  	go func() {
    91  		remainingWorkers := b.workerCount
    92  		for {
    93  			select {
    94  			case w := <-b.requestHandledChan:
    95  				b.completed(w)
    96  			case _ = <-b.workerFinishedChan:
    97  				remainingWorkers--
    98  				if remainingWorkers == 0 {
    99  					b.allWorkersFinishedChan <- true // All workers has been exited
   100  					return
   101  				}
   102  			}
   103  		}
   104  	}()
   105  
   106  	return b.errorChan, b.allWorkersFinishedChan
   107  }
   108  
   109  // closeWorkersRequestChannel closes the Request channel of all workers, this indicates that no
   110  // more work will not be send the channel so that the workers can gracefully exit after handling
   111  // any pending work in the channel.
   112  //
   113  func (b *Balancer) closeWorkersRequestChannel() {
   114  	for i := 0; i < b.workerCount; i++ {
   115  		close((b.pool.Workers[i]).RequestsToHandleChan)
   116  	}
   117  }
   118  
   119  // dispatch dispatches the request to the worker with least load. If all workers are completely
   120  // busy (i.e. there Pending request count is currently equal to the maximum load) then this
   121  // method will poll until one worker is available.
   122  //
   123  func (b *Balancer) dispatch(request *Request) {
   124  	for {
   125  		if b.pool.Workers[0].Pending >= workerQueueSize {
   126  			// Wait for a worker to be available
   127  			time.Sleep(500 * time.Millisecond)
   128  		} else {
   129  			b.pool.Lock()
   130  			worker := b.pool.Workers[0]
   131  			worker.Pending++
   132  			heap.Fix(&b.pool, worker.Index)
   133  			worker.RequestsToHandleChan <- request
   134  			b.pool.Unlock()
   135  			return
   136  		}
   137  	}
   138  }
   139  
   140  // completed is called when a worker finishes one work, it updates the load status of the given the
   141  // worker.
   142  //
   143  func (b *Balancer) completed(worker *Worker) {
   144  	b.pool.Lock()
   145  	worker.Pending--
   146  	heap.Fix(&b.pool, worker.Index)
   147  	b.pool.Unlock()
   148  }
   149  
   150  // WorkersCurrentLoad returns the load of the workers this balancer manages as comma separated string
   151  // values where each value consists of worker id (Worker.Id property) and pending requests associated
   152  // with the worker.
   153  //
   154  func (b *Balancer) WorkersCurrentLoad() string {
   155  	return b.pool.WorkersCurrentLoad()
   156  }