background
When choosing a load balancing algorithm, we hope to meet the following requirements:
With the compatibility of partition and computer room scheduling
- The node selected each time is as low as possible
- Choose the node with the fastest response every time possible
No manual intervention on the failed node
- When a node fails, the load balancing algorithm can automatically isolate the node
- When the failed node recovers, the traffic distribution to the node can be automatically restored
Based on these considerations, go-zero
chose the p2c+EWMA
algorithm to implement.
The core idea of the algorithm
p2c
p2c (Pick Of 2 Choices)
Choose one of two: Randomly select two nodes from multiple nodes.
go-zero
will randomly select 3 times. If the health condition of the node selected at one time meets the requirements, the selection will be interrupted and these two nodes will be used.
EWMA
EWMA (Exponentially Weighted Moving-Average)
Exponential moving weighted average method: It means that the weighting coefficient of each value decreases exponentially with time. The closer the value is to the current moment, the larger the weighting coefficient, which reflects the average value in the most recent period of time.
formula:
Variable explanation:
Vt
: Represents theEWMA value
t
Vt-1
: represents the firstt-1
request timesEWMA value
β
: is a constant
Advantages of the EWMA algorithm
- Compared with the ordinary average calculation algorithm,
EWMA
does not need to save all the past values, the calculation amount is significantly reduced, and the storage resources are also reduced. The traditional average calculation algorithm is not sensitive to network time consumption, but
EWMA
can adjustβ
by frequently requesting, and then quickly monitor the network glitch or more to reflect the overall average.- When the request is more frequent, it means that the network load of the node has increased. We want to monitor the time
β
.β
smaller theEWMA value is to this time-consuming, and the network glitch can be detected quickly;
- When the request is relatively infrequent, we relatively
value of 1611ccc203c477 β.
EWMA value calculated in this way is closer to the average
- When the request is more frequent, it means that the network load of the node has increased. We want to monitor the time
Beta calculation
go-zero
uses Newton's law of cooling decay function calculation model EWMA
algorithm β
values:
Where Δt
is the interval between two requests, e
and k
are constants
Implement a custom load balancer in gRPC
First of all, we need to implement the
google.golang.org/grpc/balancer/base/base.go/PickerBuilder
Build
method in the interface when the service node is updatedtype PickerBuilder interface { // Build returns a picker that will be used by gRPC to pick a SubConn. Build(info PickerBuildInfo) balancer.Picker }
Also implement the
google.golang.org/grpc/balancer/balancer.go/Picker
interface. This interface mainly implements load balancing and selects a node for request usetype Picker interface { Pick(info PickInfo) (PickResult, error) }
- Finally, register the load balancer we implemented with load balance
map
The main logic of go-zero to achieve load balancing
- In each node update,
gRPC
will call theBuild
method, at this time, all node information is savedBuild
gRPC
gets the node processing request, it will call thePick
method to get the node.go-zero
inPick
method was achievedp2c
algorithm, the selection of nodes by the nodecalculation load EWMA value, returns the low load node for
gRPC
use.- At the end of the request,
gRPC
will call thePickResult.Done
method.go-zero
realizes the storage of the time-consuming information of this request, and calculates theEWMA value and saves it for use in the calculation of load in the next request. .
Load balancing code analysis
Save all node information of the service
EWMA
by the node to process this request, 0611ccc203ca97 and other information.go-zero
designed the following structure for each node:type subConn struct { addr resolver.Address conn balancer.SubConn lag uint64 // 用来保存 ewma 值 inflight int64 // 用在保存当前节点正在处理的请求总数 success uint64 // 用来标识一段时间内此连接的健康状态 requests int64 // 用来保存请求总数 last int64 // 用来保存上一次请求耗时, 用于计算 ewma 值 pick int64 // 保存上一次被选中的时间点 }
p2cPicker
implements thebalancer.Picker
interface, andconns
saves all the node information of the servicetype p2cPicker struct { conns []*subConn // 保存所有节点的信息 r *rand.Rand stamp *syncx.AtomicDuration lock sync.Mutex }
gRPC
Build
method when the node is updated, and pass in all the node information. Here we save each node information with thesubConn
structure. And merge them together and save themp2cPicker
func (b *p2cPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker { ...... var conns []*subConn for conn, connInfo := range readySCs { conns = append(conns, &subConn{ addr: connInfo.Address, conn: conn, success: initSuccess, }) } return &p2cPicker{ conns: conns, r: rand.New(rand.NewSource(time.Now().UnixNano())), stamp: syncx.NewAtomicDuration(), } }
Randomly select node information, there are three cases here:
- There is only one service node, at this time, it can be
gRPC
- There are two service nodes, the
EWMA value, and the node with low load is returned for use
gRPC
- There are multiple service nodes. At this time,
p2c
algorithm, the load conditions are compared, and the node with low load is returned for usegRPC
The main implementation code is as follows:
switch len(p.conns) { case 0:// 没有节点,返回错误 return emptyPickResult, balancer.ErrNoSubConnAvailable case 1:// 有一个节点,直接返回这个节点 chosen = p.choose(p.conns[0], nil) case 2:// 有两个节点,计算负载,返回负载低的节点 chosen = p.choose(p.conns[0], p.conns[1]) default:// 有多个节点,p2c 挑选两个节点,比较这两个节点的负载,返回负载低的节点 var node1, node2 *subConn // 3次随机选择两个节点 for i := 0; i < pickTimes; i++ { a := p.r.Intn(len(p.conns)) b := p.r.Intn(len(p.conns) - 1) if b >= a { b++ } node1 = p.conns[a] node2 = p.conns[b] // 如果这次选择的节点达到了健康要求, 就中断选择 if node1.healthy() && node2.healthy() { break } } // 比较两个节点的负载情况,选择负载低的 chosen = p.choose(node1, node2) }
- There is only one service node, at this time, it can be
load
compute node load situationThe above
choose
method will call theload
method to calculate the node load.The formula for calculating the load is:
load = ewma * inflight
Here is a brief explanation:
ewma
equivalent to the average request time,inflight
is the number of requests being processed by the current node, and the multiplication roughly calculates the network load of the current node.func (c *subConn) load() int64 { // 通过 EWMA 计算节点的负载情况; 加 1 是为了避免为 0 的情况 lag := int64(math.Sqrt(float64(atomic.LoadUint64(&c.lag) + 1))) load := lag * (atomic.LoadInt64(&c.inflight) + 1) if load == 0 { return penalty } return load }
The request is over, update the node's
EWMA
and other information- Decrease the total number of requests being processed by the node by 1
- Saving processing time point of end of the request, for calculating a difference value from the previous processing request node, and calculates
EWMA
inbeta] value
- Calculate the time-consuming of this request, and calculate the
EWMA value and save it to the node's
lag
attribute The health status of the computing node is saved to the node's
success
attributefunc (p *p2cPicker) buildDoneFunc(c *subConn) func(info balancer.DoneInfo) { start := int64(timex.Now()) return func(info balancer.DoneInfo) { // 正在处理的请求数减 1 atomic.AddInt64(&c.inflight, -1) now := timex.Now() // 保存本次请求结束时的时间点,并取出上次请求时的时间点 last := atomic.SwapInt64(&c.last, int64(now)) td := int64(now) - last if td < 0 { td = 0 } // 用牛顿冷却定律中的衰减函数模型计算EWMA算法中的β值 w := math.Exp(float64(-td) / float64(decayTime)) // 保存本次请求的耗时 lag := int64(now) - start if lag < 0 { lag = 0 } olag := atomic.LoadUint64(&c.lag) if olag == 0 { w = 0 } // 计算 EWMA 值 atomic.StoreUint64(&c.lag, uint64(float64(olag)*w+float64(lag)*(1-w))) success := initSuccess if info.Err != nil && !codes.Acceptable(info.Err) { success = 0 } osucc := atomic.LoadUint64(&c.success) atomic.StoreUint64(&c.success, uint64(float64(osucc)*w+float64(success)*(1-w))) stamp := p.stamp.Load() if now-stamp >= logInterval { if p.stamp.CompareAndSwap(stamp, now) { p.logStats() } } } }
project address
https://github.com/tal-tech/go-zero
Welcome to use go-zero
and star support us!
WeChat Exchange Group
Follow the " Practice " public account and click on the exchange group get the community group QR code.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。