SlideShare uma empresa Scribd logo
1 de 34
Akka HTTP
安田裕介
@TanUkkii007
2016/3/4
Akka Stream & HTTP
リリースおめでとう!
ノンブロッキングで背圧制御に満ちたエコシステム
形成の幕開けになることを期待します
Akka HTTPとは
• akka IOとAkka Streamを使って、NIOかつ背圧制御
に基づいたHTTPモジュール
• 低レベルAPIのakka-http-coreと高レベルAPIのakka-
http-experimentalがある
• Akka Streamがシステム内の調和を保つとしたら、
Akka HTTPはシステム間の調和を実現する
マイクロサービスの難しさ
“every single one of your peer teams suddenly becomes a
potential DOS attacker”
周りの全ての同僚チームが、突如 DOS アタッカーになりうる
ようになった
和訳原文
Stevey の “Google プラットフォームぶっちゃけ話”にでてくる
Amazonのマイクロサービス化によるAWS誕生のくだり
システム間のインターフェースとしてもっとも使われるHTTPに
背圧制御をもたらす必要がある
アジェンダ
• Akka Streamの復習
• Akka HTTP API
• HTTPレイヤーとTCPレイヤーとの接合
• TCPレイヤーの内部構造
Akka Streamの復習
val intSource: Source[Int, NotUsed] = Source(List(1, 2, 3)) //Sourceは1つの出力をもつ。Sourceなどをグラフという。
val toStringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(_.toString) //Flowは1つの入力と1つの出力をもつ。mapなどをステージという
。
val seqSink: Sink[String, Future[Seq[String]]] = Sink.seq[String] //Sinkは1つの入力をもつ。第2型引数がMaterialized Value。
/**
* +-----------------------------------------------------------------------------------+
* | runnableGraph |
* | |
* | +------------+ +--------------+ +---------+ |
* | | | | | | | |
* | | intSource ~ Int ~> ~ Int ~> toStringFlow ~ String ~> ~ String ~> seqSink | |
* | | | | | | | |
* | +------------+ +--------------+ +---------+ |
* +-----------------------------------------------------------------------------------+
*/
// すべてのポートが閉じたグラフはRunnableGraph[Mat]になり、マテリアライズ化が可能になる
val runnableGraph: RunnableGraph[Future[Seq[String]]] =
intSource.via(toStringFlow).toMat(seqSink)((sourceMatValue, sinkMatValue) => sinkMatValue)
// RunnableGraphのrunを呼んでマテリアライズ化する。ここからストリームが動き出す。マテリアライズ化の結果としてMaterialized Valueが
返る。
val materializedValue: Future[Seq[String]] = runnableGraph.run()(ActorMaterializer())
// Materialized Valueからストリームの結果を受け取れる場合がある
val streamResult: Seq[String] = Await.result(materializedValue, 10 seconds) //Seq("1", "2", "3")
Akka Streamの復習
intSource.via(toStringFlow).toMat(seqSink)((sourceMatValue, sinkMatValue) => sinkMatValue).run()
すべては下流から始まる
Source Flow Sink
pull(in)
pull(in)
onPull
onPull
push(out, 1)
onPush
push(out, “1”)
pull(in)
pull(in)
onPull
push(out, 2) onPush
※アクターモデルとの最大の違い
onPush
onPull
Akka HTTP
クライアントサイドAPI
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] =
Http().outgoingConnection(host, port) //単一のHTTPコネクションストリーム
val responseFuture1: Future[HttpResponse] =
Source.single(HttpRequest(uri = "/"))
.via(connectionFlow)
.runWith(Sink.head)
val poolClientFlow: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), HostConnectionPool] =
Http().cachedHostConnectionPool[Int](host, port) //ホスト単位でコネクションプールをもつストリーム
val responseFuture2: Future[(Try[HttpResponse], Int)] =
Source.single(HttpRequest(uri = "/") -> 1)
.via(poolClientFlow)
.runWith(Sink.head)
サーバーサイドAPI
val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port) //bindでコネクション
のSourceを得る
val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection =>
val connFlow: Flow[HttpResponse, HttpRequest, NotUsed] = connection.flow //それぞれのコネクションのデータの送受信を表す
フロー
val requestHandler: Flow[HttpRequest, HttpResponse, NotUsed] = Flow[HttpRequest].map {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>"))
}
/**
* +----------+ +----------------+
* | | ~HttpRequest~> | |
* | connFlow | | requestHandler |
* | | <~HttpResponse~ | |
* +----------+ +----------------+
*/
connFlow.joinMat(requestHandler)(Keep.right).run() //コネクションのフローにリクエストハンドラーを接続してリクエストを消
費する
}).run()
HttpRequest, HttpResponse
final case class HttpRequest(method: HttpMethod,
uri: Uri,
headers: immutable.Seq[HttpHeader],
entity: RequestEntity,
protocol: HttpProtocol)
sealed trait RequestEntity extends HttpEntity
final case class HttpResponse(status: StatusCode,
headers: immutable.Seq[HttpHeader],
entity: ResponseEntity,
protocol: HttpProtocol)
sealed trait ResponseEntity extends HttpEntity
sealed trait HttpEntity {
def dataBytes: Source[ByteString, Any]
}
なんとHttpEntityの中身はSource[ByteString, Any]だ
これが効率的なデータの送受信と
WebsoketやSSEなどの異なるプロトコルを統一的に扱うことを可能にしている
• Futureなのでコネクションプールの数を超えて並列に呼ぶことができる
• IOの方がCPUより遅いのでコネクションが足りなくなる
• NIOなのでスレッドプールのスレッド数=コネクション数にしても解決しない
リソース間調整
import akka.io.IO
import spray.can.Http
import spray.client.pipelining._
trait RequestProvider { this: Actor =>
import context.system
import context.dispatcher
lazy val pipeline = {
sendReceive(IO(Http)(system)) ~> unmarshal[String]
}
def request(path: String): Future[String] = pipeline(Get(s"$requestUrl/$path"))
}
スレッドプールとコネクションプール
val safeRequest: Flow[String, String, NotUsed] = Flow[String].mapAsync(maxConnection)(request)
Akka StreamのmapAsync(parallelism)(asyncFunction)を使えば
parallelism以上にasyncFunctionが呼ばれることはない
Akka HTTPでもコネクションプールをもつクライアントは
mapAsyncで制御している
Spray clientの例
HTTPレイヤーとTCPレイヤーとの接合
TCPコネクションにHTTPのセマンティクスをのせる
val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new
InetSocketAddress(host, port)) //TCPコネクションのステージ
val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo()
//TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。
val outgoingTlsConnectionLayer: Flow[SslTlsOutbound, SessionBytes, Future[Http.OutgoingConnection]] =
tlsStage.joinMat(transportFlow) { (_, tcpConnFuture: Future[Tcp.OutgoingConnection]) =>
tcpConnFuture map { tcpConn => Http.OutgoingConnection(tcpConn.localAddress, tcpConn.remoteAddress) }
//TCPコネクションステージにTLSステージを接続する。Materialized ValueをTcp.OutgoingConnectionから
Http.OutgoingConnectionに変換する。
}
val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] = Http().clientLayer(Host(host,
port))
//HttpRequest -> SslTlsOutbound、SslTlsInbound -> HttpResponseへの変換ステージ
val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続
クライアント編
val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new
InetSocketAddress(host, port)) //TCPコネクションのステージ
val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo()
//TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。
/**
* +------------------------------------------------+
* | outgoingTlsConnectionLayer |
* | |
* | +----------+ +---------------+ |
* SslTlsOutbound ~~> | ~ByteString~> | | |
* | | tlsStage | | transportFlow | |
* SessionBytes <~~ | <~ByteString~ | | |
* | +----------+ +---------------+ |
* +------------------------------------------------+
*/
val outgoingTlsConnectionLayer: Flow[SslTlsOutbound, SessionBytes, Future[Http.OutgoingConnection]] =
tlsStage.joinMat(transportFlow) { (_, tcpConnFuture: Future[Tcp.OutgoingConnection]) =>
tcpConnFuture map { tcpConn => Http.OutgoingConnection(tcpConn.localAddress, tcpConn.remoteAddress) }
//TCPコネクションステージにTLSステージを接続する。Materialized ValueをTcp.OutgoingConnectionから
Http.OutgoingConnectionに変換する。
}
TCPコネクションにHTTPのセマンティクスをのせる
① TCPコネクションにTLSの解釈を接続する
val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] =
Http().clientLayer(Host(host, port))
//HttpRequest -> SslTlsOutbound、SslTlsInbound -> HttpResponseへの変換ステージ
/**
* +-------------------------------------------------------------------+
* | outgoingConnection |
* | |
* | +-------------+ +----------------------------+ |
* HttpRequest ~~> | ~SslTlsOutbound~> | | |
* | | clientLayer | | outgoingTlsConnectionLayer | |
* HttpResponse <~~ | <~SslTlsInbound~ | | |
* | +-------------+ +----------------------------+ |
* +-------------------------------------------------------------------+
*/
val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続
TCPコネクションにHTTPのセマンティクスをのせる
② HTTPのセマンティクスを解釈する
val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right)
//TCP/TLSステージとHTTPステージを接続
Source.single(httpRequest).via(outgoingConnection).toMat(Sink.head)(Keep.right).run()
Source.single(httpRequest).via(Http().outgoingConnection(host, port)).toMat(Sink.head)(Keep.right).run()
これら一連の処理は
Http().outgoingConnection(host, port)
と等価です
TCPコネクションにHTTPのセマンティクスをのせる
動かしてみる
TCPコネクションにHTTPのセマンティクスをのせる
val connections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] = Tcp().bind(host, port) //TCPのbind
val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo()
//TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。
val serverLayer: BidiFlow[HttpResponse, SslTlsOutbound, SslTlsInbound, HttpRequest, NotUsed] = Http().serverLayer()
//HttpResponse -> SslTlsOutbound、SslTlsInbound -> HttpRequestへの変換ステージ
val serverSource: Source[IncomingConnection, Future[ServerBinding]] = connections.map {
case Tcp.IncomingConnection(localAddress, remoteAddress, flow) =>
/**
* +----------------------------------------------------------------------------+
* | IncomingConnection.flow |
* | |
* | +-------------+ +----------+ +----------+ |
* HttpResponse ~~> | ~SslTlsOutbound~> | | ~ByteString~> | | |
* | | serverLayer | | tlsStage | | identity | |
* HttpRequest <~~ | <~SslTlsInbound~ | | <~ByteString~ | | |
* | +-------------+ +----------+ +----------+ |
* +----------------------------------------------------------------------------+
*/
// TCP/TLSステージとHTTPステージを接続して、Tcp.IncomingConnectionからHttp.IncomingConnectionに変換
Http.IncomingConnection(localAddress, remoteAddress,
serverLayer atop tlsStage join Flow[ByteString].map(identity)
)
}.mapMaterializedValue { bindFuture: Future[Tcp.ServerBinding] =>
bindFuture.map(tcpBinding => Http.ServerBinding(tcpBinding.localAddress)(unbindAction = () => tcpBinding.unbind()))
// Tcp.ServerBindingからHttp.ServerBindingに変換
}
サーバー編
val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection =>
val connFlow: Flow[HttpResponse, HttpRequest, NotUsed] = connection.flow
val requestHandler: Flow[HttpRequest, HttpResponse, NotUsed] = Flow[HttpRequest].map {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>"))
}
connFlow.joinMat(requestHandler)(Keep.right).run()
}).run()
val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port)
これら一連の処理は
Http().bind(host, port)
と等価です
TCPコネクションにHTTPのセマンティクスをのせる
動かしてみる
TCPレイヤーの内部構造
TCPレイヤーの内部構造
クライアント編
akka.io.Tcpの復習
case object Ack extends Tcp.Event
class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging {
import context.system
override def preStart: Unit = {
val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得
manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る
}
def receive: Receive = connecting
def connecting: Receive = {
case Tcp.Connected(remote, local) => { //TCP接続の完了
val connection = sender() //senderがコネクションWorkerアクター
context.become(connected(connection))
connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録
connection ! Tcp.ResumeReading //サーバーからの受信を再開する
}
}
def connected(connection: ActorRef): Receive = {
case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信
case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら書き込む。成功したらAckを返
してもらう。Ackが返るまで受け付けてはいけません!!
case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する
}
}
Pull Mode を使う読み込み側の背圧制御のために
akka.io.Tcpの復習
case object Ack extends Tcp.Event
class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging {
import context.system
override def preStart: Unit = {
val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得
manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る
}
def receive: Receive = connecting
def connecting: Receive = {
case Tcp.Connected(remote, local) => { //TCP接続の完了
val connection = sender() //senderがコネクションWorkerアクター
context.become(connected(connection))
connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録
connection ! Tcp.ResumeReading //サーバーからの受信を再開する
}
}
def connected(connection: ActorRef): Receive = {
case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信
case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら書き込む。成功したらAckを返
してもらう。Ackが返るまで受け付けてはいけません!!
case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する
}
}
Pull Mode を使う読み込み側の背圧制御のために
_人人人人人人人人人人人人人人人人_
> 書き込み側にも背圧制御欲しい <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
そこでAkka Streamだ
Akka Streamのステージ
final case class Map[In, Out](f: In ⇒ Out) extends GraphStage[FlowShape[In, Out]] {
val in = Inlet[In]("Map.in") //入力ポート
val out = Outlet[Out]("Map.out") //出力ポート
override def shape: FlowShape[In, Out] = FlowShape.of(in, out)
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) {
setHandler(in, new InHandler { //入力ポート`in`のイベントハンドラ
override def onPush(): Unit = { //PUSHされた時のフック
val v = f(grab(in)) //pushされた要素を取得してfを適用
push(out, v) //`out`にpush
}
})
setHandler(out, new OutHandler { //出力ポート`out`のイベントハンドラ
override def onPull(): Unit = { //PULLされた時のフック
pull(in) //`in`からpull
}
})
}
}
//使い方
def mapFlow[In, Out](f: In => Out): Flow[In, Out, NotUsed] = Flow.fromGraph(Map(f))
ストリームのステージの実装例としてMapを見てみよう
Source.map()やFlow.map()はこのように実装されている
class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) {
implicit def self: ActorRef = stageActor.ref
private def bytesIn = shape.in //読み込み用のポート
private def bytesOut = shape.out //書き込み用のポート
private var connection: ActorRef = _ //TCPコネクションのworkerアクター
override def preStart(): Unit = {
role match {
case ob @ Outbound(manager, cmd: akka.io.Tcp.Connect, _, _) ⇒
getStageActor(connecting(ob)) //このステージのアクターのreceiveをconnectingで初期化
manager ! cmd // managerはIO(Tcp)と同じ。Tcp.Connectコマンドを送る。
}
}
private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case c: akka.io.Tcp.Connected =>
role.asInstanceOf[Outbound].localAddressPromise.success(c.localAddress) //Materialized ValueのPromiseにlocalAddressを書き込む
connection = sender // senderがTCPコネクションのworkerアクター
setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定
stageActor.become(connected) //このステージのアクターのreceiveをconnectedにする(context.become(connected)と同じ)
connection ! akka.io.Tcp.Register(self)
if (isAvailable(bytesOut)) connection ! ResumeReading
pull(bytesIn) //bytesInからpullして要素を要求
}
}
private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) => push(bytesOut, data) //データを受信したらbytesOutにpushする
case WriteAck => pull(bytesIn) //書き込み成功のAckが返ってきたらbytesInにpullして要素を要求
}
}
setHandler(bytesOut, new OutHandler {
override def onPull(): Unit = ()
})
val readHandler = new OutHandler {
override def onPull(): Unit = { //pullされたときに呼ばれるイベントハンドラー
connection ! ResumeReading
}
}
setHandler(bytesIn, new InHandler {
override def onPush(): Unit = { //pushされたときに呼ばれるイベントハンドラー
val elem = grab(bytesIn) //bytesInにpushされた要素を取得
connection ! Write(elem.asInstanceOf[ByteString], WriteAck)
}
})
}
Tcp().outgoingConnectionステージの実装
https://github.com/akka/akka/blob/v2.4.2/akka-
stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L171-L287
長いけどakka.io.Tcpの復習でみた
コードと比較すると
理解しやすい
class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends
GraphStageLogic(shape) {
implicit def self: ActorRef = stageActor.ref
private def bytesIn = shape.in //読み込み用のポート
private def bytesOut = shape.out //書き込み用のポート
private var connection: ActorRef = _ //TCPコネクションのworkerアクター
override def preStart(): Unit = {
role match {
case ob @ Outbound(manager, cmd: akka.io.Tcp.Connect, _, _) ⇒
getStageActor(connecting(ob)) //このステージのアクターのreceiveをconnectingで初期化
manager ! cmd // managerはIO(Tcp)と同じ。Tcp.Connectコマンドを送る。
}
}
}
class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging {
import context.system
override def preStart: Unit = {
val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得
manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る
}
def receive: Receive = connecting
}
akka.io.Tcpの対応する部分
Tcp().outgoingConnectionステージの実装
private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case c: akka.io.Tcp.Connected =>
role.asInstanceOf[Outbound].localAddressPromise.success(c.localAddress) //Materialized ValueのPromise
にlocalAddressを書き込む
connection = sender // senderがTCPコネクションのworkerアクター
setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定
stageActor.become(connected) //このステージのアクターのreceiveをconnectedにする(
context.become(connected)と同じ)
connection ! akka.io.Tcp.Register(self)
if (isAvailable(bytesOut)) connection ! ResumeReading
pull(bytesIn) //bytesInからpullして要素を要求
}
}
}
def connecting: Receive = {
case Tcp.Connected(remote, local) => { //TCP接続の完了
val connection = sender() //senderがコネクションWorkerアクター
context.become(connected(connection))
connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録
connection ! Tcp.ResumeReading //サーバーからの受信を再開する
}
}
akka.io.Tcpの対応する部分
Tcp().outgoingConnectionステージの実装
private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする
case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求
}
}
val readHandler = new OutHandler {
override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー
connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する
}
}
setHandler(bytesIn, new InHandler {
override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー
val elem = grab(bytesIn) //bytesInにpushされた要素を取得
connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む
}
})
def connected(connection: ActorRef): Receive = {
case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信
case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら
case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する
}
akka.io.Tcpの対応する部分
Tcp().outgoingConnectionステージの実装
private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする
case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求
}
}
val readHandler = new OutHandler {
override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー
connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する
}
}
setHandler(bytesIn, new InHandler {
override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー
val elem = grab(bytesIn) //bytesInにpushされた要素を取得
connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む
}
})
def connected(connection: ActorRef): Receive = {
case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信
case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら
case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する
}
akka.io.Tcpの対応する部分
欲しいと言うまでデータは来ない
ようになった
Tcp().outgoingConnectionステージの実装
TCPレイヤーの内部構造
サーバー編
class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) {
implicit def self: ActorRef = stageActor.ref
private def bytesIn = shape.in //読み込み用のポート
private def bytesOut = shape.out //書き込み用のポート
private var connection: ActorRef = _ //TCPコネクションのworkerアクター
setHandler(bytesOut, new OutHandler {
override def onPull(): Unit = ()
})
override def preStart(): Unit = {
role match {
case Inbound(conn, _) =>
setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定
connection = conn
getStageActor(connected) //このステージのアクターのreceiveをconnectedにする(context.become(connected)と同じ)
connection ! Register(self)
pull(bytesIn) //bytesInからpullして要素を要求
}
}
private def connected(evt: (ActorRef, Any)): Unit = {
val (sender, msg) = evt
msg match {
case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする
case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求
}
}
val readHandler = new OutHandler {
override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー
connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する
}
}
setHandler(bytesIn, new InHandler {
override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー
val elem = grab(bytesIn) //bytesInにpushされた要素を取得
connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む
}
})
}
Tcp.IncomingConnection#flowステージの実装
開始の仕方が違うだけで
実装は同じ
https://github.com/akka/akka/blob/v2.4.2/akka-
stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L171-L287
Tcp().bindステージ
実はデータの受信のみでなく、
コネクションの受付けまで背圧制御されています
https://github.com/akka/akka/blob/v2.4.2/akka-
stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L30-L141
まとめ
Akka HTTPでは
• 自分が処理できなければ読み込まない
• 相手が処理できなければ書き込まない
システム同士はみんなともだち
Thank you!

