Akka-Actor模型-解决高并发的终极方案-入门篇(二)
ZealSinger 发布于 阅读:100 技术文档
Akka-actor
actor是Akka的基础构建块,支持Actor模型的所有属性。
Akka actor是实现了状态不共享的,基于异步消息传递机制的Actor模型,且支持一个成熟的错误处理结构
Akka API有意的限制对于actor的访问,actor之间的状态只能通过消息传递来实现,因为无法以同步的方式在actor中调用方法获取,所以在API和时间上,actor之间是解耦状态,Akka中通过一个ActorRef来实现上述机制,每当我们创建一个actor实际上会返回一个ActorRef对象,该对象是对真实actor对象的一个封装,将该actor与其余代码隔离开来,所有actor之间的通信都需要借助ActorRef且ActorRef不提供任何对齐包装的actor的访问权限(从快u是入门的案例中我们也可看到创建的HelloWorld,HelloWorldBot返回的其实都是一个ActorRef对象)
很明显,这里和常规的面向对象思想是不一样的。面向对象思想中,调用者 调用 被调用者需要等待被调用者返回的时候才会继续往下执行,这个过程是一个阻塞的过程。而在Akka-actor中,actor调用东西是通过ActorRef对象进行发送的,并且调用者不会阻塞而是立马执行后面的逻辑,然后 接收端/被调用者方 会一次一条信息地处理接收到的消息,就像调用者和被调用者不在一个线程环境下一样,不会互相影响。也正是因为actor的一次一条信息的处理机制也保证了Akka在单线程模式下可行,在Akka-actor内部,只要不主动去破坏actor的单线程模式,就可以确保只有一个线程将会修改actor的状态
Akka通过ActorRef实现Actor模型的消息驱动,我们也可以利用ActorRef提供的许多方法用于给actor发送消息,一般而言消息都是不可变的case类的形式,虽然我们也可以发送可变的消息(因为没有禁止),但是不推荐这么做,可变消息的存在可能会破坏actor的单线程模式
Akka中可以通过become方法实现actor的行为变化,任何actor都可以调用context.become来为下一个消息提供一个新的行为,也可以使用此技术来改变actor的状态;也可以通过actor中的可变字段和类参数来维护和更改状态
子actor
Akka中的actor存在多种可用的工厂方法来创建子actor。系统中的任何actor都可以访问其父actor和子actor并且可以自由的给他们发送消息,这种父子关系的actor也是Akka支持监听机制的原因,父actor能够自动监督子actor
当一个actor发生故障的时候,调用方是不知情的--因为调用方调用是消息通知机制即异步的过程,发送完调用消息之后,调用方的情况是未知的,调用者可能挂了也可能正常结束,也可能在另外一台机器上,因此被调用者挂的时候返回错误信息给调用者不一定是一个有意义的操作,相反,Akka会将actor的错误信息发送给其监督者actor,因为一般而言监督者actor是启动该actor的actor,监督者actor都存在一个特殊的方法来处理异常,并且通知actor抛出异常以及下一步该如何做(停止,重启,忽略等多种选项)
Akka中的思想就是,允许actor发生简单的崩溃,不会去试图解决这个崩溃而是应该对于崩溃有个正确的处理方式,例如一个actor发生崩溃,我们可以采取一些行动(停止acotr,忽略故障继续处理别的信息,重启actor)去恢复该actor,当重启actor的时候,在对用户透明的情况下将其替换为该actor的新副本,因为是通过ActorRef进行通信的,对于客户端而言是不知情的,因为其指向的还是同一个ActorRef,只是支持该ActorRef的actor已经被副本替换了,这样子相当于客户端和服务端之间通过ActorRef作为中介,错误被隔离在了单个actor的内部而不是整个系统中也不会进一步传播,整个系统的容错性变高
Akka中,actor之间除了常见的点对点传输,还有一个一对多的消息传递机制,即通过一个Event Bus事件总线,Event Bus允许一个actor对齐发布消息,其他actor定于Event Bus从而实现一对多且发送端和接收端进一步解耦
remoting:不同JVM上的actor
Akka让actor的位置变得透明,也就是说当使用actor的引用 来发送消息的时候,实际上不需要关心actor在本JVM上还是在别的远程服务器上
remoting为actor系统提供了通过网络进行通信以及对消息进行序列化和反序列的能力,使得不同JVM上的actor能相互传递,这个功能是利用actor地址的唯一性实现的,actor的地址不仅包括层次结构中的actor的唯一路径,还包括了actor的网络地址,网络地址的加入可以保证我们唯一标识任何系统中的任何actor,即使actor在远端服务器,也可以利用网络地址和actor地址确认到唯一一个远端的actor从而进行通信
其中,序列化机制和通讯协议都是可插拔的,也就意味着我们可以进行多种选择,随着业务的变化可以更换使用更高高效的序列化方式和通讯协议
Akka remoting只能通过配置来添加,当需要和远端actor通讯的时候必须显示的知道其他节点的存在即具备其他actor的网路地址才行,因为Akka remotinig不具备任何服务发现的功能,所以并不适用于大集群环境,发现机制是Akka clustering的功能
Akka clustering:集群成员的自动化管理
对于较大的集群环境, remoting就不适用了:向群组中添加节点必须通知组内所有其他 节点,这会使消息路由变得非常复杂。
为此, Akka 提供了 clustering模块来帮助简化这个过程。 Akka clustering 在 remoting 之上具有额外的功能,使其更容易提供位置透明的特性。 clustering 为在多个 JVM 中运行的 actor 系统提供了彼此交互的能力,使这些actor表现 为同一个actor 系统中的一部分。这些系统形成了单个、内聚的actor系统的集群
如果在remoting的架构下,远程通讯必须依靠编写代码添加配置才能知道远端actor的网络地址,当该网络地址出现问题需要故障恢复的时候更是棘手,因为需要自己(调用方)处理启动一个新的节点并且与整个新节点进行连接
如果在clustering的架构下,只要有种子节点(一个或多个都可以,种子节点是一种特殊指定的节点)存在,新节点的就可以通过(也是只能通过)种子节点加入到集群环境中,任何一个普通节点都可以指定为种子节点,一般推荐多个种子节点而不是一个 ; 那么我们只需要通过配置通知actor系统种子节点的位置,系统启动的时候卉自动与其中一个节点联系
集群中的actor之间虽然能直接相互发送消息,但是实际上集群中的节点管理不是直接基于这个消息机制的,而是另外一个机制,没错,我们的老朋友gossip协议
我们知道gossip协议就是通过一个瘟疫式传播,让所有的节点都能获取整个集群的全貌,但是不存在一个中心节点管理所有的节点信息,即去中心化
当集群发生与生命周期相关的事件时, 会自动发送特定的事件消息一一例如,新节点加 入或现有节点离开集群。使用集群时其实并不需要监听这些事件,但如果想要实现更细 粒度的控制或监视,也可以监听事件
集群Leader
每个集群都有一个Leader,这里的Leader和上面说的去中心化不冲突,因为此Leader不具备其余节点信息的整体化管理的功能,只是负责新节点的加入和节点的退出,也就是当有新节点加入或者旧节点推出的时候需要一个“话事人进行操作“
Akka 中的Leader没必要通过选举获取且Leader也和开发者没有什么特别的关联,所以一个集群中可以没有Leader,没有Leader只是不具备添加/删除节点的功能,其余的功能依旧能很好地进行,只有更改集群的操作将会被延迟(先建立新的Leader之后再被执行)
分布式系统都存在一个潜在的分割问题,即一个节点意外终止了,一般而言只需要其他节点顶替其功能位置即可,但是如果是一组节点突然都断开了呢?即A,B两个网络上的分别都有5个节点,因为AB网络的不可通,导致了对于B而言A全部都挂了,对于A而言B都挂了,但是AB内部的联系都还好好的,两系统分别会在过一段时候剔除对方节点,这样就可能会出现脑裂的问题。
脑裂解决的方案一般如下:
第一是禁止自动下线,即上面所说的”两系统分别会在过一段时候剔除对方节点“,禁止自动下线之后,双方无法自动剔除节点,又因为存在不可用节点双方都无法形成集群,必须人为干预,一般线上也是会要求禁止自动下线的
第二是采用最小规模法,即具备>=n/2+1节点数量的时候才能成为新的集群,否则不允许
Akka中用Lightbend提供了一种智能的脑裂分析器,可以自己分析是否产生了集群分割且采用合适的策略
集群分片
集群分片这个概念在数据库领域应该是常见的,其实就是将单机节点上的大数据量,按照某种划分规则,将其均匀的分散存放到不同的区域中。Akka中的集群分片也是类似的,只是分的不再是数据而actor。Akka中可以分出多个shard region分片区域,每个区域中至少会有一个action
专门用于集群分片环境下的消息也会被包裹上一层特殊的包层,这个包层的内容用于判断该消息需要投放到哪个分片区域中,解析到特定分片区域中的饿所有消息仅仅传递到特定的区域,从而允许相关的actor中包含的状态仅仅存在于特定的区域上
集群单例
所谓的集群单例,就是保证在整个集群环境下有且只有一个特定的actor实例,他在集群中的位置不重要,重要的是只能由一个
Akka提供了一中特殊的方法来确保每个节点都能启动单例,但在一些特殊的情况下,只能有一个节点这样做
如果由于某种原因导致这个actor的单例失效了,则相同节点或者另外一个节点将再次启动并且代替他,尽可能地保证存在单例可用
Akka HTTP
Akka HTTP深入集成了Akka 和 Akka Streams,提供了一种在Akka之上构建HTTP API的方式,也是Akka对外提供的HTPP接口的推荐方法,对外暴露成为一个REST服务端点的方法,构建了RESTful Web服务
TestKit
Akka中提供的测试工具包
异步和并发的测试是非常难的,而TestKit提供了轻松,完整的测试,他解决了”如何反复科卡的测试我们的系统“这个棘手问题,TestKit可以通过两种方式进行测试:
-
创建临时的actor
创建一个临时的actor针对于要测试的范围,允许以完全同步和确定的方式访问actor,这使得actor不会比其他代码难测试。当然,存在一定的不确定性,一个完全同步的和确定的actor和真实的线上环境的情况自然是不能完全画上等号的
-
提供验证actor在非确定性和异步模式下发送及接收消息的方法
通过一个简单的方法来存根(stub)或者伪造actor,同时提供相应的方法来断言某些消息已经在特定的时间段内被接收,而无需指定实际操作发生的先后顺序
Akka Streams
Akka Streams 提供了一个更高级别的 API 来与 actor进行交互,同时提供自动处理“背 压(back pressure)”的机制(我们将在后面详细讨论)。 Streams 提供了一种构建基于 actor 的流和图形的复杂结构,可以使用这些结构的同时结 合我们熟悉的特定领域的语言(DSL) 来处理和转换数据。 这些Streams 遵循 Reactive Streams 标准
消息传递
Akka中指定消息传递应该是actor进行通讯的唯一方式,消息通过邮箱的非阻塞队列传递给其他的actor,actor对象的本身引用不hi被直接使用而是通过ActorRef进行
Akka中有三个消息传递机制
-
tell
发送消息的首选机制就是tell,有时候也称之为”bang“,简写为”!“
tell方法是Acotr模型中经典的”first and forget“消息机制,它既不阻塞也不等待任何响应
-
ask
在使用ask方法的时候需要额外小心,因为ask操作会等待响应返回,而ask过去的消息对方不一定会立即处理,所以导致也就不会立即响应,导致这个可能会导致出现阻塞型代码
-
发布订阅
之前说到的,通过事件总线Event Bus的方式进行actor之间的交互,这个实际上就是一种发布订阅的形式
actor 系统
Akka提出了 actor系统的概念, 即容易实现消息交换的actor群组,无论是在单个JVM 中运行还是跨越多个JVM运行。 虽然不同actor 系统中的 actor也可以进行通信,但这并不常见。
文章标题:Akka-Actor模型-解决高并发的终极方案-入门篇(二)
文章链接:https://zealsinger.xyz/?post=16
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明来自ZealSinger !
如果觉得文章对您有用,请随意打赏。
您的支持是我们继续创作的动力!

微信扫一扫

支付宝扫一扫