☰
Akka.NET Akka.Discovery 服务发现机制详解:配置、聚合与动态集群组建
2026/10/14 15:45:09 网站建设 项目流程
  • 后端
  • 微服务

【免费下载链接】akka.net

Canonical actor model implementation for .NET with local + distributed actors in C# and F#.

项目地址:https://gitcode.com/gh_mirrors/ak/akka.net
点击查看免费下载

本文以 Akka.NET 仓库中的Akka.Discovery模块为核心,系统讲解服务发现扩展的加载机制、Lookup/Resolved核心 API、内置的 config 与 aggregate 两种发现方法的完整配置方式,以及如何配合 Akka.Management 与 Cluster.Bootstrap 在云环境(Azure、AWS、Kubernetes)中替代静态 seed nodes、安全地动态组建集群。读完本文,你将能够独立配置服务发现、实现自定义发现插件,并理解从发现到成簇的完整调用链。

Akka.Discovery 是什么

Akka.NET Discovery 为"如何定位服务"这一抽象问题提供统一接口,屏蔽底层是配置文件、DNS、云厂商 API 还是 Kubernetes 的差异。在本仓库中,该模块位于 src/core/Akka.Discovery,核心类型只有少数几个文件:

类型文件职责
DiscoveryDiscovery.csActorSystem 扩展(Extension),负责按配置加载默认/具名发现方法
ServiceDiscoveryServiceDiscovery.cs所有发现方法的抽象基类,定义Lookup契约
ConfigServiceDiscoveryConfigServiceDiscovery.cs内置方法之一:从 HOCON 配置读取静态端点
AggregateServiceDiscoveryAggregateServiceDiscovery.cs内置方法之二:按顺序聚合多个发现方法并回退

模块的默认配置(reference.conf)以嵌入资源方式随包发布,见 reference.conf,由 Akka.Discovery.csproj 中的<EmbeddedResource Include="Resources\reference.conf"/>打包,并在DiscoveryProvider.DefaultConfiguration()中通过ConfigurationFactory.FromResource<Discovery>("Akka.Discovery.Resources.reference.conf")注入系统配置链。

核心 API:Lookup 与 Resolved

一次服务发现的输入是Lookup,输出是Resolved。

Lookup由必填的serviceName和可选的portName、protocol三个字段组成,其含义由具体发现方法自行解释(见 ServiceDiscovery.cs):

using Akka.Actor; using Akka.Discovery; var system = ActorSystem.Create("example"); var serviceDiscovery = Discovery.Get(system).Default; // 仅带 serviceName 的便捷重载 serviceDiscovery.Lookup("akka.net", TimeSpan.FromSeconds(1)); // 完整 Lookup:portName 与 protocol 可选 Task<ServiceDiscovery.Resolved> lookup = serviceDiscovery.Lookup( new Lookup("akka.net").WithPortName("remoting").WithProtocol("tcp"), TimeSpan.FromSeconds(1));

portName的典型用途是区分同一服务暴露的多个端口,例如同时开放 HTTP 端口与 Akka remoting 端口时,用WithPortName("remoting")指明要解析哪一类端口。WithPortName/WithProtocol均返回新的不可变Lookup实例(内部调用Copy)。

源码中还有一个对 DNS SRV 记录场景很有用的静态方法Lookup.ParseSrv:它能将 RFC 2782 风格的_portName._protocol.serviceName字符串解析为Lookup(serviceName).WithPortName(portName).WithProtocol(protocol),并按 RFC 1034 校验 serviceName 部分是合法域名;格式不合法时抛出ArgumentException。这套解析逻辑在 LookupSpec.cs 中有大量边界用例覆盖,包括 label 长度(1~63 字符)、不能以数字或连字符开头/以连字符结尾等规则。

返回类型ServiceDiscovery.Resolved包含ServiceName与不可变列表Addresses,每个元素是ResolvedTarget(Host, Port?, Address?)。其中Port是可空的int?,Address(IPAddress)用于在集群启动过程中直接提供 IP(源码注释说明 "This is used during cluster bootstrap when available")。查找失败时返回的Resolved其Addresses为空列表,而不是null——这对上层"没有结果就继续尝试下一个方法"的判断很重要。

