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 }