Akka.NET 教程第一部分:构建顶层架构 —— 守卫者 Actor、层级树与监督机制
后端微服务【免费下载链接】akka.netCanonical actor model implementation for .NET with local distributed actors in C# and F#.项目地址https://gitcode.com/gh_mirrors/ak/akka.net点击查看免费下载本篇是 Akka.NET 官方教程的第一部分聚焦于「第一个 Actor 应该放在哪里」这一顶层架构问题。通过阅读本文你将掌握 Akka.NET Actor 系统的严格层级结构root/user/system 三个守卫者 Actor、IActorRef与 Actor 路径的 URL 式结构、Actor 的生命周期钩子PreStart/PostStop等的递归停止行为以及默认的故障监督策略并学会用「单一顶层 Actor 子 Actor 树」的推荐模式搭建应用骨架IoT 示例。一、Actor 总是有父级的Actor 层级树在 Akka.NET 中创建 Actor 时它必然归属于某个父级因此所有 Actor 被组织成一棵严格的树。通常情况下创建 Actor 只能在另一个 Actor 内部进行这个「创建者」Actor 就成为新 Actor 的父级新 Actor 是其子级。那么第一个 Actor 的父级是谁答案是需要先初始化一个Actor System本文下称System然后调用System.ActorOf()得到新 Actor 的引用。这种从「顶层」创建 Actor 的方式并不会产生游离的 Actor而是把它作为子 Actor 注入到一棵已经存在的树中。系统中预置的三个守卫者 Actor从顶层创建 Actor 会将其注入/user/路径下。例如创建一个名为myActor的 Actor其最终路径为/user/myActor。此时系统中其实已经存在三个预置 Actor/root guardian根守卫者系统中所有 Actor 的父级也是系统终止时最后停止的 Actor。/useruser guardian用户守卫者所有用户创建 Actor 的父级。这里的user与登录用户无关其含义是「userspace用户空间」——所有不接触 Akka.NET 内部实现、由库使用者创建的 Actor 都生活在这里。你创建的每个 Actor 都会被强制加上/user/前缀。/systemsystem guardian系统守卫者Akka.NET 内部机制使用的 Actor 所在区域。它们的名字中带有「guardian守卫者」是因为它们在**监督supervise**每一个作为其子级存在的 Actor任何子 Actor 中未被处理的失败都会冒泡到其父级由父级决定如何处理。这些预置 Actor 是整个监督体系的最后一道防线所有来自用户 Actor 或系统 Actor 的未处理失败最终都会汇聚到它们。根守卫者/有父级吗事实上它也有。这个特殊实体就是文中所称的 Bubble-Walker对用户不可见仅用于内部机制。从源码结构看这棵树的构建过程可以在ActorRefProvider.Init()中找到直接证据先创建 root guardian再依次CreateUserGuardian(_rootGuardian, user)和CreateSystemGuardian(_rootGuardian, system, _userGuardian)并让 system guardian 通过 death-watch 监视 user guardian、root guardian 监视 system guardian形成「杀死守卫者即停止应用」的联动链见 ActorRefProvider.cs。用户守卫者的创建逻辑ActorRefProvider.cs还体现了akka.user.guardian-dispatcher等配置的解析若配置了GuardianProps则允许用户守卫者运行在自定义 dispatcher 上否则使用内部 dispatcher。所谓 Bubble-Walker 在 Akka.NET 源码中的对应实体是RootGuardianSupervisor它是整个监督层级的顶层锚点负责完成 Actor System 的终止流程。其构造时路径注释直接点明了与 AkkaJVM 版的对应关系见 RootGuardianSupervisor.cs_path root / _Root-guardian-supervisor; //In akka this is root / bubble-walker当某个守卫者本身失败时它会记录失败原因、停止出错的子级并最终完成系统的终止 TaskRootGuardianSupervisor.cs。二、IActorRef 的结构与 Actor 的路径理解上述结构最直接的方式是打印IActorRef实例。下面的实验先创建第一个 Actor 并打印它的引用再在它内部创建一个子 Actor 并打印子级引用。顶层 Actor 使用System.ActorOf()创建直接位于/user之下本文沿用教程的术语称这类 Actor 为top level——严格说它们并不在整个层级树的顶端而只是在用户定义层级的顶端由于实践中我们几乎只关心/user之下的 Actor这一术语仍然方便且通用。非顶层 Actor 则可以在任意 Actor 内部通过Context.ActorOf()创建其签名与顶层版本完全一致。完整实验代码见 ActorHierarchyExperiments.cspublic class PrintMyActorRefActor : UntypedActor { protected override void OnReceive(object message) { switch (message) { case printit: IActorRef secondRef Context.ActorOf(Props.Empty, second-actor); Console.WriteLine($Second: {secondRef}); break; } } }驱动这段实验的入口在 TestKit 中Sys即 Actor Systemvar firstRef Sys.ActorOf(Props.CreatePrintMyActorRefActor(), first-actor); Console.WriteLine($First: {firstRef}); firstRef.Tell(printit, ActorRefs.NoSender);运行后控制台输出如下First : Actor[akka://testSystem/user/first-actor#1053618476] Second: Actor[akka://testSystem/user/first-actor/second-actor#1544706041]从输出可以解析出 Actor 引用的完整结构协议与系统名所有路径都以akka://testSystem/开头。Actor 引用都是合法的 URL因此需要协议字段Actor 即为akka://。与 Web 类似接下来标识系统——本例是testSystem但可以是任意名字若启用了多系统间的远程通信该名字就是系统的 hostname其他系统据此在网络中定位它。层级路径akka://testSystem/user/first-actor是直接用System.ActorOf()创建的第一个 Actor直接位于用户守卫者/user之下akka://testSystem/user/first-actor/second-actor是通过Context.ActorOf创建的第二个 Actor直接位于第一个 Actor 之下形成了层级。UID 后缀引用末尾如#1053618476的部分是路径下那个 Actor 的唯一标识符UID。这通常不是用户需要关心的东西教程将其讨论留待后续章节。三、Actor 的层级与生命周期到目前为止我们已知 Actor 被组织成一棵严格层级树顶层是预置的三个守卫者其下是用户创建的顶层 Actor直接位于/user之下再往下是它们的子级。那么「为什么需要这棵树它用来做什么」第一个用途就是管理 Actor 的生命周期Actor 创建时诞生之后应使用者要求停止而任何一个 Actor 被停止时它的所有子级都会被递归停止。这是一个非常有用的性质极大地简化了资源清理、避免了资源泄漏如打开的 socket、文件等——事实上处理底层多线程代码时被忽视的难点之一正是各种并发资源的生命周期管理。停止 Actor 可以调用Context.Stop(actorRef)完成但以这种方式停止任意 Actor 被视为不良实践。推荐的模式是在 Actor 内部调用Context.Stop(self)停止自己通常作为对用户自定义 stop 消息的响应或在 Actor 完成其工作时触发。Actor API 暴露了大量可覆写的生命周期钩子最常用的是PreStart()在 Actor 启动后、处理其第一条消息之前被调用。PostStop()在 Actor 停止前一刻被调用此后不再处理任何消息。继续用一个简单实验验证完整代码见 ActorHierarchyExperiments.cspublic class StartStopActor1 : UntypedActor { protected override void PreStart() { Console.WriteLine(first started); Context.ActorOf(Props.CreateStartStopActor2(), second); } protected override void PostStop() Console.WriteLine(first stopped); protected override void OnReceive(object message) { switch (message) { case stop: Context.Stop(Self); break; } } } public class StartStopActor2 : UntypedActor { protected override void PreStart() Console.WriteLine(second started); protected override void PostStop() Console.WriteLine(second stopped); protected override void OnReceive(object message) { } }入口代码var first Sys.ActorOf(Props.CreateStartStopActor1(), first); first.Tell(stop);运行输出first started second started second stopped first stopped可以看到停止 Actorfirst时它递归停止了子 Actorsecond然后才停止自身。这个顺序是严格的所有子级的PostStop()钩子都在父级的PostStop()钩子之前被调用。这一「先子后父」的顺序正是资源清理不发生泄漏的结构性保证。这些生命周期钩子构成的家族非常丰富所有细节建议阅读参考文档的 Actor 生命周期 一节。四、层级与故障处理Supervision父级与子级之间的关系不止是生命周期绑定。每当一个 Actor 失败抛出异常或未被处理的异常从receive中冒泡出来它会被临时挂起suspended失败信息传播给父级由父级决定如何处理子 Actor 引发的异常。默认的监督策略就是停止并重启该子 Actor如果你不修改默认策略所有失败都会导致一次 restart。从源码看默认策略定义在 SupervisorStrategy.cspublic static readonly SupervisorStrategy DefaultStrategy new OneForOneStrategy(DefaultDecider);而DefaultDecider的判定规则SupervisorStrategy.cs为对一般Exception返回Restart对ActorInitializationException、ActorKilledException与DeathPactException返回Stop。也就是说默认的OneForOne策略只处置出错的子 Actor 本身且绝大多数异常都会走「重启」路径。下面用实验验证本实验不修改默认策略完整代码见 ActorHierarchyExperiments.cspublic class SupervisingActor : UntypedActor { private IActorRef child Context.ActorOf(Props.CreateSupervisedActor(), supervised-actor); protected override void OnReceive(object message) { switch (message) { case failChild: child.Tell(fail); break; } } } public class SupervisedActor : UntypedActor { protected override void PreStart() Console.WriteLine(supervised actor started); protected override void PostStop() Console.WriteLine(supervised actor stopped); protected override void OnReceive(object message) { switch (message) { case fail: Console.WriteLine(supervised actor fails now); throw new Exception(I failed!); } } }入口代码var supervisingActor Sys.ActorOf(Props.CreateSupervisingActor(), supervising-actor); supervisingActor.Tell(failChild);运行片段后控制台输出如下supervised actor started supervised actor fails now supervised actor stopped supervised actor started [ERROR][05.06.2017 13:34:50][Thread 0003][akka://testSystem/user/supervising-actor/supervised-actor] I failed! Cause: System.Exception: I failed! at Tutorials.Tutorial1.SupervisedActor.OnReceive(Object message) at Akka.Actor.UntypedActor.Receive(Object message) at Akka.Actor.ActorBase.AroundReceive(Receive receive, Object message) at Akka.Actor.ActorCell.ReceiveMessage(Object message) at Akka.Actor.ActorCell.Invoke(Envelope envelope)可以看到Actor 失败后被停止并立即重新启动同时日志中记录了被处理的异常此例即我们抛出的测试异常。这里使用了PreStart()与PostStop()钩子它们正是重启前后默认被调用的钩子因此从 Actor 内部无法区分自己是「第一次启动」还是「重启」。这通常正是你希望的行为——重启的目的是把 Actor 重置到一个已知的良好状态通常意味着一个干净的起始阶段。但真实发生的是系统调用的是PreRestart()和PostRestart()方法如果未被覆写它们默认分别委托给PostStop()和PreStart()。你可以尝试覆写这两个额外方法观察输出如何变化。想立即深入了解监督机制细节的读者推荐参考 Supervision 参考页。五、第一个 Actor作为顶层的 IoT 监督者回顾本部分建立的结论Actor 组织成一棵严格的树每个子级的生命周期绑定于父级父级负责决定故障子级的命运。乍看之下可能并不明显如何把我们的问题映射到这样的树上但实践中这比想象中容易——只需把原先用嵌套盒子表示的架构图改写成一棵树即可用一句话概括每个组件管理其子组件的生命周期任何子组件都不能比父组件活得更久——这正是 Actor 层级的行为。此外组件处理其子组件的故障也是可取的。这两个期望性质共同推出一个结论组件之间的「包含contained-in」关系应当映射为 Actor 的「子级children-of」关系。剩下最后一个问题顶层组件如何映射到 Actor直接为每个主要组件创建顶层 Actor 看似诱人但推荐做法是创建一个显式代表整个应用的组件即 Actor System 中只有一个顶层 Actor各主要组件作为它的子级。第一个 Actor下称IotSupervisor目前相当简单因为我们还没有实现任何组件。它有两个新特点一是弃用Console.WriteLine()改用ILoggingAdapter直接使用 Akka.NET 内置的日志设施二是采用推荐的 Actor 创建模式——在 Actor 类内定义一个静态的Props()方法。完整代码见 IotSupervisor.cspublic class IotSupervisor : UntypedActor { public ILoggingAdapter Log { get; } Context.GetLogger(); protected override void PreStart() Log.Info(IoT Application started); protected override void PostStop() Log.Info(IoT Application stopped); // No need to handle any messages protected override void OnReceive(object message) { } public static Props Props() Akka.Actor.Props.CreateIotSupervisor(); }现在只需用一个带main入口的类把它串起来完整代码见 IotApp.cspublic class IotApp { public static void Init() { using (var system ActorSystem.Create(iot-system)) { // Create top level supervisor var supervisor system.ActorOf(IotSupervisor.Props(), iot-supervisor); // Exit the system after ENTER is pressed Console.ReadLine(); } } }这个应用目前做的事情很少但我们已经拥有了第一个 Actor为后续扩展打下了骨架唯一的顶层 Actoriot-supervisor位于/user/iot-supervisor由用户守卫者监督其生命周期钩子会输出应用级日志。六、后续章节预告在接下来的章节中我们将一步步扩展这个应用创建 Device 的表示层数据模型创建设备管理组件为设备组增加查询能力。至此第一部分「顶层架构」完成理解了三个守卫者、/user前缀路径、Actor 引用的 URL 结构、递归停止与监督重启的默认行为以及「单一顶层 Actor」的推荐组织模式就具备了在 Akka.NET 中搭建任何应用骨架的能力。赞分享后端微服务【免费下载链接】akka.netCanonical actor model implementation for .NET with local distributed actors in C# and F#.项目地址https://gitcode.com/gh_mirrors/ak/akka.net点击查看免费下载相关推荐Loop 免费开源 macOS 窗口管理器指南径向菜单分屏与循环布局Loop 免费开源 macOS 窗口管理器指南径向菜单分屏与循环布局 Loop 是一款免费、开源的 macOS 窗口管理工具兼容 macOS 13 及以上桌面应用Litestar Guards 实战授权守卫、分层声明与 opt 选项机制Litestar Guards 实战授权守卫、分层声明与 opt 选项机制 Guards守卫是 Litestar 框架中专门负责 授权Authoriza后端Web框架Litestream 架构解析SQLite 流式复制的分层设计、WAL 监控与多级压缩机制Litestream 架构解析SQLite 流式复制的分层设计、WAL 监控与多级压缩机制 导读 本文以 skills/litestream/referenc数据库数据同步灾备高可用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

