I/O
注:本节未经校验,如有问题欢迎提issue
介绍
akka.io
包是由Akka和spray.io团队协作开发的。它的设计结合了spray-io
模块的经验,并共同进行了改进,使其适应基于actor服务的更加普遍的消费需求。
该 I/O 实现的指导设计目标是要达到极端的可扩展性,要毫不妥协地提供一个API正确匹配底层传输机制,并且是完全的事件驱动、无阻塞和异步。该API命中注定是网络协议实现和构建更高抽象的坚实基础;它不是为终端用户提供的全套的高级别的NIO包装。
术语,概念
I/O API完全是基于actor的,意味着所有的操作实现都是通过消息传递而不是直接方法调用。每个 I/O 驱动程序 (TCP、 UDP) 有一个特殊的actor,被称为一个管理器,用作 API 的入口点。I/O 被分成几个驱动程序。用于某个特定驱动程序的管理器是通过 IO
入口点获取的。例如下面的代码查找 TCP 管理器,并返回其 ActorRef
:
import akka.io.{ IO, Tcp }
import context.system // implicitly used by IO(Tcp)
val manager = IO(Tcp)
管理器接收 I/O 命令消息并实例化工作actor作为回应。工作actor将自身返回给 API 用户作为发送该命令的答复。例如给TCP 管理器发送Connect
命令后,管理器创建了代表 TCP 连接的actor。当该actor通过发送一个Connected
消息宣布自身后,所有与给定 TCP 连接相关的操作都可以通过发送消息到连接actor来调用。
DeathWatch和资源管理
I/O 工作actor接收命令,并且也发出事件。它们通常需要一个用户端对应的actor监听这些事件 (此类事件可以是入站的连接,传入的字节或写操作确认)。这些工作actor观察它们的对应监听。如果监听器停止工作,则工作actor将自动释放它所拥有的任何资源。这种设计使得该 API 更能抵抗资源泄漏。
多亏I/O API是完全基于actor设计的,相反的方向也可以工作:一个负责处理连接的用户actor可以观察连接actor,如果它意外终止也将收到通知。
写模型(Ack, Nack)
I/O设备有一个最大吞吐量来限制写操作的频率和大小。当一个应用程序试图推相比设备处理能力更多的数据时,驱动程序不得不缓冲字节,直到设备能够继续写他们。缓冲可以处理短暂的密集写入——但没有缓冲区是无限的。这时需要"流控制"来避免设备缓冲区不足的问题。
Akka支持两种类型的流量控制:
基于Ack,当写操作成功的时候,驱动程序通知写者。
基于Nack,当写操作失败时,驱动程序会通知写者。
每一种模型在Akka I/O的 TCP 和 UDP 实现中都可用。
单独的写操作可以通过在写入消息(TCP中的Write
和UDP中的Send
)中提供一个 ack 对象来确认。写操作完成时工作者将发送 ack 对象给写actor。这可以用于实现基于Ack的流量控制;只有当老数据被确认时才发送新数据。
如果写入(或任何其他命令)失败,驱动程序会发送具有该命令一个特殊消息(UDP 和 TCP中是CommandFailed
)来通知actor。此消息也会通知写者一个失败,作为那个写的一个nack。请注意,在基于nack 的流控制设置中,写者必须准备到,失败的写操作可能不是最近写操作的事实。例如,对于W1
的写入失败通知可能在后来的写命令 W2
和W3
被发送之后到达。如果写者想要重发送任何nack消息,它可能需要保留一个挂起消息的缓冲区。
警告
一个确认的写并不意味着确认送达或存储的;收到一个写ack只是表明I/O 驱动程序成功处理了写操作。这里描述的 Ack/Nack 协议是一种流量控制手段而不是错误处理。换句话说,数据仍然可能会丢失,即使每一个写操作都被确认。
ByteString
为了保持隔离,actor应该只通过不可变对象沟通。ByteString
是bytes的不可的容器。它被用在Akka I/O系统中,作为在jvm上处理IO的传统字节容器,如Array[Byte]
和ByteBuffer
的一种高效的、 不可变的替代者。
ByteString 是一个绳状)数据结构,它不可变且提供了高效地连接和切片操作(完美的 I/O). 当两个ByteString
被连接在一起时,是将两者都保存在结果ByteString
内部而不是将它们复制到新的Array
中. 像 drop
和 take
这种操作返回的 ByteString
仍引用之前的 Array
, 只是改变了外部可见的offset和length。我们花了很大力气保证内部的 Array
不能被修改. 每次当不安全的 Array
被用于创建新的 ByteString
时,会创建一个防御性拷贝。如果你需要一个ByteString
为其内容占用尽可能少的内存,使用 compact
方法来获取一个 CompactByteString
实例。如果 ByteString
表示只是原始数组中的一片,这将导致复制在片中的所有字节。
ByteString
从IndexedSeq
继承了所有方法,它也有一些新的方法。更多信息请参考 akka.util.ByteString
类和其伴生对象的 ScalaDoc。
ByteString
还带有它自己优化类——ByteStringBuilder
和 ByteIterator
,在普通生成器和迭代器之外提供额外的功能。
与 java.io 的兼容性
ByteStringBuilder
可以通过 asOutputStream
方法包装为一个 java.io.OutputStream
。同样,ByteIterator
可以通过 asInputStream
包装为一个java.io.InputStream
。使用这些,akka.io
应用程序可以集成基于 java.io
流的遗留代码。
深入体系结构
关于内部体系结构设计有关的详细信息请参阅I/O 层设计。