抽象方法Lookup(Lookup, TimeSpan resolveTimeout)的契约是:resolveTimeout表达应用对本次查询"愿意等多久"的意愿,具体实现是否严格遵守由发现方法决定(源码注释原文:"Up to the discovery-method to adhere to this")。

扩展加载机制:Discovery.Get 与方法实例化

Discovery是标准的 Akka Extension。调用Discovery.Get(system).Default时的完整流程可以从 Discovery.cs 读出:

  1. 扩展构造时,先通过InjectTopLevelFallback(DiscoveryProvider.DefaultConfiguration())把模块自带的 reference.conf 注入配置回退链,因此即使应用未配置任何发现相关内容,akka.discovery下的config、aggregate等默认段也一定存在;
  2. 读取akka.discovery.method。reference.conf 中该值默认为占位符"<method>",若未配置,Discovery会打出一条以 "No default service discovery implementation configured inakka.discovery.method." 开头的警告并回退到config方法;
  3. 按方法名解析实现类:取配置段akka.discovery.{method},要求其中存在class字段(实现类全限定名)。若配置段不存在则抛出ArgumentException(消息形如 "Could not load discovery config from path [akka.discovery.{method}]")。

从源码结构看,实现类的实例化按优先级尝试三种构造函数签名(Discovery.cs 中的Create局部函数):

  • (ExtendedActorSystem system, Configuration.Config config);
  • (ExtendedActorSystem system);
  • 无参构造。

三者都不满足时会抛出带明确提示的ArgumentException:"Illegal akka.discovery.{method}.class value or incompatible class!",提示实现类必须继承ServiceDiscovery并提供上述构造函数之一。这些规则在 DiscoveryConfigurationSpec.cs 中有对应的单测(如OneParameterTest、TwoParametersTest、IllegalConstructorTest)。

具名方法可以通过Discovery.Get(system).LoadServiceDiscovery(method)显式获取;LoadServiceDiscovery内部用ConcurrentDictionary<string, Lazy<ServiceDiscovery>>缓存,同一 method 只创建一次实例,后续调用返回同一对象(测试ServiceDiscovery_should_return_same_instance_for_same_method验证了这一点)。Default属性则是懒加载akka.discovery.method指定方法的入口。

内置发现方法一:config(静态配置发现)

最简单的发现方式就是把端点直接写进 HOCON 配置。它的定位是"可迁移的静态列表":应用先把服务地址写在配置里,之后切换到更复杂的发现方式(DNS、云 API 等)时无需改动任何业务代码——上层只依赖ServiceDiscovery.Lookup这一个契约。

配置方式(application.conf):

akka { discovery.method = config }

服务清单默认放在akka.discovery.config.services:

akka.discovery.config.services = { service1 = { endpoints = [ "cat:1233", "dog:1234" ] }, service2 = { endpoints = [] } }

上述配置定义了两个服务service1(两个端点)和service2(空端点列表)。每个服务可以配置多个host:port形式的端点,端口可省略。

结合 ConfigServiceDiscovery.cs 可以补充几个文档未展开的实现细节:

  • 端点解析:ConfigServicesParser.Parse将每个 endpoint 按:拆分,values[0]作为 host,其余部分尝试解析为端口;因此不带端口的"dog"会解析为ResolvedTarget("dog", null)。ConfigServiceDiscoverySpec.cs 中的Config_discovery_must_load_from_config用例正好断言了这一行为;
  • 只使用 serviceName:ConfigServiceDiscovery.Lookup只按lookup.ServiceName查字典,portName/protocol被忽略,这与文档中"Configuration currently ignores all fields apart from service name"的说明一致;
  • 查找不存在的服务:返回Addresses为空的Resolved(而非抛异常),测试Config_discovery_must_return_no_resolved_targets_if_not_in_config验证;
  • 可配置服务路径:services-path字段(默认akka.discovery.config.services)可以指向任意配置路径;若该路径不存在或为空,构造器只打警告并以空字典启动;
  • 运行时动态增删:TryAddEndpoint/TryRemoveEndpoint标记为[InternalStableApi],允许在运行时向配置发现的结果中追加/移除端点,为其他组件(如集群协调流程)在内存中维护服务视图提供能力。

reference.conf 中还预置了akka.actor.deployment."/SD-DNS/async-dns"的 round-robin 路由配置,说明 DNS 类发现方法在 Akka.NET 中会复用 IO.DNS 基础设施(见 src/core/Akka/IO/Dns.cs 等实现)。

