写在开始
一般来说有两种策略用来在并发线程中进行通信:共享数据和消息传递。应用共享数据形式的并发编程面临的最大的一个问题就是数据条件竞争。解决各种锁的问题是让人非常头痛的一件事。
传统少数风行的语言并发是基于多线程之间的共享内存,应用同步办法避免写抢夺,Actors应用音讯模型,每个Actor在同一时间解决最多一个音讯,能够发送音讯给其余Actor,保障了独自写准则。从而奇妙防止了多线程写抢夺。和共享数据形式相比,消息传递机制最大的长处就是不会产生数据竞争状态。实现消息传递有两种常见的类型:基于channel(golang为典型代表)的消息传递和基于Actor(erlang为代表)的消息传递。
Actor简介
Actor模型(Actor model)首先是由Carl Hewitt在1973定义, 由Erlang OTP 推广,其 消息传递更加合乎面向对象的原始用意。Actor属于并发组件模型,通过组件形式定义并发编程范式的高级阶段,防止使用者间接接触多线程并发或线程池等根底概念。Actor模型=数据+行为+音讯。
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发送一条音讯是十分快的。
这样的设计次要劣势就是解耦了Actor,数万个Actor并发的运行,每个actor都以本人的步调运行,且发送音讯,接管音讯都不会被阻塞。
隔离
每个Actor的实例都保护这本人的状态,与其余Actor实例处于物理隔离状态,并非像 多线程+锁 模式那样基于共享数据。Actor通过音讯的模式与其余Actor进行通信,与OO式的消息传递形式不同,Actor之间音讯的传递是真正物理上的消息传递。
天生分布式
每个Actor实例的地位通明,无论Actor地址是在本地还是在近程机器上对于代码来说都是一样的。每个Actor的实例十分小,最多几百字节,所以单机几十万的Actor的实例很轻松。如果你写过golang代码,就会发现其实Actor在重量级上很像Goroutine。因为地位透明性,所以Actor零碎能够随便的横向扩大来应答并发,对于调用者来说,调用的Actor的地位就在本地,当然这也得益于Actor零碎弱小的路由零碎。
生命周期
每个Actor实例都有本人的生命周期,就像C# java 中的GC机制一样,对于须要淘汰的Actor,零碎会销毁而后开释内存等资源来保证系统的持续性。其实在Actor零碎中,Actor的销毁齐全能够手动干涉,或者做到零碎自动化销毁。
容错
说到Actor的容错,不得不说还是挺令人意外的。传统的编程形式都是在未来可能出现异常的中央去捕捉异样来保证系统的稳定性,这就是所谓的进攻式编程。然而进攻式编程也有本人的毛病,相似于事实,进攻的一方永远不能100%的进攻住所有未来可能呈现代码缺点的中央。比方在java代码中很多中央充斥着判断变量是否为nil,这些就属于进攻式编码最典型的案例。然而Actor模型的程序并不进行进攻式编程,而是遵循“任其解体”的哲学,让Actor的管理者们来解决这些解体问题。比方一个Actor解体之后,管理者能够抉择创立新的实例或者记录日志。每个Actor的解体或者异样信息都能够反馈到管理者那里,这就保障了Actor零碎在治理每个Actor实例的灵活性。
劣势
天下无完满的语言,框架/模型亦是如此。Actor作为分布式下并发模型的一种,也有其劣势。
- 因为同一类型的Actor对象是扩散在多个宿主之中,所以取多个Actor的汇合是个软肋。比方在电商零碎中,商品作为一类Actor,查问一个商品的列表在少数状况下通过以下过程:首先依据查问条件筛选出一系列商品id,依据商品id别离取商品Actor列表(很可能会产生一个商品搜寻的服务,无论是用es或者其余搜索引擎)。如果量十分大的话,有产生网络风暴的危险(尽管几率十分小)。在实时性要求不是太高的状况下,其实也能够独立进去商品Actor的列表,利用MQ接管商品信息批改的信号来解决数据一致性的问题。
- 在很多状况下基于Actor模型的分布式系统,缓存很有可能是过程内缓存,也就是说每个Actor其实都在过程内保留了本人的状态信息,业内通常把这种服务成为有状态服务。然而每个Actor又有本人的生命周期,会产生问题吗?呵呵,兴许吧。想想一下,还是拿商品作为例子, 如果环境是非Actor并发模型,商品的缓存能够利用LRU策略来淘汰非沉闷的商品缓存,来保障内存不会应用适量,如果是基于Actor模型的过程内缓存呢,每个actor其实就是缓存自身,就不那么容易利用LRU策略来保障内存使用量了,因为Actor的沉闷状态对于你来说是未知的。
- 分布式事物问题,其实这是所有分布式模型都面临的问题,非因为Actor而存在。还是以商品Actor为例,增加一个商品的时候,商品Actor和统计商品的Actor(很多状况下的确被设计为两类Actor服务)须要保障事物的完整性,数据的一致性。在很多的状况下能够就义实时一致性用最终一致性来保障。
- 每个Actor的mailBox有可能会呈现沉积或者满的状况,当这种状况产生,新音讯的解决形式是被摈弃还是期待呢,所以当设计一个Actor零碎的时候mailBox的设计须要留神。
升华一下
- 通过以上介绍,既然Actor对于地位是通明的,任何Actor对于其余Actor就如同在本地一样。基于这个个性咱们能够做很多事件了,以前传统的分布式系统,A服务器如果想和B服务器通信,要么RPC的调用(http调用不太罕用),要么通过MQ零碎。然而在Actor零碎中,服务器之间的通信都变的很优雅了,尽管实质上也属于RPC调用,然而对于编码者来说就如同在调用本地函数一样。其实当初比拟时髦的是Streaming形式。
- 因为Actor零碎的执行模型是单线程,并且异步,所以但凡有资源竞争的相似性能都非常适合Actor模型,比方秒杀流动。
- 基于以上的介绍,Actor模型在设计层面天生就反对了负载平衡,而且对于程度扩容反对的十分好。当然Actor的分布式系统也是须要服务注册核心的。
- 尽管Actor是单线程执行模型,并不意味着每个Actor都须要占用一个线程,其实Actor上执行的工作就像Golang的goroutine一样,齐全能够是一个轻量级的货色,而且一个宿主上所有的Actor能够共享一个线程池,这就保障了在应用起码线程资源的状况下,最大量化业务代码。
更多精彩文章
- 分布式大并发系列
- 架构设计系列
- 趣学算法和数据结构系列
- 设计模式系列