用Markdown与AI高效沟通:结构化提示词与输出控制实战

用Markdown与AI高效沟通:结构化提示词与输出控制实战

如果你把同一件任务,分别用一段连续的大白话和一份结构清晰的 Markdown 文档发给现在的 AI 模型,最终结果的差距往往大得惊人。我在这两年里反复测试过各种写法,可以很负责任地说一句:Markdown 就是现阶段和 AI 打交道时最值得掌握…

2026/10/14 17:31:55 阅读更多 →
从二分类到多分类:逻辑回归实战手写数字识别

从二分类到多分类:逻辑回归实战手写数字识别

1. 从二分类到多分类:逻辑回归的进阶玩法逻辑回归这个名字听起来像是做回归任务的,但它骨子里是个分类器。很多刚入门的朋友第一次听到“逻辑回归”四个字,下意识觉得它跟线性回归是一家人,用来预测房价、温度这种连续值。实际上&…

2026/10/14 17:31:55 阅读更多 →
人最大的愚蠢:沉迷底层社交?识别无效社交、重建高质量社交圈

人最大的愚蠢:沉迷底层社交?识别无效社交、重建高质量社交圈

我们经常听到一句话:"圈子决定命运",很多人把这句话理解成要拼命挤进更高级的圈子,但我看了这么多年的人和事,发现真正的问题根本不是"够不着高处的圈子",而是"陷在底层的社交泥潭里出不来&q…