Mais conteúdo relacionado

Semelhante a Akka HTTP

Let's begin WebRTC
Let's begin WebRTCLet's begin WebRTC
Let's begin WebRTCyoshikawa_t
 
Tableau Developers Club Season2 /*TableauのAPIすべて*/ Tableau Server REST API Wo...
Tableau Developers Club Season2 /*TableauのAPIすべて*/ Tableau Server REST API Wo...Tableau Developers Club Season2 /*TableauのAPIすべて*/ Tableau Server REST API Wo...
Tableau Developers Club Season2 /*TableauのAPIすべて*/ Tableau Server REST API Wo...Hiroshi Masuda
 
Couchbase MeetUP Tokyo - #11 Omoidenote
Couchbase MeetUP Tokyo - #11 OmoidenoteCouchbase MeetUP Tokyo - #11 Omoidenote
Couchbase MeetUP Tokyo - #11 Omoidenotekitsugi
 
Reactive Extensionsで非同期処理を簡単に
Reactive Extensionsで非同期処理を簡単にReactive Extensionsで非同期処理を簡単に
Reactive Extensionsで非同期処理を簡単にYoshifumi Kawai
 
Gunosy Go lang study #6 net http url
Gunosy Go lang study #6 net http urlGunosy Go lang study #6 net http url
Gunosy Go lang study #6 net http urlInnami Satoshi
 
