- 后端
- 微服务
【免费下载链接】akka.net
Canonical actor model implementation for .NET with local + distributed actors in C# and F#.
本文以 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,核心类型只有少数几个文件:
| 类型 | 文件 | 职责 |
|---|---|---|
Discovery | Discovery.cs | ActorSystem 扩展(Extension),负责按配置加载默认/具名发现方法 |
ServiceDiscovery | ServiceDiscovery.cs | 所有发现方法的抽象基类,定义Lookup契约 |
ConfigServiceDiscovery | ConfigServiceDiscovery.cs | 内置方法之一:从 HOCON 配置读取静态端点 |
AggregateServiceDiscovery | AggregateServiceDiscovery.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 读出:
- 扩展构造时,先通过
InjectTopLevelFallback(DiscoveryProvider.DefaultConfiguration())把模块自带的 reference.conf 注入配置回退链,因此即使应用未配置任何发现相关内容,akka.discovery下的config、aggregate等默认段也一定存在; - 读取
akka.discovery.method。reference.conf 中该值默认为占位符"<method>",若未配置,Discovery会打出一条以 "No default service discovery implementation configured inakka.discovery.method." 开头的警告并回退到config方法; - 按方法名解析实现类:取配置段
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实现可以确认回退语义的三个细节:
- 每个方法拿到的都是完整的
resolveTimeout,而不是逐段递减的剩余时间(源码注释:"Each discovery method is given the resolveTimeout rather than reducing it each time between methods"); - 触发回退的条件是"返回的
Resolved.Addresses为空"或方法抛出异常"——异常被捕获并记录日志后继续尝试下一个方法; - 所有方法都无结果时记录一条 "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 接管成簇。
注意事项与最佳实践
综合文档与源码行为,落地时建议遵守:
requiredContactPoints绝不为 1:单一 contact point 无法防止分区侧各自成簇;- 所有节点配置同一 discovery method 与 service name:发现结果不一致会直接导致节点间互相不可见;
- 使用 Akka.Management/Bootstrap 时清空静态
SeedNodes; - 需要更强一致性时可使用
contactWithAllContactPoints配置; - 自定义插件构造函数三选一:
(ExtendedActorSystem, Config)、(ExtendedActorSystem)或无参,且必须继承ServiceDiscovery并重写Lookup; - aggregate 中每个方法都拿到完整 timeout:设计多个慢方法时注意整体最坏耗时是各方法超时之和;
- 未配置
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#.
相关推荐
Akka.NET 集群自动组建实战:Akka.Management、Akka.Discovery 与 Cluster Bootstrap 协同机制
Akka.NET 集群自动组建实战:Akka.Management、Akka.Discovery 与 Cluster Bootstrap 协同机制 本文以 Ak
后端微服务未来展望:MMLW-retrieval-e5-large的发展路线图与社区贡献指南
未来展望:MMLW retrieval e5 large的发展路线图与社区贡献指南 MMLW retrieval e5 large作为一款专为波兰语信息检索优化
Admiral:多集群Istio服务网格的自动配置与服务发现
Admiral:多集群Istio服务网格的自动配置与服务发现 在当今的微服务架构中,服务网格已成为管理和优化服务间通信的关键技术。Istio作为领先的服务网格解
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考