2026/10/14 17:31:55 阅读更多 →

最新新闻

AI逆向工程实战:31.2K Star开源项目如何用大模型辅助二进制分析

AI逆向工程实战:31.2K Star开源项目如何用大模型辅助二进制分析

1. 当AI把目光投向二进制:逆向工程的门槛正在被重新定义第一次看到“AI开始搞逆向工程”这个说法,我的反应是:终于有人把这件事摆到台面上了。逆向工程这个领域,长期以来都是少数人的游戏——你得懂汇编,得熟悉PE/ELF文…

2026/10/14 23:16:08 阅读更多 →
ARM架构CentOS 7.6网口bond配置实战与优化指南

ARM架构CentOS 7.6网口bond配置实战与优化指南

1. 项目背景与需求拆解1.1 为什么要在ARM架构的CentOS 7.6上做网口bond在ARM服务器上跑CentOS 7.6,这个组合本身就有点意思。ARM架构的服务器这几年在数据中心里越来越常见,尤其是那些对功耗敏感、需要高密度部署的场景。但ARM平台跟传统的x86平台在硬件…

2026/10/14 23:16:08 阅读更多 →
两级式光伏MMC并网系统故障穿越Simulink仿真模型解析

两级式光伏MMC并网系统故障穿越Simulink仿真模型解析