15. running deploying camel
15. running deploying camel15. running deploying camel
15. running deploying camelJian Feng
 
MacPort_&_FTP_ver1.0
MacPort_&_FTP_ver1.0MacPort_&_FTP_ver1.0
MacPort_&_FTP_ver1.0Satoshi Kume
 
SDN Lab環境でのRobotFramework実践活用
SDN Lab環境でのRobotFramework実践活用SDN Lab環境でのRobotFramework実践活用
SDN Lab環境でのRobotFramework実践活用Toshiki Tsuboi
 
C#次世代非同期処理概観 - Task vs Reactive Extensions
C#次世代非同期処理概観 - Task vs Reactive ExtensionsC#次世代非同期処理概観 - Task vs Reactive Extensions
C#次世代非同期処理概観 - Task vs Reactive ExtensionsYoshifumi Kawai
 
Html5, Web Applications 2
Html5, Web Applications 2Html5, Web Applications 2
Html5, Web Applications 2totty jp
 
SlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみよう
SlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみようSlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみよう
SlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみようShigeo Ueda
 
Apache Camel Netty component
Apache Camel Netty componentApache Camel Netty component
Apache Camel Netty componentssogabe
 
Retrofit2 &OkHttp 
でAndroidのHTTP通信が快適だにゃん
Retrofit2 &OkHttp 
でAndroidのHTTP通信が快適だにゃんRetrofit2 &OkHttp 
でAndroidのHTTP通信が快適だにゃん
Retrofit2 &OkHttp 
でAndroidのHTTP通信が快適だにゃんYukari Sakurai
 
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみようWebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみようmganeko
 