内置发现方法二:aggregate(聚合多个发现方法)

aggregate 方法把多个发现方法串成"依次尝试、首个非空结果即返回"的链路,例如先用云 API 解析、失败或无结果时回退到静态配置。使用方式是引入聚合实现以及所有被聚合方法的依赖,然后配置method = aggregate及有序列表discovery-methods:

akka { discovery { method = aggregate aggregate { discovery-methods = ["azure", "config"] } azure { # Azure 插件配置(对应插件提供) } config { services { service1 { endpoints = [ "host1:1233", "host2:1234" ] } } } } }

按此配置,查询会先访问 Azure;只有当它失败或对该 serviceName 返回 0 个目标时,才会查询 config 方法。

从 AggregateServiceDiscovery.cs 的Lookup实现可以确认回退语义的三个细节:

  1. 每个方法拿到的都是完整的resolveTimeout,而不是逐段递减的剩余时间(源码注释:"Each discovery method is given the resolveTimeout rather than reducing it each time between methods");
  2. 触发回退的条件是"返回的Resolved.Addresses为空"或方法抛出异常"——异常被捕获并记录日志后继续尝试下一个方法;
  3. 所有方法都无结果时记录一条 "Aggregate service discovery failed." 级别的 Warning 并返回空Resolved;discovery-methods配置缺失或为空时构造阶段也会警告。

AggregateServiceDiscoverySpec.cs 用桩实现覆盖了三种路径:首方法命中即短路(must_only_call_first_one_if_returns_results)、首方法空结果时移到下一方法(must_move_onto_the_next_if_no_resolved_targets)、首方法抛异常时移到下一方法(must_move_onto_next_if_fails)。

与 Akka.Management、Cluster.Bootstrap 的协作

Akka.Discovery 本身只回答"这个服务现在有哪些端点",真正消费它的典型场景是集群动态组建:

  • Akka.Management暴露 HTTP 端点供节点互相通告、查询状态与协调成簇;
  • Akka.Discovery向环境(Azure Table Storage、AWS EC2/ECS、Kubernetes API 或本地配置)查询可用节点;
  • Cluster.Bootstrap消费发现结果来动态填充 seed nodes 列表,在确认足够多的 contact point 后可达后安全地组建或加入集群。

需要注意的前提是:Cluster.Bootstrap期望所有节点使用相同的 management 端口——当 Akka.Discovery 返回某主机上的多个开放端口时,bootstrap 协调器会过滤掉与本地配置管理端口不一致的端口。因此在一台机器上跑多个集群节点做开发测试、且各节点使用不同 management 端口时,需显式关闭回退端口过滤:

builder.WithClusterBootstrap(setup => { setup.ContactPoint.FilterOnFallbackPort = false; });
akka.management { cluster.bootstrap { contact-point { # 允许 discovery 返回同一主机上的多个端口 filter-on-fallback-port = false } } }

(该建议来自同目录的 akka-management.md。)

使用 Akka.Hosting 配置(推荐方式)

官方建议新工程用 Akka.Hosting 以类型安全、可组合的方式配置发现。下面是文档给出的完整示例——启用 remoting、集群(清空静态 seed nodes)、Akka.Management HTTP 端点、Cluster Bootstrap 以及 Azure 发现:

using Akka.Hosting; using Akka.Discovery.Azure; var builder = Host.CreateDefaultBuilder() .ConfigureServices((context, services) => { services.AddAkka("my-actor-system", (akkaBuilder, provider) => { // Remoting akkaBuilder.WithRemoting("0.0.0.0", 4053); // Clustering(必须清空静态 seed nodes,交给 bootstrap) akkaBuilder.WithClustering(new ClusterOptions { SeedNodes = Array.Empty<string>(), Roles = new[] { "backend" } }); // Akka.Management HTTP 端点 akkaBuilder.WithAkkaManagement(port: 8558); // Cluster Bootstrap akkaBuilder.WithClusterBootstrap( serviceName: "my-akka-service", // 必须与 Azure discovery 的 service name 一致 portName: "akka-remote", // 可选,用于多端口场景 requiredContactPoints: 3 // 安全值,绝不能是 1 ); // Azure Discovery akkaBuilder.WithAzureDiscovery(options => { options.ServiceName = "my-akka-service"; options.ConnectionString = "<your Azure Table Storage connection string>"; }); }); });

配套要点(与文档 Notes 一致):

  • SeedNodes必须为空,否则静态 seed nodes 与 bootstrap 并存会造成行为冲突;
  • requiredContactPoints应设为安全值(生产环境通常 2 或 3,绝不能是 1,否则会放大 split-brain 风险);
  • 两侧的serviceName(Azure Discovery 与 Cluster Bootstrap)必须完全一致;
  • remoting 端口与 management 端口按环境自行调整,但所有节点必须使用同一 discovery 方法与同一 service name,且 management 端口一致(见上一节关于filter-on-fallback-port的说明)。

本仓库的 Hosting 扩展中也体现了这一集成面:Akka.Hosting 定义了 IDiscoveryOptions 接口(继承IHoconOption),Akka.Cluster.Hosting 的WithClusterClientDiscovery系列扩展接收任意IDiscoveryOptions(如 Kubernetes、EC2、ECS 或 config 发现选项),并自动把DiscoveryProvider.DefaultConfiguration()以HoconAddMode.Append方式合入配置链(见 AkkaClusterHostingExtensions.cs)。相关用法可参考 ClusterClientDiscoverySpecs.cs 中的测试。

支持的发现插件

本仓库内建(随Akka.Discovery包发布)的是:

  • Akka.Discovery.Configuration:静态配置发现,即上文config方法;
  • Akka.Discovery.Aggregate:聚合发现,即aggregate方法。

云厂商类插件(Akka.Discovery.AwsApi的 EC2/ECS 发现、Akka.Discovery.KubernetesApi、Akka.Discovery.Azure的 Table Storage 发现)维护在独立的 Akka.Management 仓库中,不在当前仓库源码范围内,使用时需引入对应 NuGet 包并按各插件文档补齐akka.discovery.{method}配置段(class由插件的 reference.conf 提供)。

从 Seed Nodes 迁移

动态发现相对静态 seed nodes 的优势在于:在云、PaaS、Kubernetes 等地址不固定或不可靠的动态环境中,集群可以在节点任意重启、扩容后自动重新发现并重组,而不需要运维人员手工维护"哪三台机器是 seed"。静态 seed nodes 列表一旦与实际部署脱节(机器置换、IP 漂移),轻则集群无法组建,重则网络分区时两侧各自认为自己是完整集群,形成 split-brain,恢复过程繁琐且易错。迁移路径就是:保持serviceName全局一致 → 部署各环境对应的 discovery 插件 → 清空SeedNodes→ 用 Cluster.Bootstrap + Akka.Management 接管成簇。

注意事项与最佳实践

综合文档与源码行为,落地时建议遵守:

  1. requiredContactPoints绝不为 1:单一 contact point 无法防止分区侧各自成簇;
  2. 所有节点配置同一 discovery method 与 service name:发现结果不一致会直接导致节点间互相不可见;
  3. 使用 Akka.Management/Bootstrap 时清空静态SeedNodes;
  4. 需要更强一致性时可使用contactWithAllContactPoints配置;
  5. 自定义插件构造函数三选一:(ExtendedActorSystem, Config)、(ExtendedActorSystem)或无参,且必须继承ServiceDiscovery并重写Lookup;
  6. aggregate 中每个方法都拿到完整 timeout:设计多个慢方法时注意整体最坏耗时是各方法超时之和;
  7. 未配置akka.discovery.method时系统会静默回退到 config 方法并打 Warning——生产环境建议显式声明 method,避免误用默认值。

参考路径汇总

  • 文档:docs/articles/discovery/index.md、docs/articles/discovery/akka-management.md
  • 源码:Discovery.cs、ServiceDiscovery.cs、ConfigServiceDiscovery.cs、AggregateServiceDiscovery.cs、reference.conf
  • 测试:ConfigServiceDiscoverySpec.cs、AggregateServiceDiscoverySpec.cs、LookupSpec.cs、DiscoveryConfigurationSpec.cs
  • 后端
  • 微服务

【免费下载链接】akka.net

Canonical actor model implementation for .NET with local + distributed actors in C# and F#.

项目地址:https://gitcode.com/gh_mirrors/ak/akka.net
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询