1. 项目概述与问题背景做光伏并网研究的同学,对"故障穿越"这四个字应该都不陌生。无论你研究的是组串式逆变器、集中式逆变器,还是近几年大热的MMC拓扑,只要涉及并网,就绕不开电网故障时"能不能撑住、撑多久、怎么…

2026/10/14 23:16:08 阅读更多 →
AI Agent实操:五个新工具让AI学会用鼠标干活

AI Agent实操:五个新工具让AI学会用鼠标干活

1. 这次AI的进化方向,是先学会用鼠标Copilot能“直接操作”电脑这个事,让我琢磨了好几天。以前我们聊AI,聊的都是大模型能读多少token、能写多长的代码、能生成多像样的图。但那个层面的能力再强,它也是关在对话框里的——你问它答…

2026/10/14 23:16:08 阅读更多 →
OpenCV特征提取与匹配实战:SIFT、ORB、AKAZE选型与调参指南

OpenCV特征提取与匹配实战:SIFT、ORB、AKAZE选型与调参指南

1. 从一张拍歪的照片说起:为什么特征提取值得花时间搞懂很多人第一次接触OpenCV,都是从读图、灰度化、边缘检测这条线走下来的,觉得图像处理不过如此。直到某天需要把两张不同角度拍的同一场景对齐,或者想让程序在一堆商品图里找出…

2026/10/14 23:16:08 阅读更多 →
用Hermes Agent打造AI数字员工:企业自动化实战全解析

用Hermes Agent打造AI数字员工:企业自动化实战全解析

先交代一下背景,这个教程源自我在某企业内部做的一次 AI 自动化试点总结。当时团队的任务很直接,把重复性高、规则清晰但又需要人肉判断的活儿,逐步交给一套能听指挥、能调用工具、能自己干活的系统。市面上轮子很多,最终敲定用 H…

2026/10/14 23:14:55 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →