• Actors 基于消息驱动的异步编程模型


    Actors 是一种异步编程模式,它的提出主要是为了解决 “多线程,MTA” 编程带来的复杂性、困难度。

    但这些都不是 Actors 本身提供的最大优点,它真正的价值是易于解决多线程处理,容易导致共享资源(share resources)之间产生竞争,故而导致 deadlock(死锁)。

    一个软件工程项目里面并非所有的开发人员都经历过严格的 “多核编程” 数年摧残,纵然受过多年毒打的开发人员有时也容易写出 deadlock(死锁)的疑难问题,往往经验及技术越强的人搞出死锁就越难解决。

    设计模式及编程模型,提出目的都是为了让代码写起来更简单、更易于阅读、后期工程维护,但不意味着设计模式及编程模型的应用会让代码执行的效率更高,或许是更低也说不一定。

    有时候不要过于注重形式,没有意义,可以解决人们迫切问题的设计模式及编程模型才是有价值与意义的,个人多年经验之谈。

    Actors 基于消息驱动的异步编程模型,本文在标题上就已经明确提出了 Actors 异步编程模型的核心要点:“基于消息驱动”

    每个线程可以运行一个或多个 Actors 消息驱动器,有些类似 Windows 窗体编程中的消息队列(WndProc),当某个消息到达,人们对感兴趣的消息逐个进行处理。

    Actors 异步编程模型里面要求,所有的线程或进程之间都通过消息的方式进行合作,所以采用 Actors 异步编程模型的结构大约如下图所示:

     上图为简化的理解图,Actors 是一个消息生产 + 消费的驱动模型,设A与B两个端点,A为生产Actors Action,B为消费处理 Actors Action 端点

    A与B之间传递消息(Actor Action)的传输层介质,可以是进程内存、共享内存、Socket套接字、Pipe 匿名/命名管道,Actor 本身不限制消息传递的介质层。

    Actors 模型本身不解决传输介质层出现故障而导致的 Actor Actions 消息丢失的问题,例如:A节点生产一个消息经Socket套接字推送到B节点,但Socket套接字发生故障导致该生产的消息被丢失。

    如何解决此问题?对于异步编程而言,一些行为并不需要ACK确认已经处理该消息,但如果确定需要对方处理该消息,则需要实现一种名为 “停等ARQ”【停等超时自动重传】ACK确认的机制以确保消息被传递到目标节点上被处理,但这属于传输介质层设计时需要考虑的东西,与 Actors 模型并没有任何关系。

    所以,人们从此处不需要参考 Actors 更多博客/书籍/文献就可以自行推导出,Actors 从设计之初就是为了解决多线程编程的一些问题,而不是为了分布式而设计的,只是发展到分布式的时代仍然有大量的解决方案工程采用 Actors 异步编程编程模型而已。

    那么什么是本文提出的 “Actors Action”?

    比如A节点向B节点 Publish(生产)一个 Actor Action,那么它至少携带以下信息:

    1、Action ID(动作ID)

    2、进阶:Sequence ID(序列ID)用于ACK确认,注意:它与 Actor 模型本身无关

    3、Action 载荷的数据模型(贫血模型)

    4、Origin(来源信息若不需要应答则不需要)

    B节点从消息队列中 Peek 弹出顶部入列消息(FIFO先入先出原则),调度派发器则基于 Action ID 来派发消息到对应的 Actor Action Handler(Actor 动作处理器)来消费处理该动作的行为。

    那么 Action 载荷的数据模型,如何在调度派发器内解决呢?

    很容易:

    C# 工程语言可以编写工程内消息模型的序列化算法,根据ID来映射对应的 “贫血数据模型”,管理的方法有两种:

    1、基于反射检索元数据的方式来自动构建及建立映射,例如在数据模型头上声明特性,静态标记:Action ID。

    2、手动注册 Action ID 跟数据模型的映射关系

    C/C++ 工程语言可以编写工程内消息模型的静态序列化算法,如采用静态编译的序列化 google protobuf,解决方案仍是手动注册 Action ID 跟数据模型之间的映射关系。

    当我们为 Action 增加进阶的序列ID,那么则可以实现 “停等ARQ” 的机制,那么也为RPC远过程调用的实现提供可能性,如果我们在 C/C++ 语言中,最好实现的编程模型为APM(异步编程模型)

    例如:

    修改玩家状态

    参考一:ChangePlayerStatusAsync( args..., lambda... )

    参考二:BeginChangePlayerStatus( args...., [...](args...) {

       auto result = EndChangePlayerStatus(...);

       // TO:DO Your are code here.

    })

    但当我们在 Actors 异步编程模型的基础上,增加了RPC远过程调用,那么则可能带来一个全新的潜在问题,“deadlock” 逻辑层的死锁,如:A远过程调用B,B远过程调用A,调用上下层级关系不明确,或许大多数开发人员皆曾犯过该错误。

    注意:此类错误跟 Actors 异步编程模式并没有直接关系,将其归纳于 Actors 异步编程模式本身的缺点,理解上有问题的。

    Actors 异步编程模型总结:

    重点(一):

    Actors 异步编程模式,本身是没有多线程下的安全问题的,原因很明显,它是只是把消息生产并推送到消息消费者的消息队列,这个过程甚至不需要加锁(临界区)

    这取决于底层传输媒介,若同个进程内存传递的需要上锁,消息队列本身并非必是线程安全,例如:C/C++ 工程语言常常适用STL标准库中的 std::queue、std::list 泛型模板BCL基础类库。

    重点(二):

    Actors 异步编程模式,本身不提供停等ARQ,消息确认机制,因为实现类似的机制导致的逻辑死锁是开发人员的问题,不是 Actors 模式本身的缺点,扣锅还是的要点脸,没有的东西不要给别人加头上。

    重点(三):

    Actors 异步编程模式,是最初是用于解决并缓解 “单进程多线程” 内共享资源(share-resource)竞争导致的死锁、内存安全、多核编程编码复杂度的问题。

    例如:

    A线程访问共享资源竞争锁,B线程访问共享资源竞争到锁,但A线程访问另外一个共享资源竞争到锁,B线程尝试访问另外一个共享资源竞争锁,那么就会发生相互竞争死锁现象。

    当人采用:

    Actors 模式时,我们将该共享资源按需要自行划分挂在到一个具体的 Actors 调度器负责驱动处理,当其它线程/进程需要访问该共享资源时,交付请求到目的 Actors 调度器上负责的 Actors Action Handler 进行处理,那么操作该共享的资源都在单个工作线程上,所以不需要额外的增加互斥锁。

    Actors 模型系统执行非常高效,但其效能表现仍无法比拟多线程内存之间相互访问的效率,但并非绝对的,某些情况下 Actors 模型系统的效能表现优于多线程模型的系统,当然不说大家也明白不是,这么对比没有意义。

    重点(四):

    Actors 异步编程模式,基于消息驱动,势必会带来大量碎片化的消息报文数据,这必定造成严重内存碎片问题,致内存分配的效率下跌,C/C++ 开发人员应注意对内存碎片问题进行优化处理,由更先进的 “现代工业高级编程语言” 特性来解决,个人建议人们适用:“Microsoft C# by dotNET.”

    C/C++ 可以采用以下几类解决方案:

    1、定长/对齐数据报文,尽量避免随机颗粒化分配

    2、分配固定大小区块,划分若干个小区块,每个小区块缓存一个消息

    3、自行实现碎片整理(移动内存)

    4、适用开源内存池,掩耳盗铃把问题丢向内存池(例:jemalloc)

    重点(五):

    Actors 异步编程模式驱动的执行单元是一个线程(线程是程序执行最小单元),如果单个线程负载过多性能会成为瓶颈的,但这并不算 Actors 模式本身的缺点,如果需要承担的负载过重,开发人员应当思考如何优化并解决

    例如:更加细化的拆分非本职业务到其它的 Actors 的驱动器/线程进行负责,而不是说这就是 Actors 模式本身的缺点,这不是一个好的解决问题的态度。

    如果学过 “领域驱动模型” 的童靴们,应该很明白大多数某个具体 Actors 产生性能瓶颈,均是上层模型领域职责过于宽泛,负责的事务远远超出它本身应当承担的事务导致的,但凡是不是绝对的,对多数软件工程项目而言,它却是问题的本质原因。

    是的为每个模块/接口的粒度划分存在一些问题,就像人们在进行多线程(多核)编程时,需要注重锁(lock)的粒度问题,如果人们粒度太大那么多线程系统的效率可能还不如单线程的效能,如果粒度太小,编程复杂性就会直线提高,因为通常锁粒度应用越小越容易遇到死锁的问题,但相对的代码效能通常就越高(指综合并发效能指数,对硬件负载及利用率越高)。

  • 相关阅读:
    【python VS vba(系列2)】 python和vba读写EXCEL文件的方式比较 (建设ing)
    找不到d3dx9_37.dll,无法继续执行代码
    echarts使用custom类型绘制矩形
    Perl 语言开发(十二):面向对象编程,深入理解与实践
    css过渡属性的简单使用
    Android入门第24天-Adapter使用初步-最简单的一个Adapter的使用
    Maple希腊字母按键查表
    Toit faible vieil.Saluer rompre bientôt éclat.Носок очутиться коллектив болото.
    【Nuxt】04 Nuxt2-SEO: sitemap.xml、seo优化、robots.txt
    python-爬虫-requests
  • 原文地址:https://blog.csdn.net/liulilittle/article/details/127111941