Continuation with Boost.Context
Continuation with Boost.ContextContinuation with Boost.Context
Continuation with Boost.ContextAkira Takahashi
 
EchoyaGinhanazeSu_inoka.pptx
EchoyaGinhanazeSu_inoka.pptxEchoyaGinhanazeSu_inoka.pptx
EchoyaGinhanazeSu_inoka.pptxkeink
 
SSHパケットの復号ツールを作ろう_v1(Decrypt SSH .pcap File)
SSHパケットの復号ツールを作ろう_v1(Decrypt SSH .pcap File)SSHパケットの復号ツールを作ろう_v1(Decrypt SSH .pcap File)
SSHパケットの復号ツールを作ろう_v1(Decrypt SSH .pcap File)Tetsuya Hasegawa
 
サーバー実装いろいろ
サーバー実装いろいろサーバー実装いろいろ
サーバー実装いろいろkjwtnb
 

Semelhante a Akka HTTP (20)

Let's begin WebRTC
Let's begin WebRTCLet's begin WebRTC
Let's begin WebRTC
 
Tableau Developers Club Season2 /*TableauのAPIすべて*/ Tableau Server REST API Wo...
Tableau Developers Club Season2 /*TableauのAPIすべて*/ Tableau Server REST API Wo...Tableau Developers Club Season2 /*TableauのAPIすべて*/ Tableau Server REST API Wo...
Tableau Developers Club Season2 /*TableauのAPIすべて*/ Tableau Server REST API Wo...
 
Couchbase MeetUP Tokyo - #11 Omoidenote
Couchbase MeetUP Tokyo - #11 OmoidenoteCouchbase MeetUP Tokyo - #11 Omoidenote
Couchbase MeetUP Tokyo - #11 Omoidenote
 
Reactive Extensionsで非同期処理を簡単に
Reactive Extensionsで非同期処理を簡単にReactive Extensionsで非同期処理を簡単に
Reactive Extensionsで非同期処理を簡単に
 
Gunosy Go lang study #6 net http url
Gunosy Go lang study #6 net http urlGunosy Go lang study #6 net http url
Gunosy Go lang study #6 net http url
 
15. running deploying camel
15. running deploying camel15. running deploying camel
15. running deploying camel
 
P2Pって何?
P2Pって何?P2Pって何?
P2Pって何?
 
MacPort_&_FTP_ver1.0
MacPort_&_FTP_ver1.0MacPort_&_FTP_ver1.0
MacPort_&_FTP_ver1.0
 
SDN Lab環境でのRobotFramework実践活用
SDN Lab環境でのRobotFramework実践活用SDN Lab環境でのRobotFramework実践活用
SDN Lab環境でのRobotFramework実践活用
 
C#次世代非同期処理概観 - Task vs Reactive Extensions
C#次世代非同期処理概観 - Task vs Reactive ExtensionsC#次世代非同期処理概観 - Task vs Reactive Extensions
C#次世代非同期処理概観 - Task vs Reactive Extensions
 
Html5, Web Applications 2
Html5, Web Applications 2Html5, Web Applications 2
Html5, Web Applications 2
 
SlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみよう
SlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみようSlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみよう
SlackのIncomingWebhooksとOutgoingWebhooksを使って電子工作と連携させてみよう
 
Apache Camel Netty component
Apache Camel Netty componentApache Camel Netty component
Apache Camel Netty component
 
