命名服务插件
概述
名字服务模块可以将节点注册到对应的服务名下。注册信息除了 ip:port 外,还会包含运行环境、容器以及其他自定义的元数据信息。调用方根据服务名获取到所有节点后,路由模块再根据元数据信息对节点进行筛选,最后,负载均衡算法从满足要求的节点中选出一个节点来进行最终请求。
trpc 把命名能力(服务注册、服务发现、负载均衡、服务路由、服务熔断)抽象成 naming 模块,在该模块下根据具体能力抽象出 registry、discovery、loadbalance、servicerouter 和 circuitbreaker 子模块,在客户端上由 selector 插件来协调这些组件进行下游节点的获取。
额外还有一个 bannednodes 模块,这个子模块是用来临时存储某个服务下不可用节点,方便在请求链路中标记和管理不可用的服务节点,避免路由到这些节点(简单理解就是一个 Map )。
核心接口
Registry
用于在指定注册中心上注册和注销当前服务。
所有的 Registry 被注册在 registry.Register(name string, s Registry) 方法内。
// Registry 服务注册接口
type Registry interface {
// 在 Server.Serve() 调用时进行注册
// Server.Serve() 方法会堵塞住,直到程序退出
Register(service string, opt ...Option) error
// 在 Server.Serve() 调用退出时进行注销
Deregister(service string) error
}
目前开源版本的框架内内置的 Registry 是 NoopRegistry ,无能力实现。
Discovery
用于在服务调用前进行服务列表的发现。
所有的 Discovery 被注册在 discovery.Register(name string, s Discovery) 方法内。
type Discovery interface {
// 在 TrpcSelector.Select 中被调用 (TrpcSelector是默认的Selector)
// registry.Node 是服务节点, 维护单个下游服务节点的相关信息
List(serviceName string, opt ...Option) (nodes []*registry.Node, err error)
}
目前开源版本的框架内内置的 Discovery 如下:
IPDiscovery:针对ip和dns的节点服务发现
servicerouter
由于实际业务里会对服务分组,在调用的时候,只对同组内的服务节点列表进行调用。
- 按机房分组,调用的时候优先过滤出同机房的节点列表;
- 按业务标签分组,调用的时候优先使用相同业务标签的节点列表,实现服务隔离;
- 实时发现故障节点,过滤出当前可用的节点列表;
- 可配合
bannednodes模块使用
- 可配合
trpc 内置了这样的能力,支持按照实际业务情况对服务列表进行统一的过滤和管控。
type ServiceRouter interface {
// nodes为 Discovery 服务发现的节点列表
// 在 TrpcSelector.Select 中被调用 (TrpcSelector是默认的Selector)
Filter(serviceName string, nodes []*registry.Node, opt ...Option) ([]*registry.Node, error)
}
目前开源版本的框架内内置的 ServiceRouter 是 NoopServiceRouter 不对节点列表做任何过滤变更。
LoadBalancer
用于在服务列表中进行目标下游节点的选择,即负载均衡,默认使用的负载均衡为 random (随机选取)。
所有的 LoadBalancer 被注册在 loadbalance.Register(name string, s LoadBalancer) 方法内。
type LoadBalancer interface {
// list 默认是在 servicerouter 服务路由后的节点列表
// 在 TrpcSelector.Select 中被调用 (TrpcSelector是默认的Selector)
Select(serviceName string, list []*registry.Node, opt ...Option) (node *registry.Node, err error)
}
内置支持的负载均衡算法如下:
random(随机):实现简单,能均匀分配流量,但可能导致部分节点压力不均,偶发热点。round_robin(轮询):顺序分配,节点压力较均衡,适合节点性能一致场景,但不考虑节点负载和权重。weight_round_robin(加权轮询):可按权重分配流量,适合节点性能不一致场景,但权重需合理配置,动态权重调整复杂。consistent_hash(一致性哈希):同一请求分配到同一节点,适合会话保持、缓存等场景,但节点变动时可能导致部分请求漂移,算法实现复杂。
CircuitBreaker
trpc 对熔断器的实现比较开放,支持判定节点是否触发熔断机制,并且希望针对每一次请求上报调用结果。
熔断能力一般分为两种:
- 本地框架打桩:框架内部自行实现熔断逻辑,在调用的时候选择性熔断情况,做到快速失败,例如
Sentinel限流熔断实现; - 注册中心/熔断平台统一管控:针对每次请求上报调用结果,把是否熔断交给注册中心/熔断平台决定,在下次服务发现时,不返回熔断节点,例如
taf、trpc框架熔断实现;
trpc 熔断机制的抽象比较通用,针对上述两种实现都做了一定程度的支持,但是比起"本地框架打桩"方式,在实现上看出,它更建议注册中心统一调度节点熔断。
type CircuitBreaker interface {
// 判断节点是否可用
// 目前这个方法在框架内部没有调用,也就是说要支持"本地框架打桩"的方式做熔断,需要额外做 Selector 的重写
Available(node *registry.Node) bool
// 上报本次请求结果到注册中心/熔断中心,是否熔断统一进行调度
// 在 TrpcSelector.Report 中被调用 (TrpcSelector是默认的Selector)
Report(node *registry.Node, cost time.Duration, err error) error
}
目前开源版本的框架内内置的 CircuitBreaker 是 NoopCircuitBreaker 不对节点列表做任何过滤变更。
Selector
面向 client 模块(客户端),实现获取指定服务名称的目标调用节点以及调用结果上报,内部一般会集成 Discovery、ServiceRouter、LoadBalancer 和 CircuitBreaker。
// 以下接口都被 client 模块的 selectorFilter(框架内置的一个拦截器插件, 拦截器插件是trpc的一个核心插件之一) 调用
type Selector interface {
// 通过服务名称获取目标调用的服务节点
Select(serviceName string, opt ...Option) (*registry.Node, error)
//上报调用结果
Report(node *registry.Node, cost time.Duration, err error) error
}
目前框架内置的 Selector 如下:
ipSelector:面向指定ip和dns域名的调用实现的Selector- 不具备熔断器功能
trpcSelector:框架默认支持的Selector,内置所有naming模块的插件支持- 不支持 "本地框架打桩" 式熔断,仅支持上报调用结果后由注册中心统一调度熔断
func (s *TrpcSelector) Select(serviceName string, opt ...Option) (*registry.Node, error) {
// 按照指定配置,集成和获取 naming 插件
opts := &Options{
Discovery: discovery.DefaultDiscovery,
DiscoveryOptions: make([]discovery.Option, 0, defaultDiscoveryOptionsSize),
LoadBalancer: loadbalance.DefaultLoadBalancer,
LoadBalanceOptions: make([]loadbalance.Option, 0, defaultLoadBalanceOptionsSize),
ServiceRouter: servicerouter.DefaultServiceRouter,
ServiceRouterOptions: make([]servicerouter.Option, 0, defaultServiceRouterOptionsSize),
CircuitBreaker: circuitbreaker.DefaultCircuitBreaker,
}
for _, o := range opt {
o(opts)
}
// 服务列表发现
list, err := opts.Discovery.List(serviceName, opts.DiscoveryOptions...)
if err != nil {
return nil, err
}
// 服务路由
list, err = opts.ServiceRouter.Filter(serviceName, list, opts.ServiceRouterOptions...)
// 负载均衡
node, err := opts.LoadBalancer.Select(serviceName, list, opts.LoadBalanceOptions...)
// 熔断器挂在到节点元数据上
node.Metadata["circuitbreaker"] = opts.CircuitBreaker
return node, nil
}
func (s *TrpcSelector) Report(node *registry.Node, cost time.Duration, err error) error {
// 节点元数据获取熔断器
breaker, ok := node.Metadata["circuitbreaker"]
if !ok {
return ErrReportNoCircuitBreaker
}
// 上报熔断结果
return circuitbreaker.Report(node, cost, err)
}
对外操作 API
// 以下api来源于client包
// 指定下游调用地址, 例如 ip://127.0.0.1:8848, nacos://127.0.0.1:8848
// 指定的前缀决定了后续使用哪个 selector
func WithTarget(t string) Option
// 指定负载均衡器
func WithBalancerName(balancerName string) Option
// 指定服务发现器
func WithDiscoveryName(name string) Option
// 指定服务路由器
func WithServiceRouterName(name string) Option
// 指定熔断器
func WithCircuitBreakerName(name string) Option