后端微服务【免费下载链接】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),仅供参考