首页
/ Traefik UDP Service 配置指南:LoadBalancer 与 Weighted 负载均衡详解

Traefik UDP Service 配置指南:LoadBalancer 与 Weighted 负载均衡详解

2026-09-07 09:11:40作者:牧宁李

UDP Service 是 Traefik 动态配置(Dynamic Configuration)的核心抽象之一,负责把进入 UDP 路由器的数据报(datagram)转发到能够处理它们的后端服务器。本文基于当前仓库 docs/content/reference/routing-configuration/udp/service.md 文档展开,完整讲解 UDP 服务的两种类型——Servers LoadBalancer(服务器负载均衡)与 Weighted Round Robin(加权轮询,别名 WRR)——的声明格式、配置参数与底层实现原理,帮助你为 DNS、NTP、syslog 等 UDP 业务准确编排 Traefik 的流量分发策略。

UDP 服务(Service)总体结构

在 Traefik 的动态配置中,udp.services 这一节用于声明 UDP 服务。与 HTTP、TCP 服务一致,每个服务条目中的各个字段代表一种服务类型,因此对于每一个声明的服务,必须且只能启用其中一种类型字段,来决定该服务究竟被创建为哪种形式。目前可用的 UDP 服务类型只有两种:

  • LoadBalancer(服务器负载均衡):在同一服务内部的多个后端服务器之间分发数据报;
  • Weighted(加权轮询 / WRR):在多个已定义的服务之间按权重分发数据报。

上述语义直接映射到源码数据结构 pkg/config/dynamic/udp_config.go

// UDPService defines the configuration for a UDP service. All fields are mutually exclusive.
type UDPService struct {
	LoadBalancer *UDPServersLoadBalancer `json:"loadBalancer,omitempty" toml:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty" export:"true"`
	Weighted     *UDPWeightedRoundRobin  `json:"weighted,omitempty" toml:"weighted,omitempty" yaml:"weighted,omitempty" label:"-" export:"true"`
}

注意其中的注释 "All fields are mutually exclusive"(所有字段互斥)。这条约束在服务构建阶段被严格校验:服务管理器 pkg/server/service/udp/service.go 中,如果检测到 LoadBalancerWeighted 同时非空,会直接报错并拒绝创建:

if conf.LoadBalancer != nil && conf.Weighted != nil {
	err := errors.New("cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")
	conf.AddError(err, true)
	return nil, err
}

同样,如果两种类型都未定义,构建时会抛出 the UDP service %q does not have any type defined 错误。因此实践中请务必为每个 UDP 服务明确且仅明确地声明一种类型。

Servers Load Balancer(服务器负载均衡)

Servers Load Balancer 负责在同一服务的多个后端服务器之间平衡转发流量,是 UDP 流量分发的最基本形态。

Servers 字段

servers 字段定义了参与该负载均衡组的所有服务器,即承载该服务程序的每个实例地址(IP:Port)。由于 UDP 本身无连接、无路径(path)与 Host 概念,UDP 服务器的配置远比 HTTP 简单:每个 server 只有一个 address 字段(参见 pkg/config/dynamic/udp_config.go 中的 UDPServer 结构,唯一的配置项是 Address string)。

配置示例:单服务器服务

以下示例通过 File Provider(文件提供方)声明一个仅含一台后端服务器的 UDP 服务:

## 动态配置
udp:
  services:
    my-service:
      loadBalancer:
        servers:
          - address: "xx.xx.xx.xx:xx"
## 动态配置
[udp.services]
  [udp.services.my-service.loadBalancer]
    [[udp.services.my-service.loadBalancer.servers]]
      address = "xx.xx.xx.xx:xx"

在实际使用中,将 address 替换为目标后端的真实 IP 与 UDP 端口即可,例如 "192.168.1.10:9000";同一 servers 列表下可以声明多个地址条目,以实现对多副本后端的负载均衡。

底层构建逻辑

当一个 UDP 服务被声明为 loadBalancer 时,服务管理器 pkg/server/service/udp/service.go 会执行如下流程:

  1. 创建一个 udp.NewWRRLoadBalancer() 实例(见 pkg/udp/wrr_load_balancer.go);
  2. 遍历配置中的服务器列表(注意会经过一次随机 shuffle,见 service.go,使初始分发顺序更均匀);
  3. 对每个地址调用 net.SplitHostPort 校验其是否为合法的 IP:Port 格式,解析失败则跳过该服务器并记录错误日志;
  4. 为合法地址创建 udp.NewProxy(server.Address) 反向代理处理器,通过 loadBalancer.AddServer(handler) 加入均衡池。

由此可见,即使是最简单的多服务器负载均衡,底层同样复用了 WRR 负载均衡器实现,只不过每台后端服务器的权重恒为 1。

Weighted Round Robin(加权轮询)

Weighted Round Robin(别名 WRR)负载均衡器负责根据给定的权重,在多个服务之间平衡连接(数据报流)。

使用前提与支持的提供方

配置示例:在两个服务之间按 3:1 加权

下面示例定义一个名为 app 的加权服务,其流量按权重 3:1 分发到 appv1appv2 两个子服务;而 appv1appv2 本身又是各自带后端服务器的普通 LoadBalancer 服务:

