Actor模型是解决高并发的终极解决方案

写在开始

通常来讲有两种策略用来在并发线程中进行通讯:共享数据和消息传递。使用共享数据方式的并发编程面临的最大的一个问题就是数据条件竞争。处理各类锁的问题是让人十分头痛的一件事。java

   传统多数流行的语言并发是基于多线程之间的共享内存,使用同步方法防止写争夺,Actors使用消息模型,每一个Actor在同一时间处理最多一个消息,能够发送消息给其余Actor,保证了单独写原则。从而巧妙避免了多线程写争夺。和共享数据方式相比,消息传递机制最大的优势就是不会产生数据竞争状态。实现消息传递有两种常见的类型:基于channel(golang为典型表明)的消息传递和基于Actor(erlang为表明)的消息传递。golang

Actor简介

Actor模型(Actor model)首先是由Carl Hewitt在1973定义, 由Erlang OTP 推广,其 消息传递更加符合面向对象的原始意图。Actor属于并发组件模型,经过组件方式定义并发编程范式的高级阶段,避免使用者直接接触多线程并发或线程池等基础概念。

Actor模型=数据+行为+消息。redis

Actor模型是一个通用的并发编程模型,而非某个语言或框架全部,几乎能够用在任何一门编程语言中,其中最典型的是erlang,在语言层面就提供了Actor模型的支持,杀手锏应用RabbitMQ 就是基于erlang开发的。编程

更加面向对象

Actor相似面向对象编程(OO)中的对象,每一个Actor实例封装了本身相关的状态,而且和其余Actor处于物理隔离状态。举个游戏玩家的例子,每一个玩家在Actor系统中是Player 这个Actor的一个实例,每一个player都有本身的属性,好比Id,昵称,攻击力等,体现到代码级别其实和咱们OO的代码并没有多大区别,在系统内存级别也是出现了多个OO的实例缓存

class PlayerActor
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

无锁

在使用Java/C# 等语言进行并发编程时须要特别的关注锁和内存原子性等一系列线程问题,而Actor模型内部的状态由它本身维护即它内部数据只能由它本身修改(经过消息传递来进行状态修改),因此使用Actors模型进行并发编程能够很好地避免这些问题。Actor内部是以单线程的模式来执行的,相似于redis,因此Actor彻底能够实现分布式锁相似的应用。服务器

异步