Retrofit2 &OkHttp 
でAndroidのHTTP通信が快適だにゃん
Retrofit2 &OkHttp 
でAndroidのHTTP通信が快適だにゃんRetrofit2 &OkHttp 
でAndroidのHTTP通信が快適だにゃん
Retrofit2 &OkHttp 
でAndroidのHTTP通信が快適だにゃん
 
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみようWebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
WebRTC UserMedia Catalog: いろんなユーザメディア(MediaStream)を使ってみよう
 
Continuation with Boost.Context
Continuation with Boost.ContextContinuation with Boost.Context
Continuation with Boost.Context
 
EchoyaGinhanazeSu_inoka.pptx
EchoyaGinhanazeSu_inoka.pptxEchoyaGinhanazeSu_inoka.pptx
EchoyaGinhanazeSu_inoka.pptx
 
Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7Pfi Seminar 2010 1 7
Pfi Seminar 2010 1 7
 
SSHパケットの復号ツールを作ろう_v1(Decrypt SSH .pcap File)
SSHパケットの復号ツールを作ろう_v1(Decrypt SSH .pcap File)SSHパケットの復号ツールを作ろう_v1(Decrypt SSH .pcap File)
SSHパケットの復号ツールを作ろう_v1(Decrypt SSH .pcap File)
 
サーバー実装いろいろ
サーバー実装いろいろサーバー実装いろいろ
サーバー実装いろいろ
 

Mais de TanUkkii

Distributed ID generator in ChatWork
Distributed ID generator in ChatWorkDistributed ID generator in ChatWork
Distributed ID generator in ChatWorkTanUkkii
 
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...TanUkkii
 
Architecture of Falcon, a new chat messaging backend system build on Scala
Architecture of Falcon,  a new chat messaging backend system  build on ScalaArchitecture of Falcon,  a new chat messaging backend system  build on Scala
Architecture of Falcon, a new chat messaging backend system build on ScalaTanUkkii
 
Akka Clusterの耐障害設計
Akka Clusterの耐障害設計Akka Clusterの耐障害設計
Akka Clusterの耐障害設計TanUkkii
 
スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成TanUkkii
 
すべてのアクター プログラマーが知るべき 単一責務原則とは何か
すべてのアクター プログラマーが知るべき 単一責務原則とは何かすべてのアクター プログラマーが知るべき 単一責務原則とは何か
すべてのアクター プログラマーが知るべき 単一責務原則とは何かTanUkkii
 
ディープニューラルネット入門
ディープニューラルネット入門ディープニューラルネット入門
ディープニューラルネット入門TanUkkii
 
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けープログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けーTanUkkii
 
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けープログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けーTanUkkii
 
Isomorphic web development with scala and scala.js
Isomorphic web development  with scala and scala.jsIsomorphic web development  with scala and scala.js
Isomorphic web development with scala and scala.jsTanUkkii
 
Scalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリングScalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリングTanUkkii
 
ECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミングECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミングTanUkkii
 
プログラミング言語Scala
プログラミング言語Scalaプログラミング言語Scala
プログラミング言語ScalaTanUkkii
 
これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6TanUkkii
 

Mais de TanUkkii (16)

Distributed ID generator in ChatWork
Distributed ID generator in ChatWorkDistributed ID generator in ChatWork
Distributed ID generator in ChatWork
 
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
 
Architecture of Falcon, a new chat messaging backend system build on Scala
Architecture of Falcon,  a new chat messaging backend system  build on ScalaArchitecture of Falcon,  a new chat messaging backend system  build on Scala
Architecture of Falcon, a new chat messaging backend system build on Scala
 
JSON CRDT
JSON CRDTJSON CRDT
JSON CRDT
 
Akka Clusterの耐障害設計
Akka Clusterの耐障害設計Akka Clusterの耐障害設計
Akka Clusterの耐障害設計
 
WaveNet
WaveNetWaveNet
WaveNet
 
スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成スケールするシステムにおけるエンティティの扱いと 分散ID生成
スケールするシステムにおけるエンティティの扱いと 分散ID生成
 
すべてのアクター プログラマーが知るべき 単一責務原則とは何か
すべてのアクター プログラマーが知るべき 単一責務原則とは何かすべてのアクター プログラマーが知るべき 単一責務原則とは何か
すべてのアクター プログラマーが知るべき 単一責務原則とは何か
 
ディープニューラルネット入門
ディープニューラルネット入門ディープニューラルネット入門
ディープニューラルネット入門
 
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けープログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
 
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けープログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
 
Isomorphic web development with scala and scala.js
Isomorphic web development  with scala and scala.jsIsomorphic web development  with scala and scala.js
Isomorphic web development with scala and scala.js
 
Scalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリングScalaによる型安全なエラーハンドリング
Scalaによる型安全なエラーハンドリング
 
ECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミングECMAScript6による関数型プログラミング
ECMAScript6による関数型プログラミング
 
プログラミング言語Scala
プログラミング言語Scalaプログラミング言語Scala
プログラミング言語Scala
 
これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6これからのJavaScriptー関数型プログラミングとECMAScript6
これからのJavaScriptー関数型プログラミングとECMAScript6
 