udp:
  services:
    app:
      weighted:
        services:
          - name: appv1
            weight: 3
          - name: appv2
            weight: 1

    appv1:
      loadBalancer:
        servers:
          - address: "xxx.xxx.xxx.xxx:8080"

    appv2:
      loadBalancer:
        servers:
          - address: "xxx.xxx.xxx.xxx:8080"
[udp.services]
  [udp.services.app]
    [[udp.services.app.weighted.services]]
      name = "appv1"
      weight = 3
    [[udp.services.app.weighted.services]]
      name = "appv2"
      weight = 1

  [udp.services.appv1]
    [udp.services.appv1.loadBalancer]
      [[udp.services.appv1.loadBalancer.servers]]
        address = "xxx.xxx.xxx.xxx:8080"

  [udp.services.appv2]
    [udp.services.appv2.loadBalancer]
      [[udp.services.appv2.loadBalancer.servers]]
        address = "xxx.xxx.xxx.xxx:8080"

上述示例展示的正是加权轮询的经典用法:把部分流量引入新版本(如 appv2,weight=1),同时把大部分流量保留在稳定版本(如 appv1,weight=3),可用于金丝雀发布或按容量比例分流。

配置选项(Configuration Options)

加权服务(weighted)支持以下配置字段:

字段 描述 默认值 是否必填
services 定义参与负载均衡的服务列表。
services.name 参与负载均衡的目标服务名称。 ""
services.weight 在均衡连接时为该服务赋予的权重。 1

关于默认权重为 1,源码中由 UDPWRRServiceSetDefaults 方法显式兜底(pkg/config/dynamic/udp_config.go):

func (w *UDPWRRService) SetDefaults() {
	defaultWeight := 1
	w.Weight = &defaultWeight
}

底层构建与轮询算法

当服务类型为 weighted 时,服务管理器会递归地构建每个被引用的子服务(pkg/server/service/udp/service.go):

case conf.Weighted != nil:
	loadBalancer := udp.NewWRRLoadBalancer()

	for _, service := range shuffle(conf.Weighted.Services, m.rand) {
		handler, err := m.BuildUDP(ctx, service.Name)
		if err != nil {
			logger.Error().Err(err).Msg("Failed to build UDP handler")
			return nil, err
		}
		loadBalancer.AddWeightedServer(handler, service.Weight)
	}
	return loadBalancer, nil

这里 BuildUDP(ctx, service.Name) 会对 services[].name 指向的子服务做递归解析,因此加权引用链上的每个服务都必须存在且类型合法,否则构建失败。这也解释了为何 services.name 是必填项。

真正执行加权选择的调度算法位于 pkg/udp/wrr_load_balancer.go。它采用基于“最大权重 + 权重最大公约数(GCD)”的平滑加权轮询实现:每次遍历一轮后,currentWeight 减去所有权重的 GCD,当降到 0 以下时重新置为最大权重,再挑选权重不低于 currentWeight 的下一个服务器。该算法避免了简单加权轮询可能出现的“连续多次打到同一台高权重后端”的突发问题,使 3:1 的权重在长期统计上表现为约 3:1 的均匀交错分发。此外 ServeUDP 在选择服务器时通过互斥锁(sync.Mutex)保证并发安全(见 wrr_load_balancer.go)。

集成测试验证

仓库的 UDP 集成测试对 WRR 的加权分发结果做了直接断言。integration/udp_test.go 中的 TestWRR 用例基于 fixtures/udp/wrr.toml 配置(三个后端 whoami-awhoami-bwhoami-c,权重分别为 3、2、3),连续发起 8 次 UDP 探测后,断言命中分布精确等于 {"whoami-a": 3, "whoami-b": 2, "whoami-c": 3},从端到端层面验证了加权轮询的算法正确性。

与 UDP Router 的配合使用

UDP Service 本身不直接对外暴露入口,而是必须被 UDP Router 引用(router 中的 service 字段指向服务名称)才能真正接收入口点的数据报。一个完整的 UDP 路由链路示例(结构化 YAML)如下:

## 入口点(entryPoints)需在静态配置中声明,例如 "dns"、"udp-ep"
udp:
  routers:
    my-udp-router:
      entryPoints:
        - "udp-ep"
        - "dns"
      service: my-udp-service

  services:
    my-udp-service:
      loadBalancer:
        servers:
          - address: "10.0.0.1:9000"

值得留意的是,UDP 路由器只能指向 UDP 服务(不能指向 HTTP 或 TCP 服务)。尽管 UDP 是无连接的,Traefik 的 UDP 路由/转发仍基于“会话(session)”维护客户端与后端之间的状态,从而保证来自后端的数据报能够被正确送回发起请求的客户端。更多细节可参见 UDP Router 文档

小结

  • UDP 服务有两种互斥类型:loadBalancer(服务器级均衡)与 weighted(服务级加权轮询),声明时二者只能选其一。
  • loadBalancer.servers[].address 使用 IP:Port 直接指向后端实例;weighted.services[] 通过 name 引用其它已定义服务、通过 weight 控制分发比例(默认 1)。
  • WRR 策略仅支持在服务之间加权,且目前仅能通过 File ProviderKubernetes CRD(IngressRouteUDP) 定义。
  • 从源码实现看,加权分发采用基于 GCD 的平滑加权轮询算法,由 pkg/udp/wrr_load_balancer.go 提供,并经 integration/udp_test.go 的集成测试验证,可放心用于生产分流场景。
登录后查看全文
热门项目推荐
相关项目推荐