资讯详情

资讯详情

Proto Actor Go 实战指南:基于 protoactor-go 构建高性能分布式 Actor 系统

后端【免费下载链接】protoactor-goProto Actor - Ultra fast distributed actors for Go, C# and Java/Kotlin项目地址https://gitcode.com/gh_mirrors/pr/protoactor-go点击查看免费下载Proto Actor本仓库即其 Go 语言实现protoactor-go是一套面向 Go、C# 与 Java/Kotlin 的跨平台分布式 Actor 框架。本指南以仓库根目录 README.md 为主线系统讲解它的设计理念、从零到一的 Actor 编程范式Hello World、行为切换、生命周期、监督与重启、基于 gRPC 的远程通信Remoting与跨平台互操作并结合 actor 与 remote 等目录下的源码实现帮助读者理解每个 API 背后的运行机制并能在自己的项目中直接落地。一、项目定位与设计理念Proto Actor 由 Akka.NET 的创建者发起核心观点是软件应当被组合composed而不是被重复建造built。在 Akka.NET 中团队曾自建线程池、网络层、序列化与配置系统而 Proto Actor 认为正确的做法是复用成熟技术、只写粘合代码把精力集中在并发与分布式编程本身。README 中给出了四条明确的设计原则极简 APIMinimalistic APIAPI 应当小而易用避免 JVM 式企业级容器与复杂配置。构建在现有技术上Build on existing technologies网络与集群等能力不必自造轮子例如用 gRPC 流做网络传输、用 Consul.IO 做集群发现。传数据不传对象Pass data, not objects序列化是显式关注点不试图隐藏它Protobuf all the way——全程使用 Protobuf。要快Be fast绝不用花哨的 API 魔法换取性能。这四条原则在仓库中都有直接体现序列化相关代码集中在 remote/serializer.go 与 remote/proto_serializer.go网络层是 gRPC 的薄封装remote/server.go而 actor/props.go 中的Props结构以极简的配置项承载了 Actor 的创建方式。README 同时声明Go 实现目前仍处于 beta 阶段已有生产用户在 1.0 之前 API 可能发生变化——读者在跟进上游版本时需要注意兼容性。说明本文以当前仓库模块名github.com/asynkron/protoactor-go见 go.mod为准示例代码即仓库内真实源码。二、构建与测试2.1 环境准备与构建README 给出的构建流程如下基于 GOPATH 时代的写法go get github.com/asynkron/protoactor-go/... cd $GOPATH/src/github.com/asynkron/protoactor-go go get ./... make其中make会触发 Makefile 中定义的 protobuf 生成步骤与项目编译运行后你会得到根据.proto文件生成的 protobuf 定义并完成项目构建。Windows 用户可以使用 Cygwin 来运行make。需要说明的是当前仓库已采用 Go Modulesgo.mod 声明module github.com/asynkron/protoactor-gogo 1.25.3所以现代读者不需要设置$GOPATH直接使用模块化方式即可# 以替换指令引入本地仓库或直接依赖 go get github.com/asynkron/protoactor-go2.2 运行测试README 提供了跳过示例、持久化与调度器目录的测试命令go test go list ./... | grep -v /examples/ | grep -v /persistence | grep -v /scheduler这条命令会执行仓库内所有测试consul 集成测试除外——它们需要真实 consul 环境对应代码位于 cluster/clusterproviders/consul并跳过不含测试的目录。仓库同时提供 buildall.sh 与 modtidy.sh 辅助脚本分别用于全量构建与依赖整理。三、Hello World最小 Actor 程序README 给出的第一个完整示例展现了创建 Actor 系统、定义 Actor、发送消息三个核心步骤type Hello struct{ Who string } type HelloActor struct{} func (state *HelloActor) Receive(context actor.Context) { switch msg : context.Message().(type) { case Hello: fmt.Printf(Hello %v\n, msg.Who) } } func main() { context : actor.EmptyRootContext props : actor.PropsFromProducer(func() actor.Actor { return HelloActor{} }) pid, err : context.Spawn(props) if err ! nil { panic(err) } context.Send(pid, Hello{Who: Roger}) console.ReadLine() }3.1 代码逐行拆解actor.Actor接口定义于 actor/message.go仅含一个方法Receive(c Context)。所有 Actor 都只需实现它这是极简 API原则的直观体现。actor.Context定义于 actor/context.go是 Actor 与运行时交互的窗口聚合了infoPartParent/Self/Actor 信息、messagePart当前消息与消息头、senderPartSend/Request/RequestFuture、spawnerPartSpawn 子 Actor、stopperPartStop/Poison等能力。PropsFromProducer定义于 actor/props_opts.go把生产 Actor 实例的工厂函数与可选配置项绑定成*Props。Propsactor/props.go是 Actor 的全部创建配置producer、mailbox、dispatcher、supervision 策略、各类 middleware 等。Spawn与SendRootContext.Spawnactor/root_context.go会调用默认 spawneractor/props.go 中的defaultSpawner创建 actorContext → 构造 mailbox → 注册 PID 到 ProcessRegistry → 投递startedMessage系统消息并启动 mailbox。Send最终通过pid.sendUserMessage把消息投递到目标 mailbox。仓库中 examples/actor-helloworld/main.go 是这一示例的现代版本使用actor.NewActorSystem()创建系统、system.Root.Spawn(props)派生 Actor并改用slog输出func main() { system : actor.NewActorSystem() props : actor.PropsFromProducer(func() actor.Actor { return helloActor{} }) pid : system.Root.Spawn(props) system.Root.Send(pid, hello{Who: Roger}) _, _ console.ReadLine() }注意README 中的actor.EmptyRootContext在历史版本中用于无系统上下文直接通信当前仓库推荐通过actor.NewActorSystem()创建系统后使用system.Root见 actor/actor_system.go 中NewActorSystem的实现。四、状态机SetBehavior / PushBehavior / PopBehaviorREADME 用行为Behavior切换实现有限状态机。核心思路是Actor 的Receive只是一个分发器真正的处理逻辑由当前行为一个ReceiveFunc决定运行中可以动态切换。type Hello struct{ Who string } type SetBehaviorActor struct{} func (state *SetBehaviorActor) Receive(context actor.Context) { switch msg : context.Message().(type) { case Hello: fmt.Printf(Hello %v\n, msg.Who) context.SetBehavior(state.Other) } } func (state *SetBehaviorActor) Other(context actor.Context) { switch msg : context.Message().(type) { case Hello: fmt.Printf(%v, ey we are now handling messages in another behavior, msg.Who) } } func NewSetBehaviorActor() actor.Actor { return SetBehaviorActor{} } func main() { context : actor.EmptyRootContext props : actor.PropsFromProducer(NewSetBehaviorActor) pid, err : context.Spawn(props) if err ! nil { panic(err) } context.Send(pid, Hello{Who: Roger}) context.Send(pid, Hello{Who: Roger}) console.ReadLine() }运行结果第一条消息仍由Receive里的分支打印Hello Roger随后调用SetBehavior(Other)切换行为第二条消息就会被Other处理输出提示现在由另一个行为处理消息。4.1 底层实现Behavior 类型现代仓库用 actor/behavior.go 中的Behavior类型一组ReceiveFunc的栈来承载这一能力Become(receive)清空栈后压入新行为等价于旧 API 的SetBehavior直接替换当前状态BecomeStacked(receive)压栈对应旧PushBehavior支持可回退的多层状态UnbecomeStacked()弹栈对应旧PopBehavior回到上一层状态。Behavior.Receive会peek栈顶行为并调用它空栈时记录错误日志。使用模式参见 examples/actor-setbehavior/main.goActor 内持有actor.NewBehavior()构造时Become(act.One)设定初始状态Receive仅做state.behavior.Receive(context)转发。这种行为栈非常适合实现状态机例如连接管理可用BecomeStacked进入已认证状态退出时UnbecomeStacked回到初始状态。五、生命周期事件消息而非方法重写与 Akka 用 OOP 方法重写PreStart/PostStop等不同Proto Actor把生命周期事件本身建模为消息Actor 在自己的Receive中像处理普通消息一样处理它们func (state *HelloActor) Receive(context actor.Context) { switch msg : context.Message().(type) { case *actor.Started: fmt.Println(Started, initialize actor here) case *actor.Stopping: fmt.Println(Stopping, actor is about shut down) case *actor.Stopped: fmt.Println(Stopped, actor and its children are stopped) case *actor.Restarting: fmt.Println(Restarting, actor is about restart) case Hello: fmt.Printf(Hello %v\n, msg.Who) } }5.1 四种生命周期消息的定义这些消息定义在 actor/messages.go消息类型触发时机典型用途*actor.StartedActor 创建完成、mailbox 已启动即将接收消息初始化资源、启动定时器*actor.StoppingActor 即将关闭清理外部资源、通知协作者*actor.StoppedActor 及其所有子 Actor 已完全停止最终清理、向 watcher 广播 Terminated*actor.RestartingActor 因故障被监督策略决定重启、新实例替换前保存需要跨重启保留的状态5.2 停止顺序与系统消息通道示例中特意用time.Sleep(1 * time.Second)再调用context.Stop(pid)README 解释了原因Stop 是系统消息system message不经过用户消息 mailbox因此会在任何用户消息之前被处理——睡眠只是为了在控制台展示出正确的事件顺序。这一点在 actor/root_context.go 的Stop实现中得到印证它直接调用pid.ref(actorSystem).Stop(pid)走系统消息通道而Poison则走用户消息通道pid.sendUserMessage(...)因此优雅地等到当前用户消息处理完毕。两者的语义差异非常实用Stop立即停止忽略 mailbox 中剩余用户消息Poison投递毒丸处理完当前及排在其前的消息后再停止配套的StopFuture/PoisonFuture返回Future可用于确认 Actor 已终止。相关示例examples/actor-lifecycleevents/main.go。六、监督与重启故障隔离与恢复Actor 模型的核心价值之一是容错。README 明确了两层监督关系根 Actor由actor.DefaultSupervisionStrategy()监督该策略对故障 Actor总是发出actor.RestartDirective重启指令子 Actor由父 Actor 监督父 Actor 可以通过Props自定义子 Actor 的监督策略。6.1 完整监督示例type Hello struct{ Who string } type ParentActor struct{} func (state *ParentActor) Receive(context actor.Context) { switch msg : context.Message().(type) { case Hello: props : actor.PropsFromProducer(NewChildActor) child : context.Spawn(props) context.Send(child, msg) } } func NewParentActor() actor.Actor { return ParentActor{} } type ChildActor struct{} func (state *ChildActor) Receive(context actor.Context) { switch msg : context.Message().(type) { case *actor.Started: fmt.Println(Starting, initialize actor here) case *actor.Stopping: fmt.Println(Stopping, actor is about shut down) case *actor.Stopped: fmt.Println(Stopped, actor and its children are stopped) case *actor.Restarting: fmt.Println(Restarting, actor is about restart) case Hello: fmt.Printf(Hello %v\n, msg.Who) panic(Ouch) } } func NewChildActor() actor.Actor { return ChildActor{} } func main() { decider : func(reason interface{}) actor.Directive { log.Printf(handling failure for child. reason:%v, reason) // return actor.StopDirective return actor.RestartDirective } supervisor : actor.NewOneForOneStrategy(10, 1000, decider) ctx : actor.NewActorSystem().Root props : actor.PropsFromProducer(NewParentActor).WithSupervisor(supervisor) pid : ctx.Spawn(props) ctx.Send(pid, Hello{Who: Roger}) console.ReadLine() }子 Actor 在处理Hello时panic(Ouch)父 Actor 的decider收到故障原因后返回RestartDirective于是子 Actor 被重启——你会在控制台再次看到Started新实例初始化。仓库中的 examples/actor-supervision/main.go 是同一逻辑的现代写法通过actor.WithSupervisor(supervisor)配置 Props。6.2 指令Directive与策略源码四种监督指令定义在 actor/directive.go指令行为ResumeDirective忽略故障继续处理后续消息Actor 实例不重建RestartDirective丢弃旧实例用新实例替换后继续处理StopDirective停止该 ActorEscalateDirective把故障上报给父监督者处理默认策略定义在 actor/supervision.godefaultSupervisionStrategy NewOneForOneStrategy(10, 10*time.Second, DefaultDecider)DefaultDecider恒返回RestartDirective。NewOneForOneStrategyactor/strategy_one_for_one.go接受三个参数maxNrOfRetries最大重启次数、withinDuration统计窗口时长、decider决策函数。当在窗口内重启次数超过上限时策略会改为停止该子 Actor见shouldStop的实现防止故障风暴。除了 OneForOne仓库还提供 actor/strategy_all_for_one.goAllForOne一个子 Actor 故障时同时重启所有兄弟与 actor/strategy_restarting.go无条件重启策略。每次监督决策都会通过logFailure向EventStream发布SupervisorEventactor/supervision_event.go可被系统订阅用于监控与告警。七、网络与远程通信RemotingProto Actor 的远程通信是gRPC 之上的薄封装消息序列化基于Protocol Buffers天然满足跨平台、协议版本容错、久经考验的稳定性三条诉求。7.1 两个节点的完整示例README 以Node 1 发消息、Node 2 远程派生 Actor 并回包演示了完整链路。Node 1发送方type MyActor struct { count int } func (state *MyActor) Receive(context actor.Context) { switch context.Message().(type) { case *messages.Response: state.count fmt.Println(state.count) } } func main() { remote.Start(localhost:8090) context : actor.EmptyRootContext props : actor.PropsFromProducer(func() actor.Actor { return MyActor{} }) pid, _ : context.Spawn(props) message : messages.Echo{Message: hej, Sender: pid} // this is to spawn remote actor we want to communicate with spawnResponse, _ : remote.SpawnNamed(localhost:8091, myactor, hello, time.Second) // get spawned PID spawnedPID : spawnResponse.Pid for i : 0; i 10; i { context.Send(spawnedPID, message) } console.ReadLine() }Node 2接收方type MyActor struct{} func (*MyActor) Receive(context actor.Context) { switch msg : context.Message().(type) { case *messages.Echo: context.Send(msg.Sender, messages.Response{ SomeValue: result, }) } } func main() { remote.Start(localhost:8091) // register a name for our local actor so that it can be spawned remotely remote.Register(hello, actor.PropsFromProducer(func() actor.Actor { return MyActor{} })) console.ReadLine() }链路说明Node 1 通过remote.SpawnNamed(localhost:8091, myactor, hello, ...)请求 Node 2 的Activator按 kindhello派生一个名为myactor的远程 Actor拿到其 PID 后即可直接Send消息Node 2 的 MyActor 收到Echo后通过context.Send(msg.Sender, ...)把Response回给发送方。msg.Sender正是Echo消息里携带的发送方 PID。7.2 消息契约Message Contracts跨节点的消息必须可序列化README 给出的.proto定义如下syntax proto3; package messages; import actor.proto; // we need to import actor.proto, so our messages can include PIDs // this is the message the actor on node 1 will send to the remote actor on node 2 message Echo { actor.PID Sender 1; // this is the PID the remote actor should reply to string Message 2; } // this is the message the remote actor should reply with message Response { string SomeValue 1; }要点必须import actor.proto消息里才能携带actor.PIDPID 是分布式 Actor 的地址序列化后才能在节点间传递对应仓库中的 actor/actor.proto。生成代码时始终用gogoslick_out而不是go_outREADME 特别强调gogoslick_out会生成序列化所需的类型名gogo protobuf 优化。仓库的 Makefile、updateproto.bat 以及 protobuf/protoc-gen-go-grain 子项目展示了这套 proto 工具链的组织方式。更多完整可运行的远程示例集中在 examples 目录例如 examples/remote-chatserver/main.go展示了remote.NewRemoteConfigureSpawnNamed的现代 API 用法和 examples/remote-activate。7.3 现代 Remoting API 与底层实现README 中的remote.Start(...)/remote.SpawnNamed(...)/remote.Register(...)是历史全局函数。当前仓库把远程能力封装为ActorSystem 的扩展Extension用法参见 examples/remote-chat/server/main.gosystem : actor.NewActorSystem() config : remote.Configure(127.0.0.1, 8080) remoter : remote.NewRemote(system, config) remoter.Start()remote.NewRemote(system, config)remote/server.go创建 Remote 扩展并注册到 ActorSystem 的 Extensionsconfig.Kinds中的预注册 Props 会同步进r.kinds。remoter.Start()remote/server.gonet.Listen监听config.Address()Host:Port把remoteHandler注册为 ProcessRegistry 的地址解析器从此所有带远端地址的 PID 消息都会路由到远程层启动endpointManager创建 gRPC Server 并注册RemotingServer服务。remoter.Register(kind, props)remote/activator_actor.go按 kind 名保存 Props供远端派生时使用。remoter.SpawnNamed(address, name, kind, timeout)remote/activator_actor.go通过SpawnFuture向目标地址的activatorPID 发起ActorPidRequestActivator 收到后在本地context.SpawnNamed(props, Remote$name)派生 Actor 并返回其 PID若 kind 未注册Activator 会 panic 并返回ResponseStatusCodeERROR。gRPC 传输层的配置项remote/config.go 与 remote/config-opts.go包括配置项默认值说明Host/Port由Configure传入监听地址AdvertisedHost空对外宣告的地址用于 NAT/容器场景EndpointWriterBatchSize1000端点写出批量大小EndpointWriterQueueSize1000000端点写出队列容量EndpointManagerBatchSize1000端点管理批量大小EndpointManagerQueueSize1000000端点管理队列容量MaxRetryCount5断线重连最大重试次数Kinds空 map预注册的 Actor 类型map[string]*actor.Props7.4 性能数据README 原始记录README 记录了官方公布的远程性能测试结果仅用两个 Actor 在节点之间每秒传递超过 200 万条消息且保持消息顺序README 称这一成绩是 Scala Akka 基于 UDP 的 Artery 传输的 6 倍、Akka.NET 的 30 倍。下方控制台输出即为该测试node1.exe的真实记录: node1.exe Started EndpointManager Started Activator Starting Proto.Actor server address127.0.0.1:8081 Started EndpointWatcher address127.0.0.1:8080 Started EndpointWriter address127.0.0.1:8080 EndpointWriter connecting address127.0.0.1:8080 EndpointWriter connected address127.0.0.1:8080 2020/06/22 10:45:20 Starting to send 2020/06/22 10:45:20 50000 2020/06/22 10:45:20 100000 2020/06/22 10:45:20 150000 ... snip ... 2020/06/22 10:45:21 900000 2020/06/22 10:45:21 950000 2020/06/22 10:45:21 1000000 2020/06/22 10:45:21 Elapsed 732.9921ms 2020/06/22 10:45:21 Msg per sec 2728542 --日志中可以看到远程层四个核心组件的启动顺序EndpointManager端点管理、Activator远程派生、EndpointWatcher端点监视、EndpointWriter端点写出对应 remote/endpoint_manager.go、remote/activator_actor.go、remote/endpoint_watcher.go、remote/endpoint_writer.go 的实现。注意该数据是官方在特定硬件与网络条件下的历史测试结果实际吞吐受机器配置、消息大小、网络延迟等因素影响请以自建基准测试为准。八、跨平台Go 与 C# 的 Actor 互操作README 将跨平台 Actor 支持Go ↔ C#作为项目的重要卖点Go 仓库即 protoactor-go 本身C# 实现为独立的 asynkron/protoactor-dotnet 仓库。跨平台的价值体现在协议层统一双方共享 Protobuf 消息契约与 gRPC 传输Actor 的 PID 可以在异构系统间直接传递消息契约文件如 actor/actor.proto、remote/remote.proto是两端共用的共同语言。业务可迁移、组织不被绑定README 强调跨平台意味着组织不再被锁定在单一技术栈——例如从 .NET 向 Go 迁移时两边的 Actor 服务仍可相互通信实现渐进式迁移Reinvent by not reinventing。C# 端的集成方式请查阅该独立仓库的文档本仓库侧只需保证消息契约用gogoslick_out生成、消息类型名保持稳定、PID 字段使用actor.PID类型即可。九、为什么选择 Actor 模型README 用一张蝙蝠侠图片引出 Actor 模型的三大价值图片见 resources/batman.jpg解耦的并发Decoupled ConcurrencyActor 之间只通过消息通信不共享可变状态天然规避锁与竞态。默认分布式Distributed by defaultActor 持有 PID 作为地址本地发送与远程发送对业务代码几乎透明从单机扩展到集群无需重写逻辑集群能力见 cluster 目录支持 gossip、grain、pubsub 等。容错Fault tolerance监督树让故障被隔离在局部由策略统一决定恢复 / 重启 / 停止 / 上报见本文第六章。十、示例索引与进一步学习README 指出更多示例位于本仓库的 examples 目录按主题整理如下主题示例路径Hello Worldexamples/actor-helloworld行为切换/状态机examples/actor-setbehavior生命周期事件examples/actor-lifecycleevents监督与重启examples/actor-supervision远程通信聊天examples/remote-chat远程激活examples/remote-activate请求-响应examples/actor-request-response日志examples/actor-logging消息批处理examples/actor-messagebatchOpenTelemetry 追踪examples/actor-opentelemetry集群grain/gossip/pubsubexamples/cluster-grain、examples/cluster-gossip、examples/cluster-pubsub每个示例目录都带有独立的go.mod/go.sum可直接cd进入后go run main.go体验。核心源码按模块组织Actor 运行时在 actor远程在 remote集群在 cluster路由在 router持久化在 persistence。小结从 Hello World 到状态机、从生命周期到监督重启、再到基于 gRPC Protobuf 的远程通信与跨平台互操作Proto Actor Go 用极简 API 构建了一套完整、高性能的分布式 Actor 运行时。结合本仓库的源码与示例开发者可以快速评估它是否适合自己的并发与分布式场景并直接基于 examples 目录中的现成代码起步。赞分享后端【免费下载链接】protoactor-goProto Actor - Ultra fast distributed actors for Go, C# and Java/Kotlin项目地址https://gitcode.com/gh_mirrors/pr/protoactor-go点击查看免费下载相关推荐Proto Actor - Go 分布式高性能 Actor 框架Proto Actor Go 分布式高性能 Actor 框架 项目基础介绍和主要编程语言 Proto Actor 是一个跨平台的分布式 Actor 框架支持后端Proto Actor - 高性能分布式Actor框架Proto Actor 高性能分布式Actor框架 项目基础介绍和主要编程语言 Proto Actor 是一个高性能的分布式Actor框架支持多种编程语言包后端并发编程RPC框架Protoactor-go终极指南5分钟快速掌握Go语言Actor框架构建分布式系统Protoactor go终极指南5分钟快速掌握Go语言Actor框架构建分布式系统 Protoactor go是一个基于Go语言的 分布式Actor框架 后端上一篇如何专业测试显示器刷新率5种方法验证VRR功能的终极指南下一篇MobaXterm密钥生成器3分钟解锁专业版功能的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →