gRPC 和服务发现
这是为了解决去中心化的核心目标而引入的。
选择 gRPC 的原因
- gRPC 是基于 http2 双向流且可复用连接,比 http1.1 的单向流省 tcp 连接;
- 代码即文档,表现力比 http restful 强,能提高接口的标准化;
- 可支持在公网,统一内网外通信
- healthCheck,端对端异步检测,辅助平滑发布
服务发现
客户端:每个客户端实现服务实例的发现和相同的负载均衡算法,并直连服务实例进行通信;
-
服务端:中间加入网关层,网关实现服务实例的发现,客户端只连网关,好处是不需要共享负载均衡算法和服务实例发现的逻辑,可以避免这部分的共享库或多语言重复开发相同功能,但需要保证网关的可用性和扩展性;
-
service mesh:k8s pod 部署 app 和 LB(网关),LB 实现服务实例的发现和负载均衡,且 LB 可以通过镜像的方式发布,比共享库要方便;
B 站由于内部环境,采用的是客户端的发现,因为服务端和 service mesh 都比较复杂;
框架:B 站的 discover, 基于 eureka,两者是 AP 系统(最终一致),有注册和注销延迟,更好的是阿里的 nacos;
GO 训练营第 3 周总结
内存模型
内存重排,CPU、编译器可能会使指令不按程序编写的顺序执行,且 CPU 缓存的存在,多核心的缓存是独立的,所以可能只写到缓存,没有写到公共的内存上,产生内存重排现象。
多协程并发修改共享变量的时候,需要避免这种内存重排导致的问题,使用同步事件(sync, chan)保证 happen-before。
单个 machine word 才是原子操作,即一条汇编指令就是一个原子操作,比如在 32bit 机器赋值一个 64 位的值,需要两条指令,所以它不是原子的。
goroutine go 关键字一定要关注:
go 生命周期(结束、终止)
go panic
把并行扔给调用者
内存模型:
搞清楚 原子性、可见性
go memory model(了解 happen-before)
底层的 memory reordering(可以挖一挖 cpu cacline、锁总线、mesi、memory barrier)
Go 训练营第 5 周——评论系统架构设计
加餐
GC 优化:
小对象初始化时用内联方式,避免多次 new 以减少内存小对象。
type Group struct {
s sync.Mutex
}
内联方式,如果用指针的话,得 new 外层的 Group,再 new sync.Mutex,这里没加指针就是自动创建,且跟外层在同一个内存对象。
GO 训练营第 6、7 周——可用性设计
隔离
资源分割
超时控制
良好的超时策略,保证请求不堆积,释放资源给正常的流程使用。
过载保护
拒绝服务也会消耗资源,所以过载保护无法完全保护系统
参考:
token-bucket rate limit algorithm: /x/time/rate
token-bucket rate limit algorithm: /x/time/rate
限流
单机限流
分布式限流
拒绝服务也会消耗资源,所以限流也无法完全保护系统;
系统一般需要立体保护,如客户端+服务端
熔断
挂起操作,能够在高负载、高并发时候保护系统
做法:
全关闭:google sre 熔断概率计算
半开半闭
客户端流控:避免失败时,客户积极重试
gutter 双熔断,用少量资源支撑主线熔断溢出的流量
降级
必要时减少不重要的工作,提供有损的服务
重试
问题:
客户端积极重试
内部不必要的重试:nginx upstream 重试过大;多层级重试;
解决:
约定过载条件,不进行重试
负载均衡
均衡的流量分发
动态根据机器负载进行分发,参考:The power of two choices in randomized load balancing https://ieeexplore.ieee.org/document/963420
最佳实践
以上+极限压测+故障演练