Akka HTTP

  • 2. Akka Stream & HTTP リリースおめでとう! ノンブロッキングで背圧制御に満ちたエコシステム 形成の幕開けになることを期待します
  • 3. Akka HTTPとは • akka IOとAkka Streamを使って、NIOかつ背圧制御 に基づいたHTTPモジュール • 低レベルAPIのakka-http-coreと高レベルAPIのakka- http-experimentalがある • Akka Streamがシステム内の調和を保つとしたら、 Akka HTTPはシステム間の調和を実現する
  • 4. マイクロサービスの難しさ “every single one of your peer teams suddenly becomes a potential DOS attacker” 周りの全ての同僚チームが、突如 DOS アタッカーになりうる ようになった 和訳原文 Stevey の “Google プラットフォームぶっちゃけ話”にでてくる Amazonのマイクロサービス化によるAWS誕生のくだり システム間のインターフェースとしてもっとも使われるHTTPに 背圧制御をもたらす必要がある
  • 5. アジェンダ • Akka Streamの復習 • Akka HTTP API • HTTPレイヤーとTCPレイヤーとの接合 • TCPレイヤーの内部構造
  • 6. Akka Streamの復習 val intSource: Source[Int, NotUsed] = Source(List(1, 2, 3)) //Sourceは1つの出力をもつ。Sourceなどをグラフという。 val toStringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(_.toString) //Flowは1つの入力と1つの出力をもつ。mapなどをステージという 。 val seqSink: Sink[String, Future[Seq[String]]] = Sink.seq[String] //Sinkは1つの入力をもつ。第2型引数がMaterialized Value。 /** * +-----------------------------------------------------------------------------------+ * | runnableGraph | * | | * | +------------+ +--------------+ +---------+ | * | | | | | | | | * | | intSource ~ Int ~> ~ Int ~> toStringFlow ~ String ~> ~ String ~> seqSink | | * | | | | | | | | * | +------------+ +--------------+ +---------+ | * +-----------------------------------------------------------------------------------+ */ // すべてのポートが閉じたグラフはRunnableGraph[Mat]になり、マテリアライズ化が可能になる val runnableGraph: RunnableGraph[Future[Seq[String]]] = intSource.via(toStringFlow).toMat(seqSink)((sourceMatValue, sinkMatValue) => sinkMatValue) // RunnableGraphのrunを呼んでマテリアライズ化する。ここからストリームが動き出す。マテリアライズ化の結果としてMaterialized Valueが 返る。 val materializedValue: Future[Seq[String]] = runnableGraph.run()(ActorMaterializer()) // Materialized Valueからストリームの結果を受け取れる場合がある val streamResult: Seq[String] = Await.result(materializedValue, 10 seconds) //Seq("1", "2", "3")
  • 7. Akka Streamの復習 intSource.via(toStringFlow).toMat(seqSink)((sourceMatValue, sinkMatValue) => sinkMatValue).run() すべては下流から始まる Source Flow Sink pull(in) pull(in) onPull onPull push(out, 1) onPush push(out, “1”) pull(in) pull(in) onPull push(out, 2) onPush ※アクターモデルとの最大の違い onPush onPull
  • 9. クライアントサイドAPI val connectionFlow: Flow[HttpRequest, HttpResponse, Future[OutgoingConnection]] = Http().outgoingConnection(host, port) //単一のHTTPコネクションストリーム val responseFuture1: Future[HttpResponse] = Source.single(HttpRequest(uri = "/")) .via(connectionFlow) .runWith(Sink.head) val poolClientFlow: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), HostConnectionPool] = Http().cachedHostConnectionPool[Int](host, port) //ホスト単位でコネクションプールをもつストリーム val responseFuture2: Future[(Try[HttpResponse], Int)] = Source.single(HttpRequest(uri = "/") -> 1) .via(poolClientFlow) .runWith(Sink.head)
  • 10. サーバーサイドAPI val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port) //bindでコネクション のSourceを得る val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection => val connFlow: Flow[HttpResponse, HttpRequest, NotUsed] = connection.flow //それぞれのコネクションのデータの送受信を表す フロー val requestHandler: Flow[HttpRequest, HttpResponse, NotUsed] = Flow[HttpRequest].map { case HttpRequest(GET, Uri.Path("/"), _, _, _) => HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>")) } /** * +----------+ +----------------+ * | | ~HttpRequest~> | | * | connFlow | | requestHandler | * | | <~HttpResponse~ | | * +----------+ +----------------+ */ connFlow.joinMat(requestHandler)(Keep.right).run() //コネクションのフローにリクエストハンドラーを接続してリクエストを消 費する }).run()
  • 11. HttpRequest, HttpResponse final case class HttpRequest(method: HttpMethod, uri: Uri, headers: immutable.Seq[HttpHeader], entity: RequestEntity, protocol: HttpProtocol) sealed trait RequestEntity extends HttpEntity final case class HttpResponse(status: StatusCode, headers: immutable.Seq[HttpHeader], entity: ResponseEntity, protocol: HttpProtocol) sealed trait ResponseEntity extends HttpEntity sealed trait HttpEntity { def dataBytes: Source[ByteString, Any] } なんとHttpEntityの中身はSource[ByteString, Any]だ これが効率的なデータの送受信と WebsoketやSSEなどの異なるプロトコルを統一的に扱うことを可能にしている
  • 12. • Futureなのでコネクションプールの数を超えて並列に呼ぶことができる • IOの方がCPUより遅いのでコネクションが足りなくなる • NIOなのでスレッドプールのスレッド数=コネクション数にしても解決しない リソース間調整 import akka.io.IO import spray.can.Http import spray.client.pipelining._ trait RequestProvider { this: Actor => import context.system import context.dispatcher lazy val pipeline = { sendReceive(IO(Http)(system)) ~> unmarshal[String] } def request(path: String): Future[String] = pipeline(Get(s"$requestUrl/$path")) } スレッドプールとコネクションプール val safeRequest: Flow[String, String, NotUsed] = Flow[String].mapAsync(maxConnection)(request) Akka StreamのmapAsync(parallelism)(asyncFunction)を使えば parallelism以上にasyncFunctionが呼ばれることはない Akka HTTPでもコネクションプールをもつクライアントは mapAsyncで制御している Spray clientの例
  • 14. TCPコネクションにHTTPのセマンティクスをのせる val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new InetSocketAddress(host, port)) //TCPコネクションのステージ val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo() //TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。 val outgoingTlsConnectionLayer: Flow[SslTlsOutbound, SessionBytes, Future[Http.OutgoingConnection]] = tlsStage.joinMat(transportFlow) { (_, tcpConnFuture: Future[Tcp.OutgoingConnection]) => tcpConnFuture map { tcpConn => Http.OutgoingConnection(tcpConn.localAddress, tcpConn.remoteAddress) } //TCPコネクションステージにTLSステージを接続する。Materialized ValueをTcp.OutgoingConnectionから Http.OutgoingConnectionに変換する。 } val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] = Http().clientLayer(Host(host, port)) //HttpRequest -> SslTlsOutbound、SslTlsInbound -> HttpResponseへの変換ステージ val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続 クライアント編
  • 15. val transportFlow: Flow[ByteString, ByteString, Future[Tcp.OutgoingConnection]] = Tcp().outgoingConnection(new InetSocketAddress(host, port)) //TCPコネクションのステージ val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo() //TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。 /** * +------------------------------------------------+ * | outgoingTlsConnectionLayer | * | | * | +----------+ +---------------+ | * SslTlsOutbound ~~> | ~ByteString~> | | | * | | tlsStage | | transportFlow | | * SessionBytes <~~ | <~ByteString~ | | | * | +----------+ +---------------+ | * +------------------------------------------------+ */ val outgoingTlsConnectionLayer: Flow[SslTlsOutbound, SessionBytes, Future[Http.OutgoingConnection]] = tlsStage.joinMat(transportFlow) { (_, tcpConnFuture: Future[Tcp.OutgoingConnection]) => tcpConnFuture map { tcpConn => Http.OutgoingConnection(tcpConn.localAddress, tcpConn.remoteAddress) } //TCPコネクションステージにTLSステージを接続する。Materialized ValueをTcp.OutgoingConnectionから Http.OutgoingConnectionに変換する。 } TCPコネクションにHTTPのセマンティクスをのせる ① TCPコネクションにTLSの解釈を接続する
  • 16. val clientLayer: BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed] = Http().clientLayer(Host(host, port)) //HttpRequest -> SslTlsOutbound、SslTlsInbound -> HttpResponseへの変換ステージ /** * +-------------------------------------------------------------------+ * | outgoingConnection | * | | * | +-------------+ +----------------------------+ | * HttpRequest ~~> | ~SslTlsOutbound~> | | | * | | clientLayer | | outgoingTlsConnectionLayer | | * HttpResponse <~~ | <~SslTlsInbound~ | | | * | +-------------+ +----------------------------+ | * +-------------------------------------------------------------------+ */ val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続 TCPコネクションにHTTPのセマンティクスをのせる ② HTTPのセマンティクスを解釈する
  • 17. val outgoingConnection: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = clientLayer.joinMat(outgoingTlsConnectionLayer)(Keep.right) //TCP/TLSステージとHTTPステージを接続 Source.single(httpRequest).via(outgoingConnection).toMat(Sink.head)(Keep.right).run() Source.single(httpRequest).via(Http().outgoingConnection(host, port)).toMat(Sink.head)(Keep.right).run() これら一連の処理は Http().outgoingConnection(host, port) と等価です TCPコネクションにHTTPのセマンティクスをのせる 動かしてみる
  • 18. TCPコネクションにHTTPのセマンティクスをのせる val connections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] = Tcp().bind(host, port) //TCPのbind val tlsStage: BidiFlow[SslTlsOutbound, ByteString, ByteString, SessionBytes, NotUsed] = TLSPlacebo() //TLSのプラシーボ効果ステージ。ByteStringをTLSの型にラップしているだけで何もしていない。HTTPスキーム用。 val serverLayer: BidiFlow[HttpResponse, SslTlsOutbound, SslTlsInbound, HttpRequest, NotUsed] = Http().serverLayer() //HttpResponse -> SslTlsOutbound、SslTlsInbound -> HttpRequestへの変換ステージ val serverSource: Source[IncomingConnection, Future[ServerBinding]] = connections.map { case Tcp.IncomingConnection(localAddress, remoteAddress, flow) => /** * +----------------------------------------------------------------------------+ * | IncomingConnection.flow | * | | * | +-------------+ +----------+ +----------+ | * HttpResponse ~~> | ~SslTlsOutbound~> | | ~ByteString~> | | | * | | serverLayer | | tlsStage | | identity | | * HttpRequest <~~ | <~SslTlsInbound~ | | <~ByteString~ | | | * | +-------------+ +----------+ +----------+ | * +----------------------------------------------------------------------------+ */ // TCP/TLSステージとHTTPステージを接続して、Tcp.IncomingConnectionからHttp.IncomingConnectionに変換 Http.IncomingConnection(localAddress, remoteAddress, serverLayer atop tlsStage join Flow[ByteString].map(identity) ) }.mapMaterializedValue { bindFuture: Future[Tcp.ServerBinding] => bindFuture.map(tcpBinding => Http.ServerBinding(tcpBinding.localAddress)(unbindAction = () => tcpBinding.unbind())) // Tcp.ServerBindingからHttp.ServerBindingに変換 } サーバー編
  • 19. val bindingFuture: Future[ServerBinding] = serverSource.to(Sink.foreach { connection => val connFlow: Flow[HttpResponse, HttpRequest, NotUsed] = connection.flow val requestHandler: Flow[HttpRequest, HttpResponse, NotUsed] = Flow[HttpRequest].map { case HttpRequest(GET, Uri.Path("/"), _, _, _) => HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, "<html><body>Hello world!</body></html>")) } connFlow.joinMat(requestHandler)(Keep.right).run() }).run() val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(host, port) これら一連の処理は Http().bind(host, port) と等価です TCPコネクションにHTTPのセマンティクスをのせる 動かしてみる
  • 22. akka.io.Tcpの復習 case object Ack extends Tcp.Event class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging { import context.system override def preStart: Unit = { val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得 manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る } def receive: Receive = connecting def connecting: Receive = { case Tcp.Connected(remote, local) => { //TCP接続の完了 val connection = sender() //senderがコネクションWorkerアクター context.become(connected(connection)) connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録 connection ! Tcp.ResumeReading //サーバーからの受信を再開する } } def connected(connection: ActorRef): Receive = { case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信 case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら書き込む。成功したらAckを返 してもらう。Ackが返るまで受け付けてはいけません!! case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する } } Pull Mode を使う読み込み側の背圧制御のために
  • 23. akka.io.Tcpの復習 case object Ack extends Tcp.Event class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging { import context.system override def preStart: Unit = { val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得 manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る } def receive: Receive = connecting def connecting: Receive = { case Tcp.Connected(remote, local) => { //TCP接続の完了 val connection = sender() //senderがコネクションWorkerアクター context.become(connected(connection)) connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録 connection ! Tcp.ResumeReading //サーバーからの受信を再開する } } def connected(connection: ActorRef): Receive = { case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信 case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら書き込む。成功したらAckを返 してもらう。Ackが返るまで受け付けてはいけません!! case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する } } Pull Mode を使う読み込み側の背圧制御のために _人人人人人人人人人人人人人人人人_ > 書き込み側にも背圧制御欲しい <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ そこでAkka Streamだ
  • 24. Akka Streamのステージ final case class Map[In, Out](f: In ⇒ Out) extends GraphStage[FlowShape[In, Out]] { val in = Inlet[In]("Map.in") //入力ポート val out = Outlet[Out]("Map.out") //出力ポート override def shape: FlowShape[In, Out] = FlowShape.of(in, out) override def createLogic(inheritedAttributes: Attributes): GraphStageLogic = new GraphStageLogic(shape) { setHandler(in, new InHandler { //入力ポート`in`のイベントハンドラ override def onPush(): Unit = { //PUSHされた時のフック val v = f(grab(in)) //pushされた要素を取得してfを適用 push(out, v) //`out`にpush } }) setHandler(out, new OutHandler { //出力ポート`out`のイベントハンドラ override def onPull(): Unit = { //PULLされた時のフック pull(in) //`in`からpull } }) } } //使い方 def mapFlow[In, Out](f: In => Out): Flow[In, Out, NotUsed] = Flow.fromGraph(Map(f)) ストリームのステージの実装例としてMapを見てみよう Source.map()やFlow.map()はこのように実装されている
  • 25. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) { implicit def self: ActorRef = stageActor.ref private def bytesIn = shape.in //読み込み用のポート private def bytesOut = shape.out //書き込み用のポート private var connection: ActorRef = _ //TCPコネクションのworkerアクター override def preStart(): Unit = { role match { case ob @ Outbound(manager, cmd: akka.io.Tcp.Connect, _, _) ⇒ getStageActor(connecting(ob)) //このステージのアクターのreceiveをconnectingで初期化 manager ! cmd // managerはIO(Tcp)と同じ。Tcp.Connectコマンドを送る。 } } private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case c: akka.io.Tcp.Connected => role.asInstanceOf[Outbound].localAddressPromise.success(c.localAddress) //Materialized ValueのPromiseにlocalAddressを書き込む connection = sender // senderがTCPコネクションのworkerアクター setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定 stageActor.become(connected) //このステージのアクターのreceiveをconnectedにする(context.become(connected)と同じ) connection ! akka.io.Tcp.Register(self) if (isAvailable(bytesOut)) connection ! ResumeReading pull(bytesIn) //bytesInからpullして要素を要求 } } private def connected(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case akka.io.Tcp.Received(data) => push(bytesOut, data) //データを受信したらbytesOutにpushする case WriteAck => pull(bytesIn) //書き込み成功のAckが返ってきたらbytesInにpullして要素を要求 } } setHandler(bytesOut, new OutHandler { override def onPull(): Unit = () }) val readHandler = new OutHandler { override def onPull(): Unit = { //pullされたときに呼ばれるイベントハンドラー connection ! ResumeReading } } setHandler(bytesIn, new InHandler { override def onPush(): Unit = { //pushされたときに呼ばれるイベントハンドラー val elem = grab(bytesIn) //bytesInにpushされた要素を取得 connection ! Write(elem.asInstanceOf[ByteString], WriteAck) } }) } Tcp().outgoingConnectionステージの実装 https://github.com/akka/akka/blob/v2.4.2/akka- stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L171-L287 長いけどakka.io.Tcpの復習でみた コードと比較すると 理解しやすい
  • 26. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) { implicit def self: ActorRef = stageActor.ref private def bytesIn = shape.in //読み込み用のポート private def bytesOut = shape.out //書き込み用のポート private var connection: ActorRef = _ //TCPコネクションのworkerアクター override def preStart(): Unit = { role match { case ob @ Outbound(manager, cmd: akka.io.Tcp.Connect, _, _) ⇒ getStageActor(connecting(ob)) //このステージのアクターのreceiveをconnectingで初期化 manager ! cmd // managerはIO(Tcp)と同じ。Tcp.Connectコマンドを送る。 } } } class PullModeClient(remote: InetSocketAddress) extends Actor with ActorLogging { import context.system override def preStart: Unit = { val manager: ActorRef = IO(Tcp) //IOエクステンションからTCPマネージャーを取得 manager ! Tcp.Connect(remote, pullMode = true) //TCPマネージャーに接続要求を送る } def receive: Receive = connecting } akka.io.Tcpの対応する部分 Tcp().outgoingConnectionステージの実装
  • 27. private def connecting(ob: Outbound)(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case c: akka.io.Tcp.Connected => role.asInstanceOf[Outbound].localAddressPromise.success(c.localAddress) //Materialized ValueのPromise にlocalAddressを書き込む connection = sender // senderがTCPコネクションのworkerアクター setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定 stageActor.become(connected) //このステージのアクターのreceiveをconnectedにする( context.become(connected)と同じ) connection ! akka.io.Tcp.Register(self) if (isAvailable(bytesOut)) connection ! ResumeReading pull(bytesIn) //bytesInからpullして要素を要求 } } } def connecting: Receive = { case Tcp.Connected(remote, local) => { //TCP接続の完了 val connection = sender() //senderがコネクションWorkerアクター context.become(connected(connection)) connection ! Tcp.Register(self) //自分自身をコネクションWorkerアクターに登録 connection ! Tcp.ResumeReading //サーバーからの受信を再開する } } akka.io.Tcpの対応する部分 Tcp().outgoingConnectionステージの実装
  • 28. private def connected(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求 } } val readHandler = new OutHandler { override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する } } setHandler(bytesIn, new InHandler { override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー val elem = grab(bytesIn) //bytesInにpushされた要素を取得 connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む } }) def connected(connection: ActorRef): Receive = { case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信 case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する } akka.io.Tcpの対応する部分 Tcp().outgoingConnectionステージの実装
  • 29. private def connected(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求 } } val readHandler = new OutHandler { override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する } } setHandler(bytesIn, new InHandler { override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー val elem = grab(bytesIn) //bytesInにpushされた要素を取得 connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む } }) def connected(connection: ActorRef): Receive = { case Tcp.Received(data) => log.info("received {}", data) //サーバーからデータを受信 case data: ByteString => connection ! Tcp.Write(data, Ack) //ユーザーからデータの送信要求がきたら case Ack => connection ! Tcp.ResumeReading //データの送信が完了したら受信を再開する } akka.io.Tcpの対応する部分 欲しいと言うまでデータは来ない ようになった Tcp().outgoingConnectionステージの実装
  • 31. class TcpStreamLogic(val shape: FlowShape[ByteString, ByteString], val role: TcpRole) extends GraphStageLogic(shape) { implicit def self: ActorRef = stageActor.ref private def bytesIn = shape.in //読み込み用のポート private def bytesOut = shape.out //書き込み用のポート private var connection: ActorRef = _ //TCPコネクションのworkerアクター setHandler(bytesOut, new OutHandler { override def onPull(): Unit = () }) override def preStart(): Unit = { role match { case Inbound(conn, _) => setHandler(bytesOut, readHandler) //bytesOutのイベントハンドラをreadHandlerに設定 connection = conn getStageActor(connected) //このステージのアクターのreceiveをconnectedにする(context.become(connected)と同じ) connection ! Register(self) pull(bytesIn) //bytesInからpullして要素を要求 } } private def connected(evt: (ActorRef, Any)): Unit = { val (sender, msg) = evt msg match { case akka.io.Tcp.Received(data) => push(bytesOut, data) //R② データを受信したらbytesOutにpushする case WriteAck => pull(bytesIn) //W② 書き込み成功のAckが返ってきたらbytesInにpullして要素を要求 } } val readHandler = new OutHandler { override def onPull(): Unit = { //bytesOutでpullされたときに呼ばれるイベントハンドラー connection ! ResumeReading //R① bytesOutにpull要求がきたら読み込みを再開する } } setHandler(bytesIn, new InHandler { override def onPush(): Unit = { //bytesInでpushされたときに呼ばれるイベントハンドラー val elem = grab(bytesIn) //bytesInにpushされた要素を取得 connection ! Write(elem.asInstanceOf[ByteString], WriteAck) //W① pushされたデータを書き込む } }) } Tcp.IncomingConnection#flowステージの実装 開始の仕方が違うだけで 実装は同じ https://github.com/akka/akka/blob/v2.4.2/akka- stream/src/main/scala/akka/stream/impl/io/TcpStages.scala#L171-L287
  • 33. まとめ Akka HTTPでは • 自分が処理できなければ読み込まない • 相手が処理できなければ書き込まない システム同士はみんなともだち