每一个Actor都有一个专用的MailBox来接收消息,这也是Actor实现异步的基础。当一个Actor实例向另一个Actor发消息的时候,并不是直接调用Actor的方法,而是把消息传递到对应的MailBox里,就好像邮递员,并非把邮件直接送到收信人手里,而是放进每家的邮箱,这样邮递员就能够快速的进行下一项工做。因此在Actor系统里,Actor发送一条消息是很是快的。网络

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWwNZ5t0-1570964270753)(https://timgsa.baidu.com/timg...]多线程

这样的设计主要优点就是解耦了Actor,数万个Actor并发的运行,每一个actor都以本身的步调运行,且发送消息,接收消息都不会被阻塞。架构

隔离

每一个Actor的实例都维护这本身的状态,与其余Actor实例处于物理隔离状态,并不是像 多线程+锁 模式那样基于共享数据。Actor经过消息的模式与其余Actor进行通讯,与OO式的消息传递方式不一样,Actor之间消息的传递是真正物理上的消息传递。并发

天生分布式

每一个Actor实例的位置透明,不管Actor地址是在本地仍是在远程机器上对于代码来讲都是同样的。每一个Actor的实例很是小,最多几百字节,因此单机几十万的Actor的实例很轻松。若是你写过golang代码,就会发现其实Actor在重量级上很像Goroutine。因为位置透明性,因此Actor系统能够随意的横向扩展来应对并发,对于调用者来讲,调用的Actor的位置就在本地,固然这也得益于Actor系统强大的路由系统。
image

生命周期

每一个Actor实例都有本身的生命周期,就像C# java 中的GC机制同样,对于须要淘汰的Actor,系统会销毁而后释放内存等资源来保证系统的持续性。其实在Actor系统中,Actor的销毁彻底能够手动干预,或者作到系统自动化销毁。

容错

说到Actor的容错,不得不说仍是挺使人意外的。传统的编程方式都是在未来可能出现异常的地方去捕获异常来保证系统的稳定性,这就是所谓的防护式编程。可是防护式编程也有本身的缺点,相似于现实,防护的一方永远不能100%的防护住全部未来可能出现代码缺陷的地方。好比在java代码中不少地方充斥着判断变量是否为nil,这些就属于防护式编码最典型的案例。可是Actor模型的程序并不进行防护式编程,而是遵循“任其崩溃”的哲学,让Actor的管理者们来处理这些崩溃问题。好比一个Actor崩溃以后,管理者能够选择建立新的实例或者记录日志。每一个Actor的崩溃或者异常信息均可以反馈到管理者那里,这就保证了Actor系统在管理每一个Actor实例的灵活性。

劣势

天下无完美的语言,框架/模型亦是如此。Actor做为分布式下并发模型的一种,也有其劣势。

  1. 因为同一类型的Actor对象是分散在多个宿主之中,因此取多个Actor的集合是个软肋。好比在电商系统中,商品做为一类Actor,查询一个商品的列表在多数状况下通过如下过程:首先根据查询条件筛选出一系列商品id,根据商品id分别取商品Actor列表(极可能会产生一个商品搜索的服务,不管是用es或者其余搜索引擎)。若是量很是大的话,有产生网络风暴的危险(虽然概率很是小)。在实时性要求不是过高的状况下,其实也能够独立出来商品Actor的列表,利用MQ接收商品信息修改的信号来处理数据一致性的问题。
  2. 在不少状况下基于Actor模型的分布式系统,缓存颇有多是进程内缓存,也就是说每一个Actor其实都在进程内保存了本身的状态信息,业内一般把这种服务成为有状态服务。可是每一个Actor又有本身的生命周期,会产生问题吗?呵呵,也许吧。想一想一下,仍是拿商品做为例子, 若是环境是非Actor并发模型,商品的缓存能够利用LRU策略来淘汰非活跃的商品缓存,来保证内存不会使用过量,若是是基于Actor模型的进程内缓存呢,每一个actor其实就是缓存自己,就不那么容易利用LRU策略来保证内存使用量了,由于Actor的活跃状态对于你来讲是未知的。
  3. 分布式事物问题,其实这是全部分布式模型都面临的问题,非因为Actor而存在。仍是以商品Actor为例,添加一个商品的时候,商品Actor和统计商品的Actor(不少状况下确实被设计为两类Actor服务)须要保证事物的完整性,数据的一致性。在不少的状况下能够牺牲实时一致性用最终一致性来保证。
  4. 每一个Actor的mailBox有可能会出现堆积或者满的状况,当这种状况发生,新消息的处理方式是被抛弃仍是等待呢,因此当设计一个Actor系统的时候mailBox的设计须要注意。

升华一下

  1. 经过以上介绍,既然Actor对于位置是透明的,任何Actor对于其余Actor就好像在本地同样。基于这个特性咱们能够作不少事情了,之前传统的分布式系统,A服务器若是想和B服务器通讯,要么RPC的调用(http调用不太经常使用),要么经过MQ系统。可是在Actor系统中,服务器之间的通讯都变的很优雅了,虽然本质上也属于RPC调用,可是对于编码者来讲就好像在调用本地函数同样。其实如今比较时兴的是Streaming方式。
  2. 因为Actor系统的执行模型是单线程,而且异步,因此凡有资源竞争的相似功能都很是适合Actor模型,好比秒杀活动。
  3. 基于以上的介绍,Actor模型在设计层面天生就支持了负载均衡,并且对于水平扩容支持的很是好。固然Actor的分布式系统也是须要服务注册中心的。
  4. 虽然Actor是单线程执行模型,并不意味着每一个Actor都须要占用一个线程,其实Actor上执行的任务就像Golang的goroutine同样,彻底能够是一个轻量级的东西,并且一个宿主上全部的Actor能够共享一个线程池,这就保证了在使用最少线程资源的状况下,最大量化业务代码。

搜索公众号:架构师修行之路,领取福利,获取更多精彩内容
相关文章
相关标签/搜索