Trpc命名插件

25 阅读6分钟

命名服务插件

概述

名字服务模块可以将节点注册到对应的服务名下。注册信息除了 ip:port 外,还会包含运行环境、容器以及其他自定义的元数据信息。调用方根据服务名获取到所有节点后,路由模块再根据元数据信息对节点进行筛选,最后,负载均衡算法从满足要求的节点中选出一个节点来进行最终请求。

trpc 把命名能力(服务注册、服务发现、负载均衡、服务路由、服务熔断)抽象成 naming 模块,在该模块下根据具体能力抽象出 registrydiscoveryloadbalanceserviceroutercircuitbreaker 子模块,在客户端上由 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
}

目前开源版本的框架内内置的 RegistryNoopRegistry ,无能力实现。

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:针对 ipdns 的节点服务发现

servicerouter

由于实际业务里会对服务分组,在调用的时候,只对同组内的服务节点列表进行调用。

  • 按机房分组,调用的时候优先过滤出同机房的节点列表;
  • 按业务标签分组,调用的时候优先使用相同业务标签的节点列表,实现服务隔离;
  • 实时发现故障节点,过滤出当前可用的节点列表;
    • 可配合 bannednodes 模块使用

trpc 内置了这样的能力,支持按照实际业务情况对服务列表进行统一的过滤和管控。

type ServiceRouter interface {
    // nodes为 Discovery 服务发现的节点列表
    // 在 TrpcSelector.Select 中被调用 (TrpcSelector是默认的Selector)
	Filter(serviceName string, nodes []*registry.Node, opt ...Option) ([]*registry.Node, error)
}

目前开源版本的框架内内置的 ServiceRouterNoopServiceRouter 不对节点列表做任何过滤变更。

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 限流熔断实现;
  • 注册中心/熔断平台统一管控:针对每次请求上报调用结果,把是否熔断交给注册中心/熔断平台决定,在下次服务发现时,不返回熔断节点,例如 taftrpc 框架熔断实现;

trpc 熔断机制的抽象比较通用,针对上述两种实现都做了一定程度的支持,但是比起"本地框架打桩"方式,在实现上看出,它更建议注册中心统一调度节点熔断。

type CircuitBreaker interface {
    // 判断节点是否可用
    // 目前这个方法在框架内部没有调用,也就是说要支持"本地框架打桩"的方式做熔断,需要额外做 Selector 的重写
	Available(node *registry.Node) bool
    // 上报本次请求结果到注册中心/熔断中心,是否熔断统一进行调度
    // 在 TrpcSelector.Report 中被调用 (TrpcSelector是默认的Selector)
	Report(node *registry.Node, cost time.Duration, err error) error
}

目前开源版本的框架内内置的 CircuitBreakerNoopCircuitBreaker 不对节点列表做任何过滤变更。

Selector

面向 client 模块(客户端),实现获取指定服务名称的目标调用节点以及调用结果上报,内部一般会集成 DiscoveryServiceRouterLoadBalancerCircuitBreaker

// 以下接口都被 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:面向指定 ipdns 域名的调用实现的 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