github.com/lingyao2333/mo-zero@v1.4.1/zrpc/internal/balancer/p2c/p2c.go (about)

     1  package p2c
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"math/rand"
     7  	"strings"
     8  	"sync"
     9  	"sync/atomic"
    10  	"time"
    11  
    12  	"github.com/lingyao2333/mo-zero/core/logx"
    13  	"github.com/lingyao2333/mo-zero/core/syncx"
    14  	"github.com/lingyao2333/mo-zero/core/timex"
    15  	"github.com/lingyao2333/mo-zero/zrpc/internal/codes"
    16  	"google.golang.org/grpc/balancer"
    17  	"google.golang.org/grpc/balancer/base"
    18  	"google.golang.org/grpc/resolver"
    19  )
    20  
    21  const (
    22  	// Name is the name of p2c balancer.
    23  	Name = "p2c_ewma"
    24  
    25  	decayTime       = int64(time.Second * 10) // default value from finagle
    26  	forcePick       = int64(time.Second)
    27  	initSuccess     = 1000
    28  	throttleSuccess = initSuccess / 2
    29  	penalty         = int64(math.MaxInt32)
    30  	pickTimes       = 3
    31  	logInterval     = time.Minute
    32  )
    33  
    34  var emptyPickResult balancer.PickResult
    35  
    36  func init() {
    37  	balancer.Register(newBuilder())
    38  }
    39  
    40  type p2cPickerBuilder struct{}
    41  
    42  func (b *p2cPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker {
    43  	readySCs := info.ReadySCs
    44  	if len(readySCs) == 0 {
    45  		return base.NewErrPicker(balancer.ErrNoSubConnAvailable)
    46  	}
    47  
    48  	var conns []*subConn
    49  	for conn, connInfo := range readySCs {
    50  		conns = append(conns, &subConn{
    51  			addr:    connInfo.Address,
    52  			conn:    conn,
    53  			success: initSuccess,
    54  		})
    55  	}
    56  
    57  	return &p2cPicker{
    58  		conns: conns,
    59  		r:     rand.New(rand.NewSource(time.Now().UnixNano())),
    60  		stamp: syncx.NewAtomicDuration(),
    61  	}
    62  }
    63  
    64  func newBuilder() balancer.Builder {
    65  	return base.NewBalancerBuilder(Name, new(p2cPickerBuilder), base.Config{HealthCheck: true})
    66  }
    67  
    68  type p2cPicker struct {
    69  	conns []*subConn
    70  	r     *rand.Rand
    71  	stamp *syncx.AtomicDuration
    72  	lock  sync.Mutex
    73  }
    74  
    75  func (p *p2cPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
    76  	p.lock.Lock()
    77  	defer p.lock.Unlock()
    78  
    79  	var chosen *subConn
    80  	switch len(p.conns) {
    81  	case 0:
    82  		return emptyPickResult, balancer.ErrNoSubConnAvailable
    83  	case 1:
    84  		chosen = p.choose(p.conns[0], nil)
    85  	case 2:
    86  		chosen = p.choose(p.conns[0], p.conns[1])
    87  	default:
    88  		var node1, node2 *subConn
    89  		for i := 0; i < pickTimes; i++ {
    90  			a := p.r.Intn(len(p.conns))
    91  			b := p.r.Intn(len(p.conns) - 1)
    92  			if b >= a {
    93  				b++
    94  			}
    95  			node1 = p.conns[a]
    96  			node2 = p.conns[b]
    97  			if node1.healthy() && node2.healthy() {
    98  				break
    99  			}
   100  		}
   101  
   102  		chosen = p.choose(node1, node2)
   103  	}
   104  
   105  	atomic.AddInt64(&chosen.inflight, 1)
   106  	atomic.AddInt64(&chosen.requests, 1)
   107  
   108  	return balancer.PickResult{
   109  		SubConn: chosen.conn,
   110  		Done:    p.buildDoneFunc(chosen),
   111  	}, nil
   112  }
   113  
   114  func (p *p2cPicker) buildDoneFunc(c *subConn) func(info balancer.DoneInfo) {
   115  	start := int64(timex.Now())
   116  	return func(info balancer.DoneInfo) {
   117  		atomic.AddInt64(&c.inflight, -1)
   118  		now := timex.Now()
   119  		last := atomic.SwapInt64(&c.last, int64(now))
   120  		td := int64(now) - last
   121  		if td < 0 {
   122  			td = 0
   123  		}
   124  		w := math.Exp(float64(-td) / float64(decayTime))
   125  		lag := int64(now) - start
   126  		if lag < 0 {
   127  			lag = 0
   128  		}
   129  		olag := atomic.LoadUint64(&c.lag)
   130  		if olag == 0 {
   131  			w = 0
   132  		}
   133  		atomic.StoreUint64(&c.lag, uint64(float64(olag)*w+float64(lag)*(1-w)))
   134  		success := initSuccess
   135  		if info.Err != nil && !codes.Acceptable(info.Err) {
   136  			success = 0
   137  		}
   138  		osucc := atomic.LoadUint64(&c.success)
   139  		atomic.StoreUint64(&c.success, uint64(float64(osucc)*w+float64(success)*(1-w)))
   140  
   141  		stamp := p.stamp.Load()
   142  		if now-stamp >= logInterval {
   143  			if p.stamp.CompareAndSwap(stamp, now) {
   144  				p.logStats()
   145  			}
   146  		}
   147  	}
   148  }
   149  
   150  func (p *p2cPicker) choose(c1, c2 *subConn) *subConn {
   151  	start := int64(timex.Now())
   152  	if c2 == nil {
   153  		atomic.StoreInt64(&c1.pick, start)
   154  		return c1
   155  	}
   156  
   157  	if c1.load() > c2.load() {
   158  		c1, c2 = c2, c1
   159  	}
   160  
   161  	pick := atomic.LoadInt64(&c2.pick)
   162  	if start-pick > forcePick && atomic.CompareAndSwapInt64(&c2.pick, pick, start) {
   163  		return c2
   164  	}
   165  
   166  	atomic.StoreInt64(&c1.pick, start)
   167  	return c1
   168  }
   169  
   170  func (p *p2cPicker) logStats() {
   171  	var stats []string
   172  
   173  	p.lock.Lock()
   174  	defer p.lock.Unlock()
   175  
   176  	for _, conn := range p.conns {
   177  		stats = append(stats, fmt.Sprintf("conn: %s, load: %d, reqs: %d",
   178  			conn.addr.Addr, conn.load(), atomic.SwapInt64(&conn.requests, 0)))
   179  	}
   180  
   181  	logx.Statf("p2c - %s", strings.Join(stats, "; "))
   182  }
   183  
   184  type subConn struct {
   185  	lag      uint64
   186  	inflight int64
   187  	success  uint64
   188  	requests int64
   189  	last     int64
   190  	pick     int64
   191  	addr     resolver.Address
   192  	conn     balancer.SubConn
   193  }
   194  
   195  func (c *subConn) healthy() bool {
   196  	return atomic.LoadUint64(&c.success) > throttleSuccess
   197  }
   198  
   199  func (c *subConn) load() int64 {
   200  	// plus one to avoid multiply zero
   201  	lag := int64(math.Sqrt(float64(atomic.LoadUint64(&c.lag) + 1)))
   202  	load := lag * (atomic.LoadInt64(&c.inflight) + 1)
   203  	if load == 0 {
   204  		return penalty
   205  	}
   206  
   207  	return load
   208  }