学习地址自己还原 /s/1p2gVcBMq87ko35yoWzyblQ 提取码: fajt
一、P2C负载均衡算法 & EWMA
在go-zero中默认使用的是P2C的负载均衡算法。
算法的原理:即随机从所有可用节点中选择两个节点,然后计算这两个节点的负载情况,选择负载较低的一个节点来服务本次请求。
为了避免某些节点一直得不到选择导致不平衡,会在超过一定的时间后强制选择一次。
使用EWMA指数移动加权平均,记录每个节点的平均延迟,该算法相对于算数平均来说对于突然的网络抖动没有那么敏感,从而可以让算法更加均衡。
g0-zer0 ,是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。go-zero 包含极简的 AP!定义和生成工具 goctl,可以根据定义的 api文件一键生成 Go, i0s, Android,Kotlin, Dart,Typescript,javascript 代码并可直接运行。
使用 go-zero 的好处:
轻松获得支撑千万日活服务的稳定性
内建级联超时控制、限流、自适应熔断、自适应降载等微服务治理能力,无需配置和额外代码
微服务治理中间件可无缝集成到其它现有框架使用
极简的 API 描述,一键生成各端代码
自动校验客户端请求参数合法性
大量微服务治理和并发工具包
二、源码阅读
2.1EWMA记录节点的平均延迟
buildDoneFunc返回函数会复制给 PickResult.Done, RPC完成调用后会执行PickResult.Done方法。
/*
// Done is called when the RPC is completed.
rpc 完成时将会执行该函数
*/
func (p *p2cPicker) buildDoneFunc(c *subConn) func(info balancer.DoneInfo) {
//start 记录subConn被选中的时间
start := int64(timex.Now())
return func(info balancer.DoneInfo) {
//......
now := timex.Now()
last := atomic.SwapInt64(&c.last, int64(now))
//本次请求 和 上次请求 请求间隔
td := int64(now) - last
if td < 0 {
td = 0
}
//e^x 距离最后一次请求 时间约久,权重约低
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
}
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)))
//每一分钟记录一次请求日志
//......
}
go-zero 框架设计思考
对于微服务框架的设计,我们期望保障微服务稳定性的同时,也要特别注重研发效率。所以设计之初,我们就有如下一些准则:
保持简单
高可用
高并发
易扩展
弹性设计,面向故障编程
尽可能对业务开发友好,封装复杂度
尽可能约束做一件事只有一种方式
使用go-zero框架的时候,发现在API请求过程中如果出现错误,接口会直接返回http400错误。这对前端或者其它服务端很不友好,他们需要获得详细错误信息,并且不返回http错误。同时,对于有错误的请求和成功的请求,接口返回的数据不一致。
总的来说,我们需要解决以下三个问题:
处理框架自带参数解析失败后产生的error(需自己处理)
处理逻辑层处理失败后产生的error(官方已处理)
修改模板,统一接口返回值(官方已处理)
后两个官方已处理,并加入官方学习文档,可自行查阅:
逻辑层错误官方解决方案:官方错误处理方法
统一接口返回值官方解决方案:官方统一请求返回方法